- {{ line }}
- {{ ln }}
{{ apiSecret }}
channel=kuaiqian,并按需传 card_type;扣款、查询、解绑和退款继续使用原接口,系统会自动跟随绑卡/原订单通道。
贵司当前按首信易通道接入:现有请求无需增加或修改任何字段。不传 channel 时始终按首信易处理;下方快钱说明仅供开通后使用。
{{ apiInfo.gateway }}●POST●application/json · UTF-8一、通用约定
- 金额一律用「分」(整数):1 元 = 100,0.5 元 = 50,禁止传小数(如 1.00)。
- 每个请求都要带 4 个公共参数并参与签名:
app_id应用ID、timestampUnix 秒(与服务器偏差 ≤300 秒)、nonce随机串(300 秒内不可重复)、sign签名。 - 响应统一为
{ "code": 0, "msg": "ok", "data": {...} }:code=0表示接口成功,业务结果看data.status。
① 原首信易商户不传
channel,请求参数、签名、接口地址和处理结果均保持不变;② 使用快钱时,仅在首次
/bindcard 增加 channel=kuaiqian,贷记卡再传 card_type=CREDIT_CARD;③ 后续
/pay、/pay/query、/unbind、/refund、/refund/query 不传通道,平台根据 bind_id 或原订单自动路由;④ 响应和异步通知中的
channel 用于对账:payease 首信易、kuaiqian 快钱。新增字段属于向后兼容字段,解析 JSON 时请忽略不认识的扩展字段。
二、签名算法(HMAC-SHA256)
- 取除
sign外、值非空的所有参数; - 按参数名 ASCII 升序排序;
- 拼成
k1=v1&k2=v2&...(值原样,不做 URL 编码); - 以
app_secret为密钥做 HMAC-SHA256,输出十六进制小写即sign。
待签名串示例(参数 amount=100 / app_id=qcabc / bind_id=12 / nonce=x9f / out_trade_no=T1 / timestamp=1720000000):
amount=100&app_id=qcabc&bind_id=12&nonce=x9f&out_trade_no=T1×tamp=1720000000
const crypto = require('crypto');
function sign(params, secret) {
const str = Object.keys(params)
.filter(k => k !== 'sign' && params[k] != null && params[k] !== '')
.sort()
.map(k => `${k}=${params[k]}`)
.join('&');
return crypto.createHmac('sha256', secret).update(str, 'utf8').digest('hex');
}
function sign(array $params, string $secret): string {
unset($params['sign']);
$params = array_filter($params, fn($v) => $v !== null && $v !== '');
ksort($params);
$pairs = [];
foreach ($params as $k => $v) { $pairs[] = "$k=$v"; }
return hash_hmac('sha256', implode('&', $pairs), $secret);
}
import hmac, hashlib
def sign(params: dict, secret: str) -> str:
items = sorted((k, v) for k, v in params.items()
if k != 'sign' and v not in (None, ''))
base = '&'.join(f'{k}={v}' for k, v in items)
return hmac.new(secret.encode(), base.encode(), hashlib.sha256).hexdigest()
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
static String sign(Map<String,String> params, String secret) throws Exception {
TreeMap<String,String> m = new TreeMap<>();
for (Map.Entry<String,String> e : params.entrySet())
if (!"sign".equals(e.getKey()) && e.getValue() != null && !e.getValue().isEmpty())
m.put(e.getKey(), e.getValue());
StringBuilder sb = new StringBuilder();
for (Map.Entry<String,String> e : m.entrySet()) {
if (sb.length() > 0) sb.append('&');
sb.append(e.getKey()).append('=').append(e.getValue());
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
byte[] raw = mac.doFinal(sb.toString().getBytes("UTF-8"));
StringBuilder hex = new StringBuilder();
for (byte b : raw) hex.append(String.format("%02x", b));
return hex.toString();
}
import time, uuid, requests
params = {
"app_id": "qcabc...",
"timestamp": str(int(time.time())),
"nonce": uuid.uuid4().hex,
"out_trade_no": "T20260714001", # 商户订单号,幂等键
"bind_id": "12", # 已绑卡ID
"amount": "100", # 单位:分(1元=100)
"goods_name": "会员服务费",
"notify_url": "https://your.com/api/notify",
}
params["sign"] = sign(params, "YOUR_APP_SECRET")
r = requests.post("https://qiancipay.com/api/openapi/pay", json=params, timeout=30)
print(r.json())
# -> {"code":0,"data":{"out_trade_no":"T20260714001","trade_no":"Q1784180262695873","pay_order_no":"59ce7e561a6948838f5dd85260a9c43c","amount":100,"fee":1,"status":"success"}}
三、接口明细
路径均相对网关地址;金额字段单位为分。以下只列业务参数(公共参数 app_id/timestamp/nonce/sign 每个请求都要带)。
/bindcard快捷绑卡:返回银行授权地址,由持卡人跳转银行完成授权与选卡,无需上送卡号。
| 参数 | 必填 | 说明 |
|---|---|---|
| out_user_id | 是 | 商户侧用户唯一标识(同一用户复用) |
| channel | 否 | payease 首信易 / kuaiqian 快钱;不传固定默认首信易 |
| card_type | 否 | DEBIT_CARD 借记卡 / CREDIT_CARD 贷记卡;不传默认借记卡,贷记卡仅快钱支持 |
| name | 是 | 持卡人姓名 |
| id_card_num | 是 | 持卡人身份证号 |
| bank_code | 是 | 开户银行代码,取自下方「银行代码表」(也可通过完整地址 GET https://qiancipay.com/api/openapi/banks 拉取) |
| phone | 是 | 持卡人手机号(11位);仅平台留存,便于后续联系持卡人 |
| notify_url | 否 | 绑卡结果异步通知地址 |
| return_url | 否 | 银行页面流程结束后用户浏览器返回的商户页面(建议 HTTPS) |
| return_result_version | 否 | 传 1 时在 return_url 后追加签名结果参数,必须同时传 return_url;不传完全保持原跳转 |
// 请求
{
"app_id": "qcabc...",
"timestamp": "1720000000",
"nonce": "x9f...",
"out_user_id": "U10001",
"name": "张三",
"id_card_num": "110101199001011234",
"bank_code": "CCB",
"phone": "13800001234",
"notify_url": "https://your.com/api/notify",
"return_url": "https://your.com/bind/result",
"return_result_version": "1",
"sign": "9a1c..."
}
// 响应
{
"code": 0,
"msg": "ok",
"data": {
"bind_id": 12,
"out_user_id": "U10001",
"channel": "payease",
"status": "submitting",
"redirect_url": "https://<银行授权页地址>"
}
}
{
"channel": "kuaiqian",
"card_type": "CREDIT_CARD",
"bank_code": "ECITIC"
}
// 其余 app_id、timestamp、nonce、out_user_id、name、id_card_num、phone、notify_url、return_url 与原绑卡请求一致
// channel/card_type 也必须和其他非空参数一起参与签名
引导用户打开 redirect_url 完成授权;最终结果以「绑卡查询」或异步通知为准(bound 已绑定 / failed 失败)。
同步返回:首信易和快钱都支持 return_url。传入后,银行页面流程结束会将用户浏览器跳转至该商户地址;不传时保持原平台页。浏览器返回不代表绑卡已最终成功,仍需以异步通知或绑卡查询为准。
兼容说明:现有商户不传 return_result_version 时,首信易和快钱的回跳地址及参数完全不变。需要让返回页快速识别状态时,可增加 return_result_version=1(该字段参与绑卡请求签名,且必须同时传 return_url)。
版本 1 会保留商户原有查询参数和锚点,并追加以下平台保留参数:
| 参数 | 说明 |
|---|---|
| qc_return_version | 固定为 1 |
| qc_bind_id | 平台绑卡编号 |
| qc_channel | payease 或 kuaiqian |
| qc_status | bound 已绑定 / failed 失败 / processing 仍在确认 |
| qc_timestamp | 平台 Unix 秒级时间戳 |
| qc_nonce | 本次返回随机串,商户应防重放 |
| qc_sign | HMAC-SHA256 小写十六进制签名 |
验签时取上述全部 qc_ 参数但排除 qc_sign,按参数名升序拼成 k=v&k=v,使用商户 AppSecret 计算 HMAC-SHA256。原 return_url 中已有参数不参与结果签名;建议校验时间差不超过 300 秒并拒绝重复 qc_nonce。
qc_status 是页面返回时的平台快照;processing 必须继续查询,最终业务状态仍以签名异步通知或绑卡查询为准。返回 URL 不携带姓名、身份证号或失败原因。
若平台启用“限制新签约”风控,新绑卡返回 4105;已有签约的查询和解绑不受影响。
首信易银行代码表(不传 channel 时使用,仅借记卡):
| bank_code | 银行 | bank_code | 银行 |
|---|---|---|---|
| PINGANBANK | 平安银行 | HXB | 华夏银行 |
| BOC | 中国银行 | BCCB | 北京银行 |
| ECITIC | 中信银行 | CCB | 建设银行 |
| QDTH | 青岛银行 | POST | 中国邮政储蓄 |
| GDB | 广发银行 | CMBC | 民生银行 |
快钱一键绑卡银行/卡种表(channel=kuaiqian):
| bank_code | 银行 | 借记卡 | 贷记卡 |
|---|---|---|---|
| ABC | 农业银行 | 支持 | 支持 |
| BOC | 中国银行 | 支持 | 不支持 |
| POST | 邮储银行 | 支持 | 支持 |
| SPDB | 浦发银行 | 支持 | 支持 |
| PINGANBANK | 平安银行 | 支持 | 支持 |
| GDB | 广发银行 | 支持 | 支持 |
| HXB | 华夏银行 | 支持 | 支持 |
| ECITIC | 中信银行 | 不支持 | 支持 |
| CMBC | 民生银行 | 支持 | 不支持 |
card_type 可传 DEBIT_CARD/CREDIT_CARD,不传默认借记卡。免签名拉取:GET https://qiancipay.com/api/openapi/banks?channel=kuaiqian,也可加 &card_type=CREDIT_CARD 按卡种筛选。
/bindcard/query参数:bind_id(推荐)或 out_user_id。按 out_user_id 查询时可传 channel;不传固定查首信易。
// 响应
{
"code": 0,
"msg": "ok",
"data": {
"bind_id": 12,
"out_user_id": "U10001",
"bank_code": "CCB",
"card_type": "DEBIT_CARD",
"card_mask": "**** 4230",
"name_mask": "张*",
"channel": "payease",
"status": "bound",
"created_at": "2026-07-14 10:00:00"
}
}
/unbind参数:bind_id,无需传 channel,平台自动按绑卡所属通道解绑。响应 data:{ "bind_id": 12, "status": "unbound" }
/pay无需传 channel;平台根据 bind_id 自动使用首信易或快钱,原首信易请求保持不变。
平台可按通道启用“低成功率历史绑卡风控”,并独立设置起控笔数(默认 100)。当日该通道扣款未达到起控笔数时只统计不拦截;达到后若原始成功率低于 10%,历史绑卡的新扣款返回 4211,仅允许使用当日新绑卡。成功率等于或高于 10%、或贵司已获该通道豁免时不拦截。已有商户订单号的幂等查询仍返回原订单结果。
| 参数 | 必填 | 说明 |
|---|---|---|
| out_trade_no | 是 | 商户订单号(幂等键,同商户唯一) |
| bind_id | 是 | 已绑定成功的绑卡ID |
| amount | 是 | 扣款金额(分,正整数) |
| goods_name | 否 | 商品/服务名称 |
| notify_url | 否 | 扣款结果异步通知地址 |
// 响应
{
"code": 0,
"msg": "ok",
"data": {
"out_trade_no": "T20260714001",
"channel": "payease",
"trade_no": "Q1784180262695873",
"pay_order_no": "59ce7e561a6948838f5dd85260a9c43c",
"amount": 100,
"fee": 1,
"status": "success"
}
}
三种订单号:out_trade_no 商户订单号(贵司自己的单号)/trade_no 平台订单号(我方唯一单号,Q 开头,建议贵司存此号用于查单对账)/pay_order_no 支付公司订单号(下单成功才有;失败为 null)。
status:success 成功 / failed 失败 / paying 银行处理中(以通知为准)。fee 为本笔技术服务费(分),从预付费余额扣。重复 out_trade_no 返回首次结果并带 "idempotent": true。
若平台启用“限制扣款”风控,新扣款返回 4209;已存在 out_trade_no 的幂等请求仍返回原订单结果。
/pay/query参数:out_trade_no。
// 响应 data
{
"out_trade_no": "T20260714001",
"channel": "payease",
"trade_no": "Q1784180262695873",
"pay_order_no": "59ce7e561a6948838f5dd85260a9c43c",
"amount": 100,
"fee": 1,
"settle_fee": 2,
"refunded_amount": 0,
"status": "success",
"paid_at": "2026-07-14 10:05:00"
}
/refund无需传 channel;平台根据 out_trade_no 对应原支付通道自动发起退款。
默认保持原 V1。请在退款及退款查询请求头增加
X-Refund-Response-Version: 2 提前切换并联调;该请求头不参与业务参数签名。平台收到明确切换通知前不会自动停用 V1。| 参数 | 必填 | 说明 |
|---|---|---|
| out_refund_no | 是 | 商户退款单号(幂等键,同商户唯一) |
| out_trade_no | 是 | 原扣款订单号 |
| amount | 否 | 退款金额(分);不传则退全部剩余可退 |
| notify_url | 否 | 退款结果异步通知地址 |
// V2 响应 data(请求头 X-Refund-Response-Version: 2)
{
"out_refund_no": "R20260714001",
"channel": "payease",
"refund_no": "Q1784180377261987",
"pay_refund_no": "41e74e67c0044cccb08fa79fde2685ca",
"amount": 100,
"status": "processing",
"fail_reason": null,
"done_at": null,
"idempotent": false
}
refund_no 平台退款单号(Q 开头字符串)/pay_refund_no 支付公司退款流水(受理成功才有)。V2 中 code 只表示接口是否被平台正确处理,退款结果必须以 data.status 判断:success / failed / processing。失败时返回 fail_reason;重复请求返回当前状态并带 idempotent:true。过渡期 V1 对同步明确失败仍返回 code:4306,但不再返回上游内部错误。
/refund/query参数:out_refund_no。V2 响应 data:{ out_refund_no, refund_no, pay_refund_no, amount, status, fail_reason, done_at }。平台内部异常核查状态对外仍按 processing 返回。
/balance无业务参数。balance/fee_min/fee_max 单位分,fee_rate 为小数(如 0.003 = 0.3%)。
// 响应 data
{ "balance": 94800, "fee_rate": 0.003, "fee_min": 100, "fee_max": 5000 }
四、异步通知(notify)
扣款/退款/绑卡到终态时,若该请求带了 notify_url,我方会向该地址 POST 一个 JSON 通知,通知体带我方签名(同第二节算法,密钥为你的 app_secret)。你需验签确认来源,处理成功后返回纯文本 SUCCESS;否则我方按退避策略重试(30s、1m、5m、15m、30m、1h、2h、6h,最多 8 次)。
{
"app_id": "qcabc...",
"timestamp": "1720000100",
"nonce": "7c2...",
"event": "payment",
"channel": "payease",
"out_trade_no": "T20260714001",
"trade_no": "Q1784180262695873",
"pay_order_no": "59ce7e561a6948838f5dd85260a9c43c",
"amount": 100,
"fee": 1,
"status": "success",
"sign": "..."
}
// 所有通知均含 channel:payease(首信易) / kuaiqian(快钱)
// 绑卡通知 event=bindcard:含 bind_id, out_user_id, bank_code, card_mask, status;失败时另带 fail_reason
// 退款通知 event=refund:含 out_refund_no, out_trade_no, refund_no, pay_refund_no, amount, status
// 扣款/退款通知在失败时另带 fail_reason(失败原因);成功无此字段
@app.route("/api/notify", methods=["POST"])
def notify():
body = request.get_json()
if sign(body, "YOUR_APP_SECRET") != body.get("sign"):
return "FAIL", 400 # 验签不通过
# TODO: 按 body["event"] 与 status 更新你的订单...
return "SUCCESS" # 收妥,务必返回纯文本 SUCCESS
五、错误码
| code | 说明 |
|---|---|
| 0 | 成功 |
| 4001 / 4003 / 4004 | 缺 app_id / app_id 无效 / 签名校验失败 |
| 4002 | timestamp 偏差过大 或 nonce 重复(防重放) |
| 4005 / 4006 / 4007 / 4008 | 未开通API / 商户停用 / 未签约 / 未进件开通 |
| 4106 | channel 不支持;不传时固定默认首信易 |
| 4107 / 4108 / 4109 | 快钱全局未开放 / 商户未配置快钱 / 快钱通道或签约能力未开启 |
| 4105 | 平台风控限制新增银行卡签约 |
| 4201 / 4204 | 金额须为正整数(分) / 预付费余额不足 |
| 4202 / 4203 | 绑卡不存在 / 该卡未完成绑卡 |
| 4205 | 下单失败(含银行返回原因) |
| 4209 | 平台风控限制新发起扣款 |
| 4211 | 当日通道扣款已达到起控笔数且成功率低于 10%,历史绑卡受限;仅允许使用当日新绑卡 |
| 4220 / 4221 | 绑卡所属快钱通道不可用 / 绑卡所属通道不受支持 |
| 4305 | 退款金额超出可退范围 |
| 4320 / 4321 / 4322 | 原订单快钱退款能力不可用 / 原订单通道不受支持 / 快钱原支付流水不完整 |
| 5000 / 5001 | 系统错误 / 支付通道未配置 |
| 5030 | 平台安全发布中,按 Retry-After 使用原商户业务单号重签后重试 |
发布期重试:若收到 HTTP 503、code:5030 和 retryable:true,本次请求未进入业务处理。请保持原 out_trade_no / out_refund_no,重新生成 timestamp、nonce 和 sign 后重试;幂等机制会防止重复扣款或退款。
status 语义:success 成功(终态)· failed 失败(终态)· paying/processing 处理中(以异步通知或查询接口为准)。