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:应用ID
  • x-secret:应用密钥
  • x-timestamp:当前时间戳(毫秒),用于防重放攻击,允许5分钟误差

代收接口

POST/payin

创建代收交易订单,支持多种支付方式和渠道。根据 trx_mode 参数决定返回格式:

  • • 当 trx_mode 为 "api" 时:返回包含 linklinks 的API模式响应

请求参数:

参数名类型必填说明
req_idstring商户请求ID,唯一标识
ccystring币种代码(仅支持:INR)
amountstring交易金额
trx_methodstring交易方式(upi/bank_transfer等)
trx_modestring交易模式(api)
trx_appstring支付应用
pkgstring包名
didstring设备ID
product_idstring产品ID
user_ipstring用户IP地址
notify_urlstring异步通知地址
return_urlstring同步跳转地址,当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_idstring商户请求ID
ccystring币种代码(INR)
amountstring交易金额
trx_methodstring交易方式(upi)
upistring收款方UPI地址
user_ipstring用户真实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_idstring商户请求ID
ccystring币种代码(INR)
amountstring交易金额
trx_methodstring交易方式(bank_transfer)
bank_codestring银行代码/IFSC代码
bank_namestring银行名称
holder_namestring收款人姓名
card_numberstring银行账号
notify_urlstring异步通知地址

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_idstring商户请求ID(与trx_id二选一)
trx_idstring交易ID(与req_id二选一)
trx_typestring交易类型(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

验签步骤:

  1. 提取 data 字段中的非空值
  2. 将字段按键名字典序升序排序
  3. 使用 key=value&key2=value2 格式拼接
  4. 使用 SHA512 对签名字符串进行哈希
  5. 使用公钥和 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
}