概览
StablePay 商户开放接口提供 代收(Payment) 与 代付(Payout) 两类业务能力。所有接口为 POST application/json,使用 MD5 签名认证。
接入前请向 StablePay 商务申请商户账号,登录商户后台 → API 对接 页面获取以下凭据:
merchantId— 商户 ID(请求必带)apiKey— 商户密钥(仅用于本地签名,不要传输)aesKey/aesIv— 部分敏感字段加密用(如启用)
联调流程
- 商户后台获取
merchantId+apiKey - 在后台 API 对接 页配置
支付 URL/代付 URL(接收异步通知) - (可选)在 IP 白名单 配置允许调用的 IP;不配置则放行所有 IP
- 若回调接口启用了入站 IP 白名单,请联系商务获取平台回调出口 IP,并将商务提供的 IP 加入入站白名单
- 按本文档签名规则组装请求 → 调用
create接口下单 - 使用返回的
orderNo或自有merchantOrderNo调用query查询订单状态 - 代收成功、代付成功或失败时,平台会向您配置的回调 URL 推送异步通知(按相同签名规则验签);代付失败会退回本金和手续费
公共说明
请求方式
- 方法:POST
- Content-Type:
application/json - 字符集:UTF-8
请求 base URL
由 StablePay 商务为您单独提供(如 https://api.example.com)。本文档中接口路径均为相对路径。
统一响应结构
{
"code": 0, // 0=成功,非0=失败
"message": "success",
"data": { ... } // 业务数据,仅 code=0 时有意义
}
金额格式
所有金额字段在 请求体 中以 number 类型传输(如 500.00),在 签名计算 时必须格式化为保留两位小数的字符串(如 "500.00",而不是 "500" 或 "500.000")。
时间字段
所有时间字段为 Unix 时间戳(秒级,int64)。前端展示请自行转换。
签名规则
所有写接口(create)和查询接口(query)都需带 sign 字段。算法如下:
- 取出请求体中所有非空参数(值为
""、null或缺省的字段不参与签名),剔除sign本身。 - 金额类字段(如
amount)必须先转成固定两位小数的字符串:100→100.00,100.5→100.50,再参与排序和签名。整数金额也不能省略.00。 - 把参数按
key升序(ASCII)排序,拼接为k1=v1&k2=v2&...&kn=vn。 - 末尾追加
&key={apiKey}。 - 对最终字符串做 MD5(32 位),转大写,作为
sign。
"amount": 100,但签名原文必须使用 amount=100.00,不能使用 amount=100、100.0 或 100.000。服务端会把请求金额格式化成两位小数后验签;格式不一致会返回 401 签名验证失败。整数金额签名示例
以下代收请求的金额仍以 JSON 数字传输:
{
"merchantId": "SP20260101000001",
"merchantOrderNo": "DEMO_IN_100",
"amount": 100,
"notifyUrl": "https://your.site/notify",
"sign": "<由本算法计算>"
}
正确签名原文(注意 amount=100.00):
amount=100.00&merchantId=SP20260101000001&merchantOrderNo=DEMO_IN_100¬ifyUrl=https://your.site/notify&key=<您的 apiKey>
不要使用 amount=100&...。签名必须基于最终发送的参数生成:merchantOrderNo、notifyUrl 等字段必须与请求体完全一致;修改参数后需要重新计算 sign。不要使用另一笔订单的签名。
示例
请求体(amount=500.00,merchantOrderNo=E2E_OUT_001):
{
"merchantId": "SP20260101000001",
"merchantOrderNo": "E2E_OUT_001",
"amount": 500.00,
"beneName": "Raj Kumar",
"beneAccount": "1234567890123456",
"beneIfsc": "HDFC0001234",
"benePhone": "9876543210",
"notifyUrl": "https://your.site/notify",
"sign": "<由本算法计算>"
}
排序拼接(注意 amount 用 500.00):
amount=500.00&beneAccount=1234567890123456&beneIfsc=HDFC0001234&beneName=Raj Kumar&benePhone=9876543210&merchantId=SP20260101000001&merchantOrderNo=E2E_OUT_001¬ifyUrl=https://your.site/notify&key=<您的 apiKey>
对上述字符串 MD5 → 转大写 → 即为 sign。
= 和 & 之间不留空格;URL 字符(如 : /)不做编码;中文/空格保持原样。创建代收订单
/api/payment/create商户向用户收款时调用。下单成功后平台返回平台订单号;用户付款 / 平台对账完成后通过异步通知或主动查询接口获取最终状态。
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchantId | string | 必填 | 商户 ID |
| merchantOrderNo | string | 必填 | 商户系统订单号,单商户内唯一 |
| amount | number | 必填 | 下单金额,> 0,保留两位小数 |
| customerName | string | 选填 | 付款人姓名 |
| customerEmail | string | 选填 | 付款人邮箱 |
| customerPhone | string | 选填 | 付款人手机号 |
| notifyUrl | string | 选填 | 异步通知地址,覆盖商户后台默认配置 |
| returnUrl | string | 选填 | 付款完成后浏览器跳回地址 |
| remark | string | 选填 | 备注,原样回显 |
| sign | string | 必填 | 签名,详见签名规则 |
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| orderNo | string | 平台订单号(以 STABLEPAYIN 开头) |
| merchantOrderNo | string | 回显商户订单号 |
| txRef | string | 平台交易参考号(6 位大写字母/数字)。用户 UPI 转账时必须原样填入「备注」,平台按它回扫收款银行短信 Payer Remark 匹配订单 |
| amount | string | 商户下单原始金额,保留两位小数 |
| payableAmount | string | 订单实际应付金额,保留两位小数;通道金额匹配可能使其与 amount 不同,用户须按此金额付款 |
| status | string | 初始状态固定 PENDING |
| expiryDate | int64 | 订单过期时间(Unix 秒),默认 30 分钟 |
| remarks | string | UPI 转账备注,值等于 txRef,用户转账时须原样填写 |
| beneName | string | 收款人姓名 |
| beneAccount | string | 收款人银行账号 |
| beneIfsc | string | 收款人银行 IFSC |
| bank | string | 收款银行名称 |
| upiVpa | string | 收款 UPI VPA(收银台/UPI 拉起用) |
| upiStr | string | 本订单收款 UPI 的完整深链接(upi://pay?...),可生成支付二维码或拉起 UPI 应用。pa 为订单绑定的 upiVpa,am 为 payableAmount,tr/tn 为 txRef。上游收银台通道无法提供深链接时返回空字符串,请使用 payUrl |
| payUrl | string | 平台收银台地址,可直接跳转给终端用户扫码 / 拉起 UPI 应用付款 |
说明:若商户自建收银页,可直接使用 upiStr 生成二维码或拉起 UPI 应用,链接中的金额和备注已按订单填写;手动转账时,须按 payableAmount 付款并把 txRef 原样填入备注。若直接使用平台收银台,跳转 payUrl 即可。
返回示例
{
"code": 0,
"message": "success",
"data": {
"orderNo": "STABLEPAYIN20260428143022000123",
"merchantOrderNo": "E2E_IN_001",
"txRef": "K7M9P2",
"amount": "500.00",
"payableAmount": "500.00",
"status": "PENDING",
"expiryDate": 1714203600,
"remarks": "K7M9P2",
"beneName": "Raj Kumar",
"beneAccount": "1234567890123456",
"beneIfsc": "HDFC0001234",
"bank": "HDFC Bank",
"upiVpa": "raj@okhdfc",
"upiStr": "upi://pay?cu=INR&pa=raj@okhdfc&pn=Raj+Kumar&tr=K7M9P2&am=500.00&tn=K7M9P2&mc=7407&cu=INR&url=&mode=02&purpose=00&orgid=159002",
"payUrl": "https://pay.stablepay.example/?merchantId=SP20260101000001&orderNo=STABLEPAYIN20260428143022000123"
}
}
查询代收订单
/api/payment/query请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchantId | string | 必填 | 商户 ID |
| merchantOrderNo | string | 二选一 | 商户订单号 |
| orderNo | string | 二选一 | 平台订单号 |
| sign | string | 必填 | 签名 |
merchantOrderNo 与 orderNo 至少传其一。
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| orderNo | string | 平台订单号 |
| merchantOrderNo | string | 商户订单号 |
| amount | string | 下单金额 |
| paidAmount | string | 实际到账金额 |
| status | string | 订单状态,见订单状态 |
| utr | string | 银行端流水号(成功后才有) |
| createTime | int64 | 创建时间(Unix 秒) |
| updateTime | int64 | 最近更新时间(Unix 秒) |
UTR 代收补单
/api/payment/supplement三方对接本平台作为四方时,提交 UTR 和订单号即可同步获取补单成功或失败结果。核验规则与 TG /bu UTR 订单号 相同:核实收款通道、到账金额及真实成功流水后才结算,不要求匹配付款备注。仅支持当前商户自己的代收订单。
https://merchant.example.com/api/payment/supplement;下单、查单仍使用商户 API 域名。请勿直接把下单域名拼接为补单地址。请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchantId | string | 必填 | 商户 ID,沿用下单的商户账号及 apiKey |
| merchantOrderNo | string | 二选一 | 三方自己的商户订单号 |
| orderNo | string | 二选一 | 平台订单号;与 merchantOrderNo 至少填一个,同时填写必须指向同一订单 |
| utr | string | 必填 | 6–15 位数字,或 8–32 位包含字母的字母数字参考号;须保留前导零 |
| sign | string | 必填 | 按签名规则计算,merchantId、所填订单号及 utr 均参与签名;使用实际发送的原始值签名 |
沿用商户启用状态和 API IP 白名单,无需后台登录 Token 或谷歌验证码。
请求示例
{
"merchantId": "SP20260101000001",
"merchantOrderNo": "M202610170001",
"utr": "628808285413",
"sign": "按下方原文计算的MD5大写值"
}
以上请求的签名原文(将 YOUR_API_KEY 替换为商户密钥):
merchantId=SP20260101000001&merchantOrderNo=M202610170001&utr=628808285413&key=YOUR_API_KEY
补单成功
{
"code": 0,
"message": "success",
"data": {
"success": true,
"orderNo": "PAY202610170001",
"merchantOrderNo": "M202610170001",
"status": "SUCCESS",
"utr": "628808285413",
"amount": "100.00"
}
}
amount 为商户原始下单金额。仅当 code=0 才表示已结算成功。同一订单、同一 UTR 重复请求返回成功,不会重复入账;已成功订单提交不同 UTR 返回失败。
补单失败
{
"code": 500,
"message": "已查询上游流水,暂未匹配到当前订单的到账交易,请核对 UTR、金额和收款通道后重试(补单无需匹配订单备注)"
}
HTTP 200 中的非零 code 表示本次请求失败,原因见 message:400 参数缺失;401 签名错误;403 商户不可用或 IP 不在白名单;500 订单不存在、UTR 已占用、流水不符或上游查询失败等业务异常。失败响应不代表订单永远失败,最终状态可通过查单接口确认。
- 支持对待支付、已过期及失败但尚未结算的订单重新核验;不支持绕过 UTR 核验、免核销补余额等后台专用操作。
- 同一 UTR 或到账流水只能用于一笔订单;金额或收款通道不匹配时不会入账。
- 建议 HTTP 读取超时设为 180 秒。超时或连接中断时,先查单,再使用同一订单和 UTR 重试。
- 本接口直接返回本次核验结果,不创建 TG 自动重试任务。成功后的商户异步回调沿用原订单通知流程。
创建代付订单
/api/payout/create商户向第三方账户付款时调用。下单瞬间扣减商户可用余额(金额 + 手续费),订单失败后退款回账。
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchantId | string | 必填 | 商户 ID |
| merchantOrderNo | string | 必填 | 商户系统订单号,单商户内唯一 |
| amount | number | 必填 | 付款金额,> 0,保留两位小数 |
| beneName | string | 条件必填 | 收款人姓名,银行卡必填;UPI 可不传 |
| beneAccount | string | 必填 | 银行卡账号;UPI 模式填写收款 VPA,如 name@handle |
| beneIfsc | string | 条件必填 | 银行卡必填;UPI 不传 |
| benePhone | string | 选填 | 收款人手机号 |
| beneEmail | string | 选填 | 收款人邮箱 |
| bankName | string | 选填 | 收款银行名 |
| transferType | string | 选填 | 转账方式,BANK_TRANSFER(缺省)或 UPI |
| notifyUrl | string | 选填 | 异步通知地址,覆盖商户后台默认配置 |
| remark | string | 选填 | 备注 |
| sign | string | 必填 | 签名 |
{"transferType":"UPI","beneAccount":"name@handle",...}。收款姓名可空,不传 IFSC。当前自有钱包 UPI 代付仅支持 MobiKwik Pocket UPI 通道;签名算法、查单和成功回调与银行卡模式相同。响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| orderNo | string | 平台订单号(以 STABLEPAYOUT 开头) |
| merchantOrderNo | string | 回显商户订单号 |
| amount | string | 金额 |
| processFee | string | 手续费 |
| status | string | 初始状态固定 PENDING |
余额不足。查询代付订单
/api/payout/query请求参数
同代收查询:传 merchantId + (merchantOrderNo 或 orderNo) + sign。
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
| orderNo | string | 平台订单号 |
| merchantOrderNo | string | 商户订单号 |
| amount | string | 付款金额 |
| processFee | string | 手续费 |
| status | string | 订单状态,见订单状态 |
| utr | string | 银行端流水号 |
| createTime | int64 | 创建时间 |
| updateTime | int64 | 更新时间 |
查询商户余额
/api/balance/query查询当前 API 密钥对应商户的实时余额。沿用商户启用状态、API 密钥和商户 API IP 白名单校验;无需后台登录 Token 或订单号。
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchantId | string | 是 | 商户号,使用该商户的 API 密钥签名 |
sign | string | 是 | MD5 签名,规则见下方 |
签名与请求示例
仅 merchantId 参与签名;拼接 merchantId=YOUR_MERCHANT_ID&key=YOUR_API_KEY 后按 UTF-8 计算 MD5 并转大写。不要对参数值做 URL 编码。
curl -X POST "$API_BASE_URL/api/balance/query" \
-H 'Content-Type: application/json' \
-d '{"merchantId":"YOUR_MERCHANT_ID","sign":"CALCULATED_MD5_SIGN"}'
import hashlib
import requests
merchant_id = "YOUR_MERCHANT_ID"
api_key = "YOUR_API_KEY"
sign = hashlib.md5(f"merchantId={merchant_id}&key={api_key}".encode("utf-8")).hexdigest().upper()
response = requests.post(f"{api_base_url}/api/balance/query",
json={"merchantId": merchant_id, "sign": sign}, timeout=15)
print(response.json())
成功响应
{
"code": 0,
"message": "success",
"data": {
"merchantId": "YOUR_MERCHANT_ID",
"currency": "INR",
"balance": "10000.25",
"freezeBalance": "500.00",
"depositBalance": "100.00"
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
balance | string | 可用余额,单位 INR;已扣除冻结金额,不能再次减去 freezeBalance |
freezeBalance | string | 冻结余额,单位 INR |
depositBalance | string | 押金余额,单位 INR |
金额返回十进制字符串以保留精度。查询不会扣款、冻结、解冻或发起付款。缺少参数返回 400,签名不符返回 401,商户不存在、停用、未配置密钥或 IP 不在白名单返回 403。
回调通知
代收订单达到 SUCCESS、代付订单达到 SUCCESS 或 FAILED 后,平台会向您的回调地址主动推送异步通知(POST application/json)。代付失败会先退回本金和手续费,再发送失败通知;处理中或等待上游确认的代付不会提前通知失败。请在收到通知后验签并检查 status,按订单号幂等处理,不要仅凭通知到达就判定付款成功。
回调地址
- 优先使用下单请求里传的
notifyUrl; - 未传则回退商户后台配置的默认地址(代收 =
paymentUrl,代付 =payoutUrl); - 两者都为空则不推送(订单会停留在后台「回调失败」列表,可人工处理)。
回调 IP 白名单
此回调出口 IP 与商户后台的 IP 白名单 配置用途不同:后台 IP 白名单用于限制商户调用平台 API 的来源 IP,不限制平台向商户发送回调。
触发时机
| 业务 | 触发的终态 |
|---|---|
| 代收 | SUCCESS |
| 代付 | SUCCESS / FAILED |
通知体(代收示例)
{
"merchantId": "SP20260101000001",
"orderNo": "STABLEPAYIN20260101000001",
"merchantOrderNo": "E2E_IN_001",
"amount": "500.00",
"paidAmount": "499.50",
"status": "SUCCESS",
"utr": "1234567890",
"sign": "<平台按签名规则计算>"
}
代收回调金额说明
| 字段 | 说明 |
|---|---|
amount | 商户原始下单金额。严格校验订单金额及给玩家上分时,请使用此字段。 |
paidAmount | 玩家享受平台优惠后的实际支付(到账)金额,与 amount 的差额在 1 卢比以内。 |
amount 比较,并按 amount 给玩家上分。例如,下单金额为 500.00 卢比,优惠后 paidAmount 为 499.50 卢比,仍按 amount = 500.00 卢比上分。代付通知体字段为 merchantId / orderNo / merchantOrderNo / amount / processFee / status / utr / sign。所有字段均以字符串下发,金额为两位小数字符串。
代付 FAILED 通知额外包含 failReason(失败原因,参与签名)。失败退款金额为 amount + processFee;后台“未结算”表示未成功出款,不表示未退款,可按订单号核对 PAYOUT_REFUND 资金流水。
验签
与请求签名规则完全一致:取通知体里所有非空字段(剔除 sign)按 key 升序拼成 k1=v1&k2=v2&...&key=<您的 apiKey>,MD5 转大写,与 sign 比对一致才可信任。
应答与重试
200 且响应体包含 success(或纯文本 ok)。任何其它情况(非 2xx、连接拒绝、超时、响应体未表示成功)均视为失败。- 每笔订单平台总共最多推送 3 次:首次推送失败后,每隔 60 秒再推一次,累计 3 次仍未收到成功应答即停止。
- 停止后订单进入后台「回调失败」列表,运营可手动「重新发送」(会重置次数并立即再推一轮,同样最多 3 次)。
- 请保证回调接口幂等:同一订单可能被推送多次。
订单状态
| status | 说明 |
|---|---|
PENDING | 已创建,等待处理 |
SUCCESS | 成功(代收=已到账,代付=已打款) |
FAILED | 失败(代付会自动解冻并退回余额) |
EXPIRED | 已过期(仅代收) |
业务错误码
| code | 含义 |
|---|---|
| 0 | 成功 |
| 400 | 参数错误(缺字段、字段类型不对等) |
| 401 | 签名验证失败 |
| 403 | 商户不存在 / IP 不在白名单 / 功能未开通 |
| 500 | 服务端错误(含余额不足、订单号重复等业务异常) |
cURL 示例
以代付下单为例(请把 merchantId、sign、baseURL 替换为您自己的):
curl -X POST https://api.example.com/api/payout/create \
-H "Content-Type: application/json" \
-d '{
"merchantId": "SP20260101000001",
"merchantOrderNo": "E2E_OUT_001",
"amount": 500.00,
"beneName": "Raj Kumar",
"beneAccount": "1234567890123456",
"beneIfsc": "HDFC0001234",
"benePhone": "9876543210",
"notifyUrl": "https://your.site/notify",
"sign": "ABCDEF0123456789ABCDEF0123456789"
}'
Python 示例
展示完整签名 + 下单流程;可直接保存为 demo.py 运行。
import hashlib, requests, time
BASE_URL = "https://api.example.com"
MERCHANT_ID = "SP20260101000001"
API_KEY = "<在商户后台 API 对接页复制>"
AMOUNT = 500.00
def compute_sign(params: dict, api_key: str) -> str:
filtered = {k: str(v) for k, v in params.items() if k != "sign" and str(v) != ""}
raw = "&".join(f"{k}={filtered[k]}" for k in sorted(filtered)) + f"&key={api_key}"
return hashlib.md5(raw.encode()).hexdigest().upper()
def create_payout():
sign_params = {
"merchantId": MERCHANT_ID,
"merchantOrderNo": f"DEMO_{int(time.time())}",
"amount": f"{AMOUNT:.2f}", # 签名时用字符串
"beneName": "Raj Kumar",
"beneAccount": "1234567890123456",
"beneIfsc": "HDFC0001234",
"benePhone": "9876543210",
"notifyUrl": "https://your.site/notify",
}
body = dict(sign_params)
body["sign"] = compute_sign(sign_params, API_KEY)
body["amount"] = AMOUNT # 请求体里 amount 是 float
return requests.post(f"{BASE_URL}/api/payout/create", json=body, timeout=15).json()
print(create_payout())