开发 服务端 担保支付(单次支付) 附录
# 附录
更新时间:2024-09-26 14:45:28
# 附录1、请求签名算法
所有由开发者发起到快手平台的接口请求,都需要使用开发者的 app_secret (opens new window) 签名,以保证请求来源和请求数据完整性。
# 如何计算请求具体的参数签名?
请求中参与签名的字段有两类:
- URL中的请求参数;
- POST 的 body 参数字段。
注:以下两种字段不参与签名:
- 特定字段名:sign、access_token。
- 值为空的字段
将上述收集的字段(K)和对应的字段值(V),放到 Map<K, V> 中,并按照字段的 ASCII 码升序排列,按照下述方式连接,组装成参数字符串:
注:最后需要将开发者的 app_secret 拼接在参数字符串后面,使用MD5计算。
app_secret获取路径如下
# 请求示例
# 担保支付预下单
请求信息:
curl --location --request POST 'https://open.kuaishou.com/openapi/mp/developer/epay/create_order?app_id=ks707065143182423884&access_token=ChFvYXV0aC5hY2Nlc3NUb2tlbhIw0FKGX_0HpB-TDCypkcRcw3o_KHSda-Vy5hK89LPgfTa_XBClfLUgotP1YGpX0lD9hTzMiIHyaI8naVBqJZm6kCdtxZB7IBIpdenq8m9SqxQ1Py7U0KAUwAQ'
--header 'Content-Type: application/json'
--data-raw '{
"open_id":"5b748c61ef2901405450656638e8f702d3",
"out_order_no":"kdj1231113454676",
"total_amount":100,
"subject":"肯德基10元代金券",
"type":1,
"detail":"详情介绍",
"expire_time":3600,
"notify_url":"https://xxxx.kuaishou.com/zeus/epay/notify",
"sign":"dfb2a4b482d4f9a0cb4a60ad7fbe839e"
}'
签名参数信息:
Map<String, Object> signParamMap = new HashMap();
signParamMap.put("app_id", "ks707065143182423884");
signParamMap.put("open_id", "5b748c61ef2901405450656638e8f702d3");
signParamMap.put("out_order_no", "kdj1231113454676");
signParamMap.put("total_amount", 100);
signParamMap.put("subject", "肯德基10元代金券");
signParamMap.put("type", 1);
signParamMap.put("detail", "详情介绍");
signParamMap.put("expire_time", 3600);
signParamMap.put("notify_url", "https://xxxx.kuaishou.com/zeus/epay/notify");
待签名字符串:
app_id=ks707065143182423884&detail=详情介绍&expire_time=3600¬ify_url=https://xxxx.kuaishou.com/zeus/epay/notify&open_id=5b748c61ef2901405450656638e8f702d3&out_order_no=kdj1231113454676&subject=肯德基10元代金券&total_amount=100&type=1your_app_secret
签名示例代码(Java)
/**
* 小程序 app_secret
*/
private static final String APP_SECRET = "your_app_secret";
/**
* 获取参数 Map 的签名结果
*
* @param signParamsMap 含义见上述示例
* @return 返回签名结果
*/
public static String calcSign(Map<String, Object> signParamsMap) {
// 去掉 value 为空的
Map<String, Object> trimmedParamMap = signParamsMap.entrySet()
.stream()
.filter(item -> StringUtils.isNotBlank(item.getKey()) && ObjectUtils.isNotEmpty(item.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// 按照字母排序
Map<String, Object> sortedParamMap = trimmedParamMap.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldValue, newValue) -> oldValue, LinkedHashMap::new));
// 组装成待签名字符串。(注,引用了guava工具)
String paramStr = Joiner.on("&").withKeyValueSeparator("=").join(sortedParamMap.entrySet());
String signStr = paramStr + APP_SECRET;
// 生成签名返回。(注,引用了commons-codec工具)
return DigestUtils.md5Hex(signStr);
}
# 支付并签约预下单
请求信息
curl --location --request POST 'https://open.kuaishou.com/openapi/mp/developer/epay/create_contract_order?app_id=ks707065143182458884&access_token=ChFvYXV0aC5hY2Nlc3NUb2tlbhJQj7WUW3mucBZlklkzQT1nZ3g-u0129JVjCDj-H68v__XCG_u1tKpvETKfwCSVxJotPH_omhiHHbax-XAlebkX1f293Un9KHxWB2k3lBICFocaEngsUCp0IQD00y-GC9Pzapqg_iIgb0ga72EMrnCLrIbzwOewukr0fySa1lMFsjOJhI8VXTgoDzAB' \
--header 'Content-Type: application/json' \
--data '{
"open_id": "5b748c61ef290140c0656638eaa0d69c",
"out_order_no": "1703147868993contractDemo3",
"total_amount": "1",
"subject": "自动续费VIP",
"type": "89999",
"detail": "签约",
"expire_time": "300",
"attach": "",
"pay_notify_url": "https://www.abidu.com/zeus/epay/notify",
"contract_notify_url": "https://www.abidu.com/contract_notify",
"withhold_notify_url": "https://www.abidu.com/withhold_notify",
"goods_detail_url": "1222",
"contract_info": {
"withhold_amount": 1,
"template_type": 2,
"withhold_product": "ks_vip_card",
"first_withhold_time":1704274954000
},
"provider": {
"provider_channel_type": "NORMAL",
"provider": "ALIPAY"
},
"sign": "5a5546dd7b1fbb13ded3b86a901647f9"
}'
签名参数信息
Map<String, Object> signParamMap = new HashMap();
signParamMap.put("app_id", "ks707065143182423884");
signParamMap.put("open_id", "5b748c61ef290140c0656638eaa0d69c");
signParamMap.put("out_order_no", "1703147868993contractDemo3");
signParamMap.put("total_amount", 1);
signParamMap.put("subject", "自动续费VIP");
signParamMap.put("type", 89999);
signParamMap.put("detail", "签约");
signParamMap.put("expire_time", 300);
signParamMap.put("attach", "");
signParamMap.put("pay_notify_url", "https://xxxx.kuaishou.com/zeus/epay/notify");
signParamMap.put("contract_notify_url", "https://www.abidu.com/contract_notify");
signParamMap.put("withhold_notify_url", "https://www.abidu.com/withhold_notify");
signParamMap.put("goods_detail_url", "1222");
signParamMap.put("contract_info", "{\"template_type\":2,\"withhold_amount\":1,\"withhold_product\":\"ks_vip_card\",\"first_withhold_time\":1704274954000}");
signParamMap.put("provider", "{\"provider\": \"ALIPAY\",\"provider_channel_type\":\"NORMAL\"}");
待签名字符串,生成签名代码和上述一样
其中有如下注意点:
- contract_info中的json字符串,需要按照template_type/withhold_amount/withhold_product/first_withhold_time的顺序排列
- provider中的json字符串,需要按照provider/provider_channel_type的顺序排列
app_id=ks707065143182423884&contract_info={"template_type":2,"withhold_amount":1,"withhold_product":"ks_vip_card","first_withhold_time":1704274954000}&contract_notify_url=https://www.abidu.com/contract_notify&detail=签约&expire_time=300&goods_detail_url=1222&open_id=5b748c61ef290140c0656638eaa0d69c&out_order_no=1703147868993contractDemo3&pay_notify_url=https://xxxx.kuaishou.com/zeus/epay/notify&provider={"provider": "ALIPAY","provider_channel_type":"NORMAL"}&subject=自动续费VIP&total_amount=1&type=89999&withhold_notify_url=https://www.abidu.com/withhold_notifyyour_app_secret
# 苹果支付预下单
请求信息
curl --location --request POST 'https://open.kuaishou.com/openapi/mp/developer/epay/iap/create_order?app_id=ks707065143182458884&access_token=ChFvYXV0aC5hY2Nlc3NUb2tlbhJQcpTii72q9RcEb4iBAo2sQ-MNDRv4ksDSV9sA-u6yX_8BdNJRWHQxFXjdzlVph-8MGCWrzxQylHXsI6zKUKANl7tzt3O10ZK-O4UmLXmyQqcaEqr20o3-d0Tt4_ZJzk9p2G6vnyIg-5C3geR6MIqH53T7CrvtiC9nkZvAR4VU0J8kynIpi-koDzAB' \
--header 'Content-Type: application/json' \
--data-raw '{
"open_id":"5b748c61ef290140c0656638eaa0d69c",
"out_order_no":"testiap00006",
"subject":"测试描述测试iap",
"detail":"测试描述测试iap详情",
"type":"74",
"order_amount":100,
"user_pay_amount":100,
"goods_id":"1",
"goods_detail_url":"/page/index/index",
"expire_time":"300",
"attach":"iap支付demoiap支付demoiap支付demo",
"notify_url":"https://qa-mp.test.kuaishou.com/zeus/epay/notify",
"refund_notify_url": "https://qa-mp.test.kuaishou.com/zeus/epay/notify",
"sign":12333
}'
签名参数信息
Map<String, Object> signParamMap = new HashMap();
signParamMap.put("app_id", "ks707065143182423884");
signParamMap.put("open_id", "5b748c61ef290140c0656638eaa0d69c");
signParamMap.put("out_order_no", "testiap00006");
signParamMap.put("order_amount", 100);
signParamMap.put("user_pay_amount", 100);
signParamMap.put("subject", "测试描述测试iap");
signParamMap.put("type", 74);
signParamMap.put("goods_id", "1");
signParamMap.put("detail", "测试描述测试iap详情");
signParamMap.put("expire_time", 300);
signParamMap.put("attach", "iap支付demoiap支付demoiap支付demo");
signParamMap.put("goods_detail_url", "/page/index/index");
signParamMap.put("notify_url", "https://qa-mp.test.kuaishou.com/zeus/epay/notify");
signParamMap.put("refund_notify_url", "https://qa-mp.test.kuaishou.com/zeus/epay/notify");
待签名字符串如下,生成签名代码和上述一样
app_id=ks707065143182423884&attach=iap支付demoiap支付demoiap支付demo&detail=测试描述测试iap详情&expire_time=300&goods_detail_url=/page/index/index&goods_id=1¬ify_url=https://qa-mp.test.kuaishou.com/zeus/epay/notify&open_id=5b748c61ef290140c0656638eaa0d69c&order_amount=100&out_order_no=testiap00006&refund_notify_url=https://qa-mp.test.kuaishou.com/zeus/epay/notify&subject=测试描述测试iap&type=74&user_pay_amount=100your_app_secret
# 附录2、回调方式和策略
在支付、退款、结算流程中,如果流程结束,小程序平台通过POST方式回调开发者提供的URL地址,以通知开发者流程状态和信息。请求内容使用app_secret 签名,需要开发者对接收到的请求内容验签。
# 请求的内容
header
参数 | 描述 | 示例 |
kwaisign | 对本次请求的签名 | e10adc3949ba59abbe56e057f20f883e |
body
{
"data": {
"xxxx1": "0007",
"xxxx2": 1234,
"xxxx3": "this is some attach",
},
"message_id": "61901a3a-2b1f-40b6-af14-de34660d7541",
"biz_type": "REFUND",
"app_id": "ks656399649443988986",
"timestamp": 1625728322061
}
具体的字段说明
字段名 | 类型 | 说明 |
data | json string | 业务状态和信息,具体内容参考具体流程的回调接口说明。 |
message_id | string | 当前回调消息的唯一ID,在同一个消息多次通知时,保持一致。 |
biz_type | string | 业务类型。取值如下: PAYMENT - 支付 REFUND - 退款 SETTLE - 结算 WITHHOLD - 代扣 CONTRACT - 签解约 |
app_id | string | 当前小程序的AppID |
timestamp | number | 流程变动的时间戳 |
签名方式
取出http body中的原始字符串拼接app_secret,然后使用MD5进行签名:MD5(${http_body_string} + ${app_secret})
签名示例
请求如下:
curl --location --request POST
'https://yourdomain.com/kuaishou/pay/event_push' \
--header 'kwaisign: e10adc3949ba59abbe56e057f20f883e' \
--header 'Content-Type: application/json' \
--data-raw '{"data":{"out_refund_no":null,"settle_amount":null,"channel":"WECHAT","out_order_no":"2021091314414946589","out_settle_no":null,"refund_amount":null,"attach":"自定义消息","status":"SUCCESS"},"biz_type":"PAYMENT","message_id":"76a50e0c-a843-492b-9bc6-463c1b178a9c","app_id":"ks696650570360602063","timestamp":1631515320564}'
签名验证:
appSecret = "Xgm23lSgws235hlgK"; // 小程序密钥
toSignStr = ${http_body} + ${appSecret} // 待签名字符串
即:toSignStr = {"data":{"out_refund_no":null,"settle_amount":null,"channel":"WECHAT","out_order_no":"2021091314414946589","out_settle_no":null,"refund_amount":null,"attach":"自定义消息","status":"SUCCESS"},"biz_type":"PAYMENT","message_id":"76a50e0c-a843-492b-9bc6-463c1b178a9c","app_id":"ks696650570360602063","timestamp":1631515320564}Xgm23lSgws235hlgK
sign = MD5(toSignStr) // 签名结果
boolean result = sign.equals(kwaisign); // 验证是否相同
# 开发者返回
开发者在接收到回调消息,并正确处理后,需要返回以下内容格式,以通知小程序平台不再持续回调:
{
"result" : 1, //必填。 1-成功,其他-失败。失败小程序平台会尝试重推此消息
"message_id" : "ChFvYXV0aC5hY2Nlc3NUb2tlbhJQvpR51x8In46B1sDB" //当前消息的message_id
}
如果开发者没有返回或者返回的result不等于1,则小程序平台会尝试重复推送此消息,开发者务必做好消息的幂等处理!
# 回调重试策略
对于未收到成功返回的通知,消息最大重试次数为 16 次,重试次数和初次发送回调消息时间的延迟关系如下:
重试次数 | 距初次发送回调消息的延迟时间 |
1 | 10s |
2 | 30s |
3 | 1m |
4 | 2m |
5 | 3m |
6 | 4m |
7 | 5m |
8 | 6m |
9 | 7m |
10 | 8m |
11 | 9m |
12 | 10m |
13 | 11m |
14 | 12m |
15 | 1h |
16 | 2h |
# 附录3、手续费及分佣金额计算规则
一笔订单在分账给商户之前,可能产生如下费用
费率类型 | 是否必须 | 收费条件 |
苹果通道费 | 否 | 仅在苹果支付渠道下又苹果方收取,具体收费标准见:苹果价格档位信息 | 快手小程序文档 |
平台服务费 | 是 | 快手平台收取,一般为2%,部分小程序可能有特殊配置 |
达人分销费 | 否 | 订单又达人带货产生 |
服务商分销费 | 否 | 小程序借助第三方服务商开发运营上线,收取服务商分销费 |
计算规则如下:
平台服务费 = floor((订单总金额-订单结算前已退款金额 - 苹果服务费)*平台服务费费率)
苹果通道费:在30%左右,固定数额,具体见:苹果价格档位信息 | 快手小程序文档 (opens new window)
达人分销费=floor((订单总金额-已退款金额 - 苹果服务费)*达人分销费率)
服务商分销费=floor((订单总金额-已退款金额 - 苹果服务费)*服务商分销费率)
即: 在计算服务费(单位:分)时,乘费率后的金额向下取整,即为该笔交易的手续费。
# 附录4、错误码和常见问题
# 错误码
错误码 | 含义 |
10000011 | token 过期 |
10000200 | 参数有误,需要检查参数是否为空或者格式错误。 |
10000302 | 请求频率太快,被限速。 |
10000303 | 命中反垃圾策略 |
10000500 | 系统故障 |
10000501 | 稍后重试 |
10000601 | 订单不存在 |
10000602 | 订单信息不一致 |
10000603 | 订单过期 |
10000604 | 订单状态不正确 |
10000605 | 回调地址异常 |
10000606 | 接口参数签名错误 |
10000607 | 不合理的订单金额 |
10000608 | 不支持的service_id |
10000609 | 非法的orderInfo |
10000610 | 重复下单 |
10000611 | 查询订单信息失败 |
10000612 | 费用信息错误 |
10000621 | 支付中台验签失败 |
10000623 | 校验订单金额失败 |
10000624 | 支付订单回调失败 |
10000625 | 退款回调消息解析失败 |
10000626 | 结算回调消息解析失败 |
10000627 | 退款调用异常 |
10000628 | 结算调用异常 |
10000631 | 配置错误 |
10000632 | 内部错误 |
10000633 | 支付中心异常 |
10000634 | 发送webhook失败 |
10000641 | 帐户id已绑定到别的小程序 |
10000642 | 帐户已绑定进件人,无法重复绑定 |
10000643 | 帐户尚未绑定进件人 |
10000644 | 回调域名设置有误 |
10000645 | 商家提现次数限制 |
10200501 | OAuth验签失败 |
10200502 | 查询条件无效 |
10000681 | 类目被封禁 |
10000682 | 当前未到可结算时间,请在支付成功7日后发起结算 |
10000683 | 订单未支付,无可结算金额 |
10000684 | 该单已处理完成 |
10000685 | 当前订单未到可结算时间,请在核销后3天发起结算。如满足结算条件请查看商户是否被处罚。 |
10000686 | 当前有正在结算中的订单 |
10000687 | 当前有正在退款中的订单 |
10000689 | 保证金账户余额不足 |
10000690 | 保证金账户不存在 |