VPayAPI v1.0

VPay API 文档

快速、安全、可靠的支付解决方案

API 特性
  • RESTful API 设计,简单易用
  • 支持 PHP、Python、Golang 等多种语言
  • 完整的支付流程:支付、查询、代付等
  • 实时回调通知
  • RSA 签名验证

基本规则

Request URL

生产环境:

https://api.vpayid.com/vpayid-api
Request Method
POST
Content-Type
application/json
签名算法

生成 sign:按各接口「签名规则」拼接明文串 src,取 UTF-8 字节,用商户 RSA 私钥按 PKCS#1 v1.5(type1,签名填充块 00 01 FF…FF 00 + src,即 c = block^d mod n)对 src 做原始签名,再对结果做 Base64 编码得到 sign(不是 SHA256withRSA 摘要签名,勿用 Signature/openssl_sign)。

平台验签:用商户 RSA 公钥按同样算法(type1)对 sign 验算,还原出 00 01 FF…FF 00 src,与明文串 src 逐字节比对,一致才通过。

RSA 密钥生成

生成商户 RSA 密钥对(OpenSSL 命令)
推荐使用 RSA 2048 位,私钥格式必须为 PKCS#8。

1)生成私钥(PKCS#8,文件头 BEGIN PRIVATE KEY,所有 OpenSSL 版本通用):

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa_private_pkcs8.pem

2)由私钥导出公钥(X.509/SPKI,文件头 BEGIN PUBLIC KEY):

openssl rsa -in rsa_private_pkcs8.pem -pubout -out rsa_public.pem

注意:OpenSSL 3.x 的 openssl genrsa -out rsa_pkcs1_private.pem 2048 输出的其实已是 PKCS#8(BEGIN PRIVATE KEY); 但 OpenSSL 1.x 的 genrsa 输出的是 PKCS#1(BEGIN RSA PRIVATE KEY),Java 平台无法直接加载(报 algid parse error),必须先转换为 PKCS#8:

openssl genrsa -out rsa_pkcs1_private.pem 2048
openssl pkcs8 -topk8 -nocrypt -in rsa_pkcs1_private.pem -out rsa_private_pkcs8.pem
openssl rsa -in rsa_pkcs1_private.pem -pubout -out rsa_public.pem
密钥格式要求
  • 私钥:必须 PKCS#8(-----BEGIN PRIVATE KEY-----)。PKCS#1(BEGIN RSA PRIVATE KEY)无法加载。
  • 公钥:X.509/SPKI 格式(-----BEGIN PUBLIC KEY-----)。提供给平台时只提交 Base64 内容(去掉头尾行和换行)。
  • 私钥由商户妥善保管,切勿泄露或提交到代码仓库;公钥提交给平台配置到商户资料。
自检密钥对(模拟平台验签)
与平台验签逻辑一致:私钥签名 → 公钥解密还原明文串比对。

以支付接口为例,明文串 src = amount + callbackUrl + channelCode + merchantCode + orderNumber(无换行):

printf '%s' "100000http://localhost/callbackQRISTEST1604384758793" > src.txt

# 私钥签名(PKCS#1 v1.5 type1)得到 sign
openssl pkeyutl -sign -inkey rsa_private_pkcs8.pem -in src.txt -out sign.bin
openssl base64 -in sign.bin   # Base64 后即为请求中的 sign

# 公钥验签(模拟平台),输出 "Signature Verified Successfully" 即密钥对匹配
openssl pkeyutl -verify -pubin -inkey rsa_public.pem -in src.txt -sigfile sign.bin

也可以使用下方各语言示例代码中的 self-check(自验签)方法进行验证。

密钥使用说明
  • 请求签名:商户用自己的 RSA 私钥对明文串 src 做 PKCS#1 v1.5(type1)原始签名后 Base64,放入 sign 字段(signType=RSA)。不是 SHA256withRSA 摘要签名。
  • 平台验签:平台用商户提交的 RSA 公钥解密 sign 并与明文串比对。
  • 异步回调验签:平台回调中的 rsaSign 使用平台公钥解密比对(平台公钥请联系平台获取)。

支付接口

API URL: /payment
创建新的支付订单
POST

请求参数

参数名说明类型必填例子
merchantCode商户号StringYtest
orderNumber用户订单号StringY1604384758793
amount金额IntegerY40000
phone电话StringY08123456789
emailEmailStringYtest@test.com
userName名称StringYJohn Doe
walletNumber钱包账号StringN08821900000 (DANA必填,格式08********;OVO收银台必传真实号码,否则不需要此参数)
channelCode渠道编码StringYM1(若为CASH,会使用收银台支付;其他编码请见渠道编码对照表)
returnUrl返回地址StringNhttp://test/return
callbackUrl回调地址StringYhttp://test/callback
signType签名方式StringYRSA
sign签名StringYRSA(...)

签名规则

RSA(amount + callbackUrl + channelCode + merchantCode + orderNumber)

响应参数

参数名说明类型例子
code返回码String000
message返回消息Stringsuccess
orderNumber订单号StringO3915CB3C93E445C9
accountNumber银行账号StringTEST0001 (VA才有)
paymentUrl支付地址Stringhttp://xxx.xxx/
qrcode二维码StringQRIS支付会返回二维码源码
merchantCode商户号Stringtest

请求示例

{
  "merchantCode": "test",
  "orderNumber": "1604384758793",
  "amount": 40000,
  "phone": "08123456789",
  "email": "test@test.com",
  "userName": "John Doe",
  "channelCode": "M1",
  "returnUrl": "http://test/return",
  "callbackUrl": "http://test/callback",
  "sign": "a1b2c3d4e5f6..."
}

响应示例

