黔彩收银支付渠道商接入规范
v1.1.9
修订记录
日期 | 描述 | 作者 | 版本号 |
---|---|---|---|
2025-07-01 | 退款查询接口 追加{qcRefDate} |
王松乐 | V1.1.9 |
2025-04-29 | 新增退款异步通知报文 |
王松乐 | V1.1.8 |
2025-04-15 | 9171(B扫C) 交易请求参数追加{wxAppId},所有接口返回参数去掉{unionId}参数 |
王松乐 | V1.1.7 |
2025-03-26 | 追加实收手续费字段{custFee} | 王松乐 | V1.1.6 |
2025-03-05 | 1. C扫B追加统一下单 支付模式{payMode:1},统一由黔彩收银台拉起原生JS支付;2. B扫C和支付查询接口追加{unionId}返回参数 |
王松乐 | V1.1.5 |
2025-01-13 | 修改9173关单 和9174撤单 接口功能说明 |
王松乐 | V1.1.4 |
2025-01-13 | 接口必填属性添加标记 | 王松乐 | V1.1.3 |
2025-01-09 | C扫B追加payChannel 参数以支持Native原生支付 |
王松乐 | V1.1.2 |
2024-12-27 | 标准版 | 王松乐 | V1.1.1 |
2024-10-23 | 初版 | 王松乐 | V1.0.0 |
背景
本文档形成的目的是明确黔彩支付结算中心与第三方聚合支付渠道商的接入规范,结算中心统一对支付相关接口进行设计,包括通信协议、报文格式、加解密算法、签名算法、入参/出参格式、对账单格式,各三方聚合渠道商需要按此规范自行开发实现,黔彩结算中心配合各渠道商进行联调和测试。
接入规范
- 通信协议:HTTPS
- 数据格式:JSON
- 编码格式:UTF-8
- 会话密钥加密:RSA256
- 业务数据加密:AES(加密模式:AES/CBC/PKCS5Padding,IV向量固定为1983632248090917)
- IV向量:1983632248090917
公私钥交换说明
我平台做为聚合支付代理商的角色接入各个第三方聚合渠道,统一使用我方平台的公私钥与聚合渠道商进行交换
报文规范
网络传输采用SSL加密协议,双方互换RSA公钥,会话密钥由请求方随机生成AES128密钥作为会话sessionKey(每一个请求都会随机生成一个sessionKey),会话密钥采用RSA256算法进行加密传输,双向业务数据采用AES加密算法(加密模式:AES/CBC/PKCS5Padding,IV向量为1983632248090917),签名算法采用SHA256withRSA,HTTP请求仅限POST方法,报文内容由公共请求头和请求体两部分组成,具体格式如下描述:
业务数据加密说明
- 使用随机生成的AES密钥对业务报文进行加密
- 对随机生成的AES密钥做RSA256加密,放在公共请求头Session-Key栏位
代码示例
public static byte[] encrypt(String bizData, String key, String iv) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(
Cipher.ENCRYPT_MODE,
new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"),
new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8)));
return cipher.doFinal(bizData.getBytes(StandardCharsets.UTF_8));
} catch (GeneralSecurityException e) {
log.error("AES Error ->{}",e.getMessage());
return null;
}
}
签名说明
先将JSON报文按KEY名做自然排序,再使用私钥对序列化后的JSON串进行签名,签名算法采用SHA256withRSA,将生成签名串放到公共请求头Sign栏位
代码示例
public static String sign(QcyTradeModel content, String privateKey) {
//内容排序
String dataSort = JSON.toJSONString(content, SerializerFeature.MapSortField);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
KeyFactory factory = KeyFactory.getInstance("RSA");
PrivateKey secretKey = factory.generatePrivate(pkcs8EncodedKeySpec);
try {
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(secretKey);
signature.update(dataSort.getBytes(StandardCharsets.UTF_8));
return Base64.encodeBase64String(signature.sign());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
公共请求头
属性 | 值 | 必传 | 备注 |
---|---|---|---|
Channel-Isp-Code | 渠道商编码 | 是 | 示例:GT |
Session-Key | 会话密钥 | 是 | 会话密钥经过RSA256加密后传输 |
Sign | 报文签名 | 是 | SHA256WithRSA签名算法 |
代码示例
String argsSign = RSA256.sign(this.content,this.privateKey);
String secretKey = RSA256.encrypt(this.sessionKey,this.publicKey);
Map<String,String> header = new HashMap<>();
header.put("Sign", argsSign);
header.put("Session-Key", secretKey);
header.put("Channel-Isp-Code", "GT");
header.put("Content-Type", "text/plain");
BODY请求体公共参数
属性 | 值 | 必传 | 备注 |
---|---|---|---|
timestamp | 时间戳 | 是 | System.currentTimeMillis() |
接口设计规范
接口地址
- 聚合渠道服务商只需提供一个公共交易接口,通过{txnCode}交易码区分业务功能,比如交易码为9170代表客户主扫支付(C扫B)
- 所有接口仅支持POST提交方法
POST C2B客户主扫支付(电子收款码)
Body 请求参数
{
"txnCode": "交易码:9170",
"agetId": "机构号",
"merId": "商户号",
"qcOrderNo": "黔彩交易订单号",
"txnAmt": "交易金额(分)",
"expireTime": "Number类型,订单失效时间(分钟),如填15,代表15分钟后失效",
"payMode": "Number类型,支付方式:0 生成电子码牌(渠道商返回H5聚合收款码进行支付)",
"payChannel": "支付通道:2银联,3微信,4支付宝",
"asyncNotifyUrl": "支付通知地址",
"txnNotes": "交易备注"
}
应答内容
{
"code": "请求状态:000000代表请求成功,其它均为请求失败,不能代表实际交易状态",
"msg": "响应描述",
"data": {
"qrCodeUrl": "支付电子码牌链接地址"
}
}
POST C2B统一下单支付(返回原生渠道JS支付参数)
Body 请求参数
{
"txnCode": "交易码:9179",
"agetId": "机构号",
"merId": "商户号",
"qcOrderNo": "黔彩交易订单号",
"txnAmt": "交易金额(分)",
"expireTime": "Number类型,订单失效时间(分钟),如填15,代表15分钟后失效",
"payMode": "Number类型,支付方式:1 统一下单支付(直接返回原生渠道JS支付参数,由黔彩收银台统一拉起原生支付)",
"payChannel": "支付通道:2银联,3微信,4支付宝",
"wxAppId": "微信appid(黔彩圈小程序)",
"wxTxnType": "Number类型,微信交易类型:0公众号 1小程序",
"alipayAppId": "支付宝appid",
"openId": "根据微信、支付宝、银联获取的用户标识,微信是openid,支付宝是userId(以 2088 开头)",
"clientIp": "消费者IP地址,如222.137.130.75",
"asyncNotifyUrl": "支付通知地址",
"txnNotes": "交易备注"
}
应答内容
{
"code": "请求状态:000000代表请求成功,其它均为请求失败,不能代表实际交易状态",
"msg": "响应描述",
"data": {
"txnStatus": "(必填)交易状态:2交易成功(即可以支付),3交易失败",
"txnMsg": "交易描述:交易失败时请注明失败原因",
"prePayId": "下单成功后返回的预支付ID",
"qcOrderNo": "黔彩交易订单号",
"ispOrderNo": "渠道商订单号",
"actualPayAmt": "消费者实际支付金额(单位分)",
"unionPayUrl": "银联返回重定向地址",
"jsapiAppid": "微信appid(微信支付)",
"jsapiTimestamp": "时间戳(微信支付)",
"jsapiNoncestr": "随机字符串(微信支付)",
"jsapiPackage": "jsapiPackage(微信支付)",
"jsapiSignType": "jsapi签名类型(微信支付)",
"jsapiPaySign": "jsapi支付签名(微信支付)"
}
}
POST 客户被扫支付(B扫C)
Body 请求参数
{
"txnCode": "交易码:9171",
"agetId": "机构号",
"merId": "商户号",
"qcOrderNo": "黔彩交易订单号",
"wxAppId": "微信appid(黔彩圈小程序)",
"txnAmt": "交易金额(分)",
"payAuthCode": "扫码支付授权码(付款码)",
"txnIp": "交易IP",
"asyncNotifyUrl": "支付通知地址",
"txnNotes": "交易备注"
}
应答内容
{
"code": "请求状态:000000代表请求成功,其它均为请求失败,不能代表实际交易状态",
"msg": "响应描述",
"data": {
"merId": "(必填)商户号",
"qcOrderNo": "(必填)黔彩交易订单号",
"txnStatus": "(必填)交易状态:0未支付,1支付中,2支付成功,3支付失败,4未知状态",
"txnMsg": "交易描述:支付失败时请注明失败原因",
"txnAmt": "(必填)交易金额(分)",
"netAmt": "(必填)实收金额(分)",
"custFee": "商户交易实收手续费(分)",
"ispOrderNo": "(必填)渠道商订单号",
"openId": "(支付成功时必填)消费者openid,取我方公众号下对应的openid,支付宝为userId",
"payChannel": "(必填)支付通道:2银联,3微信,4支付宝,6京东,7翼支付",
"originOrderNo": "(支付成功时必填)第三方支付通道原始订单号(微信、支付宝、银联官方的订单号)",
"originTxnNo": "(支付成功时必填)第三方支付通道流水号(渠道商送给微信、支付宝、银联的订单号)"
}
}
POST 交易查询接口
Body 请求参数
{
"txnCode": "交易码:9172",
"agetId": "机构号",
"merId": "商户号",
"qcOrderNo": "黔彩交易订单号",
"txnDate": "交易日期(YYYYMMDD)"
}
应答内容
{
"code": "请求状态:000000代表请求成功,其它均为请求失败,不能代表实际交易状态",
"msg": "响应描述",
"data": {
"merId": "(必填)商户号",
"qcOrderNo": "(必填)黔彩交易订单号",
"txnStatus": "(必填)交易状态:0未支付,1支付中,2支付成功,3支付失败,4未知状态",
"txnMsg": "交易描述:支付失败时请注明失败原因",
"txnAmt": "(必填)交易金额(分)",
"netAmt": "(必填)实收金额(分)",
"custFee": "商户交易实收手续费(分)",
"ispOrderNo": "(必填)渠道商订单号",
"openId": "(支付成功时必填)消费者openid,取我方公众号下对应的openid,支付宝为userId",
"payChannel": "(必填)支付通道:2银联,3微信,4支付宝,6京东,7翼支付"
"originOrderNo": "(支付成功时必填)第三方支付通道原始订单号(微信、支付宝、银联官方的订单号)",
"originTxnNo": "(支付成功时必填)第三方支付通道流水号(渠道商送给微信、支付宝、银联的订单号)"
}
}
POST 退款接口
Body 请求参数
{
"txnCode": "交易码:9175",
"agetId": "机构号",
"merId": "商户号",
"qcRefNo": "黔彩退款订单号",
"qcOrderNo": "黔彩原交易订单号",
"refAmt": "退款金额(分)",
"payChannel": "Number类型,支付通道:2银联,3微信,4支付宝,6京东,7翼支付"
"asyncNotifyUrl": "退款结果通知地址"
}
应答内容
{
"code": "请求状态:000000代表请求成功,其它均为请求失败,不能代表实际交易状态",
"msg": "响应描述",
"data": {
"merId": "(必填)商户号",
"qcRefNo": "(必填)黔彩退款订单号",
"qcOrderNo": "(必填)黔彩交易订单号",
"ispOrderNo": "(必填)渠道商退款订单号",
"refStatus": "(必填)Number类型,退款状态:3退款失败 4退款成功 5退款中",
"refMsg": "退款状态描述",
"refAmt": "(必填)退款金额(分)",
"payChannel": "Number类型,支付通道:2银联,3微信,4支付宝,6京东,7翼支付",
"refundFee": "退款手续费(分)"
}
}
POST 退款查询接口
Body 请求参数
{
"txnCode": "交易码:9176",
"agetId": "机构号",
"merId": "商户号",
"qcRefNo": "黔彩退款订单号",
"qcRefDate": "退款日期yyyyMMdd",
}
应答内容
{
"code": "请求状态:000000代表请求成功,其它均为请求失败,不能代表实际交易状态",
"msg": "响应描述",
"data": {
"merId": "(必填)商户号",
"qcRefNo": "(必填)黔彩退款订单号",
"qcOrderNo": "(必填)黔彩交易订单号",
"ispOrderNo": "(必填)渠道商退款订单号",
"refStatus": "(必填)退款状态:3退款失败 4退款成功 5退款中",
"refMsg": "退款状态描述",
"refAmt": "(必填)退款金额(分)",
"payChannel": "支付通道:2银联,3微信,4支付宝,6京东,7翼支付",
"refundFee": "退款手续费(分)"
}
}
POST 关单接口(关闭未完成交易的订单)
Body 请求参数
{
"txnCode": "交易码:9173",
"agetId": "机构号",
"merId": "商户号",
"qcOrderNo": "黔彩交易订单号"
}
应答内容
{
"code": "请求状态:000000代表请求成功,其它均为请求失败,不能代表实际交易状态",
"msg": "响应描述",
"data": {
"merId": "(必填)商户号",
"qcOrderNo": "(必填)黔彩交易订单号",
"closeStatus": "(必填)Number类型,关单状态:0失败,1成功",
"closeMsg": "关单状态描述"
}
}
POST 撤单接口(当交易发起异常时,通过此接口可撤销该笔交易)
Body 请求参数
{
"txnCode": "交易码:9174",
"agetId": "机构号",
"merId": "商户号",
"qcOrderNo": "黔彩交易订单号"
}
应答内容
{
"code": "请求状态:000000代表请求成功,其它均为请求失败,不能代表实际交易状态",
"msg": "响应描述",
"data": {
"merId": "(必填)商户号",
"qcOrderNo": "(必填)黔彩交易订单号",
"cancelStatus": "(必填)Number类型,撤单状态:0失败,1成功",
"cancelMsg": "撤单状态描述"
}
}
POST 对账单(对账明细格式
)
Body 请求参数
{
"txnCode": "交易码:9177",
"agetId": "机构号",
"merId": "商户号(可选)",
"txnDate": "交易日期,格式YYYYMMDD"
}
应答内容
{
"code": "请求状态:000000代表请求成功,其它均为请求失败",
"msg": "响应描述",
"data": "对账单明细,内容参考‘对账明细格式’"
}
POST 支付异步通知报文
公共请求头
属性 | 值 | 必传 | 备注 |
---|---|---|---|
Channel-Isp-Code | 渠道商编码 | 是 | 示例:GT |
Sign | 报文签名 | 是 | JSON串键值排序后进行签名,使用SHA256WithRSA算法 |
通知内容(先排序后RSA256加密)
{
"merId": "(必填)商户号",
"qcOrderNo": "(必填)黔彩交易订单号",
"txnStatus": "(必填)交易状态:0未支付,1支付中,2支付成功,3支付失败,4未知状态",
"txnMsg": "交易描述:支付失败时请注明失败原因",
"txnAmt": "(必填)交易金额(分)",
"netAmt": "(必填)实收金额(分)",
"custFee": "商户交易实收手续费(分)",
"ispOrderNo": "(必填)渠道商订单号",
"openId": "(必填)消费者openid,取我方公众号下对应的openid,支付宝为userId",
"payChannel": "(必填)支付通道:2银联,3微信,4支付宝,6京东,7翼支付",
"originOrderNo": "(支付成功时必填)第三方支付通道原始订单号(微信、支付宝、银联官方的订单号)",
"originTxnNo": "(支付成功时必填)第三方支付通道流水号(渠道商送给微信、支付宝、银联的订单号)",
}
应答内容
SUCCESS代表我方处理成功
FAIL代表我方处理失败
POST 退款异步通知报文
公共请求头
属性 | 值 | 必传 | 备注 |
---|---|---|---|
Channel-Isp-Code | 渠道商编码 | 是 | 示例:GT |
Sign | 报文签名 | 是 | JSON串键值排序后进行签名,使用SHA256WithRSA算法 |
通知内容(RSA256加密)
{
"merId": "(必填)商户号",
"qcRefNo": "(必填)黔彩退款订单号",
"qcOrderNo": "(必填)黔彩交易订单号",
"ispOrderNo": "(必填)渠道商退款订单号",
"refStatus": "(必填)退款状态:3退款失败 4退款成功",
"refMsg": "退款状态描述",
"refAmt": "(必填)退款金额(分)",
"payChannel": "支付通道:2银联,3微信,4支付宝,6京东,7翼支付",
"refundFee": "退款手续费(分)"
}
应答内容
SUCCESS代表我方处理成功
FAIL代表我方处理失败
对账明细格式
对账明细仅包含成功的交易,包括支付和退款两种交易类型,每个字段用”|”符号分隔,第一行为汇总行,下面为明细行,每一行结束使用\n换行符,报文格式如下:
账期|总笔数|交易总金额(分)
商户号|黔彩收银流水号|渠道商订单号|交易码|支付通道|交易金额(分)|交易日期|消费者OPENID
报文示例
20241227|38|199825
60000001024626|1810244567531393026|20241009SS1b63u2|9170|3|18500|20241227|oSBEb7b5_E8Dot18fBa0OLCgOCqQ
60000001024626|1810244567531393027|20241009SS1b63u3|9175|3|18500|20241227|oSBEb7b5_E8Dot18fBa0OLCgOCqQ
ENUM 交易码枚举{txnCode}
枚举 | 说明 | 备注 |
---|---|---|
9170 | 客户主扫支付(C扫B) | 商家展示返回电子收款码 |
9171 | 客户被扫支付(B扫C) | 客户出示付款码 |
9172 | 支付查询 | 无 |
9173 | 订单关闭 | 关闭未完成交易的订单 |
9174 | 订单撤销 | 全额退还当日发生的订单交易额 |
9175 | 退款 | 支持跨天退款、支付部分退款 |
9176 | 退款查询 | 无 |
9177 | 获取对账单 | 联机交易实时获取 |
9179 | C2B统一下单支付 | 直接返回原生渠道JS支付参数,由黔彩收银台统一拉起原生支付 |
ENUM 支付渠道枚举{payChannel}
枚举 | 说明 | 备注 |
---|---|---|
2 | 银联 | 云闪付 |
3 | 微信 | 无 |
4 | 支付宝 | 无 |
5 | 龙支付 | 无 |
6 | 京东 | 无 |
7 | 翼支付 | 无 |