BestInPay 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创建代收交易订单,支持多种支付方式和渠道。根据 trx_mode 参数决定返回格式:
- • 当
trx_mode为 "api" 时:返回包含link和links的API模式响应
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| req_id | string | 是 | 商户请求ID,唯一标识 |
| ccy | string | 是 | 币种代码(仅支持:INR) |
| amount | string | 是 | 交易金额 |
| trx_method | string | 是 | 交易方式(upi/bank_transfer等) |
| trx_mode | string | 是 | 交易模式(api) |
| trx_app | string | 否 | 支付应用 |
| pkg | string | 否 | 包名 |
| did | string | 否 | 设备ID |
| product_id | string | 否 | 产品ID |
| user_ip | string | 是 | 用户IP地址 |
| notify_url | string | 否 | 异步通知地址 |
| return_url | string | 否 | 同步跳转地址,当trx_mode为checkout时必填 |
请求示例:
{
"req_id": "PAY20241022001",
"ccy": "INR",
"amount": "101.00",
"trx_method": "upi",
"trx_mode": "api",
"product_id": "product_001",
"user_ip": "120.79.21.198",
"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",
"product_id": "product_001",
"ccy": "INR",
"amount": "101.0000",
"fee_ccy": "INR",
"fee_amount": "5.02",
"status": "pending",
"link": "paytmmp://cash_wallet?pa=1234****@freecharge&pn=&tr=PI01KAG1YM11Z3Y7BG4VW9K86ZRJ&tn=Ordersrypay&am=101&cu=INR&featuretype=money_transfer",
"links": {
"paytm": "paytmmp://cash_wallet?pa=1234****@freecharge&pn=&tr=PI01KAG1YM11Z3Y7BG4VW9K86ZRJ&tn=Ordersrypay&am=101&cu=INR&featuretype=money_transfer",
"phonepe": "phonepe://native?data=xxxx&id=p2ppayment",
"upi": "upi://pay?pa=1234****@freecharge&pn=&am=101&cu=INR&tn=Ordersrypay"
},
"remark": "Ordersrypay",
"settle_amount": "95.98",
"settle_usd_amount": "0",
"completed_at": 1763622998065,
"expired_at": 1763624798049
}
}代付接口
POST
/payout创建代付交易订单,向指定账户转账。支持UPI支付和银行卡转账两种方式
UPI 支付参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| req_id | string | 是 | 商户请求ID |
| ccy | string | 是 | 币种代码(INR) |
| amount | string | 是 | 交易金额 |
| trx_method | string | 是 | 交易方式(upi) |
| upi | string | 是 | 收款方UPI地址 |
| user_ip | string | 是 | 用户真实IP |
UPI 请求示例:
{
"req_id": "PAYOUT20241022001",
"ccy": "INR",
"amount": "101.00",
"trx_method": "upi",
"upi": "1234****@ibl",
"user_ip": "127.0.0.1"
}银行转账(Bank Transfer)参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| req_id | string | 是 | 商户请求ID |
| ccy | string | 是 | 币种代码(INR) |
| amount | string | 是 | 交易金额 |
| trx_method | string | 是 | 交易方式(bank_transfer) |
| bank_code | string | 是 | 银行代码/IFSC代码 |
| bank_name | string | 是 | 银行名称 |
| holder_name | string | 是 | 收款人姓名 |
| card_number | string | 是 | 银行账号 |
| notify_url | string | 否 | 异步通知地址 |
Bank Transfer 请求示例:
{
"req_id": "PAYOUT20241022002",
"ccy": "INR",
"amount": "101.00",
"trx_method": "bank_transfer",
"bank_code": "ABHY0065305",
"bank_name": "test",
"holder_name": "test",
"card_number": "3288238823",
"notify_url": "https://www.google.com"
}响应示例:
{
"code": "0000",
"msg": "Success",
"data": {
"trx_id": "PO01KADRBHDG04DBC26ZZ3Q2GTZZ",
"trx_type": "payout",
"mid": "U01K7BKY89ZKEJEF7S313X6KM21",
"req_id": "62fe3807-f4fe-45b8-911f-5366b905f4fe",
"trx_method": "upi",
"ccy": "INR",
"amount": "101",
"fee_ccy": "INR",
"fee_amount": "5.02",
"upi": "1234****@ibl",
"status": "confirming",
"remark": "866511",
"expired_at": 1763547626736,
"created_at": 1763545826736
}
}查询交易
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"
}响应示例:
代收交易响应:
{
"code": "0000",
"msg": "Success",
"data": {
"trx_id": "PI01KAJ5DMS5RJCH5A6MX3FMABAB",
"trx_type": "payin",
"req_id": "6b401f86-8995-4d33-83b6-09105e17d298",
"trx_method": "upi",
"trx_mode": "api",
"product_id": "product_001",
"ccy": "INR",
"amount": "303.0000",
"fee_ccy": "INR",
"fee_amount": "9.06",
"status": "pending",
"link": "paytmmp://cash_wallet?pa=6352****@freecharge&pn=&tr=PI01KAJ5DMS5RJCH5A6MX3FMABAB&tn=OrderJEf269&am=303&cu=INR&featuretype=money_transfer",
"links": {
"paytm": "paytmmp://cash_wallet?pa=6352****@freecharge&pn=&tr=PI01KAJ5DMS5RJCH5A6MX3FMABAB&tn=OrderJEf269&am=303&cu=INR&featuretype=money_transfer",
"phonepe": "phonepe://native?data=******************************",
"upi": "upi://pay?pa=6352****@freecharge&pn=&am=303&cu=INR&tn=OrderJEf269"
},
"remark": "OrderJEf269",
"settle_amount": "293.94",
"expired_at": 1763695544933
}
}代付交易响应:
{
"code": "0000",
"msg": "Success",
"data": {
"trx_id": "PO01KAG9K32KGYX9X8CM3VXP8VVN",
"trx_type": "payout",
"req_id": "1a8ac5a7-0418-4a6b-90e4-94e4bedd7c9f",
"trx_method": "upi",
"ccy": "INR",
"amount": "100",
"fee_amount": "3",
"status": "pending",
"remark": "Payment0QQgkr"
}
}查询账户余额
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----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAugg1UsQuiBbpWkpMz/Ey 6AnRNTM2k0dOMqMJZDIkpQQXysai5kFezDmAN0z9C9E8bFvTmqOEjRlf1wyqovvk ilQKWcI0sg/+DhPxb2TDXjjNo8Vn2cpMTeoUvwlrNfd2v6HsVZt/eqAJARtNXJYT uS0Gevu/RE8bxmuNvmhXCFHBm0U9ty37crwAjd21NkUDVgDhuD0sNogm+WOnEutF omPkjQAVtpC7/LFtSH564Ek1PBU8JaduCfMFg7rKbx+CU/GZII1erj6YKLN0uNlB lQr4HqR8SDK3j60zAAplI40oOk7/PJXh+GK17/OcRWsYpv8vV4+JeczytHaWJB++ wwIDAQAB -----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.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
/**
* BestInPay API签名验证 - 简单示例
* 可以直接在线运行测试:https://www.jdoodle.com/online-java-compiler
*/
public class SignatureVerifierSimple {
private static final String PUBLIC_KEY =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAugg1UsQuiBbpWkpMz/Ey" +
"6AnRNTM2k0dOMqMJZDIkpQQXysai5kFezDmAN0z9C9E8bFvTmqOEjRlf1wyqovvk" +
"ilQKWcI0sg/+DhPxb2TDXjjNo8Vn2cpMTeoUvwlrNfd2v6HsVZt/eqAJARtNXJYT" +
"uS0Gevu/RE8bxmuNvmhXCFHBm0U9ty37crwAjd21NkUDVgDhuD0sNogm+WOnEutF" +
"omPkjQAVtpC7/LFtSH564Ek1PBU8JaduCfMFg7rKbx+CU/GZII1erj6YKLN0uNlB" +
"lQr4HqR8SDK3j60zAAplI40oOk7/PJXh+GK17/OcRWsYpv8vV4+JeczytHaWJB++" +
"wwIDAQAB";
/**
* 构建签名字符串
*/
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) {
try {
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(StandardCharsets.UTF_8));
return signature.verify(signatureBytes);
} catch (Exception e) {
System.out.println("验证出错: " + e.getMessage());
return false;
}
}
public static void main(String[] args) {
System.out.println("=== BestInPay API 签名验证测试 ===
");
// 测试用例1:完整的回调数据
System.out.println("【测试1】验证完整回调数据");
Map<String, String> notifyData = new HashMap<>();
notifyData.put("mid", "Test");
notifyData.put("trx_id", "PI01KAG1YM11Z3Y7BG4VW9K86ZRJ");
notifyData.put("req_id", "PAY20241022001");
notifyData.put("trx_type", "payin");
notifyData.put("trx_method", "upi");
notifyData.put("trx_mode", "api");
notifyData.put("product_id", "product_001");
notifyData.put("status", "success");
notifyData.put("amount", "101.00");
notifyData.put("ccy", "INR");
notifyData.put("fee_ccy", "INR");
notifyData.put("fee_amount", "5.02");
notifyData.put("reference_id", "UTR123456789");
notifyData.put("res_code", "SUCCESS");
notifyData.put("res_msg", "Transaction completed successfully");
notifyData.put("remark", "Ordersrypay");
String signature = "oRMh57ucHkmRlPQY8zTSvmqUhNJ7VNIANAQLQm5Gb+UshZoHWGDLiaGUXZFFECCqTU+4hX/eUOwL80gPcfRR7BKQMLZ9WACBL55zbVDEy6ZvBFmov6enZlvQvK+HW30dD9QI8RsdWlzC4ewJicJX7TJ6Kut/QXP2ukaiGGNv1m4Q9TSYc5KRtiwLhPFURYMkNKEYmu3EY1jixu107tU5DoNUKzOsa6LARqhhHNFD8/g969vjD6wNv1h/Y8V420jPNIWNNdf5P3QfqiInKlwwztUGY+jLv191TnWgOFUd2pRlizB1NY72RCCmk1xARbgrIdtDlR7aA4AeDFgE4mgLgA==";
String signStr = buildSignString(notifyData);
System.out.println("待签名字符串: " + signStr);
System.out.println("签名值: " + signature.substring(0, 50) + "...");
if (verifySignature(notifyData, signature)) {
System.out.println("结果: ✅ 签名验证通过
");
} else {
System.out.println("结果: ❌ 签名验证失败
");
}
// 测试用例2:篡改数据
System.out.println("【测试2】验证篡改后的数据(金额被修改)");
Map<String, String> tamperedData = new HashMap<>(notifyData);
tamperedData.put("amount", "999.00"); // 篡改金额
if (verifySignature(tamperedData, signature)) {
System.out.println("结果: ✅ 签名验证通过(不应该通过!)
");
} else {
System.out.println("结果: ❌ 签名验证失败(预期结果)
");
}
// 测试用例3:签名字符串构建测试
System.out.println("【测试3】签名字符串构建规则验证");
Map<String, String> testData = new HashMap<>();
testData.put("c", "3");
testData.put("a", "1");
testData.put("b", "2");
testData.put("empty", ""); // 空值应被过滤
testData.put("null_val", null); // null应被过滤
String result = buildSignString(testData);
String expected = "a=1&b=2&c=3";
System.out.println("构建结果: " + result);
System.out.println("预期结果: " + expected);
System.out.println("结果: " + (result.equals(expected) ? "✅ 正确" : "❌ 错误") + "
");
System.out.println("=== 测试完成 ===");
}
}
错误响应格式
{
"code": 400,
"msg": "请求参数错误",
"data": null
}