{
  "code": "000",
  "message": "success",
  "orderNumber": "O3915CB3C93E445C9",
  "paymentUrl": "http://xxx.xxx/",
  "qrcode": "data:image/png;base64,..."
}
代码示例
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * VPay /payment example (pure JDK, no third-party dependency).
 *
 * 1. PRIVATE_KEY_PATH is your merchant RSA private key (PKCS#8);
 *    PUBLIC_KEY_PATH is your merchant RSA public key (X.509) for self-verification.
 * 2. sign = the merchant RSA private key does a PKCS#1 v1.5 type1 raw signing on src
 *    (signature padding block 0x00 0x01 FF..FF 0x00 + src, then c = block^d mod n)
 *    and Base64 encodes it. The platform verifies it by recomputing with the merchant
 *    RSA public key (type1) and comparing the recovered bytes with src.
 *    NOT SHA256withRSA (do not use Signature).
 */
public class PaymentDemo {

    static final String BASE_URL = "https://api.vpayid.com/vpayid-api";
    static final String PRIVATE_KEY_PATH = "YOUR_PRIVATE_KEY.pem"; // your PKCS#8 private key
    static final String PUBLIC_KEY_PATH = "YOUR_PUBLIC_KEY.pem";   // your X.509 public key
    static final String MERCHANT_CODE = "YOUR_MERCHANT_CODE";
    static final String CALLBACK_URL = "http://localhost/callback";
    public static void main(String[] args) throws Exception {
        String orderNumber = "PAY_" + System.currentTimeMillis();
        int amount = 100000;

        // src = amount + callbackUrl + channelCode + merchantCode + orderNumber
        String src = amount + CALLBACK_URL + "QRIS" + MERCHANT_CODE + orderNumber;

        PrivateKey priv = loadPrivateKey(PRIVATE_KEY_PATH);
        String sign = rsaPrivateEncryptBase64(src, priv);
        boolean ok = rsaPublicDecryptVerify(src, sign, loadPublicKey(PUBLIC_KEY_PATH)); // self-check like the platform

        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("merchantCode", MERCHANT_CODE);
        payload.put("orderNumber", orderNumber);
        payload.put("amount", amount);
        payload.put("phone", "08123456789");
        payload.put("email", "test@test.com");
        payload.put("userName", "John Doe");
        payload.put("channelCode", "QRIS");
        payload.put("callbackUrl", CALLBACK_URL);
        payload.put("signType", "RSA");
        payload.put("sign", sign);

        String body = toJson(payload);
        System.out.println("src     = " + src);
        System.out.println("selfVerify = " + ok);
        System.out.println("request = " + body);
        // optional: POST BASE_URL + "/payment" with body
    }

    /**
     * PKCS#1 v1.5 type1 raw signing of src with the merchant private key
     * -> Base64 (the required sign).
     */
    static String rsaPrivateEncryptBase64(String src, PrivateKey privateKey) {
        RSAPrivateKey rsaPriv = (RSAPrivateKey) privateKey;
        int k = rsaPriv.getModulus().bitLength() / 8;
        byte[] data = src.getBytes(StandardCharsets.UTF_8);
        if (data.length > k - 11) throw new IllegalArgumentException("src is too long");
        byte[] block = new byte[k];
        block[0] = 0x00; block[1] = 0x01;                     // type1: 00 01 FF..FF 00 src
        java.util.Arrays.fill(block, 2, k - data.length - 1, (byte) 0xFF);
        block[k - data.length - 1] = 0x00;
        System.arraycopy(data, 0, block, k - data.length, data.length);
        BigInteger m = new BigInteger(1, block);
        BigInteger c = m.modPow(rsaPriv.getPrivateExponent(), rsaPriv.getModulus());
        byte[] sig = c.toByteArray();
        if (sig.length == k + 1 && sig[0] == 0) sig = java.util.Arrays.copyOfRange(sig, 1, k + 1); // strip BigInteger sign byte
        if (sig.length > k) throw new IllegalStateException("signature is too long");
        byte[] out = new byte[k];
        System.arraycopy(sig, 0, out, k - sig.length, sig.length);
        return Base64.getEncoder().encodeToString(out);
    }

    /**
     * Self-check like the platform: recompute with the public key (type1),
     * src is recovered from 0x00 0x01 FF..FF 0x00 src when valid.
     */
    static boolean rsaPublicDecryptVerify(String src, String sign, PublicKey publicKey) {
        try {
            RSAPublicKey rsaPub = (RSAPublicKey) publicKey;
            int k = rsaPub.getModulus().bitLength() / 8;
            byte[] sig = Base64.getDecoder().decode(sign);
            BigInteger c = new BigInteger(1, sig);
            BigInteger m = c.modPow(rsaPub.getPublicExponent(), rsaPub.getModulus());
            byte[] pad = new byte[k];
            byte[] mb = m.toByteArray();
            System.arraycopy(mb, 0, pad, k - mb.length, mb.length);
            if (pad[0] != 0x00 || pad[1] != 0x01) return false;   // needs 00 01 FF..00 data
            int sep = -1;
            for (int i = 2; i < k; i++) {
                if (pad[i] == 0x00) { sep = i; break; }
            }
            if (sep < 0) return false;
            return src.equals(new String(pad, sep + 1, k - sep - 1, StandardCharsets.UTF_8));
        } catch (Exception e) {
            return false;
        }
    }

    // load PKCS#8 private key from a PEM file
    static PrivateKey loadPrivateKey(String pemPath) throws Exception {
        String pem = new String(Files.readAllBytes(Paths.get(pemPath)), StandardCharsets.UTF_8)
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(pem)));
    }

    // load X.509 public key from a PEM file (for self-verification)
    static PublicKey loadPublicKey(String pemPath) throws Exception {
        String pem = new String(Files.readAllBytes(Paths.get(pemPath)), StandardCharsets.UTF_8)
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.getMimeDecoder().decode(pem)));
    }

    // minimal JSON builder (or use any JSON library)
    static String toJson(Map<String, Object> m) {
        StringBuilder sb = new StringBuilder("{");
        boolean first = true;
        for (Map.Entry<String, Object> e : m.entrySet()) {
            if (!first) sb.append(",");
            first = false;
            sb.append("\"").append(e.getKey()).append("\":");
            Object v = e.getValue();
            if (v instanceof Number) {
                sb.append(v);
            } else {
                sb.append("\"").append(v).append("\"");
            }
        }
        return sb.append("}").toString();
    }

}

支付查询

API URL: /checkPayment
查询支付订单状态
POST

请求参数

参数名说明类型必填例子
merchantCode商户号StringYtest
orderNumber用户订单号StringY1604384758793
signType签名方式StringYRSA
sign签名StringYRSA(...)

签名规则

RSA(merchantCode + orderNumber)

响应参数

