Skip to content

快速开始

读完这份指南,你会完成第一笔沙盒充值请求的签名与发送、读取 API 回传的付款指示,并从系统查询回该笔订单。整个流程应该在 10 分钟内走完。

本章是一份完整示例。任何涉及的主题若需要更深入内容,请循文中的交叉链接进入其他章节。

准备事项

开始前,确认以下五项都已备齐。若有任何一项缺漏,先联络你的客户经理再继续,否则下面的步骤都无法运作。

  • 由客户经理开通的沙盒 merchant_idapi_key
  • 你的来源 IP 已加入沙盒的 Cloudflare Access 允许清单。此项由客户经理处理。关于 Cloudflare Access 访问控制的背景说明,参见 Sandbox
  • 对应你开发语言的签名辅助工具:sign.sh(bash)、sign.py(Python)、sign.php(PHP)或 sign.ts(Node.js)。四份实作 byte-for-byte 等价,挑最接近你技术栈的那一份即可。
  • 任一 HTTP 客户端:curl、Postman、HTTPie 或你语言的 HTTP 函式库。
  • 与 NTP 误差在 ±5 分钟以内的时钟。服务器会拒绝超出此区间的请求。

下面的示例片段使用 Python,但每个步骤在其他三种语言中完全一致。

步骤 1:签署充值请求

每一个商户 API 呼叫都带三个标头:X-API-KeyX-TimestampX-Signature。签名是把排序后的请求参数附上 api_key 之后,取小写 hex 的 MD5。签名的参考实作放在 docs-site/shared/code-samples/

先决定要送出的 payload。充值建单端点接受以下字段,定义位于 backend/app/schemas/order.pyDepositOrderCreate

字段类型必填说明
merchant_order_nostring你自己的订单识别码。1–64 字符。每个商户内必须唯一。
amountdecimal充值金额。必须 > 0。以字符串传送以保留精度。
currency_codestringISO 4217 三字母代码,例如 THBVNDIDR
callback_urlstring网关用以 POST 异步状态更新的 HTTPS URL。
user_idstring你的终端用户识别码(如需附带)。最多 64 字符。
extra_datastring不透明的中继数据,会在 callback 中原样回传。

本示例使用以下 payload:

json
{
  "merchant_order_no": "QUICKSTART-0001",
  "amount": "100.00",
  "currency_code": "THB",
  "callback_url": "https://your-domain.example/callbacks/deposit"
}

接着计算签名。Python 参考辅助工具长这样:

python
#!/usr/bin/env python3
"""Reference implementation: MD5 signature for Payment Gateway merchant API.

Usage:
  python sign.py --api-key <key> --params 'k1=v1&k2=v2&...'

Algorithm:
  1. Parse params into a dict.
  2. Sort keys alphabetically (ASCII order).
  3. Build query string 'k=v&k=v', skipping None values.
  4. Append api_key directly (no '&').
  5. MD5 the UTF-8 bytes; output lowercase hex.
"""
import argparse
import hashlib
import sys
from urllib.parse import parse_qsl


def create_signature(params: dict[str, str], api_key: str) -> str:
    sorted_items = sorted(params.items())
    query_string = "&".join(f"{k}={v}" for k, v in sorted_items if v is not None)
    return hashlib.md5((query_string + api_key).encode("utf-8")).hexdigest()


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--api-key", required=True)
    parser.add_argument("--params", required=True)
    args = parser.parse_args()
    params = dict(parse_qsl(args.params, keep_blank_values=True))
    print(create_signature(params, args.api_key))
    return 0


if __name__ == "__main__":
    sys.exit(main())

使用方式:把每个请求参数(包含当前的 Unix timestamp)以 k=v&k=v 字符串传入,然后附上你的 API key:

bash
API_KEY="pre_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
TIMESTAMP=$(date +%s)

PARAMS="amount=100.00&callback_url=https://your-domain.example/callbacks/deposit&currency_code=THB&merchant_order_no=QUICKSTART-0001&timestamp=${TIMESTAMP}"

SIGNATURE=$(python sign.py --api-key "$API_KEY" --params "$PARAMS")
echo "$SIGNATURE"

相同的呼叫形式也适用于 sign.shsign.phpsign.ts,差别只在直译器。四份实作透过 CI parity test 绑定,相同输入会产出完全一致的 digest。

底层算法、完整 digest 示例与四份完整实作,参见 Authentication & Signing

步骤 2:发送请求

拿到新的签名后,把 JSON 主体 POST 到充值建单端点。路径是 /api/v1/merchant/orders/deposit,其中 /orders 区段来自商户路由前缀,很容易漏掉。

bash
API_KEY="pre_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE_URL="https://pre-api.open-pay.co"
TIMESTAMP=$(date +%s)

PARAMS="amount=100.00&callback_url=https://your-domain.example/callbacks/deposit&currency_code=THB&merchant_order_no=QUICKSTART-0001&timestamp=${TIMESTAMP}"
SIGNATURE=$(python sign.py --api-key "$API_KEY" --params "$PARAMS")

