Skip to content

E-commerce integration ​

Who this is for ​

Merchants on Shopify, WooCommerce or a custom storefront who want a "pay with USDT" option at checkout: the buyer selects it, sees payment details, and the order is marked paid automatically once payment confirms.

Before you start ​

  • You have an API KEY (see Get started) and a collection address configured.
  • Your server can receive webhooks (the platform's payment callback) and call the Open API (with the KEY).
  • Decide who triggers fulfilment: drive the order state from the platform's payment callback, not from a button the buyer clicks.

Order creation happens server-side, never in the checkout page

The checkout page is a browser. Every order request comes from your store backend (KEY included there), and the checkout page only displays payment details or redirects.

The shape: a custom payment method plus a webhook ​

All three platforms work the same way underneath:

Buyer selects "Pay with USDT" at checkout
        │
        ▼
Your store backend ──(with the API KEY)──▶ platform createOrder
        │
        ▼
Show payment details (address + amount + chain) or redirect to the platform checkout
        │
        ▼
Buyer pays ──▶ on-chain confirmation ──▶ platform calls your webhook
        │
        ▼
Your store backend marks the order paid / triggers fulfilment

WooCommerce: a custom payment gateway ​

WooCommerce integrates through a payment gateway plugin. The core is creating the order in process_payment and a REST endpoint to receive the callback:

php
<?php
// Simplified: a WooCommerce custom payment gateway
class WC_Gateway_Web3SDK extends WC_Payment_Gateway {
    public function __construct() {
        $this->id   = 'web3sdk';
        $this->method_title = 'USDT (Web3SDK)';
        $this->has_fields = false;
        parent::__construct();
    }

    public function process_payment($order_id) {
        $order = wc_get_order($order_id);

        // 1) Create the order on your store backend: call the Open API with the KEY
        $resp = wp_remote_post(getenv('API_BASE') . '/api/pay/open/createOrder', [
            'headers' => [
                'X-Pay-Api-Key' => getenv('PAY_API_KEY'),   // server side only
                'Content-Type'  => 'application/json',
            ],
            'body' => wp_json_encode([
                'merchantOrderNo' => (string) $order_id,     // idempotency key
                'chainId'         => 2,
                'tokenId'         => 1,
                'amount'          => (string) $order->get_total(),  // decimal string in token units
                'subject'         => 'Order #' . $order_id,
                'notifyUrl'       => home_url('/?wc-api=web3sdk_notify'),
                'returnUrl'       => $this->get_return_url($order),
            ]),
            'timeout' => 15,
        ]);

        $data = json_decode(wp_remote_retrieve_body($resp), true);
        if (empty($data['code']) && !isset($data['data']['order'])) {
            wc_add_notice('USDT order failed, please retry', 'error');
            return ['result' => 'failure'];
        }

        $o = $data['data']['order'];
        // 2) Store the payment details on the order; show them after checkout
        $order->update_meta_data('_web3_order_no',     $o['orderNo']);
        $order->update_meta_data('_web3_pay_address',  $o['payAddress']);
        $order->update_meta_data('_web3_pay_amount',   $o['payAmountDisplay']);
        $order->update_meta_data('_web3_chain',        $o['chainKey']);
        $order->save();

        return ['result' => 'success', 'redirect' => $this->get_return_url($order)];
    }
}

Receiving the callback (verify, deduplicate, update the order):

php
add_action('woocommerce_api_web3sdk_notify', function () {
    $raw = file_get_contents('php://input');                       // raw body
    $ts  = $_SERVER['HTTP_X_PAY_TIMESTAMP'] ?? '';
    $sig = $_SERVER['HTTP_X_PAY_SIGNATURE'] ?? '';
    $key = getenv('PAY_API_KEY');

    $want = hash_hmac('sha256', $ts . $raw, $key);                 // timestamp + raw body
    if (!hash_equals($want, $sig)) {                               // constant-time comparison
        status_header(401); exit;
    }

    $p = json_decode($raw, true);
    $order_id = intval($p['merchantOrderNo'] ?? 0);
    $order = $order_id ? wc_get_order($order_id) : null;
    if (!$order) { status_header(200); exit; }                     // not our order; still answer 2xx

    // Idempotent: already handled? answer and return
    if ($order->get_meta('_web3_tx_hash') === ($p['txHash'] ?? '')) {
        status_header(200); exit;
    }

    // Only "confirmed" counts; and do not trust the callback alone — optionally query the API first
    if (($p['status'] ?? 0) >= 3) {
        $order->update_meta_data('_web3_tx_hash', $p['txHash'] ?? '');
        $order->payment_complete($p['txHash'] ?? '');              // triggers WooCommerce's paid flow
    }
    status_header(200); exit;                                      // 2xx stops the retries
});

Shopify: a custom payment method ​

Shopify's custom payment method sends the buyer to a URL you control at checkout. There you show payment details, and once payment confirms you mark the order paid through the Order API:

js
// 1) Your server: create the order in the redirect landing page
app.get('/shopify/pay', async (req, res) => {
  const { orderId, total } = req.query
  const r = await fetch(`${process.env.API_BASE}/api/pay/open/createOrder`, {
    method: 'POST',
    headers: { 'X-Pay-Api-Key': process.env.PAY_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      merchantOrderNo: String(orderId),
      chainId: 2, tokenId: 1, amount: String(total),
      subject: `Shopify Order ${orderId}`,
      notifyUrl: 'https://your-shop.com/shopify/notify',
    }),
  }).then((x) => x.json())
  res.render('pay', { order: r.data.order })
})

