VPay API 文档
快速、安全、可靠的支付解决方案
- RESTful API 设计,简单易用
- 支持 PHP、Python、Golang 等多种语言
- 完整的支付流程:支付、查询、代付等
- 实时回调通知
- RSA 签名验证
基本规则
生产环境:
https://api.vpayid.com/vpayid-apiapplication/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 密钥生成
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使用平台公钥解密比对(平台公钥请联系平台获取)。
支付接口
请求参数
| 参数名 | 说明 | 类型 | 必填 | 例子 |
|---|---|---|---|---|
| merchantCode | 商户号 | String | Y | test |
| orderNumber | 用户订单号 | String | Y | 1604384758793 |
| amount | 金额 | Integer | Y | 40000 |
| phone | 电话 | String | Y | 08123456789 |
| String | Y | test@test.com | ||
| userName | 名称 | String | Y | John Doe |
| walletNumber | 钱包账号 | String | N | 08821900000 (DANA必填,格式08********;OVO收银台必传真实号码,否则不需要此参数) |
| channelCode | 渠道编码 | String | Y | M1(若为CASH,会使用收银台支付;其他编码请见渠道编码对照表) |
| returnUrl | 返回地址 | String | N | http://test/return |
| callbackUrl | 回调地址 | String | Y | http://test/callback |
| signType | 签名方式 | String | Y | RSA |
| sign | 签名 | String | Y | RSA(...) |
签名规则
RSA(amount + callbackUrl + channelCode + merchantCode + orderNumber)
响应参数
| 参数名 | 说明 | 类型 | 例子 |
|---|---|---|---|
| code | 返回码 | String | 000 |
| message | 返回消息 | String | success |
| orderNumber | 订单号 | String | O3915CB3C93E445C9 |
| accountNumber | 银行账号 | String | TEST0001 (VA才有) |
| paymentUrl | 支付地址 | String | http://xxx.xxx/ |
| qrcode | 二维码 | String | QRIS支付会返回二维码源码 |
| merchantCode | 商户号 | String | test |
请求示例
{
"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();
}
}支付查询
请求参数
| 参数名 | 说明 | 类型 | 必填 | 例子 |
|---|---|---|---|---|
| merchantCode | 商户号 | String | Y | test |
| orderNumber | 用户订单号 | String | Y | 1604384758793 |
| signType | 签名方式 | String | Y | RSA |
| sign | 签名 | String | Y | RSA(...) |
签名规则
RSA(merchantCode + orderNumber)
响应参数
| 参数名 | 说明 | 类型 | 例子 |
|---|---|---|---|
| code | 返回码 | String | 000 |
| message | 返回消息 | String | complete |
| userOrderNumber | 用户订单号 | String | 1604384968942 |
| orderNumber | 平台订单号 | String | test16043849702603 |
| amount | 支付金额 | Integer | 40000 |
| merchantCode | 商户号 | String | test |
| phone | 电话 | String | 已隐藏,显示00 |
| String | 已隐藏,显示00 | ||
| userName | 名称 | String | John Doe |
| channelCode | 渠道编码 | String | M1 |
| status | 支付状态 | int | 0 待支付,1 支付成功,2 订单失败 |
| createTime | 订单发起时间 | Date | 2020-11-03 13:29:30 |
| payTime | 支付时间 | Date | 2020-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();
}
}代付接口
请求参数
| 参数名 | 说明 | 类型 | 必填 | 例子 |
|---|---|---|---|---|
| merchantCode | 商户号 | String | Y | test |
| orderNumber | 用户订单号 | String | Y | 1604384758793 |
| amount | 金额 | Integer | Y | 40000 |
| bankAccount | 银行账号 | String | Y | 100100100 |
| bankCode | 银行编码 | String | Y | PERMATA |
| phone | 电话 | String | Y | 08123456789 |
| String | Y | test@test.com | ||
| userName | 对方名称 | String | Y | John Doe |
| channelCode | 渠道编码 | String | Y | ALL |
| returnUrl | 返回地址 | String | N | http://test/return |
| callbackUrl | 回调地址 | String | Y | http://test/callback |
| signType | 签名方式 | String | Y | RSA |
| sign | 签名 | String | Y | RSA(...) |
响应参数
| 参数名 | 说明 | 类型 | 例子 |
|---|---|---|---|
| code | 返回码 | String | 000 |
| message | 返回消息 | String | success |
| amount | 支付金额 | Integer | 40000 |
| merchantCode | 商户号 | String | test |
签名规则
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();
}
}代付查询
请求参数
| 参数名 | 说明 | 类型 | 必填 | 例子 |
|---|---|---|---|---|
| merchantCode | 商户号 | String | Y | test |
| orderNumber | 用户订单号 | String | Y | 1604384758793 |
| signType | 签名方式 | String | Y | RSA |
| sign | 签名 | String | Y | RSA(...) |
响应参数
| 参数名 | 说明 | 类型 | 例子 |
|---|---|---|---|
| code | 返回码 | String | 000 |
| message | 返回消息 | String | complete |
| userOrderNumber | 用户订单号 | String | 1604384968942 |
| orderNumber | 平台订单号 | String | test16043849702603 |
| amount | 支付金额 | Integer | 40000 |
| merchantCode | 商户号 | String | test |
| phone | 电话 | String | 已隐藏,显示00 |
| String | 已隐藏,显示00 | ||
| userName | 对方名称 | String | John Doe |
| bankCode | 银行编码 | String | PERMATA |
| bankAccount | 银行账号 | String | 100100100 |
| channelCode | 渠道编码 | String | M1 |
| status | 支付状态 | int | 1 (0 待支付,1 代付成功,2 代付失败) |
| createTime | 订单发起时间 | Date | 2020-11-03 13:29:30 |
| payTime | 支付时间 | Date | 2020-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();
}
}可用渠道查询
请求参数
| 参数名 | 说明 | 类型 | 必填 | 例子 |
|---|---|---|---|---|
| merchantCode | 商户号 | String | Y | test |
| timestamp | 时间戳 | String | Y | 1604384758793 |
| signType | 签名方式 | String | Y | RSA |
| sign | 签名 | String | Y | RSA(merchantCode + timestamp) |
响应参数
| 参数名 | 说明 | 类型 | 例子 |
|---|---|---|---|
| code | 返回码 | String | 000 |
| message | 返回消息 | String | query success |
| channelCodeList | 渠道集合 | LIST | |
| - channelCode | 渠道代码 | String | BT |
| - channelType | 渠道类型 | Integer | 0 支付渠道 1 代付渠道 |
| - channelRate | 渠道费率 | Double | 7.0 |
| - channelFee | 渠道服务费 | Integer | 200 |
签名规则
RSA(merchantCode + timestamp)
请求示例
{
"merchantCode": "test",
"timestamp": 1604384758793,
"sign": "a1b2c3d4e5f6..."
}响应示例
{
"code": "000",
"channels": [
{
"channelCode": "M1",
"channelName": "QRIS",
"minAmount": 1000,
"maxAmount": 10000000
}
]
}余额查询
请求参数
| 参数名 | 说明 | 类型 | 必填 | 例子 |
|---|---|---|---|---|
| merchantCode | 商户号 | String | Y | test |
| timestamp | 时间戳 | String | Y | 1604384758793 |
| signType | 签名方式 | String | Y | RSA |
| sign | 签名 | String | Y | RSA(merchantCode + timestamp) |
响应参数
| 参数名 | 说明 | 类型 | 例子 |
|---|---|---|---|
| code | 返回码 | String | 000 |
| message | 返回消息 | String | balance query success |
| merchantCode | 商户号 | String | 1604384968942 |
| merchantBlance | 商户余额 | Integer | 540000 |
| merchantFrozenAmount | 冻结金额 | Integer | 40000 |
| canUseAmount | 可用金额 | Integer | 500000 |
| settlementList | 待结算金额列表 | LIST | |
| - settlementDate | 结算日期 | String | 2020-12-28 |
| - settlementAmount | 待结算金额 | Integer | 44500 |
签名规则
RSA(merchantCode + timestamp)
请求示例
{
"merchantCode": "test",
"timestamp": 1604384758793,
"sign": "a1b2c3d4e5f6..."
}响应示例
{
"code": "000",
"balance": 1000000
}异步回调
支付完成后,系统会向您配置的 callbackUrl 发送 POST 请求
回调参数
| 参数名 | 说明 | 类型 | 示例 |
|---|---|---|---|
| code | 返回码 | String | 000 |
| message | 返回消息 | String | complete |
| orderNumber | 用户订单号 | String | 1604384968942 |
| amount | 支付金额 | Integer | 40000 |
| merchantCode | 商户号 | String | test |
| phone | 电话(已隐藏) | String | 00 |
| Email(已隐藏) | String | 00 | |
| userName | 名称 | String | John Doe |
| channelCode | 渠道编码 | String | M1 |
| status | 支付状态:0待支付,1成功 | int | 1 |
| createTime | 订单发起时间(时间戳) | Date | 1617695193459 |
| payTime | 支付时间(时间戳,成功时返回) | Date | 1617695193459 |
| rsaSign | RSA签名 | String | RSA(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 | 返回码 | String | 000 |
| message | 返回消息 | String | complete |
| orderNumber | 用户订单号 | String | 1604384968942 |
| amount | 代付金额 | Integer | 40000 |
| merchantCode | 商户号 | String | test |
| phone | 电话(已隐藏) | String | 00 |
| Email(已隐藏) | String | 00 | |
| userName | 对方名称 | String | John Doe |
| bankCode | 银行编码 | String | PERMATA |
| bankAccount | 银行账号 | String | 100100100 |
| channelCode | 渠道编码 | String | ALL |
| status | 代付状态:1成功,2失败 | int | 1 |
| createTime | 订单发起时间(时间戳) | Date | 1617695193459 |
| payTime | 支付时间(时间戳,成功时返回) | Date | 1617695193459 |
| rsaSign | RSA签名 | String | RSA(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 确认收到回调
支付渠道编码对照表
| channel_code | channel_name | 类型 |
|---|---|---|
| QRIS | QRIS | 收款 |
| VC | Credit Card (Visa / Master) | 收款 |
| BK | BCA KlikPay | 收款 |
| BCA | BCA Virtual Account | 收款 |
| BBC | BCA BC | 收款 |
| M1 | Mandiri Virtual Account | 收款 |
| ME | Mandiri eCash | 收款 |
| MP | Mandiri PC | 收款 |
| BT | Permata Bank Virtual Account | 收款 |
| PN | Permata Net | 收款 |
| A1 | ATM Bersama | 收款 |
| B1 | CIMB Niaga Virtual Account | 收款 |
| I1 | BNI Virtual Account | 收款 |
| VA | Maybank Virtual Account | 收款 |
| M2U | Maybank2U | 收款 |
| FT | Ritel | 收款 |
| OV | OVO | 收款 |
| LA | LINKAJA | 收款 |
| DANA | DANA | 收款 |
| SP | Shopee Pay | 收款 |
| SA | Shopee Pay Apps | 收款 |
| AG | Bank Artha Graha | 收款 |
| S1 | Sahabat Sampoerna | 收款 |
| NI | Bank Negara Indonesia | 收款 |
| RI | Bank Rakyat Indonesia | 收款 |
| RIE | BRI ePay | 收款 |
| IN | INDOMARET | 收款 |
| AL | ALFAMART | 收款 |
| DOB | Danamon Online Banking | 收款 |
| DVA | Danamon Virtual Account | 收款 |
| AI | Alto iDebit | 收款 |
| JIB | Jenius IB | 收款 |
| JA | JAKONE | 收款 |
| OC | Octo Clicks | 收款 |
| SK | Sakuku | 收款 |
| ALF | Alfagroup | 收款 |
| KR | Kredivo | 收款 |
| BL | BTNONLINE | 收款 |
| SVA | Sinarmas Virtual Account | 收款 |
| CASH | 收银台支付 | 收款 |
| ALL | 印尼代付全渠道 | 代付 |
| channel_code | channel_name | 类型 |
|---|---|---|
| PK_JAZZ | JAZZ钱包 | 收款 |
| PK_EASY | EASY钱包 | 收款 |
| PK_ALL | 巴基斯坦代付全渠道 | 代付 |
| channel_code | channel_name | 类型 |
|---|---|---|
| BR_BRL | 二维码支付 (QR Code) | 收款 |
| BR_ALL | PIX代付渠道 | 代付 |
| channel_code | channel_name | 类型 |
|---|---|---|
| TH_QR | 泰国二维码支付 (QR Code) | 收款 |
| TH_ALL | 泰国代付全渠道 | 代付 |
银行编码对照表
🇮🇩 印度尼西亚 (Indonesia)
| bank_code | bank_name | activate |
|---|---|---|
| ACEH | BPD ACEH | ✓ |
| AGRIS | Bank Agris | |
| ARTOS | Bank Artos Indonesia | |
| BANK_NOBU | Bank Nobu | ✓ |
| BANK_SU | BPD SULAWESI TENGAH | ✓ |
| BCA | Bank BCA | ✓ |
| BCASYR | BANK BCA SYARIAH | ✓ |
| BJB | Bank BJB | ✓ |
| BJB_SYR | Bank BJB Syariah | ✓ |
| BNI | BANK BNI | ✓ |
| BNI_SYR | Bank BNI Syariah | ✓ |
| BRI | Bank BRI | ✓ |
| BRI_SYR | BANK BRI SYARIAH | ✓ |
| BTN | Bank BTN | ✓ |
| BTPN | Bank BTPN | ✓ |
| BUKOPIN | Bank Bukopin | ✓ |
| BUKOPIN_SYR | Bank Syariah Bukopin | ✓ |
| BUMI_ARTA | Bank Bumi Arta | ✓ |
| CHINATRUST | Bank Chinatrust Indonesia | ✓ |
| CIMB | BANK CIMB NIAGA | ✓ |
| COMMONWEALTH | Bank Commonwealth | ✓ |
| DANAMON | Bank Danamon | ✓ |
| DBS | Bank DBS Indonesia | ✓ |
| DINAR_INDONESIA | Bank Dinar Indonesia | |
| DKI | Bank DKI | ✓ |
| GANESHA | Bank Ganesha | ✓ |
| HARDA_INTERNASIONAL | Bank Harda Internasional | |
| ICBC | Bank ICBC Indonesia | ✓ |
| INDEX_SELINDO | Bank Index Selindo | ✓ |
| INDIA | Bank of India Indonesia | ✓ |
| JASA_JAKARTA | Bank Jasa Jakarta | ✓ |
| KESEJAHTERAAN_EKONOM | Bank Kesejahteraan Ekonomi | |
| MANDIRI | Bank Mandiri | ✓ |
| MANDIRI_SYR | Bank Syariah Mandiri | ✓ |
| MASPION | Bank Maspion Indonesia | ✓ |
| MAYBANK | BANK MAYBANK INDONESIA | ✓ |
| MAYORA | Bank Mayora | ✓ |
| MEGA | Bank Mega | ✓ |
| MUAMALAT | Bank Muamalat Indonesia | ✓ |
| NATIONALNOB | BANK NATIONALNOBU | ✓ |
| OCBC | Bank OCBC NISP | ✓ |
| PANIN | Bank Panin | ✓ |
| PERMATA | Bank Permata | ✓ |
| SAHABAT_SAMPOERNA | Bank Sahabat Sampoerna | ✓ |
| SINARMAS | Bank Sinarmas | ✓ |
| STANDARD_CHARTERED | Standard Chartered Bank | ✓ |
| SULUT | BANK SULUT | ✓ |
| UOB | Bank UOB Indonesia | ✓ |
| VICTORIA_SYR | Bank Victoria Syariah | ✓ |
| CITIBANK | Citibank | ✓ |
| ARTHA | Bank Artha Graha Internasional | ✓ |
| TOKYO | Bank of Tokyo Mitsubishi UFJ | ✓ |
| CAPITAL | Bank Capital Indonesia | ✓ |
| ANZ | ANZ Indonesia | ✓ |
| BOC | Bank of China (Hong Kong) Limited | ✓ |
| HSBC | HSBC Indonesia | ✓ |
| RABOBANK | Rabobank International Indonesia | ✓ |
| MAYAPADA | Bank Mayapada | ✓ |
| DAERAH_ISTIMEWA | BPD DIY | ✓ |
| JAWATIMUR | BPD JAWA TIMUR/SYARIAH | ✓ |
| JAMBI | Bank Jambi | ✓ |
| SUMUT | Bank Sumut | ✓ |
| SUMATERA_BARAT | BPD SUMATERA BARAT/NAGARI SYARIAH | ✓ |
| RIAU_DAN_KEPRI | Bank Riau Kepri | ✓ |
| SUMSEL_DAN_BABEL | Bank Sumsel Babel | ✓ |
| LAMPUNG | Bank Lampung | ✓ |
| KALIMANTAN_SELATAN | BPD KALIMANTAN SELATAN | ✓ |
| KALIMANTAN_TIMUR | BPD KALIMANTAN TIMUR DAN KALIMANTAN UTARA | ✓ |
| KALIMANTAN_TENGAH | BPD KALIMANTAN TENGAH | ✓ |
| SULSELBAR | Bank Sulselbar | ✓ |
| NUSA_TENGGARA_BARAT | BPD NUSA TENGGARA BARAT SYARIAH | ✓ |
| BALI | BPD Bali | ✓ |
| NUSA_TENGGARA_TIMUR | BPD NUSA TENGGARA TIMUR | ✓ |
| MALUKU | Bank Maluku | ✓ |
| PAPUA | Bank Papua | ✓ |
| BENGKULU | Bank Bengkulu | ✓ |
| SULAWESI | Bank Sulteng | ✓ |
| NUSANTARAPARAHYANGA | Bank Nusantara Parahyangan | |
| MESTIKADHARMA | Bank Mestika Dharma | ✓ |
| QNBKESAWAN | QNB Indonesia | ✓ |
| WOORI | Bank Woori Saudara | ✓ |
| BTPNSYR | Bank BTPN Syariah | ✓ |
| HANA | KEB Hana Bank Indonesia | ✓ |
| MNC_INTERNASIONAL | Bank MNC Internasional | ✓ |
| AGRONIAGA | AGRONIAGA | ✓ |
| SBIINDONESIA | SBI Indonesia | ✓ |
| ROYAL | Bank Royal Indonesia | ✓ |
| MEGASYR | Bank Mega Syariah | ✓ |
| INAPERDANA | Bank Ina Perdana | ✓ |
| VICTORIAINTERNASION | Bank Victoria International | ✓ |
| BANTEN | BPD Banten | ✓ |
| MUTIARA | Bank Mutiara | |
| PANINSYR | Panin Dubai Syariah | ✓ |
| ANTARDAERAH | Bank Antardaerah | |
| CCB | Bank China Construction Bank Indonesia | ✓ |
| CNB | Bank CNB (Centratama Nasional Bank) | ✓ |
| EKA | BPR EKA (Bank Eka) | ✓ |
| MANTAP | BANK MANTAP (Mandiri Taspen) | ✓ |
| MAS | Bank Multi Arta Sentosa (Bank MAS) | ✓ |
| PRIMA | Bank Prima Master | ✓ |
| SHINHAN | Bank Shinhan Indonesia | ✓ |
| YUDHA_BAKTI | Bank Yudha Bakti | |
| GOPAY | GoPay | ✓ |
| OVO | OVO | ✓ |
| SHOPEEPAY | ShopeePay | ✓ |
| DANA | Dana | ✓ |
| LINKAJA | LinkAja | |
| KALIMANTANBARAT | BPD Kalimantan Barat Bank Kalbar | ✓ |
| JAWATENGAH | BPD JAWA TENGAH | ✓ |
| ALTO | ALTO | |
| BSI | BSI (Bank Syariah Indonesia) | ✓ |
| AMAR | BANK AMAR INDONESIA | ✓ |
| CTBC | BANK CTBC INDONESIA | ✓ |
| IBK | BANK IBK INDONESIA | ✓ |
| BRA_CPF | BRA CPF | ✓ |
| BNEO | BANK NEO COMMERCE | |
| BSEAI | BANK SEABANK INDONESIA | ✓ |
| BJAGO | BANK JAGO | |
| ALADIN | BANK ALADIN SYARIAH | |
| BAC | BANK API CHANNEL |
🇵🇰 巴基斯坦 (Pakistan)
| bank_code | bank_name |
|---|---|
| PK_EASY | EASY钱包 (EASY Wallet) |
| PK_JAZZ | JAZZ钱包 (JAZZ Wallet) |
🇧🇷 巴西 (Brazil)
| bank_code | bank_name |
|---|---|
| BR_CPF | CPF号码 (CPF Number) |
| BR_PHONE | 手机号 (Phone Number) |
| BR_EMAIL | 邮箱 (Email) |
🇹🇭 泰国 (Thailand)
| bank_code | bank_name |
|---|---|
| TH_BAAC | BANK FOR AGRICULTURE AND AGRICULTURAL COOPERATIVES |
| TH_BAY | BANK OF AYUDHYA PUBLIC COMPANY LIMITED |
| TH_BBL | BANGKOK BANK PUBLIC COMPANY LTD. |
| TH_CIMBT | CIMB THAI BANK PUPBLIC COMPANY LTD. |
| TH_CITI | CITIBANK, N.A. |
| TH_GHB | THE GOVERNMENT HOUSING BANK |
| TH_GSB | THE GOVERNMENT SAVINGS BANK |
| TH_ISBT | ISLAMIC BANK OF THAILAND |
| TH_ICBCT | INDUSTRIAL AND COMMERCIAL BANK OF CHINA (THAI) PUBLIC COMPANY LIMITED |
| TH_KBANK | KASIKORNBANK PUBLIC COMPANY LTD. |
| TH_KK | KIATNAKIN BANK PUBLIC COMPANY LIMITED |
| TH_KTB | KRUNG THAI BANK PUBLIC COMPANY LTD. |
| TH_LHBANK | LAND AND HOUSES BANK PUBLIC COMPANY LIMITED |
| TH_MIZUHO | MIZUHO CORPORATE BANK, LTD. |
| TH_SCB | SIAM COMMERCIAL BANK PUBLIC COMPANY LTD |
| TH_SCBT | STANDARD CHARTERED BANK (THAI) PUBLIC COMPANY LIMITED |
| TH_SMBC | SUMITOMO MITSUI BANKING CORPORATION |
| TH_TBANK | THANACHART BANK PUBLIC COMPANY LTD. |
| TH_TCR | THE THAI CREDIT RETAIL BANK PUBLIC COMPANY LIMITED |
| TH_TISCO | TISCO BANK PUBLIC COMPANY LIMITED |
| TH_TMB | TMB BANK PUBLIC COMPANY LIMITED |
| TH_TTB | TMBTHANACHART BANK PUBLIC COMPANY LIMITED |
| TH_UOBT | UNITED OVERSEAS BANK (THAI) PUBLIC COMPANY LIMITED |
| TH_PROMPTPAY | PromptPay |