# OTT订单直充接口
# 简介
# 业务介绍
合作方通过本文档的接口实现同步订单操作
# 接口定义
# 应用场景
合作方订单完成后调用本接口,通知爱奇艺为用户发放会员权益
# 接口域名
环境 | 域名 |
---|---|
生产 | openapi.vip.iqiyi.com |
测试 | test-openapi.vip.iqiyi.com |
# URL
/ott/subscribe.action
# 请求方式
参数 | 说明 |
---|---|
Method | GET/POST |
Content-Type | application/x-www-form-urlencoded |
# 请求参数
正式参数由爱奇艺商务提供,测试参数参见在线测试
序号 | 变量名 | 名称 | 是否必须 | 类型 | 说明 |
---|---|---|---|---|---|
1 | partner | 合作方编码 | 是 | String | 合作方的唯一标识 |
2 | signature | 签名串 | 是 | String | RSA签名,签名方法见RSA加密描述 |
3 | data | 内容 | 是 | String | 内容为json格式的字符串,并采用base64编码 |
# data参数
变量名 | 名称 | 是否必须 | 说明 |
---|---|---|---|
user_id | 用户唯一标识 | 2选1 | OTT的用户唯一标识,即给该用户开通权益(此项和mobile至少要填一个,两者不能都为空,如果两项都传,以user_id为准) |
mobile | 用户手机号 | 2选1 | 用户手机号(此项和user_id至少要填一个,两者不能都为空,如果两项都传,以user_id为准),建议不带国家代码 |
dev_mac | 设备MAC | 可选 | 设备MAC |
order_id | 订单号 | 是 | OTT的订单号(最长128字符) |
order_fee | 订单总价格 | 是 | 订单总金额=sum(total_fee),单位为“分”。爱奇艺侧会根据这个金额以及双方合同约定的结算方式和分成比例等,计算出结算价,用于双方最终结算。 |
order_products | 产品信息 | 是 | 要订购的产品信息,虽然参数体为数组,但只能传一个产品信息,如果传多个,将只取第一个 |
id | 包月产品或单点内容标识 | 是 | 包月产品或单点内容标识,双方约定配置,单点不能与包月订单产品ID相同(最长64字符) |
quantity | 购买数量 | 是 | 购买数量,OTT厂商只能填写1 |
cp_content_id | 内容ID | 单点产品必填 | 爱奇艺侧的内容ID,产品类型为单点时有意义 |
total_fee | 产品价格 | 是 | 产品价格,值必须大于0,否则认为无效订单(单位分) |
pay_time | 定单支付时间 | 是 | 定单支付UTC时间(单位秒) |
pay_code | 优惠资格标志 | 否 | 是否有优惠资格标志,如果没有,不需要传 |
tag | 标签信息json | 否 | 合作方侧用户标签,样例:{"tagNewUser":"Price",#标签1及对应的值"tagCityLevel":"highPrice" #标签2及对应的值 } |
fc | 爱奇艺内部渠道来源信息标识,非爱奇艺售卖渠道无需填写 | 否 | |
fr_version | 爱奇艺内部渠道收银台标识,非爱奇艺售卖渠道无需填写 | 否 |
# 请求示例
样例如下:
data:{
"user_id": "XXXXXXX" , #String类型,OTT的用户唯一标识,即给该用户开通权益,由32位或者64的字母和数字的组合(此项和mobile至少要填一个,两者不能都为空,如果两项都传,以user_id为准)。
"mobile": "138XXXXXXXX", #String类型,用户手机号(此项和user_id至少要填一个,两者不能都为空,如果两项都传,以user_id为准)
"dev_mac": "10:48:b1:00:ff:f3", #String类型,设备MAC(可选)
"order_id": "XXXXXX", #String类型,OTT的订单号(必填)。
"order_fee": 1000, #int类型,订单总价格=sum(total_fee),单位分,这个价格涉及到商务结算、双方分成(必填)
"order_products": [{
"id": "1001" , #String类型,包月产品或单点内容标识,双方约定配置,单点不能与包月订单产品ID相同(必填)
"quantity":1, #int类型,购买数量(必填,OTT厂商只能填写1)
"cp_content_id": "101" #String类型,爱奇艺侧的内容ID,产品类型为单点时有意义(单点产品必填)
"total_fee": 1500, #int类型,产品价格,值必须大于0,否则认为无效订单(单位分)(必填)
}],
"pay_time": 1369193066 #Long类型,定单支付UTC时间(单位秒)(必填)
"pay_code": "adakdj2n12344" #String类型,是否有优惠资格标志,如果没有,不需要传(非必填)
}
order_products数组说明:虽然参数体为数组,但只能传一个产品信息,如果传多个,将只取第一个。
# 返回参数
字段名 | 类型 | 说明 |
---|---|---|
data | String | 消息数据,内容为json格式的字符串,采用UrlBase64 Encode |
signature | String | 对上面data数据的签名,爱奇艺用RSA私钥进行签名,同时提供相应的public key给合作方用于验证 |
# 返回示例
data格式如下:
{
"err_code":200, #200代表成功,其他为错误码。 (int型)
"err_msg": "OK", #成功填:OK,其他情况填写具体的错误信息。 (String型)
"time":1369193070, #应答时间戳UTC时间(单位秒)。 (long型)
"gift_msg_code": "A00000",#状态码,是否可领取爱奇艺黄金会员,A00000表示可领取,并会返回领取url。 (String型)
"gift_receive_deadline":"",#领取当前订单的爱奇艺会员截止时间UTC时间(单位秒)。 (long型)
"gift_receive_url": "", #爱奇艺会员领取地址。 (String型)
"present_gold_vip": 1 #是否自动赠送了会员,0不赠送,1赠送黄金,2赠送星钻。 (int型)
}
# 返回码定义
返回码code | 描述 | 备注 |
---|---|---|
200 | 成功 | |
301 | 参数错误 | |
302 | RSA解密错误 | |
303 | RSA签名错误 | |
306 | 系统错误 | |
307 | 单点内容校验失败 | |
308 | 获取UID失败 | 建议重试 |
309 | 库存不足 | |
327 | 价格无效 | |
330 | 查询会员信息失败 | 建议重试 |
333 | 合作方用户没有可用的优惠资格 | |
335 | 优惠资格的产品与传入的不匹配 | |
336 | 产品的价格与传入的不匹配 | |
407 | 交易下单失败,系统在重试中 | 建议重试 |
# 注意事项
1、gift_msg_code、gift_receive_deadline、gift_receive_url、present_gold_vip四个字段,在只有支持赠送黄金会员的产品中(买奇异果会员赠送等时长黄金会员)且present_gold_vip=0时,才会返回。当present_gold_vip等于1时,不返回gift_msg_code、gift_receive_deadline、gift_receive_url。
2、订单同步时需要考虑失败订单重试机制,使网络问题或其他问题导致同步失败的订单能够再次同步到爱奇艺服务器。可先同步重试一到两次次,仍失败改为异步重试,重试的间隔时间可以逐步扩大(例如最多重试5次,间隔时间依次为1s,5s,30s,1m,3m),直至订单最终同步成功;如果重试多次后,订单同步依然失败,我方又未回调,就需要联系爱奇艺侧技术同学排查具体失败的原因。
# 附录
# 生成RSA key命令参考
openssl genrsa -out rsa_private_key.pem 1024
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt > pkcs8_rsa_private_key.pem
openssl rsa -in pkcs8_rsa_private_key.pem -pubout -out pkcs8_rsa_public_key.pem
# RSA加密描述
采用RSA计算签名,具体计算方法如下:
1、 调用接口时对data参数进行base64计算,得到base64编码处理的data;
2、 使用合作方私钥加密数据,生成signature;
# JAVA版本示例代码如下:
public class Test {
public static String content = "{\"user_id\":\"111\",\"order_id\":\"111\",\"order_fee\":1000,\"order_products\":[{\"id\":\"1001\",\"quantity\":1,\"cp_content_id\":\"101\",\"total_fee\":1500,}],\"pay_time\":1369193066}";
public static String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALbjYMJwIW+PcvzM+k4vSr09IEKbaXWam33bnqDO+Qsj4POHevGe/jvOuUI/bQn/jXD6WHMivk/N+9Q4firmU1IDRl6fZIA1rfn4S0j+U8Z4bIxFURaBfcrRkA8I3cEQnuCMoD3cJXYZ/uuWogFXzIkTipHj7KrbULIOiR2Zp7ePAgMBAAECgYBJFgi+6yyRdpQPLqMAx6logpr3wz+bvdNRsohr3wprR0VITOX21QDoSa6DKPGcQ0H02jaqnEHNhpWSs5jH8A9vU4XwprOLmo21GYqcnBGjMhZqrJ/IetATqoVBl41zfnsAzZwKQw4hZBsdnhQXMUwoCB4rdUyNMGV4itjhZgBm0QJBAOd7f5j7OAkwU/bfkEtacM1/emgZ5a0Ys0J5RcRPrbhkkwiePKr0O+TQ3tDTRT0NY26DWm/U5+OwKEGt/VyNBl0CQQDKQkcKl8l+Ds518DTj3V3Xj6y0glj6eMYNLIYqi0UPPqtY1ZvvU41FYaTCCZDx/hBQQoOkk0ouefVCqqQST/7bAkAlhXguVPJFUwcZKi3aeQN12+b8fs4i27Ea4ktzwbKYA/1tVTDiSQp4UX78fHJprgTjAfmjzO/1kTVFSC2cVeOlAkB78OFXvGvcs3YRD4FZoO1AiupqMvYThq7Wo9ITgARxsxWM+ljz719ChPNRdEs9/1I/3IKO9zMeB94jXC3uitbBAkEAiT0dweF9U/7OyE74DV5tHbVHWbqEMfIzZ+NzfVlXA91wcS6uAhovjOwCk1/ngToudUbLCazTkSOlWl1mhKBA+A==";
public static void main(String[] args) throws Exception {
String encodeData = Base64.encode(content.getBytes("UTF-8"));
System.out.println(encodeData);
String signature = RSASignature.sign(encodeData, privateKey, "utf-8");
System.out.println(signature);
}
}
# 加密工具类
# RSA加密
public class RSASignature{
public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
/**
* RSA签名
*
* @param content 待签名数据
* @param privateKey 商户私钥
* @param encode 字符集编码
* @return 签名值
*/
public static String sign(String content, String privateKey, String encode) {
String charset = "utf-8";
if (!StringUtils.isEmpty(encode)) {
charset = encode;
}
try {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64Util.decode(privateKey));
KeyFactory keyf = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyf.generatePrivate(priPKCS8);
java.security.Signature signature = java.security.Signature
.getInstance(SIGN_ALGORITHMS);
signature.initSign(priKey);
signature.update(content.getBytes(charset));
byte[] signed = signature.sign();
return Base64Util.encode(signed);
} catch (Exception e) {
log.error("[sign exception]", e);
}
return null;
}
}
# 返回数据
1、接口返回参数时解密数据:
public class Test{
String decodeData = new String(Base64.decode(data),"UTF-8");
}
# 在线测试
参数 | 值 | 备注 |
---|---|---|
partner | ott_test | |
id | t_prod_1 t_prod_month t_prod_season t_prod_year single | t_prod_1:奇异果天卡 t_prod_month:奇异果月卡 t_prod_season:奇异果季卡 t_prod_year:奇异果年卡 single:单点产品(cp_content_id=245025400) |
所需密钥:
参数 | 值 |
---|---|
合作方私钥 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALbjYMJwIW+PcvzM+k4vSr09IEKbaXWam33bnqDO+Qsj4POHevGe/jvOuUI/bQn/jXD6WHMivk/N+9Q4firmU1IDRl6fZIA1rfn4S0j+U8Z4bIxFURaBfcrRkA8I3cEQnuCMoD3cJXYZ/uuWogFXzIkTipHj7KrbULIOiR2Zp7ePAgMBAAECgYBJFgi+6yyRdpQPLqMAx6logpr3wz+bvdNRsohr3wprR0VITOX21QDoSa6DKPGcQ0H02jaqnEHNhpWSs5jH8A9vU4XwprOLmo21GYqcnBGjMhZqrJ/IetATqoVBl41zfnsAzZwKQw4hZBsdnhQXMUwoCB4rdUyNMGV4itjhZgBm0QJBAOd7f5j7OAkwU/bfkEtacM1/emgZ5a0Ys0J5RcRPrbhkkwiePKr0O+TQ3tDTRT0NY26DWm/U5+OwKEGt/VyNBl0CQQDKQkcKl8l+Ds518DTj3V3Xj6y0glj6eMYNLIYqi0UPPqtY1ZvvU41FYaTCCZDx/hBQQoOkk0ouefVCqqQST/7bAkAlhXguVPJFUwcZKi3aeQN12+b8fs4i27Ea4ktzwbKYA/1tVTDiSQp4UX78fHJprgTjAfmjzO/1kTVFSC2cVeOlAkB78OFXvGvcs3YRD4FZoO1AiupqMvYThq7Wo9ITgARxsxWM+ljz719ChPNRdEs9/1I/3IKO9zMeB94jXC3uitbBAkEAiT0dweF9U/7OyE74DV5tHbVHWbqEMfIzZ+NzfVlXA91wcS6uAhovjOwCk1/ngToudUbLCazTkSOlWl1mhKBA+A== |
← 激活码直充查询接口 OTT订单状态查询接口 →