参数名说明类型例子
code返回码String000
message返回消息Stringcomplete
userOrderNumber用户订单号String1604384968942
orderNumber平台订单号Stringtest16043849702603
amount支付金额Integer40000
merchantCode商户号Stringtest
phone电话String已隐藏,显示00
emailEmailString已隐藏,显示00
userName名称StringJohn Doe
channelCode渠道编码StringM1
status支付状态int0 待支付,1 支付成功,2 订单失败
createTime订单发起时间Date2020-11-03 13:29:30
payTime支付时间Date2020-11-03 13:39:30 支付成功时返回

请求示例

{
  "merchantCode": "test",
  "orderNumber": "1604384758793",
  "sign": "a1b2c3d4e5f6..."
}

响应示例

{
  "code": "000",
  "status": 1,
  "amount": 40000
}
代码示例
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * VPay /checkPayment example (pure JDK, no third-party dependency).
 *
 * 1. PRIVATE_KEY_PATH is your merchant RSA private key (PKCS#8);
 *    PUBLIC_KEY_PATH is your merchant RSA public key (X.509) for self-verification.
 * 2. sign = the merchant RSA private key does a PKCS#1 v1.5 type1 raw signing on src
 *    (signature padding block 0x00 0x01 FF..FF 0x00 + src, then c = block^d mod n)
 *    and Base64 encodes it. The platform verifies it by recomputing with the merchant
 *    RSA public key (type1) and comparing the recovered bytes with src.
 *    NOT SHA256withRSA (do not use Signature).
 */
public class CheckPaymentDemo {

    static final String BASE_URL = "https://api.vpayid.com/vpayid-api";
    static final String PRIVATE_KEY_PATH = "YOUR_PRIVATE_KEY.pem"; // your PKCS#8 private key
    static final String PUBLIC_KEY_PATH = "YOUR_PUBLIC_KEY.pem";   // your X.509 public key
    static final String MERCHANT_CODE = "YOUR_MERCHANT_CODE";

    public static void main(String[] args) throws Exception {
        String orderNumber = "PAY_" + System.currentTimeMillis();

        // src = merchantCode + orderNumber
        String src = MERCHANT_CODE + orderNumber;

        PrivateKey priv = loadPrivateKey(PRIVATE_KEY_PATH);
        String sign = rsaPrivateEncryptBase64(src, priv);
        boolean ok = rsaPublicDecryptVerify(src, sign, loadPublicKey(PUBLIC_KEY_PATH)); // self-check like the platform

        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("merchantCode", MERCHANT_CODE);
        payload.put("orderNumber", orderNumber);
        payload.put("signType", "RSA");
        payload.put("sign", sign);

        String body = toJson(payload);
        System.out.println("src     = " + src);
        System.out.println("selfVerify = " + ok);
        System.out.println("request = " + body);
        // optional: POST BASE_URL + "/checkPayment" with body
    }

    /**
     * PKCS#1 v1.5 type1 raw signing of src with the merchant private key
     * -> Base64 (the required sign).
     */
    static String rsaPrivateEncryptBase64(String src, PrivateKey privateKey) {
        RSAPrivateKey rsaPriv = (RSAPrivateKey) privateKey;
        int k = rsaPriv.getModulus().bitLength() / 8;
        byte[] data = src.getBytes(StandardCharsets.UTF_8);
        if (data.length > k - 11) throw new IllegalArgumentException("src is too long");
        byte[] block = new byte[k];
        block[0] = 0x00; block[1] = 0x01;                     // type1: 00 01 FF..FF 00 src
        java.util.Arrays.fill(block, 2, k - data.length - 1, (byte) 0xFF);
        block[k - data.length - 1] = 0x00;
        System.arraycopy(data, 0, block, k - data.length, data.length);
        BigInteger m = new BigInteger(1, block);
        BigInteger c = m.modPow(rsaPriv.getPrivateExponent(), rsaPriv.getModulus());
        byte[] sig = c.toByteArray();
        if (sig.length == k + 1 && sig[0] == 0) sig = java.util.Arrays.copyOfRange(sig, 1, k + 1); // strip BigInteger sign byte
        if (sig.length > k) throw new IllegalStateException("signature is too long");
        byte[] out = new byte[k];
        System.arraycopy(sig, 0, out, k - sig.length, sig.length);
        return Base64.getEncoder().encodeToString(out);
    }

    /**
     * Self-check like the platform: recompute with the public key (type1),
     * src is recovered from 0x00 0x01 FF..FF 0x00 src when valid.
     */
    static boolean rsaPublicDecryptVerify(String src, String sign, PublicKey publicKey) {
        try {
            RSAPublicKey rsaPub = (RSAPublicKey) publicKey;
            int k = rsaPub.getModulus().bitLength() / 8;
            byte[] sig = Base64.getDecoder().decode(sign);
            BigInteger c = new BigInteger(1, sig);
            BigInteger m = c.modPow(rsaPub.getPublicExponent(), rsaPub.getModulus());
            byte[] pad = new byte[k];
            byte[] mb = m.toByteArray();
            System.arraycopy(mb, 0, pad, k - mb.length, mb.length);
            if (pad[0] != 0x00 || pad[1] != 0x01) return false;   // needs 00 01 FF..00 data
            int sep = -1;
            for (int i = 2; i < k; i++) {
                if (pad[i] == 0x00) { sep = i; break; }
            }
            if (sep < 0) return false;
            return src.equals(new String(pad, sep + 1, k - sep - 1, StandardCharsets.UTF_8));
        } catch (Exception e) {
            return false;
        }
    }

    // load PKCS#8 private key from a PEM file
    static PrivateKey loadPrivateKey(String pemPath) throws Exception {
        String pem = new String(Files.readAllBytes(Paths.get(pemPath)), StandardCharsets.UTF_8)
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(pem)));
    }

    // load X.509 public key from a PEM file (for self-verification)
    static PublicKey loadPublicKey(String pemPath) throws Exception {
        String pem = new String(Files.readAllBytes(Paths.get(pemPath)), StandardCharsets.UTF_8)
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.getMimeDecoder().decode(pem)));
    }

    // minimal JSON builder (or use any JSON library)
    static String toJson(Map<String, Object> m) {
        StringBuilder sb = new StringBuilder("{");
        boolean first = true;
        for (Map.Entry<String, Object> e : m.entrySet()) {
            if (!first) sb.append(",");
            first = false;
            sb.append("\"").append(e.getKey()).append("\":");
            Object v = e.getValue();
            if (v instanceof Number) {
                sb.append(v);
            } else {
                sb.append("\"").append(v).append("\"");
            }
        }
        return sb.append("}").toString();
    }

}

