# USNpay 电商对接指南 / Integration Guide

> 加密货币收款 API 接入文档 · Crypto Payment Integration
> 适用于任何电商系统 / For any e-commerce platform

---

## 中文版

### 1. 概述

USNpay 提供加密货币收款 API（USDT / USDC / BTC / ETH / TRX 等）。集成后，买家用加密货币付款，资金**直达你绑定的钱包**，非托管、实时到账。

集成只需三步：

1. **创建订单** — 你的服务器调用 `POST /v1/orders`，得到收银台地址（`pay_url`）
2. **跳转付款** — 把买家引导到 `pay_url` 完成付款
3. **接收回调** — 到账后 USNpay 向你的 `notify_url` 推送 Webhook，你验签后更新订单状态

> ⚠️ **签名必须在服务端完成**，API Secret 绝不能出现在前端/浏览器中。

### 2. 准备

在 USNpay「会员中心 → API 密钥」获取：

- **商户 ID**（Merchant ID）：形如 `M88021`
- **API Key**（公钥）：`pk_live_xxx`
- **API Secret**（私钥）：用于请求签名，仅创建时显示一次
- **Webhook 密钥**：用于验证回调签名

并确保已**绑定收款钱包**（否则下单失败）。

### 3. 鉴权与签名

所有 API 请求需带以下请求头：

| 请求头 | 说明 |
|--------|------|
| `X-Merchant-Id` | 商户 ID |
| `X-Api-Key` | API 公钥 |
| `X-Timestamp` | Unix 秒级时间戳（±300 秒内有效，防重放） |
| `X-Signature` | HMAC-SHA256 签名 |

**签名算法**：

```
payload   = METHOD + "\n" + path + "\n" + timestamp + "\n" + body
signature = HMAC-SHA256(payload, API_Secret)   // 输出小写十六进制
```

- `METHOD`：大写请求方法，如 `POST`
- `path`：请求路径（不含域名、不含查询串），如 `/v1/orders`
- `timestamp`：与 `X-Timestamp` 头相同
- `body`：请求体原文（GET 请求为空字符串）

### 4. 创建订单

`POST /v1/orders`

请求体（JSON）：

| 字段 | 必填 | 说明 |
|------|------|------|
| `fiat` | 是 | 计价法币，如 `USD`、`CNY` |
| `amount` | 是 | 法币金额，字符串，如 `"99.00"` |
| `asset` | 否 | 收款币种，如 `USDT`（默认平台配置） |
| `network` | 否 | 网络，如 `TRC20` |
| `subject` | 否 | 订单标题 |
| `out_trade_no` | 否 | 你系统的订单号（幂等键，强烈建议传） |
| `notify_url` | 否 | 回调地址（Webhook 推送到此） |
| `return_url` | 否 | 付款后买家返回的地址 |

成功响应：

```json
{
  "code": 0,
  "message": "订单创建成功",
  "data": {
    "order_no": "CP-20260531-XXXX",
    "out_trade_no": "10086",
    "fiat": "USD",
    "fiat_amount": "99.00",
    "crypto_amount": "99.012",
    "pay_address": "T....",
    "pay_url": "https://usnpay.com/pay/CP-20260531-XXXX",
    "status": "pending",
    "expired_at": "2026-05-31 12:30:00"
  }
}
```

把买家跳转到 `data.pay_url` 即可。

### 5. Webhook 回调

到账后，USNpay 向你的 `notify_url` 发送 `POST` 请求：

请求头：`X-ChainPay-Signature: <hmac>`（用 **Webhook 密钥** 对请求体原文做 HMAC-SHA256）

请求体：

```json
{
  "order_no": "CP-20260531-XXXX",
  "out_trade_no": "10086",
  "status": "completed",
  "fiat": "USD",
  "fiat_amount": "99.00",
  "crypto_amount": "99.012",
  "paid_amount": "99.012",
  "tx_hash": "....",
  "timestamp": 1716883200
}
```

**验签**：用 Webhook 密钥对收到的请求体原文计算 HMAC-SHA256，与 `X-ChainPay-Signature` 比对（恒定时间比较）。一致才可信。

