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 flaskSignature 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 == signatureStep 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 orderAmountStep 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, CLOSEDStep 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"})Related Documentation
- API Checkout Integration Guide — Step-by-step integration walkthrough
- API Checkout Reference — Full endpoint and field specification
- Signature Algorithm — Signature calculation details