代付接口

API URL: /disbursement
发起代付请求
POST

请求参数

参数名说明类型必填例子
merchantCode商户号StringYtest
orderNumber用户订单号StringY1604384758793
amount金额IntegerY40000
bankAccount银行账号StringY100100100
bankCode银行编码StringYPERMATA
phone电话StringY08123456789
emailEmailStringYtest@test.com
userName对方名称StringYJohn Doe
channelCode渠道编码StringYALL
returnUrl返回地址StringNhttp://test/return
callbackUrl回调地址StringYhttp://test/callback
signType签名方式StringYRSA
sign签名StringYRSA(...)

响应参数

参数名说明类型例子
code返回码String000
message返回消息Stringsuccess
amount支付金额Integer40000
merchantCode商户号Stringtest

签名规则

RSA(amount + bankAccount + bankCode + callbackUrl + channelCode + merchantCode + orderNumber)

PS

channelCode 可用通道:ALL 所有通道可用,ID IDN US ZH 不同国家,无特殊说明请使用ALL

同步请求响应 "code": "000" 为成功,其他均为提交失败

如请求超时的时候请务必使用查询接口复核订单状态

订单最终状态以查询结果或者异步回调的 "status" 状态为准

请求示例

{
  "merchantCode": "test",
  "orderNumber": "1604384758793",
  "amount": 50000,
  "bankAccount": "1234567890",
  "bankCode": "BCA",
  "sign": "a1b2c3d4e5f6..."
}

响应示例

{
  "code": "000",
  "message": "success",
  "orderNumber": "1604384758793"
}
代码示例
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * VPay /disbursement example (pure JDK, no third-party dependency).
 *
 * 1. PRIVATE_KEY_PATH is your merchant RSA private key (PKCS#8);
 *    PUBLIC_KEY_PATH is your merchant RSA public key (X.509) for self-verification.
 * 2. sign = the merchant RSA private key does a PKCS#1 v1.5 type1 raw signing on src
 *    (signature padding block 0x00 0x01 FF..FF 0x00 + src, then c = block^d mod n)
 *    and Base64 encodes it. The platform verifies it by recomputing with the merchant
 *    RSA public key (type1) and comparing the recovered bytes with src.
 *    NOT SHA256withRSA (do not use Signature).
 */
public class DisbursementDemo {

    static final String BASE_URL = "https://api.vpayid.com/vpayid-api";
    static final String PRIVATE_KEY_PATH = "YOUR_PRIVATE_KEY.pem"; // your PKCS#8 private key
    static final String PUBLIC_KEY_PATH = "YOUR_PUBLIC_KEY.pem";   // your X.509 public key
    static final String MERCHANT_CODE = "YOUR_MERCHANT_CODE";
    static final String CALLBACK_URL = "http://localhost/callback";
    public static void main(String[] args) throws Exception {
        String orderNumber = "OUT_" + System.currentTimeMillis();
        int amount = 1000000;
        String bankAccount = "7550390595";
        String bankCode = "BCA";

        // src = amount + bankAccount + bankCode + callbackUrl + channelCode + merchantCode + orderNumber
        String src = amount + bankAccount + bankCode + CALLBACK_URL + "ALL" + MERCHANT_CODE + orderNumber;

        PrivateKey priv = loadPrivateKey(PRIVATE_KEY_PATH);
        String sign = rsaPrivateEncryptBase64(src, priv);
        boolean ok = rsaPublicDecryptVerify(src, sign, loadPublicKey(PUBLIC_KEY_PATH)); // self-check like the platform

        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("merchantCode", MERCHANT_CODE);
        payload.put("orderNumber", orderNumber);
        payload.put("amount", amount);
        payload.put("bankAccount", bankAccount);
        payload.put("bankCode", bankCode);
        payload.put("phone", "08000000000");
        payload.put("email", "noreply@test.com");
        payload.put("userName", "Fandi");
        payload.put("channelCode", "ALL");
        payload.put("callbackUrl", CALLBACK_URL);
        payload.put("signType", "RSA");
        payload.put("sign", sign);

        String body = toJson(payload);
        System.out.println("src     = " + src);
        System.out.println("selfVerify = " + ok);
        System.out.println("request = " + body);
        // optional: POST BASE_URL + "/disbursement" with body
    }

    /**
     * PKCS#1 v1.5 type1 raw signing of src with the merchant private key
     * -> Base64 (the required sign).
     */
    static String rsaPrivateEncryptBase64(String src, PrivateKey privateKey) {
        RSAPrivateKey rsaPriv = (RSAPrivateKey) privateKey;
        int k = rsaPriv.getModulus().bitLength() / 8;
        byte[] data = src.getBytes(StandardCharsets.UTF_8);
        if (data.length > k - 11) throw new IllegalArgumentException("src is too long");
        byte[] block = new byte[k];
        block[0] = 0x00; block[1] = 0x01;                     // type1: 00 01 FF..FF 00 src
        java.util.Arrays.fill(block, 2, k - data.length - 1, (byte) 0xFF);
        block[k - data.length - 1] = 0x00;
        System.arraycopy(data, 0, block, k - data.length, data.length);
        BigInteger m = new BigInteger(1, block);
        BigInteger c = m.modPow(rsaPriv.getPrivateExponent(), rsaPriv.getModulus());
        byte[] sig = c.toByteArray();
        if (sig.length == k + 1 && sig[0] == 0) sig = java.util.Arrays.copyOfRange(sig, 1, k + 1); // strip BigInteger sign byte
        if (sig.length > k) throw new IllegalStateException("signature is too long");
        byte[] out = new byte[k];
        System.arraycopy(sig, 0, out, k - sig.length, sig.length);
        return Base64.getEncoder().encodeToString(out);
    }

    /**
     * Self-check like the platform: recompute with the public key (type1),
     * src is recovered from 0x00 0x01 FF..FF 0x00 src when valid.
     */
    static boolean rsaPublicDecryptVerify(String src, String sign, PublicKey publicKey) {
        try {
            RSAPublicKey rsaPub = (RSAPublicKey) publicKey;
            int k = rsaPub.getModulus().bitLength() / 8;
            byte[] sig = Base64.getDecoder().decode(sign);
            BigInteger c = new BigInteger(1, sig);
            BigInteger m = c.modPow(rsaPub.getPublicExponent(), rsaPub.getModulus());
            byte[] pad = new byte[k];
            byte[] mb = m.toByteArray();
            System.arraycopy(mb, 0, pad, k - mb.length, mb.length);
            if (pad[0] != 0x00 || pad[1] != 0x01) return false;   // needs 00 01 FF..00 data
            int sep = -1;
            for (int i = 2; i < k; i++) {
                if (pad[i] == 0x00) { sep = i; break; }
            }
            if (sep < 0) return false;
            return src.equals(new String(pad, sep + 1, k - sep - 1, StandardCharsets.UTF_8));
        } catch (Exception e) {
            return false;
        }
    }

    // load PKCS#8 private key from a PEM file
    static PrivateKey loadPrivateKey(String pemPath) throws Exception {
        String pem = new String(Files.readAllBytes(Paths.get(pemPath)), StandardCharsets.UTF_8)
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(pem)));
    }

    // load X.509 public key from a PEM file (for self-verification)
    static PublicKey loadPublicKey(String pemPath) throws Exception {
        String pem = new String(Files.readAllBytes(Paths.get(pemPath)), StandardCharsets.UTF_8)
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.getMimeDecoder().decode(pem)));
    }

    // minimal JSON builder (or use any JSON library)
    static String toJson(Map<String, Object> m) {
        StringBuilder sb = new StringBuilder("{");
        boolean first = true;
        for (Map.Entry<String, Object> e : m.entrySet()) {
            if (!first) sb.append(",");
            first = false;
            sb.append("\"").append(e.getKey()).append("\":");
            Object v = e.getValue();
            if (v instanceof Number) {
                sb.append(v);
            } else {
                sb.append("\"").append(v).append("\"");
            }
        }
        return sb.append("}").toString();
    }

}

