Skip to content

Python — API Checkout ​

REPLACE EXAMPLE CREDENTIALS

Replace all placeholder values before running:

  • "your-api-secret" → Your actual API Secret from the merchant portal
  • "MCH_20240101_ABC123" → Your actual Merchant ID

API Checkout does not use X-Api-Key. Only X-Timestamp and X-Signature are required.

PERMISSION REQUIRED

API Checkout must be explicitly enabled for your account. Contact support to activate it.

Dependencies ​

bash
pip install requests flask

Signature Helper ​

API Checkout uses the same signing algorithm as Hosted Payments but without 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:
    """Sign a POST request."""
    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:
    """Sign a GET request with no query string."""
    return _hmac_base64(f"{timestamp}\nGET\n{path}")


def verify_webhook(timestamp: str, signature: str, raw_body: str) -> bool:
    """Verify the signature of an incoming webhook."""
    expected = _hmac_base64(f"{timestamp}\n{raw_body}")
    return expected == signature

Step 1: Fetch Available Payment Methods ​

Call this at runtime before rendering your payment UI. Never hardcode currency/network combinations.

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))
    # Each method includes:
    #   cryptoCurrency, network, networkDisplayName,
    #   minAmount, maxAmount, estimatedConfirmationTimeSec, displayOrder
    # Sort by displayOrder (ascending) and render options to the user.

Step 2: Create a Checkout Order ​

After the customer selects a currency and network, create the order immediately.

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 must be a stablecoin (USDC, USDT, or USD1)
    # network must match the customer's selected payment method
    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))
    # Display to the customer:
    #   result["data"]["depositAddress"]              — text + QR code
    #   result["data"]["cryptoPaymentAmount"]["value"] — exact amount to send
    #   result["data"]["networkDisplayName"]           — PROMINENT WARNING
    #   result["data"]["expiresAt"]                   — countdown timer
    #
    # IMPORTANT: Show cryptoPaymentAmount, NOT orderAmount

Step 3: Query Checkout Order Status ​

Use as a fallback if the webhook is not received.

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__":
    # Poll every 5–10 seconds until status is terminal
    result = query_checkout_order("ORDER_1738112345000")
    status = result.get("data", {}).get("status")
    print(f"Status: {status}")
    # Terminal states: SUCCEEDED, FAILED, CLOSED

Step 4: Verify Incoming Webhook ​

API Checkout webhooks require a strict response: HTTP 200 with {"code":"00000"} within 5 seconds.

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)

    # Step 1: Verify signature
    if not verify_webhook(timestamp, signature, raw_body):
        return jsonify({"error": "Invalid signature"}), 401

    # Step 2: Verify timestamp is within 5 minutes
    if abs(int(time.time() * 1000) - int(timestamp)) > 300_000:
        return jsonify({"error": "Timestamp expired"}), 401

    # Step 3: Return HTTP 200 + {"code":"00000"} within 5 seconds (REQUIRED)
    # Process business logic asynchronously after responding

    event = request.get_json(force=True)
    acquiring_order_id = event.get("acquiringOrderId")
    merchant_order_id  = event.get("merchantOrderId")
    # Use either ID for deduplication — the same event may arrive more than once.

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

Released under the MIT License.