StableOps
SDK

Python API SDK

Python SDK 安装、配置与调用。

安装

pip install stableops

要求 Python 3.8+,依赖 httpxpydantic v2。同时提供同步 StableOps 与异步 AsyncStableOps 两套客户端,API 形状一致。

想看可运行的完整示例?

Playground 在浏览器里串起「建单 → 钱包支付 → 确认 → finalized」 全流程,并附带可阅读的源码。

配置

import os

from stableops import StableOps

client = StableOps(
    api_key=os.environ["STABLEOPS_API_KEY"],
    # 可选项:
    # base_url="https://api.stableops.dev",
    # timeout=30.0,
    # max_retries=2,
    # checkout_base_url="https://pay.stableops.dev",  # 仅影响 checkout url 拼接
)

环境(sandbox / live)由 API Key 前缀(sk_sandbox_… / sk_live_…)决定,无需额外参数。

异步用法:

import asyncio

from stableops import AsyncStableOps


async def main() -> None:
    async with AsyncStableOps(api_key="sk_sandbox_...") as client:
        order = await client.payment_orders.create(
            merchant_order_id="sub_89231_2026_06",
            amount="49.00",
            accepted_assets=[{"chain": "base", "asset": "USDC"}],
            expires_at="2026-06-20T12:30:00Z",
        )
        print(order.id)


asyncio.run(main())

支付订单

from datetime import datetime, timedelta, timezone

order = client.payment_orders.create(
    merchant_order_id="sub_89231_2026_06",
    amount="49.00",
    accepted_assets=[
        {"chain": "base", "asset": "USDC"},
        {"chain": "tron", "asset": "USDT"},
    ],
    # 30 分钟后未支付自动过期,订单进入 expired 并释放地址。
    expires_at=(datetime.now(timezone.utc) + timedelta(minutes=30)).isoformat(),
    # 可选:'auto' 让服务端把金额微调到唯一(SHARED 地址免手动错开金额)。
    amount_mode="auto",
)

client.payment_orders.retrieve(order.id)
client.payment_orders.list(status="detected", limit=50)
client.payment_orders.cancel(order.id)

merchant_order_id 同时作为幂等键:worker 重试时落到同一记录,不会重复建单。

amount 是最小单位字符串,不要float(amount)requested_amount 为 商户传入的基准金额(auto 单为微调前金额,用于对账)。

Checkout Sessions(托管收银台)

checkout_sessions.create 返回一个托管支付页(WalletConnect),把用户跳转到 session.url 即可。

session = client.checkout_sessions.create(
    merchant_order_id="sub_89231_2026_06",
    amount="49.00",
    accepted_assets=[{"chain": "base", "asset": "USDC"}],
    expires_at="2026-06-20T12:30:00Z",
    title="Pro 套餐",
    success_url="https://your-app.example.com/pay/success",
    cancel_url="https://your-app.example.com/pay/cancel",
)

print(session.url)  # 跳转用户到此链接完成支付

Webhook 端点

endpoint = client.webhooks.create_endpoint(
    url="https://your-app.example.com/hooks/stableops",
    enabled_events=["payment.detected", "payment.confirmed", "payment.finalized"],
    # 可选:投递 payload 中剔除订单 metadata。
    redact_metadata=True,
)

endpoints = client.webhooks.list_endpoints()
client.webhooks.update_endpoint(endpoint.id, description="生产环境")

# endpoint.secret 只在创建/轮换时出现一次,请妥善保存。
client.webhooks.rotate_secret(endpoint.id)

投递与重放

# 列出投递记录(可按状态 / 端点 / 订单过滤)。
deliveries = client.webhooks.list_deliveries(status="failed", limit=20)

# 把某个事件重新投递到指定端点。
client.webhooks.replay(endpoint.id, event_id)

# 重放单条投递。
client.webhooks.replay_delivery(delivery_id)

# 批量重放死信。
result = client.webhooks.replay_dead_letters(endpoint_id=endpoint.id, limit=100)
print(result.replayed)

Webhook 验签

在你的回调处理函数里校验签名(以 Flask 为例):

from stableops import SIGNATURE_HEADER, verify_webhook_signature


@app.route("/hooks/stableops", methods=["POST"])
def handle_webhook():
    body = request.get_data(as_text=True)
    result = verify_webhook_signature(
        body=body,
        header=request.headers.get(SIGNATURE_HEADER),
        secret=os.environ["STABLEOPS_WEBHOOK_SECRET"],
    )
    if not result.valid:
        return {"error": result.reason}, 401
    # 用原始 body(不要先 json 化再回写)做验签。
    ...
    return "", 204

轮换密钥期间可传 secrets=[old, new] 同时接受多个密钥。

错误

所有非 2xx 都会抛 StableOpsError,带 .status / .code / .message / .details

from stableops import StableOpsError

try:
    client.payment_orders.create(
        merchant_order_id="sub_89231_2026_06",
        amount="49.00",
        accepted_assets=[{"chain": "base", "asset": "USDC"}],
        expires_at="2026-06-20T12:30:00Z",
    )
except StableOpsError as err:
    if err.status == 409:
        # 幂等键被相同 key 不同 body 复用
        ...
    raise

网络错误(超时 / 连接失败)也会包成 StableOpsError,此时 status == 0

这篇文档怎么样?

最后更新

本页内容