生成下单参数与签名
收藏我的收藏
接口说明
使用说明
- •开发者按照文档说明构造data,其结果为string类型,且必须符合json格式
- •开发者基于data生成签名,并按文档说明构造byteAuthorization,其结果为string类型
data
属性名 | 类型 | 默认值 | 必填 | 说明 |
skuList | SkuList[] SkuList说明见下文 | 无 | 是 | 下单商品信息 注意:目前只支持传入一项 |
outOrderNo | string | 无 | 是 | 外部订单号 |
totalAmount | number | 无 | 是 | 订单总金额,单位参考 currency 字段说明,若有商家营销,则需要去除商家营销价格订单总金额 单位:分 |
currency | string | CNY | 否 | 枚举值说明:CNY-人民币(单位为分),DIAMOND-钻石(单位为钻石数量)。 iOS 必须指定 currency=DIAMOND(Android 可不传此字段)。 因钻石退款拆分问题,暂不支持一笔订单下多份商品(即skuList.quantity仅能为1) |
payExpireSeconds | number | 无 | 否 | 支付超时时间,单位秒,例如 300 表示 300 秒后过期;不传或传 0 会使用默认值 300,不能超过48小时。 |
payNotifyUrl | string | 无 | 否 | 支付结果通知地址,必须是 HTTPS 类型,传入后该笔订单将通知到此地址。 |
merchantUid | string | 无 | 否 | 开发者自定义收款商户号 |
orderEntrySchema | Schema Schema说明见下文 | 无 | 是 | 订单详情页 |
limitPayWayList | number[] | 无 | 否 | 屏蔽的支付方式,当开发者没有进件某个支付渠道,可在下单时屏蔽对应的支付方式。如:[1, 2]表示屏蔽微信和支付宝 枚举说明: 1-微信 2-支付宝 |
promotionId | string | 无 | 否 | 商家营销id,在tt.triggerRetentionPopup回调结果中返回的参数:promotionId |
SkuList
属性名 | 类型 | 默认值 | 必填 | 说明 |
skuId | string | 无 | 是 | 外部商品id,如:号卡商品id、会员充值套餐id、某类服务id、付费工具id等 |
price | number | 无 | 是 | 价格,与 data.totalAmount 单位保持一致,若有商家营销,则需要去除商家营销价格 |
quantity | number | 无 | 是 | 购买数量 0 < quantity <= 100 |
title | string | 无 | 是 | 商品标题,长度 <= 256字节 |
imageList | string[] | 无 | 是 | 商品图片链接,长度 <= 512 字节 注意:目前只支持传入一项 |
type | number | 无 | 是 | 商品类型,详见此处的商品类型枚举值 示例:
|
tagGroupId | string | 无 | 是 | 交易规则标签组 点此查看对应商品类型的标签组ID 注意:根据接入规范,选择适合的标签组ID传入,该标签组对应标签将会在用户收银台展示。 |
entrySchema | Schema Schema说明见下文 | 无 | 否 | 商品详情页链接 |
skuAttr | string | 无 | 否 | 商品信息:需要将不同商品类型定义的具体结构,转换成json string 号卡类商品必填,即当前商品类型 type in [101、102、103、104、105、106、107]的商品必填,内部结构请详见下文 ”skuAttr“ 小节说明。 示例:
内容消费:当前商品类型 type in [402、403、404、405、406]的商品必填,内部结构请详见下文 ”skuAttr“ 小节“内容消费”小节说明。 402 会员示例:
|
skuAttr
号卡类
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
package_cost | Json Object | 无 | 是 |
| 号卡商品套餐详情,具体字段说明详见下文”package_cost字段说明“小结 |
call_duration | number | 无 | 否,call_duration与traffic_bundle至少需要填一个 | 1000 | 套餐包含的通话时长,单位:分钟, |
traffic_bundle | number | 无 | 否 | 50 | 套餐包含的流量包大小,单位:G |
telecom_operator_type | string | 无 | 是 | official | 提供套餐的运营方性质,枚举值 official:官方 private:私营 |
package_cost字段说明
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
amount | number | 无 | 是 | 1450 | 套餐售价,单位:分 |
time_len | number | 无 | 是 | 2 | 套餐时长 |
unit | string | 无 | 是 | month | 时长单位,枚举值 year:年 month:月 day:天 |
内容消费
会员商品-402
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
member_name | string | 无 | 是 | 会员 | 会员名称 |
member_type | string | 无 | 是 | 会员类型 | 请固定填写 VIP(此字段用于未来扩展会员类型) |
benefit_time | object | 无 | 是 | 参考BenefitTime对象 | 会员对应的权益时长 |
虚拟币商品-403
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
coin_name | string | 无 | 是 | K币 | 虚拟币名称 |
coin_type | string | 无 | 是 | COIN | 请固定填写 COIN(此字段用于未来扩展虚拟币类型) |
amount | number | 无 | 是 | 10 | 虚拟币数量 |
can_expire | bool | FALSE | 否 | TRUE | 是否存在过期时间(如果存在过期时间,则一定要填写TRUE) |
benefit_time | object | 无 | 否 | 参考BenefitTime对象 | 如果can_expire=true时,对应权益时长 |
content_promotion_coins | list<object> | 无 | 否 | 参考ContentPromotionCoin对象 | 赠送的虚拟币,是一个列表,列表中的每个元素参考ContentPromotionCoin对象,如果无赠送虚拟币,不需要传递该值 |
ContentPromotionCoin
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
coin_name | string | 无 | 是 | K币 | 虚拟币名称 |
coin_type | string | 无 | 是 | COIN_GIFT | 目前请固定填写 COIN_GIFT(标识该虚拟币是赠送所得) |
amount | number | 无 | 是 | 10 | 虚拟币数量 |
can_expire | bool | FALSE | 否 | TRUE | 是否存在过期时间(如果存在过期时间,则一定要填写TRUE) |
benefit_time | object | 无 | 否 | 参考BenefitTime对象 | 如果can_expire=true时,对应权益时长 |
剧集商品-404
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
album_name | string | 无 | 是 | 权利游戏 | 剧名称 |
album_id | string | 无 | 是 | 7324950105420005914 | 剧ID(抖音的剧ID) |
episode_id_list | []string | 无 | 是 | [7324950105420005915,7324950105420005916] | 集ID列表(抖音的集ID) |
can_expire | bool | FALSE | 否 | TRUE | 是否存在过期时间(如果存在过期时 间,则一定要填写TRUE) |
benefit_time | object | 无 | 否 | 参考BenefitTime对象 | 如果can_expire=true时,对应权益时长 |
整剧商品-405
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
album_name | string | 无 | 是 | 权利游戏 | 剧名称 |
album_id | string | 无 | 是 | 7324950105420005914 | 剧ID(抖音的剧ID) |
can_expire | bool | FALSE | 否 | TRUE | 是否存在过期时间(如果存在过期时间,则一定要填写TRUE) |
benefit_time | object | 无 | 否 | 参考BenefitTime对象 | 如果can_expire=true时,权益时长 |
券商品-406
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
coupon_type | string | 无 | 是 | EPISODE_COUPON | 券类型,目前仅支持剧集券,请固定传 EPISODE_COUPON 值 |
episode_coupon_info | object | 无 | 当券类型为EPISODE_COUPON时,必填 | 参考EpisodeCouponInfo定义 | 剧集券的信息 |
can_expire | bool | FALSE | 否 | TRUE | 是否存在过期时间(如果存在过期时间,则一定要填写TRUE) |
benefit_time | object | 无 | 否 | 参考BenefitTime对象 | 如果can_expire=true时,权益时长 |
EpisodeCouponInfo
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
album_use_type | number | 无 | 是 | 1 | 剧集券 的使用范围 1-可兑换所有剧的剧集 2-仅支持兑换部分剧的剧集 |
episode_nums | number | 无 | 是 | 1 | 剧集券兑换的集数 1-表示可兑换1集 2-表示可兑换2集 |
part_albums | list<object> | 无 | 否 | 参考Album的定义 | 如果album_use_type=2,需要传递该值表明可在哪些剧进行兑换,列表长度不超过50,每一个元素是一个album结构 |
all_album_episode_range | string | 无 | 是 | 1-9999 | 可兑换集的范围(对全剧生效),如果全集可兑换,默认传1-9999即可 如果album_use_type=2,该值不会生效,传递1-9999作为默认即可 如果只能兑换第1集,传递1 如果可兑换1-3集,传递1-3 如果可兑换第1集和第3集,传递1|3 |
Album
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
album_id | string | 无 | 是 | 7262626 | 剧id |
episode_range | string | 无 | 是 | 1-9999 | 此剧可兑换集的范围(对单剧生效),如果全集可兑换,默认传1-9999即可 如果只能兑换第1集,传递1即可 如果可兑换1-3集,传递1-3 如果可兑换第1集和第3集,传递1|3 |
BenefitTime对象
属性名 | 类型 | 默认值 | 必填 | 示例值 | 说明 |
num_of_year | number | 0 | 是 | 1 | 1年会员 |
num_of_month | number | 0 | 是 | 2 | 2月会员 |
num_of_day | number | 0 | 是 | 3 | 3天会员 |
num_of_hour | number | 0 | 是 | 4 | 4小时会员 |
num_of_minute | number | 0 | 是 | 5 | 5分钟会员 |
备注:num_of_year , num_of_month , num_of_day ,num_of_hour,num_of_minute 只能有一个有效(只有一个值大于 0 的,另外四个为 0),不能同时按照年,月,日,小时,分钟定义权益时长,不然会报参数错误。
Schema:
名称 | 类型 | 是否必传 | 描述 | 正确示例 | 错误示例 |
path | string | 是 | 小程序xxx详情页跳转路径,没有前导的“/”,路径后不可携带query参数,路径中不可携带『?: & *』等特殊字符,路径只可以是『英文字符、数字、_、/ 』等组成,长度<=512byte | page/path/index | page/path/index?id=1234page/path:hello/index |
params | string | 否 | xx情页路径参数,自定义的json结构,内部为k-v结构,序列化成字符串存入该字段,平台不限制,但是写入的内容需要能够保证生成访问xx详情的schema能正确跳转到小程序内部的xx详情页,长度须<=512byte,params内key不可重复。 | '{"id":1234, "name":"hello"}' | {"id":1234,"id":2334,"name":hello} |
结果示例:
{"skuList":[{"skuId":"657","price":1,"quantity":1,"title":"test_title","imageList":["https://xxxx.com/xxxxx.jpg"],"type":401,"tagGroupId":"tag_group_7272625659888041996"}],"outOrderNo":"test_out_order_no","totalAmount":1,"payExpireSeconds":300,"orderEntrySchema":{"path":"","params":""},"payNotifyUrl":"https://xxxxx/xxx"}
byteAuthorization
推荐开发者基于代码示例进行开发。
第一步:构造待签名串。
待签名串一共有五行(且必须严格按照以下顺序),每一行必须以
\n
(换行符,ASCII 编码值为 0x0A)结束。POST\n/requestOrder\n请求时间戳\n请求随机串\ndata\n
第二步:计算签名值。
使用「应用私钥」对待签名串进行
SHA256-RSA2048
签名,并对签名结果进行Base64
编码得到签名值。第三步:生成 byteAuthorization。
以下为具体格式:
SHA256-RSA2048 应用appid,请求随机串nonce_str,请求时间戳timestamp,公钥版本key_version,签名值signature
示例:
SHA256-RSA2048 appid=tt8629f0941xxxxxxxx,nonce_str=7CC7D26A52F05BA5CFD,timestamp=1698916641,key_version=1,signature=jz1+pplJX2IRcS24lsdsFFOF7+Wky3KiQhOMUz58u7n5VEDNr/axPLzm8+i232Aw4vxjoxavI77n8iUSaGcjHOXS1e4pkdJEHE1qQkQ1bNGsT1X2yXOMB5t8gceGiBJimwQkTSeq+KK/OzYTlCRaNuE784Mx7wtUdXCnBDKZDMxWBxu6R7L2eZ8mC5v67lV+oJcu7tRD10PePRXH1z+9zaX1CSwEg3K0IkLFxHqgrpsER7OeVBE8w9glsxjvuc2Gg==
代码示例
JAVA
GO
PHP
import java.security.PrivateKey; import java.security.Signature; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.security.KeyFactory; import java.security.spec.PKCS8EncodedKeySpec; import java.util.UUID; public class DYSign { public static void main(String []args) { // 请求时间戳 long timestamp = System.currentTimeMillis()/1000L; // 开发者填入自己的小程序app_id String appId = "testAppId"; // 随机字符串 String nonceStr = UUID.randomUUID().toString(); // 应用公钥版本,每次重新上传公钥后需要更新,可通过「开发管理-开发设置-密钥设置」处获取 String keyVersion = "1"; // 应用私钥,用于加签 重要:1.测试时请修改为开发者自行生成的私钥;2.请勿将示例密钥用于生产环境;3.建议开发者不要将私钥文本写在代码中 String privateKeyStr = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZSHNcFfthd/bV\nYexEJWOBVEjjDcXjfr1fYevuraNFfMmLPKV836BbvCiUSWHzJYEpkJ934e/j28NB\nEcEbPDLiGlLTd6AVwR22TkUwpLr41oQprz0HKFwhVPZ0HQCGIv0pVMA53TFSitIq\niqbNLmgm5yzSNqNy1t/0X/RfqEtA6Eoxw9u/Sx57i+pBFuLlZYanlm57+b7t1khg\n9JHvF0ulo7DScyJ4qgrD7oQf0RIQB0rqCFIeYuYO1cfvnxb9x4DPodEyVoAM4i9Y\ndFop9ZHt73W/icuLku/P8/G1+arzB5b7S1S3ky5/KdS8AEA9Ww5czZcdf9Jgm2S6\nRymjFGjzAgMBAAECggEBAIryGNgdePyWcSJmHHR9a+CdFWD0aDBa/7CJpAN8VKc1\ngcB8Xgp+7+6X9jTM/EQa+CVEWrmiDgF/gVPnkyNsAzff4rqcEnoFzzglZSS9/lp4\nod7jYa+uTy1LxgflDkeJSfEASStqrT4EZpR3kNInQfQZ1BBNxQXhb6smm+9mL6kK\nQJjAqBgEqtUAmNv0GnH89ZPPgZuIZeeL4cb4BhMEoa5MBnI+HDf07cN1nECQXRJl\nHU/iyhAPfP7RpO8O9KGDEDE36qebu0Cu4yUjWANXiqECFv93sQzONotkl3VPealv\nXM+jzGT7YdgHo/t3QKE8flMBo/XUzGTqi8j5AOXiaBkCgYEAylKVtjQMYgg4qMwd\n9Je+KZ9qL6QVHCsB2NPUt8N99oj70efsG4aGaEAadr8meNhIJ5lpoK+FXqSBIbbD\nS/xeOVI3XoMx/EdKLw/ZNi87G/EHYK9z7Fr3W7q8DFXe2hZ/ojFXC/aaBanjVVBK\n/6RfPzXfnx+vGX/t1FhcLC+yQD8CgYEAwfMtrXfH+3dW77dxXT/CTFJVs/o1K2qb\nepnQ6A33KMHPLBtPZZ6z5rzIO7OMSNItOTXTEoRXHmOKc5FtXGtbCvGSRByb6FgD\nWG3p2Bp2sZLuJBzXmLbSnEbHTNHM6uTgxNgWAh8pYpjPY8xF7BqYz2rGT47OPBmc\ntRzDjnzjak0CgYAqkM1mk/S2+zvQZ4E14GblouBYPZEjZ/jvgUGTl9F8eL1iIAUQ\nlXDZpgLrULPrYLVtf101rTfF/Z4dVbIo3mOEc8OqYre1d9onpJHyUGWDL2Z59O/S\nniDEb7j4b2h/QZSArxi9L5if8GofnNDqj85qIg92Dthr6PpEXoKl2TMLSQKBgFzQ\nBHHYukiqSV4ZyRQ4qMBhPkYMXFlUgObgqMoDtN06MewHfa1BjxHCEYgQWfeXLLEO\nAt3/mrkeJWk8lLr/XOgVxkr17d34EFHG93rE3zwG9hMuAjZAdvT2IfWvCIL32GAa\nkB2fz+ww+D3nySY9bBcGH7R+wE6eaxF4nFSZizKZAoGBAJzuaWCnVK0djfgvUsjm\nGUtyDvgyREcpAsXvES1pB2NLVeEUxm0uRtj6k4DhCv3rJfUwfMr0+sa9NUnXuaSR\nVqLYvAD8bNPKXwn7ymzQ7WioCqmZuUhLnQRppkjhfQGKLH0MnMw9Xh9FwJ9kzGNE\nUnTEhaaHsoaHMlLlRET32gyG".replace("\n",""); // 生成好的data String data = "{\"skuList\":[{\"skuId\":\"1\",\"price\":9999,\"quantity\":1,\"title\":\"标题\",\"imageList\":[\"https://dummyimage.com/234x60\"],\"type\":401,\"tagGroupId\":\"idxxx\"}],\"outOrderNo\":\"1213\",\"totalAmount\":9999,\"limitPayWayList\":[],\"payExpireSeconds\":3000,\"orderEntrySchema\":{\"path\":\"page/index/index\",\"params\":\"{\\\"poi\\\":\\\"6601248937917548558\\\",\\\"aweme_useTemplate\\\":1}\"}}"; String byteAuthorization = getByteAuthorization(privateKeyStr, data, appId, nonceStr, timestamp, keyVersion); System.out.println(byteAuthorization); } public static String getByteAuthorization(String privateKeyStr, String data, String appId, String nonceStr, long timestamp, String keyVersion) { String byteAuthorization = ""; try { // 生成签名 String signature = getSignature(privateKeyStr, "POST", "/requestOrder", timestamp, nonceStr, data); // 构造byteAuthorization StringBuilder sb = new StringBuilder(); sb.append("SHA256-RSA2048 "). append("appid=").append(appId).append(","). append("nonce_str=").append(nonceStr).append(","). append("timestamp=").append(timestamp).append(","). append("key_version=").append(keyVersion).append(","). append("signature=").append(signature); byteAuthorization = sb.toString(); } catch (Exception ex) { ex.printStackTrace(); return ""; } return byteAuthorization; } public static String getSignature(String privateKeyStr, String method, String uri, long timestamp, String nonce, String data) throws Exception { String rawStr = method + "\n" + uri + "\n" + timestamp + "\n" + nonce + "\n" + data + "\n"; System.out.println(rawStr); Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(string2PrivateKey(privateKeyStr)); sign.update(rawStr.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(sign.sign()); } public static PrivateKey string2PrivateKey(String privateKeyStr) { PrivateKey prvKey = null; try { byte[] privateBytes = Base64.getDecoder().decode(privateKeyStr); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); prvKey = keyFactory.generatePrivate(keySpec); } catch (Exception ex) { ex.printStackTrace(); } return prvKey; } }
密钥生成
推荐使用签名调试工具
适用于JAVA(PKCS8格式)
非JAVA适用(PKCS1格式)
openssl genrsa -out private_key.pem 2048 rsa -in private_key.pem -pubout -out public_key.pem pkcs8 -topk8 -inform PEM -in private_key.pem -outform PEM -nocrypt -out rsa_private_key.pem rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem exit // 注:生成出来的私钥文件为rsa_private_key.pem, 公钥文件为rsa_public_key.pem // PKCS8格式,私钥以-----BEGIN PRIVATE KEY-----开头,-----END PRIVATE KEY-----结尾
应用公钥上传方式