Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion client.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ const { walletRequest, waitForTxConfirmation, log } = require('./helpers');
const tokenArg = process.argv.find(a => a.startsWith('--token='))?.split('=')[1]
|| (process.argv.includes('--token') ? process.argv[process.argv.indexOf('--token') + 1] : null);

function decodePaymentResponseHeader(resp) {
const encoded = resp.headers.get('PAYMENT-RESPONSE') || resp.headers.get('X-Payment-Response');
if (!encoded) return null;

try {
return JSON.parse(Buffer.from(encoded, 'base64').toString('utf8'));
} catch (err) {
log('CLIENT', `Could not decode PAYMENT-RESPONSE header: ${err.message}`);
return null;
}
}

function pickPaymentOption(accepts) {
if (tokenArg === 'custom' && config.customTokenUid) {
const match = accepts.find(a => a.asset === config.customTokenUid);
Expand Down Expand Up @@ -130,11 +142,13 @@ async function main() {
}

const result = await paidResp.json();
const paymentResponse = decodePaymentResponseHeader(paidResp) || result.payment;
log('CLIENT', '');
log('CLIENT', '=== Resource Received! ===');
log('CLIENT', JSON.stringify(result.data, null, 2));
log('CLIENT', '');
log('CLIENT', `Payment ncId: ${result.payment.ncId}`);
log('CLIENT', `Payment ncId: ${paymentResponse?.ncId || result.payment?.ncId}`);
log('CLIENT', `Settlement txId: ${paymentResponse?.settleTxId || 'not provided'}`);
log('CLIENT', '');
log('CLIENT', 'Waiting a few seconds for settlement to complete...');

Expand Down
38 changes: 34 additions & 4 deletions dapp/components/X402Fetch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ interface PaymentHistory {
timestamp: Date;
}

interface PaymentResponse {
success?: boolean;
scheme?: string;
network?: string;
ncId?: string;
channelId?: string;
settleTxId?: string;
chargedAmount?: string | number;
refundAmount?: string | number;
refundTxId?: string;
[key: string]: unknown;
}

type Step = 'idle' | 'fetching' | 'got402' | 'creating' | 'waiting_confirmation' | 'retrying' | 'done' | 'error';

const CHANNEL_STORAGE_KEY = 'x402_active_channel';
Expand All @@ -53,6 +66,21 @@ function clearStoredChannel() {
localStorage.removeItem(CHANNEL_STORAGE_KEY);
}

function decodePaymentResponseHeader(resp: Response): PaymentResponse | null {
const encoded = resp.headers.get('PAYMENT-RESPONSE') || resp.headers.get('X-Payment-Response');
if (!encoded) return null;

try {
return JSON.parse(atob(encoded));
} catch {
return null;
}
}

function readPaymentResponse(resp: Response, body: any): PaymentResponse | null {
return decodePaymentResponseHeader(resp) || body?.payment || null;
}

export function X402Fetch() {
const { sendNanoContractTx, address, refreshBalance } = useWallet();
const { network, isConnected, addEscrow } = useHathor();
Expand Down Expand Up @@ -93,7 +121,8 @@ export function X402Fetch() {
if (!resp.ok) return false;

const data = await resp.json();
setResourceData(data);
const payment = readPaymentResponse(resp, data);
setResourceData(payment ? { ...data, payment } : data);
setContractId(channelId);
setStep('done');
addToHistory('hathor-channel', channelId, '100');
Expand Down Expand Up @@ -212,11 +241,12 @@ export function X402Fetch() {
}

const data = await paidResp.json();
setResourceData(data);
const payment = readPaymentResponse(paidResp, data);
setResourceData(payment ? { ...data, payment } : data);
setStep('done');
toast.success('Resource received!');
const charged = data?.payment?.chargedAmount != null ? String(data.payment.chargedAmount) : undefined;
const refund = data?.payment?.refundAmount != null ? String(data.payment.refundAmount) : undefined;
const charged = payment?.chargedAmount != null ? String(payment.chargedAmount) : undefined;
const refund = payment?.refundAmount != null ? String(payment.refundAmount) : undefined;
addToHistory(opt.scheme, id, opt.price, charged, refund);
refreshBalance('00', network);
} catch (err: any) {
Expand Down