普通小游戏开发
API
开放能力
数据分析
视频拍摄器
多端支持

服务端回调

开发者需要在开发者平台支付页面设置订单回调相关配置;

⚠️ 如遇扣款成功但却不回调或回调信息不全等问题,请在 tt.requestGamePayment 接口确认是否 customId 和 extraInfo 字段是否填写,强烈建议必填这两个字段,请参考tt.requestGamePayment

回调地址填写

调试服务端回调分为两步:

  1. 验证游戏开发者回调接口的可访问性
  2. 调试正式服务端支付回调

开发者需要在开发者平台的 支付页面 中填写回调 http 地址和 token。

对于开发者填写的回调地址,平台会向其进行验证,需回传平台发送的 echostr 字符串完成验证

接口可访问性验证

开发者服务端通过以下逻辑校验请求是否来源于字节小程序服务端,并返回 echostr 字段

  1. token 开发者自己保存,也可以通过开发者平台查询到
  2. timestamp 是开发者平台服务端请求开发者的回调地址 body 里面的参数
  3. nonce 是开发者平台服务端请求开发者的回调地址 body 里面的参数
  4. msg 是开发者平台服务端请求开发者的回调地址 body 里面的参数
  5. echostr 是开发者平台服务端请求开发者的回调地址 body 里面的参数
  6. 开发者用 token,timestamp,nonce,msg 计算签名 signature(拼接这些参数前需要对这些参数按照字符串自然大小进行排序),和开发者平台服务端请求开发者的回调地址 body 里面的 signature 对比,如果一致,说明请求来自于开发者服务端。此时返回 echostr 完成验证,示例代码如下:

golang 示例

sortedString := make([]string, 4)
sortedString = append(sortedString, token)
sortedString = append(sortedString, timestamp)
sortedString = append(sortedString, nonce)
sortedString = append(sortedString, msg)
//需要对这些参数按字符串自然大小进行排序
sort.Strings(sortedString)
//使用SHA1算法
h := sha1.New()
h.Write([]byte(strings.Join(sortedString, "")))

bs := h.Sum(nil)
_signature := fmt.Sprintf("%x", bs)

// signature 一致表示请求来源于 字节小程序服务端
if _signature == Signature {
    return echostr
}
return nil

nodejs 示例(Koa)

// http handler GET
// function handler(ctx) {
    const { signature, timestamp, msg: '', nonce, echostr } = ctx.query;
    //需要对这些参数按字符串自然大小进行排序
    const strArr = [token, timestamp, nonce, msg].sort();
    const str = strArr.join('');
    //使用SHA1算法生成signature
    const _signature = require('crypto').createHash('sha1').update(str).digest('hex');

    // signature 一致表示请求来源于 字节小程序服务端
    if (_signature === signature) {
        ctx.body = echostr;
        return;
    }

    ctx.body = '';
// }

java 示例

        //创建list
        List<String> sortedString = new ArrayList<>();
        sortedString.add(token);
        sortedString.add(timestamp);
        sortedString.add(nonce);
        sortedString.add(msg);
        //按字符串自然大小排序
        Collections.sort(sortedString);
        //拼接字符串
        String joinStr = String.join("", sortedString);
        //用sha1算法进行加密
        MessageDigest md = MessageDigest.getInstance("SHA1");;
        //获取密文  (完成摘要计算)
        byte[] b = md.digest(joinStr.getBytes());
        //将密文转换成十六进制字符串(小写)
        String s = DatatypeConverter.printHexBinary(b).toLowerCase();
        if (s.equals(signature)) {
           //signature 一致表示请求来源于 字节小程序服务端
            return echostr;
        }else {
            return null;
        }

地址可访问性校验通过之后,游戏侧线上支付订单信息会通过填写的回调地址进行回调

tips

开发者自定义的订单号和额外参数字段仅在基础库 1.55.0+ 版本支持,低版本的客户端发生支付行为后,服务端回调接口并不会携带对应的 cp_ordernocp_extra,低于 1.55.0+ 的支付行为要保留原先的对账逻辑。

正式订单回调接口示例

当订单成功支付之后,服务端会通过 post 方式回调开发者提供的 http 接口,使用 token 进行校验。回调信息包括

  • 小程序 appid
  • 开发者自定义的订单号
  • 开发者传的额外参数

golang 示例

type OrderSuccessPayInfo struct {
   Appid         string `json:"appid"`       // 小程序appid
   CpOrderNo     string `json:"cp_orderno"`  // 开发者自定义订单号
   CpExtra       string `json:"cp_extra"`    // 开发者传的额外参数
}

type OrderInfo struct {
   Timestamp    string `json:"timestamp"`    // 时间戳
   Nonce        string `json:"nonce"`        // 随机数
   Msg          string `json:"msg"`          // 包体
   Signature    string `json:"signature"`// 根据token生成的签名
}

func TestPaySuccessCallback(c *gin.Context) {
   logs.CtxInfo(c, "TestPaySuccessCallback: request = [%+v]", c.Request)
   var oi OrderInfo
   err := c.BindJSON(&oi)
   if err != nil {
      logs.CtxError(c, "TestPaySuccessCallback: request = [%+v], err = [%v]", c.Request, err)
      retJSON(c, map[string]string{"status" : "unsuccess"})
      return
   }
   logs.CtxInfo(c, "TestPaySuccessCallback: request = [%+v], oi = [%+v]", c.Request, oi)


   var ospi OrderSuccessPayInfo
   err = json.Unmarshal([]byte(oi.Msg), &ospi)
   if err != nil {
      logs.CtxError(c, "TestPaySuccessCallback: m = [%+v], err = [%v]", oi, err)
      retJSON(c, map[string]string{"status" : "fail"})
   }
   logs.CtxInfo(c, "TestPaySuccessCallback: oi = [%+v], ospi = [%+v]", oi, ospi)
   retJSON(c, map[string]string{"status" : "success"})
}

nodejs 示例(Koa)

// http handler POST content-type: application/json
// function handler(ctx) {
   const { signature, timestamp, msg: '', nonce, echostr } = ctx.request;
   const strArr = [token, timestamp, nonce, msg].sort();
   const str = strArr.join('');
   const _signature = require('crypto').createHash('sha1').update(str).digest('hex');

   // signature 一致表示请求来源于 字节小程序服务端
   if (_signature !== signature) {
      ctx.body = '';
      return;
   }

   // ...... 处理量msg业务逻辑

   ctx.body = echostr;
// }

验证请求方法

根据 token,查看生成的 bs 是否与小程序服务端返回的 MsgSignature 相同

sortedString := make([]string, 4)
sortedString = append(sortedString, token)
sortedString = append(sortedString, timestamp)
sortedString = append(sortedString, nonce)
sortedString = append(sortedString, string(msg))
sort.Strings(sortedString)

h := sha1.New()
h.Write([]byte(strings.Join(sortedString, "")))

bs := h.Sum(nil)

queryPayState

根据 access_token 和自定义订单号 orderno 查询订单支付状态。建议开发者接入此接口,因为回调可能由于网络异常等原因导致无法 100%触达,此时开发者应回查订单状态,防止超发漏发。

接口地址

Get https://developer.toutiao.com/api/apps/game/payment/queryPayState

输入

名称描述
access_token小程序 access_token,获取方法
ordernocp 自定义订单号,在下单的时候传入的,参考下单接口 requestGamePayment中的 customId

输出

名称描述
status支付状态。success 表示支付成功,unsuccess 表示支付失败
点击纠错
评价此篇文档