X402 Order Payment Tutorial

In addition to directly paying via API requests, YuJun also supports paying for orders through the X402 payment console. The core protocol for order payment and API calls is the same: the first request returns 402, the client checks out PAYMENT-SIGNATURE, and then retries the same request.

The difference is that order payment belongs to the platform API and requires an account token; while directly calling the x402.acedata.cloud AI API can be done using only X402, without needing an API Token.

Prepare the Order

Go to the YuJun Console, select the order you need to pay for, and record the order ID.

If you do not have an order yet, you can create a pending payment order on the package page. The order price is based on what is displayed on the page, and the amount in the X402 402 response is the final basis for the signature.

Create Account Token

The order payment request requires an account token. Open the Platform Token Page and create a token in the platform-v1-... format.

Subsequent requests use:

Authorization: Bearer {platform_token}

The account token is different from a regular API Token. A regular API Token is used to consume API quotas; the account token is used to operate platform resources on behalf of your account, such as order payments.

Trigger 402

First, send a request without the PAYMENT-SIGNATURE:

POST https://platform.acedata.cloud/api/v1/orders/{order_id}/pay/
Authorization: Bearer {platform_token}
Content-Type: application/json

{
  "pay_way": "X402"
}

The returned status is 402, and the response includes accepts:

{
  "x402Version": 2,
  "error": "Payment required for this order.",
  "resource": {
    "url": "http://platform.acedata.cloud/api/v1/orders/.../pay/",
    "description": "YuJun Credits x 10.0",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "1200000",
      "payTo": "0x...",
      "maxTimeoutSeconds": 120,
      "extra": {
        "name": "USD Coin",
        "version": "2",
        "chainId": 8453,
        "verifyingContract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "decimals": 6
      }
    }
  ],
  "paywall": {
    "app_name": "YuJun",
    "app_logo": "https://platform.acedata.cloud/favicon.ico"
  }
}

Order payment follows the official x402 v2: x402Version is 2, and network uses CAIP-2 identifiers, with the amount field being amount.

The program output after creating a 10 Credits order and triggering 402:

created order 78481793-304e-47f7-bc0c-8231aec9cc1e
created state Pending
created price 1.26

http_status=402
x402Version 2
error Payment required for this order.
accepts [
  ('eip155:8453', 'exact', '1200000', '0x4F0E2D3477a1B94CF33d16E442CEe4733dadCeE7'),
  ('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', 'exact', '1200000', '5iVXFrYaYWX2GUTbkQj8mDBoBhAX8bneYigS2LJTia43')
]
description YuJun Credits x 10.0

Result explanation:

  • The order status is Pending after successful creation, and payment has not yet been made on-chain.
  • The first pay/ request did not carry PAYMENT-SIGNATURE, so it returned HTTP 402.
  • accepts provides both Base exact and Solana exact, with this tutorial subsequently choosing Base.
  • The price at order creation is 1.26, and after entering the X402 payment process, the X402 payment discount is applied, resulting in a final signed and settled amount of 1.2 USDC, corresponding to 1200000 atomic USDC.

Note that the resource is a field returned by the server and involved in the signature; the client should not modify the protocol, path, or order ID within it.

Sign and Retry

Order payment can reuse the low-level signing functions from @acedatacloud/x402-client or acedatacloud-x402. Here is a TypeScript example:

import { Wallet } from 'ethers';
import { signEVMPayment } from '@acedatacloud/x402-client';

const platformToken = process.env.ACE_PLATFORM_TOKEN!;
const orderId = process.env.ACE_ORDER_ID!;
const wallet = new Wallet(process.env.EVM_PRIVATE_KEY!);

const url = `https://platform.acedata.cloud/api/v1/orders/${orderId}/pay/`;
const body = { pay_way: 'X402' };

const first = await fetch(url, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${platformToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(body)
});

if (first.status !== 402) {
  throw new Error(`expected 402, got ${first.status}`);
}

const paymentRequired = await first.json();
const requirement = paymentRequired.accepts.find(
  (item: any) => item.network === 'eip155:8453' && item.scheme === 'exact'
);

const evmProvider = {
  async request({ method, params }: { method: string; params?: unknown[] }) {
    if (method !== 'eth_signTypedData_v4') throw new Error(`unsupported method: ${method}`);
    const [, typedDataJson] = params as [string, string];
    const typedData = JSON.parse(typedDataJson);
    return wallet.signTypedData(typedData.domain, typedData.types, typedData.message);
  }
};

const envelope = await signEVMPayment(requirement, evmProvider, wallet.address);
const xPayment = Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64');

const paid = await fetch(url, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${platformToken}`,
    'Content-Type': 'application/json',
    'PAYMENT-SIGNATURE': xPayment
  },
  body: JSON.stringify(body)
});

if (!paid.ok) {
  throw new Error(`payment failed: ${paid.status} ${await paid.text()}`);
}

console.log(await paid.json());

The program output after signing and retrying the same order with Base exact:

status 200
payer 0x5d4f08D5c2bb60703284bc06671Eb680fA41B105
has_x_payment_response True
settle_header {'success': True, 'network': 'base', 'transaction': '0xfec08cc00a159ea1ec692b32faa9bf3d17595a986301169e689d94f58bc44151', 'errorReason': None}
order {'id': '78481793-304e-47f7-bc0c-8231aec9cc1e', 'state': 'Finished', 'pay_way': 'X402', 'price': 1.2, 'pay_id': '0xfec08cc00a159ea1ec692b32faa9bf3d17595a986301169e689d94f58bc44151'}

On-chain confirmation result:

tx 0xfec08cc00a159ea1ec692b32faa9bf3d17595a986301169e689d94f58bc44151
status 1
block 46726704
explorer https://basescan.org/tx/0xfec08cc00a159ea1ec692b32faa9bf3d17595a986301169e689d94f58bc44151
transfer {"from":"0x5d4f08D5c2bb60703284bc06671Eb680fA41B105","to":"0x4F0E2D3477a1B94CF33d16E442CEe4733dadCeE7","value":"1200000"}

Result explanation:

  • status 200 indicates that the platform's order payment interface has accepted this PAYMENT-SIGNATURE.
  • has_x_payment_response True indicates that the response header contains a Base64 encoded PAYMENT-RESPONSE receipt.
  • settle_header.success=True and network=base indicate that the Facilitator has completed the Base settlement.
  • The final status of the order is Finished, pay_way is X402, and pay_id is written into the on-chain transaction hash.
  • The Transfer event on BaseScan shows that the payment address transferred 1200000 atomic USDC to the platform's receiving address, which is 1.2 USDC.

Successful Response and Receipt

After the order payment is successful, the response body contains the order information. The platform will also carry a Base64 encoded settlement response in the response header PAYMENT-RESPONSE, common fields after decoding include:

Field Description
success Whether the Facilitator settlement was successful.
transaction On-chain settlement transaction hash.
network Payment network.
payer Payer wallet address.
amount Actual settlement amount, using atomic units.

If you need to reconcile, it is recommended to save the order ID, payer wallet address, transaction, and the final status of the order.

Notes

  • Order payment requires a platform account token and cannot be completed solely with the X402 wallet signature.
  • amount uses USDC atomic units, 1200000 represents 1.2 USDC.
  • Do not manually concatenate the receiving address or asset address; refer to the accepts in the 402 response.
  • If the same PAYMENT-SIGNATURE is submitted multiple times, the Facilitator will perform replay protection based on nonce.