# 附录

更新时间:2024-09-26 14:45:28

# 附录1、请求签名算法

所有由开发者发起到快手平台的接口请求,都需要使用开发者的 app_secret (opens new window) 签名,以保证请求来源和请求数据完整性。

# 如何计算请求具体的参数签名?

请求中参与签名的字段有两类:

  1. URL中的请求参数;
  2. 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\"}");

待签名字符串,生成签名代码和上述一样

其中有如下注意点:

  1. contract_info中的json字符串,需要按照template_type/withhold_amount/withhold_product/first_withhold_time的顺序排列
  2. 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&notify_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
}

具体的字段说明

字段名类型说明
datajson string业务状态和信息,具体内容参考具体流程的回调接口说明。
message_idstring当前回调消息的唯一ID,在同一个消息多次通知时,保持一致。
biz_typestring业务类型。取值如下:
PAYMENT - 支付
REFUND - 退款
SETTLE - 结算
WITHHOLD - 代扣
CONTRACT - 签解约
app_idstring当前小程序的AppID
timestampnumber流程变动的时间戳

签名方式

取出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 次,重试次数和初次发送回调消息时间的延迟关系如下:

重试次数距初次发送回调消息的延迟时间
110s
230s
31m
42m
53m
64m
75m
86m
97m
108m
119m
1210m
1311m
1412m
151h
162h

# 附录3、手续费及分佣金额计算规则

一笔订单在分账给商户之前,可能产生如下费用

费率类型是否必须收费条件
苹果通道费仅在苹果支付渠道下又苹果方收取,具体收费标准见:苹果价格档位信息 | 快手小程序文档
平台服务费快手平台收取,一般为2%,部分小程序可能有特殊配置
达人分销费订单又达人带货产生
服务商分销费小程序借助第三方服务商开发运营上线,收取服务商分销费

计算规则如下:

平台服务费 = floor((订单总金额-订单结算前已退款金额 - 苹果服务费)*平台服务费费率)

苹果通道费:在30%左右,固定数额,具体见:苹果价格档位信息 | 快手小程序文档 (opens new window)

达人分销费=floor((订单总金额-已退款金额 - 苹果服务费)*达人分销费率)

服务商分销费=floor((订单总金额-已退款金额 - 苹果服务费)*服务商分销费率)

即: 在计算服务费(单位:分)时,乘费率后的金额向下取整,即为该笔交易的手续费。

# 附录4、错误码和常见问题

# 错误码

错误码含义
10000011token 过期
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商家提现次数限制
10200501OAuth验签失败
10200502查询条件无效
10000681类目被封禁
10000682当前未到可结算时间,请在支付成功7日后发起结算
10000683订单未支付,无可结算金额
10000684该单已处理完成
10000685当前订单未到可结算时间,请在核销后3天发起结算。如满足结算条件请查看商户是否被处罚。
10000686当前有正在结算中的订单
10000687当前有正在退款中的订单
10000689保证金账户余额不足
10000690保证金账户不存在
Copyright ©2024, All Rights Reserved