`status` 含义：`completed` / `paid` = 已收款；`expired` = 超时；`refunded` = 已退款。

收到并处理成功后，返回 HTTP 200 + 文本 `ok`。处理要**幂等**（同一订单可能收到多次回调）。

### 6. 通用集成代码

#### PHP

```php
<?php
function usnpay_request($method, $path, $bodyArr, $cfg) {
    $ts   = (string) time();
    $body = $bodyArr ? json_encode($bodyArr, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : '';
    $sig  = hash_hmac('sha256', strtoupper($method)."\n".$path."\n".$ts."\n".$body, $cfg['secret']);
    $ch = curl_init($cfg['base'] . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => strtoupper($method),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS     => $body,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-Merchant-Id: ' . $cfg['merchant_id'],
            'X-Api-Key: '     . $cfg['api_key'],
            'X-Timestamp: '   . $ts,
            'X-Signature: '   . $sig,
        ],
    ]);
    $res = curl_exec($ch);
    curl_close($ch);
    return json_decode($res, true);
}

// 创建订单并跳转
$cfg = [
    'base'        => 'https://usnpay.com',
    'merchant_id' => 'M88021',
    'api_key'     => 'pk_live_xxx',
    'secret'      => 'sk_live_xxx',
];
$resp = usnpay_request('POST', '/v1/orders', [
    'fiat'         => 'USD',
    'amount'       => '99.00',
    'asset'        => 'USDT',
    'out_trade_no' => '10086',
    'notify_url'   => 'https://yourstore.com/usnpay-notify.php',
    'return_url'   => 'https://yourstore.com/order/done',
], $cfg);
if (($resp['code'] ?? -1) === 0) {
    header('Location: ' . $resp['data']['pay_url']);
    exit;
}

// 验证 Webhook 回调
function usnpay_verify_webhook($webhookSecret) {
    $payload = file_get_contents('php://input');
    $sig = $_SERVER['HTTP_X_CHAINPAY_SIGNATURE'] ?? '';
    $expected = hash_hmac('sha256', $payload, $webhookSecret);
    return hash_equals($expected, $sig) ? json_decode($payload, true) : null;
}
```

#### Python

```python
import time, hmac, hashlib, json, requests

def usnpay_request(method, path, body_arr, cfg):
    ts = str(int(time.time()))
    body = json.dumps(body_arr, separators=(',', ':'), ensure_ascii=False) if body_arr else ''
    msg = f"{method.upper()}\n{path}\n{ts}\n{body}"
    sig = hmac.new(cfg['secret'].encode(), msg.encode(), hashlib.sha256).hexdigest()
    headers = {
        'Content-Type': 'application/json',
        'X-Merchant-Id': cfg['merchant_id'],
        'X-Api-Key': cfg['api_key'],
        'X-Timestamp': ts,
        'X-Signature': sig,
    }
    r = requests.request(method, cfg['base'] + path, headers=headers, data=body, timeout=30)
    return r.json()

# 验证 Webhook
def verify_webhook(raw_body: bytes, signature: str, webhook_secret: str):
    expected = hmac.new(webhook_secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return json.loads(raw_body) if hmac.compare_digest(expected, signature) else None
```

#### Node.js

```javascript
const crypto = require('crypto');

async function usnpayRequest(method, path, bodyObj, cfg) {
  const ts = String(Math.floor(Date.now() / 1000));
  const body = bodyObj ? JSON.stringify(bodyObj) : '';
  const msg = `${method.toUpperCase()}\n${path}\n${ts}\n${body}`;
  const sig = crypto.createHmac('sha256', cfg.secret).update(msg).digest('hex');
  const res = await fetch(cfg.base + path, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'X-Merchant-Id': cfg.merchantId,
      'X-Api-Key': cfg.apiKey,
      'X-Timestamp': ts,
      'X-Signature': sig,
    },
    body: body || undefined,
  });
  return res.json();
}

// 验证 Webhook（Express，需用 raw body）
function verifyWebhook(rawBody, signature, webhookSecret) {
  const expected = crypto.createHmac('sha256', webhookSecret).update(rawBody).digest('hex');
  const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature || ''));
  return ok ? JSON.parse(rawBody) : null;
}
```