代付查询

API URL: /checkDisbursement
查询代付订单状态
POST

请求参数

参数名说明类型必填例子
merchantCode商户号StringYtest
orderNumber用户订单号StringY1604384758793
signType签名方式StringYRSA
sign签名StringYRSA(...)

响应参数

参数名说明类型例子
code返回码String000
message返回消息Stringcomplete
userOrderNumber用户订单号String1604384968942
orderNumber平台订单号Stringtest16043849702603
amount支付金额Integer40000
merchantCode商户号Stringtest
phone电话String已隐藏,显示00
emailEmailString已隐藏,显示00
userName对方名称StringJohn Doe
bankCode银行编码StringPERMATA
bankAccount银行账号String100100100
channelCode渠道编码StringM1
status支付状态int1 (0 待支付,1 代付成功,2 代付失败)
createTime订单发起时间Date2020-11-03 13:29:30
payTime支付时间Date2020-11-03 13:39:30 支付成功时返回

签名规则

RSA(merchantCode + orderNumber)

请求示例

{
  "merchantCode": "test",
  "orderNumber": "1604384758793",
  "sign": "a1b2c3d4e5f6..."
}

响应示例

{
  "code": "000",
  "status": 1,
  "amount": 50000
}
代码示例
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * VPay /checkDisbursement example (pure JDK, no third-party dependency).
 *
 * 1. PRIVATE_KEY_PATH is your merchant RSA private key (PKCS#8);
 *    PUBLIC_KEY_PATH is your merchant RSA public key (X.509) for self-verification.
 * 2. sign = the merchant RSA private key does a PKCS#1 v1.5 type1 raw signing on src
 *    (signature padding block 0x00 0x01 FF..FF 0x00 + src, then c = block^d mod n)
 *    and Base64 encodes it. The platform verifies it by recomputing with the merchant
 *    RSA public key (type1) and comparing the recovered bytes with src.
 *    NOT SHA256withRSA (do not use Signature).
 */
public class CheckDisbursementDemo {

    static final String BASE_URL = "https://api.vpayid.com/vpayid-api";
    static final String PRIVATE_KEY_PATH = "YOUR_PRIVATE_KEY.pem"; // your PKCS#8 private key
    static final String PUBLIC_KEY_PATH = "YOUR_PUBLIC_KEY.pem";   // your X.509 public key
    static final String MERCHANT_CODE = "YOUR_MERCHANT_CODE";

    public static void main(String[] args) throws Exception {
        String orderNumber = "OUT_" + System.currentTimeMillis();

        // src = merchantCode + orderNumber
        String src = MERCHANT_CODE + orderNumber;

        PrivateKey priv = loadPrivateKey(PRIVATE_KEY_PATH);
        String sign = rsaPrivateEncryptBase64(src, priv);
        boolean ok = rsaPublicDecryptVerify(src, sign, loadPublicKey(PUBLIC_KEY_PATH)); // self-check like the platform

        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("merchantCode", MERCHANT_CODE);
        payload.put("orderNumber", orderNumber);
        payload.put("signType", "RSA");
        payload.put("sign", sign);

        String body = toJson(payload);
        System.out.println("src     = " + src);
        System.out.println("selfVerify = " + ok);
        System.out.println("request = " + body);
        // optional: POST BASE_URL + "/checkDisbursement" with body
    }

    /**
     * PKCS#1 v1.5 type1 raw signing of src with the merchant private key
     * -> Base64 (the required sign).
     */
    static String rsaPrivateEncryptBase64(String src, PrivateKey privateKey) {
        RSAPrivateKey rsaPriv = (RSAPrivateKey) privateKey;
        int k = rsaPriv.getModulus().bitLength() / 8;
        byte[] data = src.getBytes(StandardCharsets.UTF_8);
        if (data.length > k - 11) throw new IllegalArgumentException("src is too long");
        byte[] block = new byte[k];
        block[0] = 0x00; block[1] = 0x01;                     // type1: 00 01 FF..FF 00 src
        java.util.Arrays.fill(block, 2, k - data.length - 1, (byte) 0xFF);
        block[k - data.length - 1] = 0x00;
        System.arraycopy(data, 0, block, k - data.length, data.length);
        BigInteger m = new BigInteger(1, block);
        BigInteger c = m.modPow(rsaPriv.getPrivateExponent(), rsaPriv.getModulus());
        byte[] sig = c.toByteArray();
        if (sig.length == k + 1 && sig[0] == 0) sig = java.util.Arrays.copyOfRange(sig, 1, k + 1); // strip BigInteger sign byte
        if (sig.length > k) throw new IllegalStateException("signature is too long");
        byte[] out = new byte[k];
        System.arraycopy(sig, 0, out, k - sig.length, sig.length);
        return Base64.getEncoder().encodeToString(out);
    }

    /**
     * Self-check like the platform: recompute with the public key (type1),
     * src is recovered from 0x00 0x01 FF..FF 0x00 src when valid.
     */
    static boolean rsaPublicDecryptVerify(String src, String sign, PublicKey publicKey) {
        try {
            RSAPublicKey rsaPub = (RSAPublicKey) publicKey;
            int k = rsaPub.getModulus().bitLength() / 8;
            byte[] sig = Base64.getDecoder().decode(sign);
            BigInteger c = new BigInteger(1, sig);
            BigInteger m = c.modPow(rsaPub.getPublicExponent(), rsaPub.getModulus());
            byte[] pad = new byte[k];
            byte[] mb = m.toByteArray();
            System.arraycopy(mb, 0, pad, k - mb.length, mb.length);
            if (pad[0] != 0x00 || pad[1] != 0x01) return false;   // needs 00 01 FF..00 data
            int sep = -1;
            for (int i = 2; i < k; i++) {
                if (pad[i] == 0x00) { sep = i; break; }
            }
            if (sep < 0) return false;
            return src.equals(new String(pad, sep + 1, k - sep - 1, StandardCharsets.UTF_8));
        } catch (Exception e) {
            return false;
        }
    }

    // load PKCS#8 private key from a PEM file
    static PrivateKey loadPrivateKey(String pemPath) throws Exception {
        String pem = new String(Files.readAllBytes(Paths.get(pemPath)), StandardCharsets.UTF_8)
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(pem)));
    }

    // load X.509 public key from a PEM file (for self-verification)
    static PublicKey loadPublicKey(String pemPath) throws Exception {
        String pem = new String(Files.readAllBytes(Paths.get(pemPath)), StandardCharsets.UTF_8)
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.getMimeDecoder().decode(pem)));
    }

    // minimal JSON builder (or use any JSON library)
    static String toJson(Map<String, Object> m) {
        StringBuilder sb = new StringBuilder("{");
        boolean first = true;
        for (Map.Entry<String, Object> e : m.entrySet()) {
            if (!first) sb.append(",");
            first = false;
            sb.append("\"").append(e.getKey()).append("\":");
            Object v = e.getValue();
            if (v instanceof Number) {
                sb.append(v);
            } else {
                sb.append("\"").append(v).append("\"");
            }
        }
        return sb.append("}").toString();
    }

}

