# 附录

# 附录1、请求签名算法

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

具体的参数签名方式如下:

  1. 参加签名的字段:① URL中的请求参数;② POST 的 body 参数字段。以下两种字段不参与签名:
  • 特定字段名:sign、access_token。
  • 值为空的字段。

将第1步收集的字段(K)和对应的字段值(V),放到 Map<K, V> 中,并按照字段的 ASCII 码升序排列,按照下述方式连接,组装成参数字符串:

将开发者的 app_secret 拼接在参数字符串后面,使用MD5计算。

# 请求示例(如何获取签名字段)

请求信息:

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&notify_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 -> !Strings.isNullOrEmpty(item.getValue().toString()))
            .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);
    }

# 附录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-支付
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、手续费/分佣金额计算规则

平台服务费 = floor((订单总金额-已退款金额)*0.02),平台服务费后续将根据商品类目调整。

平台服务费计算需要根据当前用户可操作金额(订单总金额-已退款或结算金额)计算,对乘费率后的金额向下取整并保留两位小数,即为该笔交易的手续费。
平台服务费会在调用结算时扣减,若该笔交易在结算后产生退款等行为,平台服务费不会回退。

服务商分佣金额以及达人分佣金额,均按照:当前用户可操作金额*分佣比例后,向下取整并保留两位小数 的规则计算。

# 附录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 保证金账户不存在
MD5(参数字符串 + app_secret)
K1=V1&K2=V2&...&Kn=Vn // 参数字符串
Copyright ©2023, All Rights Reserved