商户对接接口文档印度 UPI
面向游戏厂商 / 商户技术对接 · 版本 1.0.4 · 更新 2026-09-20 · 响应字段以网关实际序列化为准 · 最低代收 100 INR(amount = 10000)
接口清单
对接前准备
1. 平台分配
- 商户后台:https://amipay.net/merchant/
merchantNo(商户号),如C1788073687appId(应用 ID)- 登录账号 / 初始密码(开通时下发)
- 平台公钥(验签响应 / 异步通知)
- 商户自行生成 RSA2048:私钥自持签名;公钥交给平台写入 appSecret
2. IP 白名单
商户服务器出口公网 IP 需配置到应用白名单,否则返回 ip not in white list。
3. 支付方式 wayCode
| 请求 wayCode | 响应 / 回调常见值 | 说明 |
|---|---|---|
IND_UPI | India upi | 代收 |
IND_UPI_OUT | India upi payout | 代付 |
下单请求必须用左侧编码;查单/回调里是展示名,不要拿展示名当下单参数。
4. 金额约定
- 单位:分;1 INR = 100 分
- 最低金额:100 INR → amount 最小 10000(低于可能拒单)
- 例:100 INR →
10000;200 INR →20000 - 响应 / 回调金额多为小数(如
20000.00),验签按原文保留小数位
通用约定
- Method:POST(查单同样走签名校验)
- Content-Type:
application/json; charset=UTF-8
请求头(必填)
| Header | 说明 | 示例 |
|---|---|---|
Version | 接口版本 | 1.0.0 |
Sign-Type | 签名类型 | RSA |
Req-Time | Unix 秒级时间戳 | 1725000000 |
Sign | 请求体 RSA 签名(Base64) | (见签名章节) |
Content-Type | JSON | application/json |
参与签名的仅为 Body 业务字段(不含 Header、不含 sign)。
统一响应外壳
{
"code": "0",
"msg": "SUCCESS",
"data": { },
"sign": "..."
}
code=0 仅表示接口调用成功,不等于支付成功。
订单状态
| state | 含义 |
|---|---|
| 0 | 订单生成 |
| 1 | 处理中 / 支付中 |
| 2 | 成功 |
| 3 | 失败 |
| 4 | 已撤销 |
| 5 | 已退款 |
| 6 | 已关闭 |
| 7 | 拒付 |
orderState,查单/回调用 state。请求 wayCode 传 IND_UPI,响应里常为展示名 India upi。签名算法(RSA)
- 算法:SHA256withRSA
- 请求:商户私钥签名;响应 / 回调:平台公钥验签
- 签名结果:Base64
验签算法(商户侧,响应 / 异步通知)
data 内字段拼串,用顶层 sign;异步通知:对整包扁平 JSON 拼串。- 取 Body JSON 全部字段(同步响应则取
data对象内字段) - 去掉
sign(以及若有signature/fileData) - 跳过值为
null或空字符串""的字段 - 按参数名 ASCII 自然序排序(Java
TreeMap,区分大小写) - 拼成:
key1=value1&key2=value2&...(末尾无&)- value 用字段原始字符串形式(小数保留两位如
20000.00,不能写成20000) channelExtra若存在,整段作为 value,不要拆内部字段
- value 用字段原始字符串形式(小数保留两位如
- 用 平台公钥 对上述字符串做 SHA256withRSA 验签(报文中的
sign为 Base64);通过后再处理业务,失败则丢弃
待签名字符串(商户请求签名时同样按上表 1~5 步拼串,第 6 步改为商户私钥签名)
请求签名与验签拼串规则相同;仅最后一步:请求用商户私钥 sign,响应/回调用平台公钥 verify。
Java 验签示例
// signStr:按上文规则拼好的待签名串;signBase64:报文里的 sign;platformPubKeyBase64:平台公钥(无 PEM 头尾)
byte[] keyBytes = Base64.getDecoder().decode(platformPubKeyBase64);
PublicKey pub = KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(keyBytes));
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(pub);
signature.update(signStr.getBytes(StandardCharsets.UTF_8));
boolean ok = signature.verify(Base64.getDecoder().decode(signBase64));
// ok == true 才可入账
Java 签名示例(商户请求)
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(signStr.getBytes("UTF-8"));
String sign = Base64.getEncoder().encodeToString(signature.sign());
更多 Node / PHP / Go 示例见 merchant-api.txt 第三章,或签名工具页 Tab3。
统一下单
Body 字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchantNo | String | Y | 商户号 |
appId | String | Y | 应用 ID |
orderNo | String | Y | 商户订单号(商户侧唯一) |
wayCode | String | Y | IND_UPI / IND_UPI_OUT |
amount | Number | Y | 商户下单交易金额,单位分(非元)。例:100 INR → 传 10000;最低 10000 |
currency | String | Y | INR |
description | String | Y | 商品 / 订单描述 |
channelExtra | String | Y | 渠道扩展,JSON 字符串 |
notifyUrl | String | N | 异步通知地址(强烈建议填【贵方】公网回调) |
returnUrl | String | N | 支付完成跳转 |
clientIp | String | N | 玩家端 IP |
userId | String | N | 商户侧用户 ID,≤50 |
expiredTime | String | N | 可选,单位秒。可传可不传。多数印度通道会忽略;平台目前不会按此字段自动关单,终态以回调/查单为准 |
extParam | String | N | 透传字段,回调原样返回 |
代收 channelExtra(IND_UPI)
| 字段 | 必填 | 说明 |
|---|---|---|
firstName | Y | 名 |
lastName | Y | 姓 |
email | Y | 邮箱(合法格式) |
phoneNumber | Y | 手机号 |
name / upi / 地址 | N | 建议补充 |
代付 channelExtra(IND_UPI_OUT)
| 字段 | 必填 | 说明 |
|---|---|---|
name | Y | 收款人姓名(玩家) |
cardNo | Y | 银行账号(玩家账户) |
ifscCode | Y | IFSC |
bankName | Y | 银行名 |
email | Y | 邮箱 |
phoneNumber | Y | 手机号 |
paymentMode | N | 如 IMPS |
accountType | N | 如 1=Savings |
city / state / postalCode / address | N | 地址信息 |
channelExtra 传入(玩家提现绑卡),平台再转给上游。代收请求示例(IND_UPI,100 INR)
{
"merchantNo": "C1788116547",
"appId": "6a947e438f0872229b1e778c",
"orderNo": "GAME_20260901120000_001",
"wayCode": "IND_UPI",
"amount": 10000,
"currency": "INR",
"clientIp": "1.2.3.4",
"description": "Game diamond recharge",
"notifyUrl": "https://your-game.com/pay/notify",
"returnUrl": "https://your-game.com/pay/return",
"userId": "player_10086",
"extParam": "optional_biz_tag",
"channelExtra": "{\"firstName\":\"stephen\",\"lastName\":\"curry\",\"email\":\"[email protected]\",\"phoneNumber\":\"9566778899\",\"name\":\"stephen curry\",\"upi\":\"demo@upi\",\"country\":\"IND\",\"state\":\"Mumbai\",\"city\":\"Bellevue\",\"postalCode\":\"123546\"}"
}
代收成功响应示例
{
"code": "0",
"msg": "SUCCESS",
"data": {
"payOrderId": "TSPPI08312094417832285474817",
"mchOrderNo": "GAME_20260901120000_001",
"orderState": 1,
"payDataType": "payurl",
"payData": "https://amipay.net/gateway/api/pay/upi/TSPPI08312094417832285474817",
"upiUrl": "upi://pay?cu=INR&pa=demo@upi&am=100.00&tn=xxx",
"channelExtra": {
"payUrl": "https://pi.example.com/pay/xxxx",
"upiUrl": "upi://pay?cu=INR&pa=demo@upi&am=100.00&tn=xxx"
}
},
"sign": "Base64..."
}
引导玩家打开 payData / upiUrl 付款。测试即时成功通道可能直接 orderState=2,并带 fee / settleAmount 等。
代付请求示例(IND_UPI_OUT,100 INR → 打到玩家银行卡)
{
"merchantNo": "C1788116547",
"appId": "6a947e438f0872229b1e778c",
"orderNo": "WD_20260901120000_001",
"wayCode": "IND_UPI_OUT",
"amount": 10000,
"currency": "INR",
"clientIp": "1.2.3.4",
"description": "Player withdrawal",
"notifyUrl": "https://your-game.com/pay/notify",
"returnUrl": "https://your-game.com/pay/return",
"userId": "player_10086",
"extParam": "optional_biz_tag",
"channelExtra": "{\"name\":\"stephen curry\",\"cardNo\":\"123456789012\",\"ifscCode\":\"HDFC0001234\",\"bankName\":\"HDFC Bank\",\"email\":\"[email protected]\",\"phoneNumber\":\"9566778899\",\"paymentMode\":\"IMPS\",\"accountType\":1,\"city\":\"Mumbai\",\"state\":\"MH\",\"postalCode\":\"400001\",\"address\":\"Andheri West\"}"
}
代付受理成功响应示例
{
"code": "0",
"msg": "SUCCESS",
"data": {
"payOrderId": "TSPPO09011200001234567890123",
"mchOrderNo": "WD_20260901120000_001",
"orderState": 1,
"payDataType": "payurl",
"payData": ""
},
"sign": "Base64..."
}
upiUrl;code=0 只表示受理成功。终态以异步通知 + 查单为准。瞬时成功时可能直接 orderState=2,并带 fee / feeCurrency / settleAmount / settleCurrency / serviceFee / checkDate。data 字段对照
| 字段 | 代收 IND_UPI | 代付 IND_UPI_OUT |
|---|---|---|
payOrderId / mchOrderNo / orderState | 有 | 有 |
payData / upiUrl | 常见,引导玩家付款 | 通常空 / 无,勿依赖 |
channelExtra.payUrl 等 | 常见 | 通常无 |
fee / settleAmount 等 | 成功记账后常见 | 成功记账后常见 |
errCode / errMsg | 失败时 | 失败时(含余额不足) |
- 同步看
orderState;查单/回调查state - 正式通道多数同步
orderState=1;可能code=0但orderState=3 - 代付下单前会校验商户余额 ≥ amount + 手续费
订单查询
Body
{
"merchantNo": "C1788073687",
"appId": "6a93d6d78f08fd1adc3ef1f5",
"mchOrderNo": "GAME_20260830120000_001",
"payOrderId": ""
}
mchOrderNo 与 payOrderId 不能同时为空。
data 字段(QueryPayOrderRS)
| 字段 | 说明 |
|---|---|
payOrderId / appId / mchOrderNo | 订单与应用 |
wayCode | 展示名,如 India upi(不是 IND_UPI) |
amount | 商户下单交易金额,单位分(与下单请求一致)。例:200 INR → 20000.00。不是元;报文常带两位小数 |
currency | 交易币种,如 INR |
state | 订单状态(查单用 state,下单同步用 orderState,数值相同)。含义见上文通用约定 · 订单状态:0 生成 · 1 支付中 · 2 成功 · 3 失败 · 4 撤销 · 5 退款 · 6 关闭 · 7 拒付。仅 2 入账 |
fee | 商户手续费,单位分。按费率等计算后入库,库类型 decimal(20,2),允许非整数分(如 9.50、1005.00)。仅支付成功且已记账时常见 |
feeCurrency | 手续费币种(通常与 settleCurrency 一致) |
settleAmount | 结算入账金额,单位分。同币种时一般为 记账金额 − fee(例:20000.00 − 1005.00 = 18995.00)。同样可为两位小数非整数分;成功记账后常见 |
settleCurrency | 结算 / 记账币种 |
clientIp / channelOrderNo / extParam | 有则返回 |
successTime / createdAt | 毫秒时间戳 |
signType | 如 RSA |
walletBrandName / checkDate / marginAmount | 有则返回 |
errCode / errMsg | 失败时 |
amount=下单交易额(分),fee=手续费(分),settleAmount=结算额(分)。同币种常见:settleAmount ≈ amount − fee。验签按报文原文小数位拼串,勿自行改成整数或元。merchantNo,但当前实现通常不返回,请勿依赖。查单 data 无 channelExtra / reqTime;sign 在外壳顶层。响应示例:支付中(state=1)
代收:玩家尚未完成付款;代付:出款处理中。勿入账,继续等回调或退避查单。
{
"code": "0",
"msg": "SUCCESS",
"data": {
"payOrderId": "TSPPI08312094417832285474817",
"appId": "6a947e438f0872229b1e778c",
"mchOrderNo": "GAME_20260830120000_001",
"wayCode": "India upi",
"amount": 10000.00,
"currency": "INR",
"state": 1,
"clientIp": "1.2.3.4",
"extParam": "optional_biz_tag",
"createdAt": 1788177513995,
"signType": "RSA"
},
"sign": "..."
}
支付中通常无 successTime、fee、settleAmount;errCode / errMsg 一般也不出现(非错误)。
响应示例:支付成功(state=2)
{
"code": "0",
"msg": "SUCCESS",
"data": {
"payOrderId": "TSPPI08312094417832285474817",
"appId": "6a947e438f0872229b1e778c",
"mchOrderNo": "P20944178244636221461788183096",
"wayCode": "India upi",
"amount": 20000.00,
"currency": "INR",
"state": 2,
"clientIp": "127.0.0.1",
"channelOrderNo": "fHGoc",
"extParam": "P20944178244636221461788183096",
"successTime": 1788177514000,
"createdAt": 1788177513995,
"signType": "RSA",
"fee": 1005.00,
"feeCurrency": "INR",
"settleAmount": 18995.00,
"settleCurrency": "INR",
"walletBrandName": "India upi",
"checkDate": "20260831"
},
"sign": "..."
}
响应示例:支付失败(state=3)
终态失败,勿入账。请读取 errCode / errMsg 记录或展示给用户。
{
"code": "0",
"msg": "SUCCESS",
"data": {
"payOrderId": "TSPPI08312094417832285474817",
"appId": "6a947e438f0872229b1e778c",
"mchOrderNo": "GAME_20260830120000_001",
"wayCode": "India upi",
"amount": 10000.00,
"currency": "INR",
"state": 3,
"errCode": "100007",
"errMsg": "Channel payment failed",
"createdAt": 1788177513995,
"signType": "RSA"
},
"sign": "..."
}
state=2 入账并做幂等。state=1 继续等待;state=3 按失败处理。余额查询
请求
{
"merchantNo": "C1788073687",
"appId": "6a93d6d78f08fd1adc3ef1f5"
}
完整响应示例
{
"code": "0",
"msg": "SUCCESS",
"data": {
"walltes": [
{
"currency": "INR",
"balance": 0.00,
"freezeBalance": 0.00,
"waitSettleBalance": 18995.00,
"marginAmount": 0.00,
"preBalance": 0,
"specialBalance": 0.00
}
]
},
"sign": "..."
}
data.walltes[](历史拼写,不是 wallets):currency / balance / freezeBalance / waitSettleBalance / marginAmount / preBalance / specialBalance。金额单位:分。
异步通知(商户 notifyUrl)
验签算法(商户侧,必读)
- 取 Body JSON 全部字段
- 去掉
sign(以及若有signature/fileData) - 跳过值为
null或空字符串""的字段 - 按参数名 ASCII 自然序排序(Java
TreeMap,区分大小写) - 拼成:
key1=value1&key2=value2&...(末尾无&)- value 用字段原始字符串形式(小数保留两位如
20000.00)
- value 用字段原始字符串形式(小数保留两位如
- 用 平台公钥 对上述字符串做 SHA256withRSA 验签(签名为 Base64);通过后再入账
Java 验签代码见下方「签名算法」同页示例,或点左侧 签名算法。也可直接用工具:sign-tool.html → Tab「验签」→ 模式「异步通知 JSON」。
其他说明
- 平台 → 商户:HTTP POST,
Content-Type: application/json - 商户应答 Body 必须是纯文本
SUCCESS(大小写不敏感);否则约最多重试 6 次 - 务必幂等:同一订单可能多次通知
平台推送 Body 示例(代收成功,真实字段)
{
"payOrderId": "TSPPI08312094394410113363970",
"appId": "6a947e438f0872229b1e778c",
"mchOrderNo": "P20943944073414287371788177513",
"wayCode": "India upi",
"amount": 20000.00,
"currency": "INR",
"state": 2,
"clientIp": "127.0.0.1",
"channelOrderNo": "fHGoc",
"extParam": "P20943944073414287371788177513",
"successTime": 1788177514000,
"createdAt": 1788177513995,
"signType": "RSA",
"fee": 1005.00,
"feeCurrency": "INR",
"settleAmount": 18995.00,
"settleCurrency": "INR",
"walletBrandName": "India upi",
"checkDate": "20260831",
"reqTime": 1788177514646,
"channelExtra": { "utr": "422253713113" },
"sign": "Base64..."
}
| 字段 | 说明 |
|---|---|
state | 终态通知;成功为 2 |
amount | 商户下单交易金额,单位分(与下单一致)。例:200 INR → 20000.00。不是元 |
fee | 商户手续费,单位分。可为两位小数非整数分(如 9.50)。成功记账后常见 |
feeCurrency | 手续费币种 |
settleAmount | 结算入账金额,单位分。同币种常见 amount − fee;成功记账后常见 |
settleCurrency | 结算 / 记账币种 |
wayCode | 展示名 India upi(不是 IND_UPI) |
reqTime | 本次通知毫秒时间戳(回调特有) |
sign | 平台签名;验签时剔除 |
channelExtra | 如 {"utr":"..."};无则常省略 |
amount(交易额)/ fee(手续费)/ settleAmount(结算额)均为分,不是元;同币种常见 settleAmount ≈ amount − fee。merchantNo 通常不出现在回调 JSON,请勿依赖。商户应答(必须)
SUCCESS
处理流程
- 读取 POST JSON
- 按上方「验签算法」用平台公钥验签(失败则丢弃,勿返回 SUCCESS)
- 校验 appId / amount / mchOrderNo(及本地订单)
- 若 state==2 且本地未入账 → 入账(幂等)
- 返回纯文本 SUCCESS
错误与联调注意
| 现象 | 处理 |
|---|---|
| ip not in white list | 把出口 IP 加入应用白名单 |
| 验签失败 | 核对平台公钥、跳过 null/""、TreeMap 区分大小写排序、金额小数位(如 20000.00)、channelExtra 整段参与 |
| Bean Validation | Header Version / Sign-Type;代收必填 firstName/lastName/email/phoneNumber |
| amount 过小 | amount ≥ 10000 |
| code=0 且 orderState/state=3 | 看 data.errCode / errMsg |
| notifyUrl 填错 | 填游戏服地址,不要填上游通道地址 |
| 字段对不上文档 | 同步看 orderState;查单/回调查 state;wayCode 请求用编码、响应用展示名;勿依赖 merchantNo |
联调检查清单
- RSA 密钥对已生成,公钥已交给平台
- IP 白名单已配置
- 代收下单 IND_UPI,amount ≥ 10000
- 能解析完整外壳 {code,msg,data,sign},并从 data 取 payData / upiUrl
- notifyUrl 公网可达,成功返回 SUCCESS
- 回调验签通过,幂等入账(按真实字段,勿依赖 merchantNo)
- 查单接口可用,作为回调补偿
- (如需)代付 IND_UPI_OUT 银行字段正确
附录
结算提现
- 申请:
POST /api/account/cashout— 请求 merchantNo / appId / currency / mchOrderNo / amount(分)/ orderType(可选) / remarks(可选);响应 data:{ "state": <Byte> } - 查询:
POST /api/account/cashoutQuery— 请求 merchantNo / appId / mchOrderNo;响应 data:state / fee / feeCurrency / actualAmount / fxRate
常用地址
- 商户后台:https://amipay.net/merchant/
- 网关:https://amipay.net/gateway
- 签名工具:https://amipay.net/tools/sign-tool.html
- 本文档:https://amipay.net/
纯文本源:商户接口.txt · 切勿把私钥提交到代码仓库或发给无关第三方。