可用渠道查询

API URL: /queryChannel
查询可用支付渠道
POST

请求参数

参数名说明类型必填例子
merchantCode商户号StringYtest
timestamp时间戳StringY1604384758793
signType签名方式StringYRSA
sign签名StringYRSA(merchantCode + timestamp)

响应参数

参数名说明类型例子
code返回码String000
message返回消息Stringquery success
channelCodeList渠道集合LIST
- channelCode渠道代码StringBT
- channelType渠道类型Integer0 支付渠道 1 代付渠道
- channelRate渠道费率Double7.0
- channelFee渠道服务费Integer200

签名规则

RSA(merchantCode + timestamp)

请求示例

{
  "merchantCode": "test",
  "timestamp": 1604384758793,
  "sign": "a1b2c3d4e5f6..."
}

响应示例

{
  "code": "000",
  "channels": [
    {
      "channelCode": "M1",
      "channelName": "QRIS",
      "minAmount": 1000,
      "maxAmount": 10000000
    }
  ]
}

余额查询

API URL: /checkBalance
查询商户余额
POST

请求参数

参数名说明类型必填例子
merchantCode商户号StringYtest
timestamp时间戳StringY1604384758793
signType签名方式StringYRSA
sign签名StringYRSA(merchantCode + timestamp)

响应参数

参数名说明类型例子
code返回码String000
message返回消息Stringbalance query success
merchantCode商户号String1604384968942
merchantBlance商户余额Integer540000
merchantFrozenAmount冻结金额Integer40000
canUseAmount可用金额Integer500000
settlementList待结算金额列表LIST
- settlementDate结算日期String2020-12-28
- settlementAmount待结算金额Integer44500

签名规则

RSA(merchantCode + timestamp)

请求示例

{
  "merchantCode": "test",
  "timestamp": 1604384758793,
  "sign": "a1b2c3d4e5f6..."
}

响应示例

{
  "code": "000",
  "balance": 1000000
}

异步回调

支付回调

支付完成后,系统会向您配置的 callbackUrl 发送 POST 请求

回调参数

参数名说明类型示例
code返回码String000
message返回消息Stringcomplete
orderNumber用户订单号String1604384968942
amount支付金额Integer40000
merchantCode商户号Stringtest
phone电话(已隐藏)String00
emailEmail(已隐藏)String00
userName名称StringJohn Doe
channelCode渠道编码StringM1
status支付状态:0待支付,1成功int1
createTime订单发起时间(时间戳)Date1617695193459
payTime支付时间(时间戳,成功时返回)Date1617695193459
rsaSignRSA签名StringRSA(amount + channelCode + email + merchantCode + orderNumber + phone + userName)

签名规则

RSA(amount + channelCode + email + merchantCode + orderNumber + phone + userName)

验签方式:商户收到回调后,将上方字段按签名规则顺序拼接为明文串 src,再用平台提供的 RSA 公钥按 PKCS#1 v1.5(type1)对 rsaSign 验算(还原出 00 01 FF…FF 00 src),与明文串 src 逐字节比对,一致才视为有效回调(与请求签名同一算法,不是 SHA256withRSA)。

回调示例