// 2) On the platform callback, mark the Shopify order paid
app.post('/shopify/notify', express.raw({ type: '*/*' }), async (req, res) => {
  const raw = req.body.toString('utf8')
  if (!verify(raw, req.get('X-Pay-Timestamp'), req.get('X-Pay-Signature'))) {
    return res.sendStatus(401)
  }
  const p = JSON.parse(raw)
  if (isProcessed(p.merchantOrderNo)) return res.sendStatus(200)   // idempotent

  await fetch(`https://${process.env.SHOP}/admin/api/2024-07/orders/${p.merchantOrderNo}/transactions.json`, {
    method: 'POST',
    headers: {
      'X-Shopify-Access-Token': process.env.SHOPIFY_TOKEN,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      transaction: { kind: 'capture', status: 'success', amount: p.amountDisplay },
    }),
  })
  markPaid(p.merchantOrderNo, p.txHash)
  res.sendStatus(200)
})

Shopify API versions and fields change; check the official docs. What is shown here is the integration shape.

Callback handling ​

Whichever platform you use, callbacks follow the same rules:

  • Verify: HMAC-SHA256(apiKey, timestamp + raw body), compared in constant time.
  • Idempotent: deduplicate on merchantOrderNo (usually your store's order id) so you never ship twice.
  • Return fast: about a 10-second timeout; if updating the order is slow, persist first and act asynchronously.
  • 2xx stops retries: a 2xx means you received it; any other status retries on the backoff schedule.
  • Query is authoritative: verify with Query order before fulfilment.

FAQ ​

QuestionAnswer
Can I drop a JS snippet in the checkout page to create the order?No — that exposes the KEY. Order creation must be server-side
Do platform callbacks clash with the store's own webhooks?No. The platform callback drives "order paid"; the store's own webhooks stay as they are
amount or the store total?The Open API wants a decimal string in token units, which is the store total in the same unit
What about price volatility?Pricing is in a stablecoin, so you pass the order amount directly; no FX conversion involved
What if a callback is lost?The platform retries; you can also reconcile on a schedule with the query API
How long is stock held?For the order's validity window (expiresAt); after that it is void and you need not wait

Web3 payment infrastructure for global merchants