Skip to content

Python — API Checkout

請替換示例憑證

運行前請替換所有佔位符:

  • "your-api-secret" → 商戶後台的實際 API Secret
  • "MCH_20240101_ABC123" → 您的實際商戶號

API Checkout 不使用 X-Api-Key,只需 X-TimestampX-Signature

需要開通權限

API Checkout 為獨立產品,需單獨申請開通。請聯繫技術支持啟用。

依賴

bash
pip install requests flask

簽名函數

API Checkout 使用與託管支付相同的簽名算法,但不包含 X-Api-Key

python
import base64
import hashlib
import hmac
import json
import time
import requests

BASE_URL    = "https://api.paystablecoin.global"
MERCHANT_ID = "MCH_20240101_ABC123"
API_SECRET  = "your-api-secret"


def _hmac_base64(data: str) -> str:
    sig = hmac.new(API_SECRET.encode(), data.encode(), hashlib.sha256).digest()
    return base64.b64encode(sig).decode()


def sign_post(timestamp: str, path: str, body: str) -> str:
    """為 POST 請求生成簽名。"""
    body_hash = base64.b64encode(hashlib.sha256(body.encode()).digest()).decode()
    return _hmac_base64(f"{timestamp}\nPOST\n{path}\n{body_hash}")


def sign_get(timestamp: str, path: str) -> str:
    """為無查詢字符串的 GET 請求生成簽名。"""
    return _hmac_base64(f"{timestamp}\nGET\n{path}")


def verify_webhook(timestamp: str, signature: str, raw_body: str) -> bool:
    """驗證 Webhook 簽名。"""
    expected = _hmac_base64(f"{timestamp}\n{raw_body}")
    return expected == signature

第一步:獲取可用支付方式

渲染支付界面前必須在運行時調用此接口,禁止硬編碼幣種/網絡組合。

python
def get_payment_methods() -> dict:
    timestamp = str(int(time.time() * 1000))
    path = f"/api/v1/merchants/{MERCHANT_ID}/checkout/payment-methods"

    headers = {
        "X-Timestamp": timestamp,
        "X-Signature": sign_get(timestamp, path)
    }

    response = requests.get(BASE_URL + path, headers=headers)
    return response.json()


if __name__ == "__main__":
    methods = get_payment_methods()
    print(json.dumps(methods, indent=2))
    # 每個支付方式包含:
    #   cryptoCurrency, network, networkDisplayName,
    #   minAmount, maxAmount, estimatedConfirmationTimeSec, displayOrder
    # 按 displayOrder 升序排列後展示給用戶。

第二步:創建 Checkout 訂單

客戶選擇幣種和網絡後,立即創建訂單。

python
def create_checkout_order(network: str, currency: str, amount: str) -> dict:
    timestamp = str(int(time.time() * 1000))
    path = f"/api/v1/merchants/{MERCHANT_ID}/checkout/orders"

    # orderAmount.currency 必須為穩定幣:USDC、USDT 或 USD1
    # network 必須與客戶選擇的支付方式匹配
    body = json.dumps({
        "merchantOrderId": f"ORDER_{timestamp}",
        "orderAmount": {"value": amount, "currency": currency},
        "paymentMethodType": "ON_CHAIN_TRANSFER",
        "network": network,
        "expiresAt": "2026-12-31T23:59:59Z",
        "callbackUrl": "https://yoursite.com/webhook/checkout"
    }, separators=(",", ":"))

    headers = {
        "X-Timestamp":  timestamp,
        "X-Signature":  sign_post(timestamp, path, body),
        "Content-Type": "application/json"
    }

    response = requests.post(BASE_URL + path, data=body, headers=headers)
    return response.json()


if __name__ == "__main__":
    result = create_checkout_order(network="tron", currency="USDC", amount="100.50")
    print(json.dumps(result, indent=2))
    # 展示給客戶:
    #   result["data"]["depositAddress"]                     — 文字 + 二維碼
    #   result["data"]["cryptoPaymentAmount"]["value"]       — 需支付的精確金額
    #   result["data"]["networkDisplayName"]                 — 醒目展示(轉錯網絡=資產丟失)
    #   result["data"]["expiresAt"]                          — 倒計時依據
    #
    # 重要:展示 cryptoPaymentAmount,而非 orderAmount

第三步:查詢 Checkout 訂單狀態

當 Webhook 未收到時作為備選方案。

python
def query_checkout_order(merchant_order_id: str) -> dict:
    timestamp = str(int(time.time() * 1000))
    path = f"/api/v1/merchants/{MERCHANT_ID}/checkout/orders/{merchant_order_id}"

    headers = {
        "X-Timestamp": timestamp,
        "X-Signature": sign_get(timestamp, path)
    }

    response = requests.get(BASE_URL + path, headers=headers)
    return response.json()


if __name__ == "__main__":
    # 每 5-10 秒輪詢一次,直到狀態達到終態
    result = query_checkout_order("ORDER_1738112345000")
    status = result.get("data", {}).get("status")
    print(f"狀態:{status}")
    # 終態:SUCCEEDED、FAILED 或 CLOSED

第四步:驗證 Webhook 簽名

API Checkout Webhook 要求嚴格響應:5 秒內返回 HTTP 200 + {"code":"00000"}

python
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route("/webhook/checkout", methods=["POST"])
def checkout_webhook():
    timestamp = request.headers.get("X-Timestamp", "")
    signature = request.headers.get("X-Signature", "")
    raw_body  = request.get_data(as_text=True)

    # 第一步:驗證簽名
    if not verify_webhook(timestamp, signature, raw_body):
        return jsonify({"error": "Invalid signature"}), 401

    # 第二步:驗證時間戳在 5 分鐘內
    if abs(int(time.time() * 1000) - int(timestamp)) > 300_000:
        return jsonify({"error": "Timestamp expired"}), 401

    # 第三步:5 秒內返回 HTTP 200 + {"code":"00000"}(必須)
    # 異步處理業務邏輯

    event = request.get_json(force=True)
    acquiring_order_id = event.get("acquiringOrderId")
    merchant_order_id  = event.get("merchantOrderId")
    # 使用任一 ID 進行去重——同一事件可能多次推送。

    return jsonify({"code": "00000"})

相關文檔

Released under the MIT License.