Skip to content

Payment API

This page covers the current payment integration flow: creating transactions, redirecting users, handling callbacks, and checking order status.

Create Transaction

Create a new payment order. Epusdt locks a wallet address and amount for the order window.

Live endpoint:

POST /payments/epusdt/v1/order/create-transaction

Also available:

POST /payments/gmpay/v1/order/create-transaction

TIP

/payments/... is the current live API prefix. /api/v1/order/create-transaction is a legacy path from older docs, not a registered route in current source.

Request Parameters

Request method/content-type for create-order:

  • POST
  • application/json

INFO

Signature verification runs on the raw incoming JSON body before the legacy /payments/epusdt/v1/... wrapper injects default values. That means requests omitting currency, token, and network on the Epusdt-compatible route are still valid as long as the signature matches the fields you actually sent.

After signature verification passes, the compatibility wrapper rewrites the request body and fills missing values with cny, usdt, and TRON before the shared create-order handler validates it.

FieldTypeRequiredDescription
order_idstringYour unique order ID, max 32 chars
amountfloatFiat amount; must be greater than 0.01
notify_urlstringCallback URL; Epusdt POSTs here after payment success
redirect_urlstringCustomer redirect URL after payment
currencystring✅*Fiat currency code
tokenstring✅*Token symbol
networkstring✅*Blockchain network
signaturestringMD5 signature

* The shared request validator requires currency, token, and network. On the live /payments/epusdt/v1/... route, current source injects defaults when omitted:

  • currency = cny
  • token = usdt
  • network = TRON

The /payments/gmpay/v1/... route does not inject these defaults.

Signature Generation

Generate signature with these rules:

  1. Collect all non-empty parameters except signature
  2. Sort by key in ASCII ascending order
  3. Join as key=value&key=value
  4. Append api_auth_token directly to the end
  5. Compute lowercase MD5

Example

Given:

order_id = "20220201030210321"
amount = 42
notify_url = "http://example.com/notify"
redirect_url = "http://example.com/redirect"

Token:

api_auth_token = "epusdt_password_xasddawqe"

Sorted string:

amount=42&notify_url=http://example.com/notify&order_id=20220201030210321&redirect_url=http://example.com/redirect

Append token:

amount=42&notify_url=http://example.com/notify&order_id=20220201030210321&redirect_url=http://example.com/redirectepusdt_password_xasddawqe

MD5:

1cd4b52df5587cfb1968b0c0c6e156cd

Code Examples

php
<?php
function epusdtSign(array $parameter, string $signKey): string {
    ksort($parameter);
    reset($parameter);
    $sign = '';
    foreach ($parameter as $key => $val) {
        if ($val === '' || $val === null) continue;
        if ($key === 'signature') continue;
        if ($sign !== '') $sign .= '&';
        $sign .= "$key=$val";
    }
    return md5($sign . $signKey);
}
python
import hashlib

def epusdt_sign(params: dict, token: str) -> str:
    filtered = {k: v for k, v in params.items() if v != '' and v is not None and k != 'signature'}
    sorted_str = '&'.join(f"{k}={v}" for k, v in sorted(filtered.items()))
    return hashlib.md5((sorted_str + token).encode()).hexdigest()
go
import (
    "crypto/md5"
    "fmt"
    "sort"
    "strings"
)

func EpusdtSign(params map[string]string, token string) string {
    keys := make([]string, 0)
    for k, v := range params {
        if v != "" && k != "signature" {
            keys = append(keys, k)
        }
    }
    sort.Strings(keys)
    parts := make([]string, 0, len(keys))
    for _, k := range keys {
        parts = append(parts, k+"="+params[k])
    }
    raw := strings.Join(parts, "&") + token
    return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
}

Response

Current JSON API responses are wrapped in HTTP 200. Inspect top-level status_code for the business result.

json
{
  "status_code": 200,
  "message": "success",
  "data": {
    "trade_id": "EP20240101XXXXXXXX",
    "order_id": "ORDER_20240101_001",
    "amount": 100.0,
    "currency": "cny",
    "actual_amount": 14.28,
    "receive_address": "TXXXXXXXXXXXXXXXXXXxx",
    "token": "usdt",
    "expiration_time": 1712500000,
    "payment_url": "http://your-server:8000/pay/checkout-counter/EP20240101XXXXXXXX"
  },
  "request_id": "b1344d70-ff19-4543-b601-37abfb3b3686"
}

Response Fields

FieldTypeDescription
trade_idstringEpusdt internal trade ID
order_idstringYour original order ID
amountfloatOriginal fiat amount
currencystringFiat currency code
actual_amountfloatActual token amount the customer needs to pay
receive_addressstringPayment address
tokenstringToken symbol, such as usdt
expiration_timeintExpiration Unix timestamp in seconds
payment_urlstringHosted checkout URL

Current create-order response does not include network.

Payment Flow

1. Your server calls the create-transaction API
2. Epusdt returns `trade_id`, `receive_address`, and `payment_url`
3. Redirect the customer to `payment_url` or render the address / QR code yourself
4. Customer sends funds to `receive_address`
5. Epusdt detects payment on-chain
6. Epusdt POSTs a callback to `notify_url`
7. Your server verifies the signature and returns exact body `ok`
8. If `redirect_url` exists, the hosted checkout page redirects the user after success

