INPAY API 对接文档
1)概览
- 基础域名:
https://api.inpark.online
- 认证方式:
Authorization: Basic <base64(mid:signature)>
- 时间戳:
Request-Time(13 位毫秒时间戳),必须与签名中使用的一致
- 货币:
INR
- 支付方式(pm):
NATIVE / INVOKE / QR / WALLET / BANK
2)签名规则
POST 请求签名
signatureString = Request-Time + "." + body
signature = hex(hmac-sha256(signatureString, secret))
Authorization: Basic base64(mid:signature)
GET 请求签名
将查询参数按 key 升序排序,拼接为:key=value,再以 & 连接
signatureString = Request-Time + "." + queryParams
signature = hex(hmac-sha256(signatureString, secret))
回调签名
signatureString = Request-Time + "." + body
signature = hex(hmac-sha256(signatureString, secret))
请求头:
Content-Type: application/json
Signature: <signature>
Request-Time: <13位毫秒时间戳>
验签方式(商户服务端):
1) 读取请求头 Signature、Request-Time
2) 获取原始 body 字符串(不可二次序列化或重排)
3) 本地计算并对比;建议统一小写比较
4) 建议校验时间戳偏差(如 ±300s)
5) 结合 ref/txid 做幂等
3)代收(入金)/ Pay-in
3.1 代收订单提交
接口:POST https://api.inpark.online/api/mcht/payment/submit
Headers
Content-Type: application/json
Authorization: Basic <base64(mid:signature)>
Request-Time: <13位毫秒时间戳>
| 字段 | 类型 | 必填 | 说明 |
| amount | number | 是 | 金额(两位小数) |
| pm | string | 是 | 支付方式(NATIVE / INVOKE / QR / WALLET / BANK) |
| ref | string | 是 | 商户订单号,必须唯一 |
| payer.email | string | 否 | 付款人邮箱(可空值) |
| payer.name | string | 否 | 付款人姓名(可空值) |
| payer.phone | string | 否 | 付款人电话(可空值) |
| redirect | string | 否 | 支付完成后跳转地址 |
| callbackUrl | string | 是 | 异步通知回调地址 |
Body 示例
{
"amount": 100.00,
"pm": "NATIVE",
"ref": "UNIQUE_ORDER_ID_01",
"payer": {
"email": "user@gmail.com",
"name": "PayerName",
"phone": "+88100000000"
},
"redirect": "https://example.com",
"callbackUrl": "https://example.com"
}
Python 示例
import time, hmac, hashlib, base64, json, requests
mid = "c70f1719017e290354017d1c101d0cc288d06ceb"
secret = "ME2VRe6tWH6weK/NAUJA5lhmewHkB23rA6CdWlrHrAs+/E/E3j3eG3io/GCHbQKqMMurfTNrBj/R4Yy84UziM5YJheiKFKbsWQc5xRoE46E3/0EYy4ZjbK9jhwGyHS+C"
url = "https://api.inpark.online/api/mcht/payment/submit"
body = {
"amount": 220.00,
"pm": "NATIVE",
"ref": "ORDER_SUBMIT_013",
"payer": {
"email": "user@gmail.com",
"name": "PayerName",
"phone": "+88100000000"
},
"redirect": "https://example.com",
"callbackUrl": "https://example.com"
}
request_time = str(int(time.time() * 1000))
body_str = json.dumps(body, ensure_ascii=False, separators=(',', ':'))
signature_str = f"{request_time}.{body_str}"
signature_hex = hmac.new(secret.encode(), signature_str.encode(), hashlib.sha256).hexdigest()
auth_header = "Basic " + base64.b64encode(f"{mid}:{signature_hex}".encode()).decode()
headers = {
"Content-Type": "application/json",
"Authorization": auth_header,
"Request-Time": request_time
}
resp = requests.post(url, headers=headers, data=body_str, timeout=30)
print("Status:", resp.status_code)
print("Response:", resp.text)
响应示例
{
"success": true,
"code": 200,
"data": {
"amount": "220",
"pm": "NATIVE",
"ref": "ORDER_SUBMIT_013",
"redirect": "https://example.com",
"currency": "INR",
"txid": "202509081321A6DI4P0V",
"fee": "2.2",
"netAmount": "217.8",
"createTime": 1757308907315,
"payer": {
"name": "PayerName",
"phone": "+88100000000",
"email": "user@gmail.com"
},
"paymentUrl": "/x3v83q7d",
"callbackUrl": "https://example.com"
}
}
3.2 代收订单查询
接口:GET https://api.inpark.online/api/mcht/payment/retrieve?txid=<平台交易ID>
Headers
Authorization: Basic <base64(mid:signature)>
Request-Time: <13位毫秒时间戳>
| 字段 | 类型 | 必填 | 说明 |
| txid | string | 是 | 平台交易ID |
响应示例
{
"success": true,
"code": 200,
"message": "Error message",
"data": {
"txid": "202409061809AE4IK4EZ",
"amount": "100.00",
"fee": "1.00",
"netAmount": "99.00",
"type": "PAYMENT",
"ref": "234d5678d345d5d6",
"currency": "INR",
"status": "PAID",
"paidTime": 1725617395000,
"createTime": 1725617356000,
"completeTime": 1725617395000,
"callbackUrl": "",
"redirectUrl": ""
}
}
3.3 代收回调数据格式
接口:POST <callbackUrl>
Headers
Content-Type: application/json
Signature: <hex(hmac-sha256(Request-Time + "." + body, secret))>
Request-Time: <13位毫秒时间戳>
| 字段 | 类型 | 说明 |
| amount | string | 金额 |
| callbackUrl | string | 异步通知回调地址 |
| createTime | number | 创建时间(13 位毫秒时间戳) |
| currency | string | 币种(如:INR) |
| fee | string | 手续费 |
| netAmount | string | 净金额 |
| ref | string | 商户订单号 |
| status | string | 订单状态(PAYMENT: PAID / COMPLETE / FAILED) |
| txid | string | 平台交易ID |
| type | string | 交易类型(PAYMENT) |
| paidTime | number | 支付时间(可选,状态为 PAID/COMPLETE 时出现) |
回调数据示例
POST https://callback.example.com
Content-Type: application/json
Signature: 8f6f1c7a...e2d
Request-Time: 1721872092000
{
"amount": "56.00",
"currency": "INR",
"fee": "0.00",
"netAmount": "0.00",
"createTime": 1721872092000,
"paidTime": 1721872092000,
"ref": "R1721872058703",
"txid": "202407250947A87IG0MA",
"type": "PAYMENT",
"status": "PAID"
}
4)代付(出金)/ Disbursement
4.1 代付订单提交
接口:POST https://api.inpark.online/api/mcht/disbursement/create
Headers
Content-Type: application/json
Authorization: Basic <base64(mid:signature)>
Request-Time: <13位毫秒时间戳>
| 字段 | 类型 | 必填 | 说明 |
| amount | number | 是 | 金额(两位小数) |
| bankAccountName | string | 是 | 收款人姓名 |
| bankAccountNumber | string | 是 | 收款人账号 |
| bankCode | string | 是 | 收款银行代码(IFSC) |
| ref | string | 是 | 商户订单号,必须唯一 |
| callbackUrl | string | 是 | 异步通知回调地址 |
Python 示例
import time, hmac, hashlib, base64, json, requests
mid = "c70f1719017e290354017d1c101d0cc288d06ceb"
secret = "ME2VRe6tWH6weK/NAUJA5lhmewHkB23rA6CdWlrHrAs+/E/E3j3eG3io/GCHbQKqMMurfTNrBj/R4Yy84UziM5YJheiKFKbsWQc5xRoE46E3/0EYy4ZjbK9jhwGyHS+C"
url = "https://api.inpark.online/api/mcht/disbursement/create"
body = {
"amount": 100.00,
"bankAccountName": "PATANGRAO",
"bankAccountNumber": "33672747179",
"bankCode": "SBIN0011132",
"ref": "WITHDRAW_TEST_002",
"callbackUrl": "https://example.com"
}
request_time = str(int(time.time() * 1000))
body_str = json.dumps(body, ensure_ascii=False, separators=(',', ':'))
signature_str = f"{request_time}.{body_str}"
signature_hex = hmac.new(secret.encode(), signature_str.encode(), hashlib.sha256).hexdigest()
auth_header = "Basic " + base64.b64encode(f"{mid}:{signature_hex}".encode()).decode()
headers = {
"Content-Type": "application/json",
"Authorization": auth_header,
"Request-Time": request_time
}
resp = requests.post(url, headers=headers, data=body_str, timeout=30)
print("Status:", resp.status_code)
print("Response:", resp.text)
响应示例
{
"success": true,
"code": 200,
"data": {
"amount": "100",
"bankAccountName": "PATANGRAO",
"bankAccountNumber": "33672747179",
"bankCode": "SBIN0011132",
"ref": "WITHDRAW_TEST_002",
"currency": "INR",
"callbackUrl": "https://example.com",
"txid": "PO202509081405A6DIK5PK",
"netAmount": "100",
"fee": "0",
"createTime": 1757311533901
}
}
4.2 代付订单查询
接口:GET https://api.inpark.online/api/mcht/disbursement/retrieve?txid=<平台交易ID>
Headers
Authorization: Basic <base64(mid:signature)>
Request-Time: <13位毫秒时间戳>
| 字段 | 类型 | 必填 | 说明 |
| txid | string | 是 | 平台交易ID |
响应示例
{
"success": true,
"code": 200,
"message": "Error message",
"data": {
"txid": "PO202408231658A87IKRYL",
"amount": "100.00",
"fee": "1.00",
"netAmount": "99.00",
"type": "DISBURSEMENT",
"ref": "234d5678d345d5d6",
"currency": "INR",
"status": "PAID",
"paidTime": 1725617395000,
"createTime": 1725617356000,
"callbackUrl": "",
"redirectUrl": ""
}
}
4.3 代付回调数据格式
接口:POST <callbackUrl>
Headers
Content-Type: application/json
Signature: <hex(hmac-sha256(Request-Time + "." + body, secret))>
Request-Time: <13位毫秒时间戳>
| 字段 | 类型 | 说明 |
| amount | string | 金额 |
| callbackUrl | string | 异步通知回调地址 |
| createTime | number | 创建时间(13 位毫秒时间戳) |
| currency | string | 币种(如:INR) |
| fee | string | 手续费 |
| netAmount | string | 净金额 |
| paidTime | number | 支付时间(13 位毫秒时间戳) |
| ref | string | 商户订单号 |
| status | string | 订单状态(DISBURSEMENT: PAID / FAILED) |
| txid | string | 平台交易ID |
| type | string | 交易类型(DISBURSEMENT) |
回调数据示例
Content-Type: application/json
Signature: 5a3c9b...f12
Request-Time: 1757910583000
{
"amount": "521.30",
"callbackUrl": "https://yourcallbackurl.app/webhooks/payout",
"createTime": 1757910583000,
"currency": "INR",
"fee": "0.00",
"netAmount": "533.30",
"paidTime": 1757910583000,
"ref": "REF460128f04955612de",
"status": "PAID",
"txid": "PO202509151229664IDW3M83",
"type": "DISBURSEMENT"
}
5)查询余额 / Wallet Balance
接口:GET https://api.inpark.online/api/mcht/wallet?currency=INR
Headers
Authorization: Basic <base64(mid:signature)>
Request-Time: <13位毫秒时间戳>
| 字段 | 类型 | 必填 | 说明 |
| currency | string | 是 | 币种(如:INR) |
Python 示例
import time, hmac, hashlib, base64, requests
mid = "c70f1719017e290354017d1c101d0cc288d06ceb"
secret = "ME2VRe6tWH6weK/NAUJA5lhmewHkB23rA6CdWlrHrAs+/E/E3j3eG3io/GCHbQKqMMurfTNrBj/R4Yy84UziM5YJheiKFKbsWQc5xRoE46E3/0EYy4ZjbK9jhwGyHS+C"
url = "https://api.inpark.online/api/mcht/wallet"
params = {"currency": "INR"}
request_time = str(int(time.time() * 1000))
query_str = "&".join([f"{k}={v}" for k, v in sorted(params.items())])
signature_str = f"{request_time}.{query_str}"
signature_hex = hmac.new(secret.encode(), signature_str.encode(), hashlib.sha256).hexdigest()
auth_header = "Basic " + base64.b64encode(f"{mid}:{signature_hex}".encode()).decode()
headers = {
"Authorization": auth_header,
"Request-Time": request_time
}
resp = requests.get(url, headers=headers, params=params, timeout=30)
print("Status:", resp.status_code)
print("Response:", resp.text)
响应示例
{
"success": true,
"code": 200,
"data": [
{
"currency": "INR",
"balance": "1000",
"freeze": "0",
"total": "1000"
}
]
}
6)订单状态说明
代收(入金)订单状态
| 状态 | 说明 |
| PAYING | 订单已创建,正在处理中,等待付款人完成支付 |
| PAID | 付款人已完成支付,资金已到达平台账户 |
| COMPLETE | 订单已完成,资金已结算至商户账户 |
| FAILED | 订单失败,可能由于付款人未支付、支付超时或异常等原因 |
代付(出金)订单状态
| 状态 | 说明 |
| PAYING | 订单已创建,正在处理中,等待银行处理 |
| PAID | 资金已成功转账至收款人账户 |
| FAILED | 订单失败,可能由于银行账户信息错误、余额不足或其他异常 |
7)注意事项
- 幂等性:
ref 必须唯一,可用毫秒时间戳拼接生成。
- JSON 序列化:签名用的 JSON 必须与实际请求体完全一致。
- 时间戳:
Request-Time 使用 13 位毫秒时间戳,并与签名保持一致。
- 回调:
callbackUrl 必须公网可访问,平台会在交易状态变更时异步通知。
- 回调签名验证:商户需验证
Signature 与 Request-Time。
- 安全:
secret 仅在服务端保存,严禁下发至前端或客户端。
- 异常处理:请对 4xx/5xx 错误做好重试与告警,记录签名串以便排查。
8)多语言验签示例(服务端)
Python (Flask)
import hmac, hashlib
from flask import Flask, request, abort
SECRET = b"YOUR_SECRET"
app = Flask(__name__)
@app.route("/callback", methods=["POST"])
def callback():
sig = (request.headers.get("Signature") or "").lower()
ts = request.headers.get("Request-Time") or ""
raw = request.get_data(as_text=True) # 原始body字符串
calc = hmac.new(SECRET, f"{ts}.{raw}".encode(), hashlib.sha256).hexdigest()
if sig != calc:
abort(401, "invalid signature")
# 可选:校验时间戳 ±300s;根据 txid/ref 做幂等
return "ok"
Node.js (Express)
const crypto = require("crypto");
const express = require("express");
const app = express();
// 关键:保留原始body字符串(不进行 JSON.parse)
app.use(express.raw({ type: "application/json" }));
const SECRET = Buffer.from("YOUR_SECRET");
app.post("/callback", (req, res) => {
const sig = (req.header("Signature") || "").toLowerCase();
const ts = req.header("Request-Time") || "";
const raw = req.body.toString("utf8");
const calc = crypto
.createHmac("sha256", SECRET)
.update(`${ts}.${raw}`)
.digest("hex");
if (sig !== calc) return res.status(401).send("invalid signature");
res.send("ok");
});
app.listen(3000);
PHP (原生)
<?php
$secret = "YOUR_SECRET";
$sig = strtolower($_SERVER['HTTP_SIGNATURE'] ?? '');
$ts = $_SERVER['HTTP_REQUEST_TIME'] ?? ''; // 自定义头“Request-Time”
$raw = file_get_contents('php://input'); // 原始body
$calc = hash_hmac('sha256', $ts . "." . $raw, $secret);
if ($sig !== $calc) {
http_response_code(401);
echo "invalid signature";
exit;
}
echo "ok";
Java (Spring Boot)
@PostMapping(value="/callback", consumes=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> callback(HttpServletRequest request) throws IOException {
String sig = Optional.ofNullable(request.getHeader("Signature")).orElse("").toLowerCase();
String ts = Optional.ofNullable(request.getHeader("Request-Time")).orElse("");
// 使用 ContentCachingRequestWrapper 在过滤器中缓存原始body
String raw = new String(((ContentCachingRequestWrapper)request)
.getContentAsByteArray(), StandardCharsets.UTF_8);
String calc = hmacHex(ts + "." + raw, "YOUR_SECRET");
if (!sig.equals(calc)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("invalid signature");
}
return ResponseEntity.ok("ok");
}
private String hmacHex(String data, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] d = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : d) sb.append(String.format("%02x", b));
return sb.toString();
} catch(Exception e){ throw new RuntimeException(e); }
}