X402 Order Payment Tutorial

In addition to directly paying via API requests, Ace Data Cloud 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 X-Payment, and then retries the same request.

The difference is that order payment belongs to the platform API and requires an account token; whereas 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 Ace Data Cloud 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 maxAmountRequired in the X402 402 response is the final basis for signing.

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 will 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 X-Payment:

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": 1,
  "error": "Payment required for this order.",
  "accepts": [
    {
      "scheme": "exact",
      "network": "base",
      "maxAmountRequired": "1200000",
      "resource": "http://platform.acedata.cloud/api/v1/orders/.../pay/",
      "payTo": "0x...",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "extra": {
        "name": "USD Coin",
        "version": "2",
        "chainId": 8453,
        "verifyingContract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
      }
    }
  ]
}

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 1
error Payment required for this order.
accepts [
  ('base', 'exact', '1200000', '0x4F0E2D3477a1B94CF33d16E442CEe4733dadCeE7'),
  ('solana', 'exact', '1200000', '5iVXFrYaYWX2GUTbkQj8mDBoBhAX8bneYigS2LJTia43')
]
description Ace Data Cloud Credits x 10.0

Result explanation:

  • After the order is successfully created, the status is Pending, and no on-chain payment has been made yet.
  • The first pay/ request did not carry X-Payment, 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 upon entering the X402 payment process, the X402 payment discount is applied, resulting in a signed and settled amount of 1.2 USDC, corresponding to 1200000 atomic USDC.

Note that the resource here is a field returned by the server and involved in the signing; 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. Below 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 === 'base' && 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',
    'X-Payment': 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 X-Payment.
  • has_x_payment_response True indicates that the response header contains a Base64 encoded X-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 X-PAYMENT-RESPONSE, and 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 at the same time.

Notes

  • Order payment requires a platform account token and cannot be completed solely with the X402 wallet signature.
  • maxAmountRequired uses USDC atomic units, where 1200000 represents 1.2 USDC.
  • Do not construct the receiving address or asset address on your own; refer to the accepts in the 402 response.
  • If the same X-Payment is submitted multiple times, the Facilitator will implement replay protection based on nonce.