商户接入文档
对接说明
两个功能:代收、代付。全部接口为 HTTPS + POST + JSON,接口地址
https://api.nickpay.net。你要写的只有签名、调接口、接通知三件事。
2026-09-18 起平台更名 NickPay、接口域名换为 api.nickpay.net;
原 api.hans-pay.cc.cd 继续可用,接口与签名规则不变,请尽快切到新域名。
通用约定
凭据与白名单
运营提供 merchant_no(商户号)与 api_secret(签名密钥,只展示一次),
并登记你的出口 IP。平台不登记默认回调地址:代收与代付的通知通常打到不同接收端,请每笔请求自带 notify_url。
请求头
Content-Type: application/json,并设置自定义 User-Agent(如 MyShop/1.0)。
金额
一律字符串。请求传整数 VND("50000"),响应固定 2 位小数("50000.00")。
时间
expire_at、paid_at 为越南时间 UTC+7,格式 YYYY-MM-DD HH:mm:ss;
timestamp 为 Unix 秒,与服务器相差超过 5 分钟拒绝。
响应外壳
{"code":"0000","msg":"success","data":{…},"sign":"…"},HTTP 恒为 200,看 code。
幂等
同一 merchant_order_no 参数一致的重复请求返回同一笔单;参数不一致返回 1005。
请求超时请用同一单号重发或查单,不要换单号。
签名
HMAC-SHA256,请求、响应、通知三个方向用同一个算法。
-
1
取字段
请求 JSON 第一层全部字段(不含
sign),去掉值为空字符串的。 -
2
拼接
按字段名 ASCII 升序拼成
k1=v1&k2=v2&…。 -
3
HMAC
sign = hex(HMAC_SHA256(拼接串, api_secret)),小写。响应验签:对 data 内的字段签。通知验签:对通知 JSON 去掉 sign 后的全部字段签。
import hashlib, hmac
def sign(params: dict, secret: str) -> str:
items = sorted(
(k, v) for k, v in params.items()
if k != "sign" and v != ""
)
raw = "&".join(f"{k}={v}" for k, v in items)
return hmac.new(
secret.encode(), raw.encode(), hashlib.sha256
).hexdigest()
# 通知验签
def verify_notify(body: dict, secret: str) -> bool:
got = body.get("sign", "")
rest = {k: v for k, v in body.items() if k != "sign"}
return hmac.compare_digest(got, sign(rest, secret))
# 响应验签:对 data 签,错误响应没有 sign
def verify_resp(resp: dict, secret: str) -> bool:
return resp.get("code") == "0000" and \
hmac.compare_digest(resp.get("sign", ""), sign(resp["data"], secret))
接口
代收三个,代付三个(加一个银行列表),余额一个。请求都带 merchant_no、timestamp、sign。
POST /api/v1/pay/create pay_method 由运营告知(当前 BANK_QR)。
付款人四项传真实值。订单 30 分钟有效。
pay_type=URL:引导用户打开 pay_payload(平台收银台,整段原样使用);
pay_type=QRCODE:把 pay_payload 渲染成二维码。按字段判断,不要写死。
{
"merchant_no": "M1001",
"merchant_order_no": "ORD20260820001", // ≤ 64
"amount": "50000", // 整数 VND
"currency": "VND", // 选填
"pay_method": "BANK_QR",
"payer_name": "NGUYEN VAN A",
"payer_email": "a@example.com",
"payer_phone": "0912345678",
"payer_account_no": "0123456789",
"payer_account_type": "BANK", // 选填,或 PHONE
"user_id": "U10086", // 选填
"notify_url": "https://your.domain/nickpay/notify", // 请每笔都传:本笔通知地址;不传则不推送通知、只能查单
"timestamp": "1755657600",
"sign": "9f8e...c1"
}
{
"order_no": "P2408201234",
"merchant_order_no": "ORD20260820001",
"amount": "50000.00",
"currency": "VND",
"pay_type": "URL",
"pay_payload": "https://api.nickpay.net/pay/P24...?t=...",
"expire_at": "2026-08-20 15:30:00"
}
POST /api/v1/pay/query 传 merchant_order_no 或 order_no。
status:CREATED(待支付)/ SUCCESS / FAILED / EXPIRED。
paid_at 仅 SUCCESS 时出现。未支付时若已有支付凭据,会附带
pay_type / pay_payload,可直接给用户续付。
{
"merchant_no": "M1001",
"merchant_order_no": "ORD20260820001",
"timestamp": "1755657600",
"sign": "5c2f...9a"
}
{
"order_no": "P2408201234",
"merchant_order_no": "ORD20260820001",
"amount": "50000.00",
"currency": "VND",
"fee_amount": "1000.00",
"net_amount": "49000.00",
"status": "SUCCESS",
"paid_at": "2026-08-20 15:02:11"
}
平台 POST 到下单时传的 notify_url;没传的单不推送通知,只能查单 status 为 SUCCESS 或 FAILED;
FAILED 时 fee_amount / net_amount 为 "0.00",无 paid_at。
notify_url 须公网可达的 http(s) 地址,本机/内网地址下单时即回 1002;不参与幂等比对(同号重试以首次为准);不要做跳转,平台不跟 3xx。
过期(EXPIRED)不通知,请查单。
收到后:验签 → 按 order_no 幂等 → 无论 status 是什么,返回 HTTP 200 + 响应体 SUCCESS,
否则平台按 15s ~ 6h 重试 10 次。SUCCESS 为终态:先收到 FAILED 再收到
SUCCESS 必须按成功处理;已成功后再收到 FAILED 忽略。建议查单确认再发货。
{
"order_no": "P2408201234",
"merchant_order_no": "ORD20260820001",
"amount": "50000.00",
"currency": "VND",
"fee_amount": "1000.00",
"net_amount": "49000.00",
"status": "SUCCESS",
"paid_at": "2026-08-20 15:02:11",
"timestamp": "1755658931",
"sign": "..."
}
@app.post("/notify")
def notify():
body = request.get_json()
if not verify_notify(body, API_SECRET):
return "invalid sign", 403
no = body["order_no"]
if already_paid(no): # 按「是否已成功」判幂等
return "SUCCESS"
if body["status"] == "SUCCESS":
mark_paid(no, body["amount"])
else:
mark_failed(no)
return "SUCCESS" # 无论哪种 status
POST /api/v1/transfer/create 把 VND 打到收款人银行账户,需运营开通。
下单即从可用余额冻结 amount + fee_amount,成功扣除、失败退回。
status:PROCESSING / SUCCESS / FAILED(附 fail_reason)。
请求超时或没拿到响应,用同一 merchant_order_no 重发或查单,不要换单号——换单号就是第二笔出款。
同号且 amount / bank_code / account_no 一致返回原单。
bank_code 用 POST /api/v1/transfer/banks 取(只传 merchant_no / timestamp / sign,
返回 banks[{bank_code, bank_name}],6 位越南银行代码,可缓存一天)。沙箱不支持代付。
怎么拿到 bank_code:你的服务端每天调一次 /api/v1/transfer/banks 缓存 banks 数组;
出款页面用它做「选择银行」下拉——给用户看 bank_name,选中后取 bank_code 原样传给 create;
账号、户名由用户填写。不要让用户手填 6 位代码;不在列表里的银行当前不支持(1002)。
当前支持的 40 家银行代码表(以接口实时返回为准)
{
"merchant_no": "M1001",
"merchant_order_no": "TRF20260915001", // ≤ 64
"amount": "1000000", // 整数 VND,实收
"bank_code": "970416",
"account_no": "0011223344", // ≤ 40
"account_name": "NGUYEN VAN A", // 按银行开户名
"memo": "Hoan tien", // 选填,≤ 40
"notify_url": "https://your.domain/nickpay/notify", // 选填,本笔通知地址
"timestamp": "1755657600",
"sign": "5c2f...9a"
}
{
"transfer_no": "D260915120000123456",
"merchant_order_no": "TRF20260915001",
"amount": "1000000.00",
"fee_amount": "8000.00",
"frozen_amount": "1008000.00",
"currency": "VND",
"status": "PROCESSING"
}
POST /api/v1/transfer/query 传 transfer_no 或 merchant_order_no。
响应 data 与发起相同,SUCCESS 时附 paid_at,FAILED 时附 fail_reason。
PROCESSING 可能持续几分钟到数小时,不要因此换单号重发。
{
"merchant_no": "M1001",
"transfer_no": "D260915120000123456",
"timestamp": "1755657600",
"sign": "5c2f...9a"
}
{
"transfer_no": "D260915120000123456",
"merchant_order_no": "TRF20260915001",
"amount": "1000000.00",
"fee_amount": "8000.00",
"frozen_amount": "1008000.00",
"currency": "VND",
"status": "SUCCESS",
"paid_at": "2026-09-15 12:01:30"
}
平台 POST 到下单时传的 notify_url;没传的单不推送通知,只能查单 status 为 SUCCESS 或 FAILED;
FAILED 时无 paid_at,冻结金额已退回可用余额。代收与代付通知可以用同一个地址:
代收报文带 order_no(P…),代付报文带 transfer_no(D…),按此分发。
处理规则与代收通知相同:验签 → 按 transfer_no 幂等 → 返回 SUCCESS;
SUCCESS 为终态。代付单 SUCCESS 但用户说没收到:联系运营,不要自己再发一笔。
{
"transfer_no": "D260915120000123456",
"merchant_order_no": "TRF20260915001",
"amount": "1000000.00",
"fee_amount": "8000.00",
"currency": "VND",
"status": "SUCCESS",
"paid_at": "2026-09-15 12:01:30",
"timestamp": "1789474890",
"sign": "..."
}
{
"transfer_no": "D260915120000123456",
"merchant_order_no": "TRF20260915001",
"amount": "1000000.00",
"fee_amount": "8000.00",
"currency": "VND",
"status": "FAILED",
"timestamp": "1789474890",
"sign": "..."
}
POST /api/v1/balance/query 只传 merchant_no、timestamp、sign。
available 可用余额;frozen 处理中的代付 / 提现占用;
withdrawable 可提现整数金额。
{
"merchant_no": "M1001",
"timestamp": "1755657600",
"sign": "5c2f...9a"
}
{
"available": "1205554.67",
"frozen": "500000.00",
"withdrawable": "1205554",
"currency": "VND"
}
错误码
code 为 0000 表示成功。HTTP 403 + 纯文本 error code: 1010
不是本接口错误,是安全防护拦了你的 User-Agent,换成自定义 UA 即可。
请求
订单
商户与系统
沙箱
代收下单请求多带 "sandbox": "1"(参与签名),其余不变:不产生真实支付,
order_no 以 T 开头,pay_payload 是模拟收银台,
页面上点「模拟支付成功 / 失败」即触发与生产完全一致的通知;查单同样带 "sandbox": "1"。
T 开头的单号绝不能发货。沙箱不支持代付。