Accept USDT in WooCommerce without a plugin
ChainPay does not ship a WooCommerce plugin yet. The API is small enough to integrate with about sixty lines of PHP: show a deposit address on the order page and mark the order paid when the webhook arrives.
How it works
- The customer places an order with a payment method that leaves it
pendingoron-hold(for example "Direct bank transfer", renamed to "USDT"). - The thank-you page asks ChainPay for a deposit address for that order and displays it.
- The customer pays. ChainPay sweeps the funds to your wallet and sends a webhook to your site.
- The webhook handler checks the secret and the amount and calls
payment_complete().
The mu-plugin
Save this as wp-content/mu-plugins/chainpay.php:
<?php
// wp-content/mu-plugins/chainpay.php
const CHAINPAY_API = 'https://api.chainpay.site';
const CHAINPAY_SECRET = 'change-me';
const CHAINPAY_WALLET = 'TYourOwnTronWallet...';
function chainpay_address(WC_Order $order): ?string {
$cached = $order->get_meta('_chainpay_address');
if ($cached) {
return $cached;
}
$res = wp_remote_post(CHAINPAY_API . '/deposit-address', [
'headers' => ['Content-Type' => 'application/json'],
'timeout' => 15,
'body' => wp_json_encode([
'ticker' => 'trc20/usdt',
'callbackUrl' => rest_url('chainpay/v1/webhook') . '?token=' . CHAINPAY_SECRET,
'forwardAddress' => CHAINPAY_WALLET,
'externalReference' => 'wc-' . $order->get_id(),
]),
]);
if (is_wp_error($res)) {
return null;
}
$address = json_decode(wp_remote_retrieve_body($res), true)['address'] ?? null;
if ($address) {
$order->update_meta_data('_chainpay_address', $address);
$order->save();
}
return $address;
}
add_action('woocommerce_thankyou', function ($order_id) {
$order = wc_get_order($order_id);
if (!$order || !$order->has_status(['pending', 'on-hold'])) {
return;
}
$address = chainpay_address($order);
if ($address) {
echo '<p>Send ' . esc_html($order->get_total()) . ' USDT (TRC20) to <code>'
. esc_html($address) . '</code></p>';
}
});
add_action('rest_api_init', function () {
register_rest_route('chainpay/v1', '/webhook', [
'methods' => 'POST',
'permission_callback' => '__return_true',
'callback' => function (WP_REST_Request $req) {
if (!hash_equals(CHAINPAY_SECRET, (string) $req->get_param('token'))) {
return new WP_REST_Response(null, 403);
}
$event = $req->get_json_params();
if (($event['status'] ?? '') !== 'confirmed') {
return ['ok' => true];
}
$order = wc_get_order((int) str_replace('wc-', '', $event['externalReference'] ?? ''));
if ($order && !$order->is_paid() && (float) $event['amount'] >= (float) $order->get_total()) {
$order->payment_complete($event['txHash']);
}
return ['ok' => true];
},
]);
});
Notes
- Set the store currency to USD (or another dollar-pegged value) so that the order total equals the USDT amount. For other currencies convert first, for example with
GET /rates/convert. - Replace
CHAINPAY_SECRETwith a long random value and keep it out of version control. - The webhook URL must be publicly reachable over HTTPS. Pretty permalinks must be enabled so that
rest_url()works. payment_complete()is guarded byis_paid(), so repeated deliveries do not process an order twice.- Underpayments are ignored by the amount check; handle them manually or extend the handler to add an order note.
- For BNB Smart Chain use
bep20/usdtand a0x…wallet instead oftrc20/usdt.