{
  "code": "000",
  "message": "complete",
  "orderNumber": "1604384968942",
  "amount": 40000,
  "merchantCode": "test",
  "phone": "00",
  "email": "00",
  "userName": "John Doe",
  "channelCode": "M1",
  "status": 1,
  "createTime": 1617695193459,
  "payTime": 1617695193459,
  "rsaSign": "a1b2c3d4e5f6..."
}

应答:请返回状态值 200 确认收到回调

代付回调

代付完成后,系统会向您配置的 callbackUrl 发送 POST 请求

回调参数

参数名说明类型示例
code返回码String000
message返回消息Stringcomplete
orderNumber用户订单号String1604384968942
amount代付金额Integer40000
merchantCode商户号Stringtest
phone电话(已隐藏)String00
emailEmail(已隐藏)String00
userName对方名称StringJohn Doe
bankCode银行编码StringPERMATA
bankAccount银行账号String100100100
channelCode渠道编码StringALL
status代付状态:1成功,2失败int1
createTime订单发起时间(时间戳)Date1617695193459
payTime支付时间(时间戳,成功时返回)Date1617695193459
rsaSignRSA签名StringRSA(amount + bankAccount + bankCode + channelCode + email + merchantCode + orderNumber + phone + userName)

签名规则

RSA(amount + bankAccount + bankCode + channelCode + email + merchantCode + orderNumber + phone + userName)

验签方式:商户收到回调后,将上方字段按签名规则顺序拼接为明文串 src,再用平台提供的 RSA 公钥按 PKCS#1 v1.5(type1)对 rsaSign 验算(还原出 00 01 FF…FF 00 src),与明文串 src 逐字节比对,一致才视为有效回调(与请求签名同一算法,不是 SHA256withRSA)。

回调示例

{
  "code": "000",
  "message": "complete",
  "orderNumber": "1604384968942",
  "amount": 40000,
  "merchantCode": "test",
  "phone": "00",
  "email": "00",
  "userName": "John Doe",
  "bankCode": "PERMATA",
  "bankAccount": "100100100",
  "channelCode": "ALL",
  "status": 1,
  "createTime": 1617695193459,
  "payTime": 1617695193459,
  "rsaSign": "a1b2c3d4e5f6..."
}

应答:请返回状态值 200 确认收到回调

支付渠道编码对照表

🇮🇩 印度尼西亚 (Indonesia)
channel_codechannel_name类型
QRISQRIS收款
VCCredit Card (Visa / Master)收款
BKBCA KlikPay收款
BCABCA Virtual Account收款
BBCBCA BC收款
M1Mandiri Virtual Account收款
MEMandiri eCash收款
MPMandiri PC收款
BTPermata Bank Virtual Account收款
PNPermata Net收款
A1ATM Bersama收款
B1CIMB Niaga Virtual Account收款
I1BNI Virtual Account收款
VAMaybank Virtual Account收款
M2UMaybank2U收款
FTRitel收款
OVOVO收款
LALINKAJA收款
DANADANA收款
SPShopee Pay收款
SAShopee Pay Apps收款
AGBank Artha Graha收款
S1Sahabat Sampoerna收款
NIBank Negara Indonesia收款
RIBank Rakyat Indonesia收款
RIEBRI ePay收款
ININDOMARET收款
ALALFAMART收款
DOBDanamon Online Banking收款
DVADanamon Virtual Account收款
AIAlto iDebit收款
JIBJenius IB收款
JAJAKONE收款
OCOcto Clicks收款
SKSakuku收款
ALFAlfagroup收款
KRKredivo收款
BLBTNONLINE收款
SVASinarmas Virtual Account收款
CASH收银台支付收款
ALL印尼代付全渠道代付
🇵🇰 巴基斯坦 (Pakistan)
channel_codechannel_name类型
PK_JAZZJAZZ钱包收款
PK_EASYEASY钱包收款
PK_ALL巴基斯坦代付全渠道代付
🇧🇷 巴西 (Brazil)
channel_codechannel_name类型
BR_BRL二维码支付 (QR Code)收款
BR_ALLPIX代付渠道代付
🇹🇭 泰国 (Thailand)
channel_codechannel_name类型
TH_QR泰国二维码支付 (QR Code)收款
TH_ALL泰国代付全渠道代付

银行编码对照表

代付 银行编码列表

🇮🇩 印度尼西亚 (Indonesia)