### 7. 常见问题

- **签名验证失败**：检查 ① body 是否与签名时完全一致（不要二次序列化）② 服务器时间是否同步（±300 秒）③ path 是否含了域名或查询串（不应包含）。
- **下单失败**：确认已绑定对应币种的收款钱包。
- **重复回调**：用 `out_trade_no` 做幂等，已处理的订单直接返回 `ok`。
- **必须 HTTPS**：收款页与回调地址都应为 HTTPS。

---

## English Version

### 1. Overview

USNpay provides a crypto payment API (USDT / USDC / BTC / ETH / TRX, etc.). Once integrated, buyers pay with crypto and funds go **straight to your own wallet** — non-custodial, settled in real time.

Integration takes three steps:

1. **Create an order** — your server calls `POST /v1/orders` and receives a checkout URL (`pay_url`)
2. **Redirect to pay** — send the buyer to `pay_url`
3. **Receive the callback** — once paid, USNpay pushes a Webhook to your `notify_url`; verify the signature and update the order

> ⚠️ **Signing must happen server-side.** Never expose the API Secret in the frontend/browser.

### 2. Prerequisites

From USNpay "Member Center → API Keys":

- **Merchant ID**: e.g. `M88021`
- **API Key** (public): `pk_live_xxx`
- **API Secret** (private): for request signing, shown only once at creation
- **Webhook Secret**: for verifying callback signatures

Make sure a **receiving wallet is bound** (otherwise order creation fails).

### 3. Authentication & Signing

Every API request must include these headers:

| Header | Description |
|--------|-------------|
| `X-Merchant-Id` | Merchant ID |
| `X-Api-Key` | API public key |
| `X-Timestamp` | Unix seconds (valid within ±300s, anti-replay) |
| `X-Signature` | HMAC-SHA256 signature |

**Signing algorithm**:

```
payload   = METHOD + "\n" + path + "\n" + timestamp + "\n" + body
signature = HMAC-SHA256(payload, API_Secret)   // lowercase hex
```

- `METHOD`: uppercase HTTP method, e.g. `POST`
- `path`: request path (no domain, no query string), e.g. `/v1/orders`
- `timestamp`: same as the `X-Timestamp` header
- `body`: raw request body (empty string for GET)

### 4. Create Order

`POST /v1/orders` — JSON body:

| Field | Required | Description |
|-------|----------|-------------|
| `fiat` | Yes | Pricing currency, e.g. `USD` |
| `amount` | Yes | Fiat amount as string, e.g. `"99.00"` |
| `asset` | No | Token, e.g. `USDT` |
| `network` | No | Network, e.g. `TRC20` |
| `subject` | No | Order title |
| `out_trade_no` | No | Your order number (idempotency key, strongly recommended) |
| `notify_url` | No | Webhook callback URL |
| `return_url` | No | Where the buyer returns after paying |

On success, redirect the buyer to `data.pay_url` (see the JSON example in the Chinese section).

### 5. Webhook Callback

On payment, USNpay sends a `POST` to your `notify_url` with header `X-ChainPay-Signature` (HMAC-SHA256 of the raw body using your **Webhook Secret**).

**Verify**: compute HMAC-SHA256 of the received raw body with the Webhook Secret and compare (constant-time) against `X-ChainPay-Signature`. Process only if they match.

`status`: `completed` / `paid` = received; `expired` = timed out; `refunded` = refunded.

Return HTTP 200 with body `ok`. Make processing **idempotent** (the same order may be notified more than once).

### 6. Integration Code

See the PHP / Python / Node.js snippets in the Chinese section above — the code is language-agnostic and works the same regardless of UI language.

### 7. FAQ

- **Signature mismatch**: check that ① the body matches exactly what was signed (don't re-serialize), ② server time is synced (±300s), ③ `path` excludes domain and query string.
- **Order creation fails**: ensure a receiving wallet for the token is bound.
- **Duplicate callbacks**: use `out_trade_no` for idempotency; return `ok` for already-processed orders.
- **HTTPS required**: both checkout and callback URLs should be HTTPS.

---

© 2026 USNpay. Integration reference document.