Checkout Page

Hosted checkout page:

GET /pay/checkout-counter/:trade_id

Example:

https://pay.example.com/pay/checkout-counter/EP20240101XXXXXXXX

This page route is separate from the API prefix. In source, payment_url is built as:

text
{app_uri}/pay/checkout-counter/{trade_id}

So if app_uri is https://pay.example.com, the checkout URL becomes https://pay.example.com/pay/checkout-counter/{trade_id}.

If your public deployment includes an extra external prefix, for example https://example.com/epusdt, set app_uri to that public base and let your proxy rewrite requests to the app's root-mounted /pay/... routes.

Check Order Status

Status polling endpoint:

GET /pay/check-status/:trade_id

Example:

GET https://pay.example.com/pay/check-status/EP20240101XXXXXXXX

This is a checkout polling endpoint under /pay/..., not a create-order API route.

Response:

json
{
  "status_code": 200,
  "message": "success",
  "data": {
    "trade_id": "EP20240101XXXXXXXX",
    "status": 2
  },
  "request_id": "b1344d70-ff19-4543-b601-37abfb3b3686"
}

Order Status Values

StatusMeaning
1Waiting for payment
2Payment success
3Expired

Callback / Webhook

When payment is confirmed on-chain, Epusdt POSTs to the notify_url from the create-order request.

The callback body is signed with the same MD5 algorithm and api_auth_token used for create-order requests, and source sends it as a JSON body.

Callback Payload

json
{
  "trade_id": "EP20240101XXXXXXXX",
  "order_id": "ORDER_20240101_001",
  "amount": 100.0,
  "actual_amount": 14.28,
  "receive_address": "TXXXXXXXXXXXXXXXXXXxx",
  "token": "usdt",
  "block_transaction_id": "4d7d...",
  "status": 2,
  "signature": "a1b2c3d4e5f6..."
}

Callback Fields

FieldTypeDescription
trade_idstringEpusdt internal trade ID
order_idstringYour original order ID
amountfloatOriginal fiat amount
actual_amountfloatActual token amount received
receive_addressstringPayment address
tokenstringToken symbol
block_transaction_idstringOn-chain transaction ID
statusintOrder status (2 = paid)
signaturestringMD5 signature for verification

Current callback payload does not include network.

Verifying the Callback Signature

Use the same signing rules as the create-order request:

  1. Keep all non-empty fields except signature
  2. Sort by key
  3. Join as key=value&key=value
  4. Append api_auth_token
  5. Compute lowercase MD5 and compare

Responding to the Callback

Important

Your server must return the exact plain-text body ok with HTTP 200.

Examples:

  • ok
  • OK, ok\n, {"message":"ok"}

Current retry behavior is configuration-driven, not a fixed hardcoded retry count:

  • order_notice_max_retry — maximum retry count after the first attempt
  • callback_retry_base_seconds — exponential backoff base delay

Default .env.example values are:

  • order_notice_max_retry=0
  • callback_retry_base_seconds=5

So with default settings, Epusdt performs the first callback attempt, but no extra retries after a failure.

Full Integration Example

python
import hashlib
import requests

API_BASE = "https://pay.example.com"
API_AUTH_TOKEN = "your_api_auth_token"

def epusdt_sign(params: dict, token: str) -> str:
    filtered = {k: v for k, v in params.items() if v != '' and v is not None and k != 'signature'}
    sorted_str = '&'.join(f"{k}={v}" for k, v in sorted(filtered.items()))
    return hashlib.md5((sorted_str + token).encode()).hexdigest()

def create_order(order_id: str, amount: float, notify_url: str):
    params = {
        "order_id": order_id,
        "amount": amount,
        "notify_url": notify_url,
        "currency": "cny",
        "token": "usdt",
        "network": "TRON",
    }
    params["signature"] = epusdt_sign(params, API_AUTH_TOKEN)

    response = requests.post(
        f"{API_BASE}/payments/epusdt/v1/order/create-transaction",
        json=params,
    )
    result = response.json()

    if result["status_code"] == 200:
        data = result["data"]
        print(f"Trade ID:     {data['trade_id']}")
        print(f"USDT Amount:  {data['actual_amount']}")
        print(f"Address:      {data['receive_address']}")
        print(f"Payment URL:  {data['payment_url']}")
        return data
    else:
        print(f"Error: {result['message']}")
        return None

create_order("ORDER_001", 100.00, "https://example.com/callback")

This explicit example works on both live create-order routes. If you call /payments/epusdt/v1/..., current source also accepts requests that omit currency, token, and network because that compatibility route injects defaults after signature verification.

If you expose the service behind a proxy subpath, keep the request URL and API_BASE aligned with the public base URL you configured in app_uri.

Error Handling

Status CodeMessageHow to Fix
400system / validation errorCheck request body and required fields
401signature verification failedVerify your signature logic and api_auth_token
10002order already existsUse a unique order_id
10003no available wallet addressAdd more wallet addresses
10004invalid payment amountCheck amount limits and minimum value
10005no available amount channelRetry with another amount or review channel allocation
10008order does not existVerify the queried trade_id
最近更新