bank_codebank_nameactivate
ACEHBPD ACEH✓
AGRISBank Agris
ARTOSBank Artos Indonesia
BANK_NOBUBank Nobu✓
BANK_SUBPD SULAWESI TENGAH✓
BCABank BCA✓
BCASYRBANK BCA SYARIAH✓
BJBBank BJB✓
BJB_SYRBank BJB Syariah✓
BNIBANK BNI✓
BNI_SYRBank BNI Syariah✓
BRIBank BRI✓
BRI_SYRBANK BRI SYARIAH✓
BTNBank BTN✓
BTPNBank BTPN✓
BUKOPINBank Bukopin✓
BUKOPIN_SYRBank Syariah Bukopin✓
BUMI_ARTABank Bumi Arta✓
CHINATRUSTBank Chinatrust Indonesia✓
CIMBBANK CIMB NIAGA✓
COMMONWEALTHBank Commonwealth✓
DANAMONBank Danamon✓
DBSBank DBS Indonesia✓
DINAR_INDONESIABank Dinar Indonesia
DKIBank DKI✓
GANESHABank Ganesha✓
HARDA_INTERNASIONALBank Harda Internasional
ICBCBank ICBC Indonesia✓
INDEX_SELINDOBank Index Selindo✓
INDIABank of India Indonesia✓
JASA_JAKARTABank Jasa Jakarta✓
KESEJAHTERAAN_EKONOMBank Kesejahteraan Ekonomi
MANDIRIBank Mandiri✓
MANDIRI_SYRBank Syariah Mandiri✓
MASPIONBank Maspion Indonesia✓
MAYBANKBANK MAYBANK INDONESIA✓
MAYORABank Mayora✓
MEGABank Mega✓
MUAMALATBank Muamalat Indonesia✓
NATIONALNOBBANK NATIONALNOBU✓
OCBCBank OCBC NISP✓
PANINBank Panin✓
PERMATABank Permata✓
SAHABAT_SAMPOERNABank Sahabat Sampoerna✓
SINARMASBank Sinarmas✓
STANDARD_CHARTEREDStandard Chartered Bank✓
SULUTBANK SULUT✓
UOBBank UOB Indonesia✓
VICTORIA_SYRBank Victoria Syariah✓
CITIBANKCitibank✓
ARTHABank Artha Graha Internasional✓
TOKYOBank of Tokyo Mitsubishi UFJ✓
CAPITALBank Capital Indonesia✓
ANZANZ Indonesia✓
BOCBank of China (Hong Kong) Limited✓
HSBCHSBC Indonesia✓
RABOBANKRabobank International Indonesia✓
MAYAPADABank Mayapada✓
DAERAH_ISTIMEWABPD DIY✓
JAWATIMURBPD JAWA TIMUR/SYARIAH✓
JAMBIBank Jambi✓
SUMUTBank Sumut✓
SUMATERA_BARATBPD SUMATERA BARAT/NAGARI SYARIAH✓
RIAU_DAN_KEPRIBank Riau Kepri✓
SUMSEL_DAN_BABELBank Sumsel Babel✓
LAMPUNGBank Lampung✓
KALIMANTAN_SELATANBPD KALIMANTAN SELATAN✓
KALIMANTAN_TIMURBPD KALIMANTAN TIMUR DAN KALIMANTAN UTARA✓
KALIMANTAN_TENGAHBPD KALIMANTAN TENGAH✓
SULSELBARBank Sulselbar✓
NUSA_TENGGARA_BARATBPD NUSA TENGGARA BARAT SYARIAH✓
BALIBPD Bali✓
NUSA_TENGGARA_TIMURBPD NUSA TENGGARA TIMUR✓
MALUKUBank Maluku✓
PAPUABank Papua✓
BENGKULUBank Bengkulu✓
SULAWESIBank Sulteng✓
NUSANTARAPARAHYANGABank Nusantara Parahyangan
MESTIKADHARMABank Mestika Dharma✓
QNBKESAWANQNB Indonesia✓
WOORIBank Woori Saudara✓
BTPNSYRBank BTPN Syariah✓
HANAKEB Hana Bank Indonesia✓
MNC_INTERNASIONALBank MNC Internasional✓
AGRONIAGAAGRONIAGA✓
SBIINDONESIASBI Indonesia✓
ROYALBank Royal Indonesia✓
MEGASYRBank Mega Syariah✓
INAPERDANABank Ina Perdana✓
VICTORIAINTERNASIONBank Victoria International✓
BANTENBPD Banten✓
MUTIARABank Mutiara
PANINSYRPanin Dubai Syariah✓
ANTARDAERAHBank Antardaerah
CCBBank China Construction Bank Indonesia✓
CNBBank CNB (Centratama Nasional Bank)✓
EKABPR EKA (Bank Eka)✓
MANTAPBANK MANTAP (Mandiri Taspen)✓
MASBank Multi Arta Sentosa (Bank MAS)✓
PRIMABank Prima Master✓
SHINHANBank Shinhan Indonesia✓
YUDHA_BAKTIBank Yudha Bakti
GOPAYGoPay✓
OVOOVO✓
SHOPEEPAYShopeePay✓
DANADana✓
LINKAJALinkAja
KALIMANTANBARATBPD Kalimantan Barat Bank Kalbar✓
JAWATENGAHBPD JAWA TENGAH✓
ALTOALTO
BSIBSI (Bank Syariah Indonesia)✓
AMARBANK AMAR INDONESIA✓
CTBCBANK CTBC INDONESIA✓
IBKBANK IBK INDONESIA✓
BRA_CPFBRA CPF✓
BNEOBANK NEO COMMERCE
BSEAIBANK SEABANK INDONESIA✓
BJAGOBANK JAGO
ALADINBANK ALADIN SYARIAH
BACBANK API CHANNEL

🇵🇰 巴基斯坦 (Pakistan)

bank_codebank_name
PK_EASYEASY钱包 (EASY Wallet)
PK_JAZZJAZZ钱包 (JAZZ Wallet)

🇧🇷 巴西 (Brazil)

bank_codebank_name
BR_CPFCPF号码 (CPF Number)
BR_PHONE手机号 (Phone Number)
BR_EMAIL邮箱 (Email)

🇹🇭 泰国 (Thailand)

bank_codebank_name
TH_BAACBANK FOR AGRICULTURE AND AGRICULTURAL COOPERATIVES
TH_BAYBANK OF AYUDHYA PUBLIC COMPANY LIMITED
TH_BBLBANGKOK BANK PUBLIC COMPANY LTD.
TH_CIMBTCIMB THAI BANK PUPBLIC COMPANY LTD.
TH_CITICITIBANK, N.A.
TH_GHBTHE GOVERNMENT HOUSING BANK
TH_GSBTHE GOVERNMENT SAVINGS BANK
TH_ISBTISLAMIC BANK OF THAILAND
TH_ICBCTINDUSTRIAL AND COMMERCIAL BANK OF CHINA (THAI) PUBLIC COMPANY LIMITED
TH_KBANKKASIKORNBANK PUBLIC COMPANY LTD.
TH_KKKIATNAKIN BANK PUBLIC COMPANY LIMITED
TH_KTBKRUNG THAI BANK PUBLIC COMPANY LTD.
TH_LHBANKLAND AND HOUSES BANK PUBLIC COMPANY LIMITED
TH_MIZUHOMIZUHO CORPORATE BANK, LTD.
TH_SCBSIAM COMMERCIAL BANK PUBLIC COMPANY LTD
TH_SCBTSTANDARD CHARTERED BANK (THAI) PUBLIC COMPANY LIMITED
TH_SMBCSUMITOMO MITSUI BANKING CORPORATION
TH_TBANKTHANACHART BANK PUBLIC COMPANY LTD.
TH_TCRTHE THAI CREDIT RETAIL BANK PUBLIC COMPANY LIMITED
TH_TISCOTISCO BANK PUBLIC COMPANY LIMITED
TH_TMBTMB BANK PUBLIC COMPANY LIMITED
TH_TTBTMBTHANACHART BANK PUBLIC COMPANY LIMITED
TH_UOBTUNITED OVERSEAS BANK (THAI) PUBLIC COMPANY LIMITED
TH_PROMPTPAYPromptPay