Bestin Pay API 文档
基本信息
- 版本:1.0.0
- 协议:HTTP/HTTPS
- 响应格式:JSON
- API 地址:
https://api.bestinpay.com
认证
所有接口均需通过以下 Header 进行认证:
x-app-id: your_app_id x-secret: your_app_secret x-timestamp: 1764989924791
认证参数说明:
x-app-id:应用IDx-secret:应用密钥x-timestamp:当前时间戳(毫秒),用于防重放攻击,允许5分钟误差
代收接口
POST
/payin创建代收交易订单,支持多种支付方式和渠道。
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| req_id | string | 是 | 商户请求ID,唯一标识 |
| ccy | string | 是 | 币种代码(仅支持:INR) |
| amount | string | 是 | 交易金额 |
| trx_method | string | 否 | 交易方式(upi/bank_card等) |
| trx_mode | string | 否 | 交易模式(api/checkout) |
| notify_url | string | 否 | 异步通知地址 |
| return_url | string | 否 | 同步跳转地址 |
API 模式请求示例:
{
"req_id": "PAY20241022001",
"ccy": "INR",
"amount": "101.00",
"trx_method": "upi",
"trx_mode": "api",
"product_id": "product_001",
"user_ip": "192.168.1.1",
"notify_url": "https://merchant.com/notify",
"return_url": "https://merchant.com/return"
}响应示例:
{
"code": "0000",
"msg": "Success",
"data": {
"trx_id": "PI01KAG1YM11Z3Y7BG4VW9K86ZRJ",
"trx_type": "payin",
"req_id": "5af6bebb-16aa-4cbd-ad8d-936bdc71780f",
"trx_method": "upi",
"trx_mode": "api",
"ccy": "INR",
"amount": "101.0000",
"status": "pending",
"link": "upi://pay?pa=1234****@freecharge&pn=&am=101...",
"links": {
"paytm": "paytmmp://cash_wallet?...",
"phonepe": "phonepe://native?...",
"upi": "upi://pay?..."
}
}
}代付接口
POST
/payout创建代付交易订单,向指定账户转账。支持UPI支付和银行卡转账。
UPI 支付参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| req_id | string | 是 | 商户请求ID |
| ccy | string | 是 | 币种代码(INR) |
| amount | string | 是 | 交易金额 |
| trx_method | string | 是 | 交易方式(upi) |
| upi | string | 是 | 收款方UPI地址 |
| user_ip | string | 是 | 用户真实IP |
请求示例:
{
"req_id": "PAYOUT20241022001",
"ccy": "INR",
"amount": "101.00",
"trx_method": "upi",
"upi": "1234****@ibl",
"user_ip": "127.0.0.1"
}取消订单
POST
/cancel取消指定的交易订单(代收或代付)。
请求示例:
{
"trx_id": "TRX20241022001",
"trx_type": "payin"
}查询交易
POST
/query根据请求ID或交易ID查询交易状态和详情。
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| req_id | string | 否 | 商户请求ID(与trx_id二选一) |
| trx_id | string | 否 | 交易ID(与req_id二选一) |
| trx_type | string | 是 | 交易类型(payin/payout) |
请求示例:
{
"req_id": "f5a8d8f9-cafb-45b6-bf92-35fbea37e8eb",
"trx_type": "payin"
}查询账户余额
POST
/balance查询商户账户的各币种余额信息。
响应示例:
{
"code": 200,
"msg": "success",
"data": [
{
"ccy": "INR",
"balance": "10000.00",
"available_balance": "9500.00",
"frozen_balance": "500.00"
}
]
}交易状态说明
pending:处理中confirming:确认中success:成功failed:失败canceled:已取消expired:已过期
支付方式说明
upi:UPI支付bank_transfer:银行转账
通知回调
商户在创建交易时可设置 notify_url,当交易状态发生变化时,系统会向该地址发送 POST 通知。
通知数据结构:
{
"notify_id": "NOTIFY20241022001",
"event_type": "trx_update",
"data": {
"mid": "M01K7BKY89ZKEJEF7S313X6KM21",
"trx_id": "PI01KAG1YM11Z3Y7BG4VW9K86ZRJ",
"req_id": "PAY20241022001",
"trx_type": "payin",
"status": "success",
"amount": "101.00",
"ccy": "INR"
},
"timestamp": 1729641600000,
"sign": "QL/JqWlEO/D6k0CE4cSZMXbYzwwBp..."
}接收响应要求:
- • HTTP 状态码必须为
200 - • 响应内容必须为字符串
"success"
签名验证
为确保通知的真实性和完整性,系统会对所有通知进行数字签名。
验签公钥:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA37PVibAurnVe+0l0k9DC Pc3whYA6QscL5KBxh7jdUGEN3RO1vd3AAMPhOqmhWBjckEzlZmojF9GPjqAPZH4s iNYRE60tV96cCDFOLspxaFmfOqyYOpUhcfDQf+QsFi3njVbEX5X1qcH4U0SL4ctm yTyJorcbn6qa2XuHgWeomvQ9yyZapjknmiypHd8Mn5niYqUCSlDs9AtCUqX8h38v HoUD714j5HUdMFhYCRwOJT0vmzCmFPdaI1GKxSp8y5CN+RPsZJ6CKD/8F/T+XHqW I5HtTKttZqevBG/XJDpThxreQzKb9l0JbjOHZ5fbZH6DSoyd5vfRdao+TQq7eR9n 5QIDAQAB -----END PUBLIC KEY-----
签名算法:
- • 算法:RSA-SHA512
- • 签名格式:PKCS#1 v1.5
- • 编码方式:Base64
验签步骤:
- 提取 data 字段中的非空值
- 将字段按键名字典序升序排序
- 使用
key=value&key2=value2格式拼接 - 使用 SHA512 对签名字符串进行哈希
- 使用公钥和 RSA-PKCS1-v1_5 算法验证签名
代码示例
Go 示例
package main
import (
"crypto"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"sort"
"strings"
)
func BuildSignString(data map[string]interface{}) string {
keys := make([]string, 0, len(data))
for k, v := range data {
if v != nil && v != "" {
keys = append(keys, k)
}
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, fmt.Sprintf("%s=%v", k, data[k]))
}
return strings.Join(parts, "&")
}
func VerifySignature(data map[string]interface{}, signature string) bool {
// 验证签名逻辑...
return true
}PHP 示例
<?php
function buildSignString($data) {
$filtered = array_filter($data, function($value) {
return $value !== null && $value !== '';
});
ksort($filtered);
$parts = [];
foreach ($filtered as $key => $value) {
$parts[] = $key . '=' . $value;
}
return implode('&', $parts);
}
function verifySignature($data, $signature) {
$signStr = buildSignString($data);
$sig = base64_decode($signature);
$publicKey = openssl_pkey_get_public(PUBLIC_KEY);
$result = openssl_verify(
$signStr,
$sig,
$publicKey,
OPENSSL_ALGO_SHA512
);
return $result === 1;
}Java 示例
import java.security.*;
import java.util.*;
public class SignatureVerifier {
public static String buildSignString(Map<String, String> data) {
TreeMap<String, String> sortedMap = new TreeMap<>();
for (Map.Entry<String, String> entry : data.entrySet()) {
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
sortedMap.put(entry.getKey(), entry.getValue());
}
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
if (sb.length() > 0) sb.append('&');
sb.append(entry.getKey()).append('=').append(entry.getValue());
}
return sb.toString();
}
public static boolean verifySignature(
Map<String, String> data,
String signatureStr
) throws Exception {
String signStr = buildSignString(data);
byte[] publicKeyBytes = Base64.getDecoder().decode(PUBLIC_KEY);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
byte[] signatureBytes = Base64.getDecoder().decode(signatureStr);
Signature signature = Signature.getInstance("SHA512withRSA");
signature.initVerify(publicKey);
signature.update(signStr.getBytes());
return signature.verify(signatureBytes);
}
}错误响应格式
{
"code": 400,
"msg": "请求参数错误",
"data": null
}