curl -X POST "${BASE_URL}/api/v1/merchant/orders/deposit" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ${API_KEY}" \
  -H "X-Timestamp: ${TIMESTAMP}" \
  -H "X-Signature: ${SIGNATURE}" \
  -d '{
    "merchant_order_no": "QUICKSTART-0001",
    "amount": "100.00",
    "currency_code": "THB",
    "callback_url": "https://your-domain.example/callbacks/deposit"
  }'

执行前需要替换的几个占位符:

  • API_KEY:客户经理给你的沙盒 api_key。沙盒密钥通常以 pre_ 为前缀。
  • callback_url:你能控制且可公开连线的 HTTPS 端点。首次测试用 request bin 或打到开发机的 ngrok tunnel 都可以。
  • merchant_order_no:每个商户内必须唯一。若重复带相同值,第二次呼叫会以 duplicate-order 错误失败。

JSON 主体承载的参数与进入签名的那一组相同,差别在于少了 timestamp(它透过 X-Timestamp 标头传递)。注意:签名不在乎传输时的 JSON key 顺序,只在乎你计算 digest 时 key 是依字母排序的。

步骤 3:读取响应

成功的请求会回传 HTTP 201 Created 与标准信封。data 区块的结构由 backend/app/schemas/order.py 中的 DepositPaymentInfo 定义。data 有两种型态,取决于订单被导向支付渠道或导向我方账上的银行账户:

json
{
  "success": true,
  "code": "SUCCESS",
  "message": "OK",
  "data": {
    "order_no": "DEP20260520000001",
    "requested_amount": "100.00",
    "amount": "100.00",
    "currency_code": "THB",
    "bank_code": "KBANK",
    "bank_name": "ธนาคารกสิกรไทย (Kasikorn Bank)",
    "bank_branch": null,
    "account_no": "1234567890",
    "account_holder": "Mock Payment Co., Ltd.",
    "remark": "DEP20260520000001",
    "expire_at": "2026-05-20T08:15:00Z",
    "payment_url": null,
    "payment_info": null
  }
}

你最常用到的字段:

  • order_no:本笔充值在网关侧的识别码。保存它。你会用它来查询状态、并对应异步 callback。
  • amount:付款人实际应转账的金额。若渠道为了让订单可区分而套用浮动金额,这个值可能与 requested_amount 不同;显示给付款人的应是 amount
  • expire_at:超过此时间后订单不再接受付款。
  • bank_codebank_nameaccount_noaccount_holderremark:账户导向订单要显示给付款人的付款指示。
  • payment_urlpayment_info:渠道导向订单会改为带这两个字段。将付款人导向 payment_url,或由你自行渲染 payment_info

完整的信封合约,包含你应该分支处理的错误码清单,参见 Errors。每个端点的每个字段定义,参见 API Reference

步骤 4:查询订单

要确认订单已落入系统,用 order_no 反查。查询端点仅支持 GET,没有主体要签,因此签名只覆盖 timestamp。

bash
ORDER_NO="DEP20260520000001"
API_KEY="pre_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
TIMESTAMP=$(date +%s)

PARAMS="timestamp=${TIMESTAMP}"
SIGNATURE=$(python sign.py --api-key "$API_KEY" --params "$PARAMS")

curl -X GET "https://pre-api.open-pay.co/api/v1/merchant/query/deposit/${ORDER_NO}" \
  -H "X-API-Key: ${API_KEY}" \
  -H "X-Timestamp: ${TIMESTAMP}" \
  -H "X-Signature: ${SIGNATURE}"

刚建立的订单会回 status: "pending",且尚未有 matched_atcompleted_at

json
{
  "success": true,
  "code": "SUCCESS",
  "message": "OK",
  "data": {
    "id": "01J...",
    "order_no": "DEP20260520000001",
    "merchant_order_no": "QUICKSTART-0001",
    "merchant_id": "01J...",
    "amount": "100.00",
    "currency_code": "THB",
    "status": "pending",
    "expire_at": "2026-05-20T08:15:00Z",
    "matched_at": null,
    "completed_at": null,
    "callback_url": "https://your-domain.example/callbacks/deposit",
    "callback_status": null,
    "callback_count": 0,
    "created_at": "2026-05-20T08:00:00Z",
    "updated_at": "2026-05-20T08:00:00Z"
  }
}

看到 status: "pending" 就是成功判准。订单已进入网关、签名已被接受,正在等待付款人转账。从此刻起,后续状态转换会透过你提供的 callback_url 异步送达。

后续步骤

你已经把整条对接从头到尾跑通。以下章节对每个环节有更深入的内容:

  • Authentication:完整签名参考、5 分钟 timestamp 容忍区间、完整 digest 示例,以及四种语言辅助工具的并列对照。
  • Deposits:完整充值指南,包含 cashier-mode 对接与充值状态机(PENDINGMATCHEDCOMPLETED 及其失败路径)。
  • Callbacks:网关如何送达异步状态更新、callback 的签名方案,以及你这侧的重试语义。
  • Sandbox:完整的沙盒与生产环境对照、Cloudflare Access 访问控制、mock-channel 行为,以及常见沙盒陷阱。
  • Errors:信封合约、你应该分支处理的 code 值清单,以及 HTTP 状态码的重试建议。