搭建印尼支付网关:开发者指南
1. 了解印尼支付市场特点
在开始开发前,需要了解印尼支付的独特之处:
- 银行转账主导:Mandiri、BCA、BNI等本地银行转账非常普遍
- 电子钱包流行:GoPay、OVO、DANA等电子钱包使用率高
- 便利店支付:Indomaret和Alfamart的线下支付渠道重要
- 分期付款需求:Akulaku等分期服务受欢迎
2. 选择适合的技术架构
基础架构选项:
1. 自建解决方案(完全控制但成本高)
2. 第三方聚合平台(如Xendit, Midtrans, Doku)
3. Hybrid方案(核心自建+部分外包)
推荐技术栈:
- 后端:Node.js/Java/Python + RESTful API
- 前端:React/Vue.js + Responsive Design
- 数据库:PostgreSQL/MongoDB (考虑分片应对高并发)
- 安全层:PCI DSS合规加密 + Tokenization
3. API集成关键步骤
a) Bank Transfer集成示例(使用Midtrans):
const midtransClient = require('midtrans-client');
let snap = new midtransClient.Snap({
isProduction: false,
serverKey: 'YOUR_SERVER_KEY',
clientKey: 'YOUR_CLIENT_KEY'
});
let parameter = {
"transaction_details": {
"order_id": "ORDER-ID-" + Math.round((new Date()).getTime() /1000),
"gross_amount": AMOUNT_IN_IDR // Integer in IDR currency unit
},
"bank_transfer":{
"bank": "bca" // or permata, bni, etc.
}
};
snap.createTransaction(parameter)
.then((transaction)=>{
console.log('Transaction token:', transaction.token);
});
b) E-Wallet集成要点:
- OVO需要商户注册并获取API密钥
- GoPay需要通过Gojek商户平台申请接入权限
- DANA提供SDK和沙箱环境测试
4. PCI DSS合规要求
必须满足的安全措施:
✔️ SAQ A或SAQ D认证 (取决于处理方式)
✔️ TLS v1.+加密所有交易数据
✔️ PAN数据不得存储在日志中
✔️ QSA季度漏洞扫描报告
✔️ Tokenization替换原始卡号存储
FAQ常见问题解答:
Q:如何处理印尼独特的VA(Virtual Account)号码分配?
A:需与当地银行或聚合商合作获取专用VA前缀,每个商户ID有唯一编号段。
Q:Rupiah金额处理有何特殊要求?
A:所有金额必须以整数形式传输(无小数点),例如Rp10,000传为10000。
建议从沙箱环境开始测试,逐步对接各渠道。完整上线通常需要4
印尼支付网关开发进阶指南(续)
5. 处理印尼特有的支付流程
a) 便利店支付集成 (Indomaret/Alfamart)
// Midtrans便利店支付示例
const convenienceStoreParams = {
payment_type: 'cstore',
transaction_details: {
order_id: generateOrderId(),
gross_amount: totalAmount
},
cstore: {
store: 'indomaret', // or 'alfamart'
message: 'Thank you for your purchase'
}
};
snap.createTransaction(convenienceStoreParams)
.then((transaction) => {
// 返回付款码和过期时间(通常24小时)
console.log('Payment Code:', transaction.payment_code);
});
关键点:
- 生成的付款码需显示给用户并支持打印
- 交易状态轮询间隔建议15分钟一次(避免API限制)
- Alfamart需要额外merchant_id参数
b) QRIS统一标准二维码集成
# Xendit QRIS示例(Python)
from xendit import Xendit
xendit_instance = Xendit(api_key='your_xendit_key')
qris = xendit_instance.QRCode.create(
external_id="qr_12345",
amount=50000,
type="DYNAMIC", # DYNAMIC/STATIC
)
print(f"QR Image URL:{qris.qr_code_url}")
print(f"Callback URL:{qris.callback_url}")
6. Webhook与异步通知处理
必须实现的端点:
POST /api/payments/notifications [认证方式:IP白名单+签名验证]
PHP验证签名示例:
function verifyXenditSignature($requestBody, $xCallbackToken, $expectedSignature) {
$computedSignature = hash_hmac('sha256', $requestBody, $xCallbackToken);
return hash_equals($computedSignature, $expectedSignature);
}
常见状态码处理逻辑:
Status | Action Required |
---|---|
PAID | 发货并标记订单完成 |
EXPIRED | 释放库存/重新下单 |
FAILED | 记录失败原因分析 |
7. Rupiah货币特殊处理
-
金额格式化规则
// Java金额转换示例(IDR没有小数位)
public String formatRupiah(long amount) {
DecimalFormat formatter = new DecimalFormat("Rp#,");
return formatter.format(amount).replace(",", ".");
}
// Input:150000 → Output:Rp150.000
-
汇率锁定机制
--数据库设计建议--
CREATE TABLE currency_rates (
id SERIAL PRIMARY KEY,
from_currency CHAR(3),
to_currency CHAR(3),
rate DECIMAL(18,6), --足够存储高精度汇率
locked_at TIMESTAMP,
expiry TIMESTAMP --通常锁定2-5分钟
);
8.性能优化策略
✅ 银行通道降级方案
主通道BCA → BNI → Mandiri → Permata (根据成功率自动切换)
✅ 缓存层设计
// Go并发缓存示例
var paymentMethodsCache struct{
sync.RWMutex
data map[string][]PaymentMethod
}
func GetCachedMethods(country string)([]PaymentMethod){
paymentMethodsCache.RLock()
defer paymentMethodsCache.RUnlock()
return paymentMethodsCache.data[country]
}
✅ CDN静态资源分发
将JS SDK、QR图片等托管至Akamai/Linode雅加达节点
延迟可从200ms降至50ms以内
下一步行动建议:
1️⃣ 先申请各渠道的沙箱账户:
Midtrans Sandbox|Xendit测试模式
2️⃣ 实施监控仪表板需包含:
🔴实时成功率 🔵平均响应时间 🟢渠道可用性
需要更详细的某个环节实现方案吗?可以具体说明您关注的方面。
# 印尼支付网关深度开发指南(最终篇)
9. 高级风控与合规实践
a) BI (Bank Indonesia) 监管要求
“`mermaid
graph TD
A[交易监控] –> B[大额交易报告>IDR500jt]
A –> C[可疑活动标记]
D[数据存储] –> E[至少7年交易记录]
D –> F[本地化服务器要求]
“`
必须实现的KYC流程:
1. 身份证验证:通过`Dukcapil`API验证印尼公民身份证(KTP)
2. 自拍比对:活体检测+与证件照匹配度检查
3. 地址证明:PLN电费单或Telkom账单验证
b) 实时风控引擎规则示例
“`python
# Python伪代码示例
def risk_evaluation(transaction):
risk_score = 0
# IP地理位置分析
if transaction.ip_country != ‘ID’:
risk_score +=20
# 设备指纹异常检测
if not transaction.device_fingerprint:
risk_score +=15
# AMLCheck黑名单筛查
if aml_check(transaction.user_id):
return {‘status’:’REJECTED’, ‘code’:’AML_ALERT’}
return {‘status’:’APPROVED’, ‘score’:risk_score}
“`
10.多语言与本地化优化
UI/UX关键注意事项:
– 宗教敏感设计:避免猪/酒类图标,斋月期间特殊提示
– 货币显示规范:
“`javascript
// React组件示例
function RupiahDisplay({value}) {
const formatted = new Intl.NumberFormat(‘id-ID’, {
style: ‘currency’,
currency: ‘IDR’,
minimumFractionDigits:0
}).format(value);
return {formatted.replace(‘IDR’,’Rp’)};
}
“`
– 支付方式排序逻辑
“`sql
/* MySQL推荐查询 */
SELECT * FROM payment_methods
WHERE country=’ID’
ORDER BY FIELD(name,’OVO’,’GoPay’,’DANA’,’BCA Virtual Account’) DESC;
“`
11.灾备与高可用方案
a) AWS雅加达区域架构图
“`
ELB → Auto Scaling Group → EC2 Jakarta → RDS Multi-AZ ↘︎ ↗︎ SQS死信队列
↓ Backup to Singapore Region
CloudWatch警报触发Lambda回滚
“`
b)数据库切换脚本示例(Bash)
“`bash
#!/bin/bash
# DNS故障转移脚本
FAILOVER_RECORD=”payment-api.example.com”
if [ $(curl -s -o /dev/null -w “%{http_code}” https://primary-db-host/ping) != “200” ]; then
aws route53 change-resource-record-sets \
–hosted-zone-id Z123456789 \
–change-batch ‘{ “Changes”: [{ “Action”: “UPSERT”, “ResourceRecordSet”: {
“Name”: “‘”$FAILOVER_RECORD”‘”,
“Type”: “CNAME”,
“TTL”:60,
“ResourceRecords”:[{“Value”:”standby-db-host”} ]}}]}’
echo “$(date): DB failover executed” >> /var/log/failover.log
fi
“`
12.运营数据分析指标
| KPI | Target | Measurement |
|——|——–|————-|
| Approval Rate | >92% | (成功交易数/总尝试数)×100 |
| Settlement Time | <48h | Bank Transfer实际到账时间 |
| Chargeback Ratio | <0.5% | CBK数量/总交易量 |
Prometheus监控配置片段:
```yaml
scrape_configs:
- job_name:'payment_gateway'
metrics_path:'/actuator/prometheus'
static_configs:
targets:['gateway-service:8080']
alert_rules:
- alert:'HighFailureRate'
expr:|-
sum(rate(payment_failed_total{country="ID"}[5m])) by(method)
/
sum(rate(payment_attempted_total{country="ID"}[5m])) by(method)
>0.1
“`
—
🚀 *实施路线图建议*:
阶段① : *基础通道对接*(2周)
– [ ] BCA VA + Mandiri VA集成测试
– [ ] GoPay沙箱环境联调
阶段② : *增强功能*(1周)
– [ ] Indomaret付款码生成页面开发
– [ ] BI要求的审计日志表设计
阶段③ : *上线准备*(3天)