Payment States
Every payment in X402r goes through a defined lifecycle represented by the PaymentState enum:
import { PaymentState } from '@x402r/core';
// PaymentState values:
PaymentState.NonExistent // 0 - Payment doesn't exist
PaymentState.InEscrow // 1 - Funds held in escrow
PaymentState.Released // 2 - Funds released to merchant
PaymentState.Settled // 3 - Payment fully settled
PaymentState.Expired // 4 - Payment expired
Payment Info
The PaymentInfo struct uniquely identifies a payment and contains all its parameters:
interface PaymentInfo {
operator: `0x${string}`; // PaymentOperator contract address
payer: `0x${string}`; // Payer's address
receiver: `0x${string}`; // Merchant's address
token: `0x${string}`; // Payment token (e.g., USDC)
maxAmount: bigint; // Maximum payment amount
preApprovalExpiry: bigint; // Pre-approval expiry timestamp (0n if not used)
authorizationExpiry: bigint; // When payer can reclaim funds
refundExpiry: bigint; // Deadline for refund requests
minFeeBps: number; // Minimum fee in basis points
maxFeeBps: number; // Maximum fee in basis points
feeReceiver: `0x${string}`; // Address that receives fees
salt: bigint; // Unique salt for this payment
}
Use computePaymentInfoHash() from @x402r/core to compute the unique hash of a payment. Use parsePaymentInfo() to deserialize a JSON PaymentInfo back into the typed struct.
Escrow Period
The EscrowPeriod contract tracks when a payment was authorized and enforces a configurable waiting period before funds can be released. During the escrow period:
- Payers can request refunds or freeze the payment
- Merchants can refund but cannot release
- After the period — merchants can release funds to themselves
import { X402rClient } from '@x402r/client';
// Check when payment was authorized
const authTime = await client.getAuthorizationTime(paymentInfo, escrowPeriodAddress);
// Check if still within escrow period
const inEscrow = await client.isDuringEscrowPeriod(paymentInfo, escrowPeriodAddress);
if (!inEscrow) {
console.log('Escrow period has passed - funds can be released');
}
Refund Requests
When a payer wants a refund, they create a refund request that goes through approval:
import { RequestStatus } from '@x402r/core';
// RequestStatus values:
RequestStatus.Pending // 0 - Awaiting decision
RequestStatus.Approved // 1 - Approved by merchant/arbiter
RequestStatus.Denied // 2 - Denied by merchant/arbiter
RequestStatus.Cancelled // 3 - Cancelled by payer
Refund Flow
The Nonce Parameter
All refund methods require a nonce parameter. This is the record index from the PaymentIndexRecorder that identifies which charge the refund request applies to. For the first charge, use 0n.
// Request refund for the first charge
await client.requestRefund(paymentInfo, amount, 0n);
// Check status for the first charge
const status = await client.getRefundStatus(paymentInfo, 0n);
Freeze / Unfreeze
The Freeze contract allows payers to freeze a payment during the escrow period, preventing release until the freeze expires or is lifted:
// Payer freezes payment (requires payer authorization)
await client.freezePayment(paymentInfo, freezeAddress);
// Merchant unfreezes payment (requires receiver authorization)
await merchant.unfreezePayment(paymentInfo, freezeAddress);
// Check frozen status
const frozen = await client.isFrozen(paymentInfo, freezeAddress);
Freezing a payment does not automatically escalate to an arbiter. It pauses the release condition to allow time for dispute resolution.
Roles and Permissions
| Role | Can Do |
|---|
| Payer | Request refunds, freeze payments, cancel requests, query escrow state |
| Merchant | Release payments, charge, approve/deny refunds, unfreeze payments |
| Arbiter | Approve/deny disputed refunds, execute refunds, batch operations, registry |
Contract Architecture
Next Steps