# 内容购买下单接口
# 简介
# 业务介绍
合作方通过本文档的接口实现同步订单操作,购买内容权益。
# 接口定义
# 应用场景
合作方订单完成后调用本接口,通知爱奇艺发放权益。
# 接口域名
环境 | 域名 |
---|---|
生产 | openapi.vip.iqiyi.com |
测试 | test-openapi.vip.iqiyi.com |
# URL
/content/subscribe
# 请求方式
参数 | 说明 |
---|---|
Method | POST |
Content-Type | application/x-www-form-urlencoded |
# 请求参数
变量名 | 名称 | 是否必须 | 类型 | 说明 |
---|---|---|---|---|
encryptContent | 加密后的业务参数 | 是 | String | 由业务参数 AES加密得到,具体过程见AES+RSA加密描述 |
encryptAesPassword | 加密后的AES秘钥 | 是 | String | RSA加密AES随机秘钥等到生成具体过程见AES+RSA加密描述 |
partnerNo | 合作方编码 | 是 | String | 合作方的唯一标识 |
# data参数
变量名 | 名称 | 是否必须 | 说明 |
---|---|---|---|
userId | 用户唯一标识 | 3选1 | OTT的用户唯一标识,即给该用户开通权益,由32位或者64的字母和数字的组合(userId和mobile,openid至少要填一个,使用的优先级user_id,openId, mobile 依次递减) |
mobile | 用户手机号 | 3选1 | 用户手机号(user_id和mobile,openid至少要填一个,使用的优先级userId,openId, mobile 依次递减) |
openid | 系统用户id | 3选1 | 合作方系统用户id (userId和mobile,openid至少要填一个,使用的优先级user_id,openId, mobile 依次递减) |
partnerOrderCode | 订单号 | 是 | 合作方的订单号 |
orderFee | 订单总价格 | 是 | 订单总价格=sum(totalFee),单位分,这个价格涉及到商务结算、双方分成 |
partnerProductCode | 包月产品或单点内容标识 | 是 | 包月产品或单点内容标识,双方约定配置,单点不能与包月订单产品ID相同 |
cpContentId | 内容ID | 单点产品必填 | 爱奇艺侧的内容ID,(对应其他接口的aid),产品类型为单点时有意义 |
totalFee | 产品价格 | 是 | 产品价格,值必须大于0,否则认为无效订单(单位分) |
payTime | 定单支付时间 | 是 | 定单支付UTC时间(毫秒) |
fc | 爱奇艺内部渠道来源信息标识,非爱奇艺售卖渠道无需填写 | 否 | |
fr_version | 爱奇艺内部渠道收银台标识,非爱奇艺售卖渠道无需填写 | 否 |
# 业务参数
样例如下:
{
"userId": "XXXXXXX" , #String类型,OTT的用户唯一标识,即给该用户开通权益,由32位或者64的字母和数字的组合(userId和mobile,openid至少要填一个,使用的优先级user_id,openId, mobile 依次递减)。
"mobile": "138XXXXXXXX", #String类型,用户手机号(user_id和mobile,openid至少要填一个,使用的优先级userId,openId, mobile 依次递减)
"openid":"xxxx", #String 合作方系统用户id (userId和mobile,openid至少要填一个,使用的优先级user_id,openId, mobile 依次递减)
"partnerOrderCode": "XXXXXX", #String类型,合作方的订单号(必填)。
"orderFee": 1000, #int类型,订单总价格=sum(totalFee),单位分,这个价格涉及到商务结算、双方分成(必填)
"orderProducts": [{
"partnerProductCode": "1001" , #String类型,包月产品或单点内容标识,双方约定配置,单点不能与包月订单产品ID相同(必填)
"cpContentId": "101" #String类型,爱奇艺侧的内容ID,(对应其他接口的aid),产品类型为单点时有意义
"totalFee": 1500, #int类型,产品价格,值必须大于0,否则认为无效订单(单位分)(必填),
"pid":"pidxxx" #String
}],
"payTime": 1589359821000 #Long类型,定单支付UTC时间(毫秒)(必填)
}
orderProducts 数组说明:虽然参数体为数组,但只能传一个产品信息,如果传多个,将只取第一个。
# 返回参数
爱奇艺在收到合作方的下单通知后,返回内容为Json String,其参数如下
字段名 | 类型 | 说明 |
---|---|---|
msg | String | 业务处理文字说明 |
data | Object | 消息数据,内容为json格式的字符串,采用UrlBase64 Encode |
data. encryptContent | String | 业务参数经过AES 加密得到,具体解密过程见AES+RSA解密描述 |
data. encryptAesPassword | String | AES加密秘钥通过RSA合作方公钥得到,具体解密过程见AES+RSA解密描述 |
code | String | 业务状态码A00000 正常业务处理 |
# 返回示例
data.encryptContent()解密后的数据格式如下:
{
"iqiyiOrderCode":"xxx", #订单号 爱奇艺
"startTime":1589159821000, #权益开始时间 (毫秒)
"endTime": 1589359821000 #权益结束时间 (毫秒)
}
# 返回码定义
返回码code | 描述 |
---|---|
A00000 | 成功 |
Q00302 | 加解密错误 |
301 | 参数错误 |
306 | 系统错误 |
307 | 单点内容校验失败 |
308 | 获取UID失败 |
327 | 价格无效 |
330 | 查询会员信息失败 |
333 | 合作方用户没有可用的优惠资格 |
335 | 优惠资格的产品与传入的不匹配 |
336 | 产品的价格与传入的不匹配 |
# 附录
# 生成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
# AES + RSA加密描述
总体流程:随机生成32位AES密码串 originAesPassword 对请求内容进行AES加密 得到 encryptContent —> 通过爱奇艺公钥对 AES密码串 aesPassword进行RSA 加密得到:encryptAesPassword
采用AES加密业务参数,具体计算方法如下:
1、 随机生成AES 秘钥originAesPassword(不超过64位)
2、 通过AES加密参数
3、 通过RSA使用爱奇艺公钥加密AES密码串
# JAVA版本示例代码如下:
public class Test {
public static String originAesPassword = "EHgvANZJO8YzTQG4ZJyuGiiXbwU8n58coJDo9t6kg8FiR0I6C22UQPwWWT7clYBW";
public static String content = "{\"userId\": \"111111\",\"partnerOrderCode\":\"111111\",\"orderFee\": 1000,\"orderProducts\":[{\"partnerProductCode\": \"1001\",\"totalFee\": 1500,\"pid\":\"123123\"}],\"payTime\": 1589359821000 }";
public static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYfgx6oYHOOyb4b9Os9zgtKfUaSqFCJSFS1CGRk1YYzuaUioK46nI+FiMY6OI1xgNvgaVEuIKYgM9niYaODVbvLIj8hx/By4nj91VcIg5b5lTj3no+yDewiecoBp3rohAgpfGDYwLmzuB8lL74XMr3GJPA8MDxdNncvtcXeYpohwIDAQAB";
public static void main(String[] args) throws Exception {
System.out.println(AES.AESEncode(originAesPassword,content));
System.out.println("----------------");
System.out.println(AES.encryptByPublicKey(originAesPassword,publicKey));
}
}
注意:AES加密参数后会换行,提交时需删掉空格
# 加密工具类
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Encoder;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
public class AES {
public static String AESEncode(String aesPassword,String content) throws NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidKeyException {
try {
SecretKey original_key = null;
try {
KeyGenerator _generator = KeyGenerator.getInstance( "AES" );
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
secureRandom.setSeed(aesPassword.getBytes());
_generator.init(128,secureRandom);
original_key = _generator.generateKey();
} catch (Exception e) {
throw new RuntimeException( " 初始化密钥出现异常 " );
}
byte [] raw=original_key.getEncoded();
SecretKey key=new SecretKeySpec(raw, "AES");
Cipher cipher=Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte [] byte_encode=content.getBytes("utf-8");
byte [] byte_AES=cipher.doFinal(byte_encode);
String AES_encode=new String(new BASE64Encoder().encode(byte_AES));
return AES_encode;
} catch (Exception e){
e.printStackTrace();
throw e;
}
}
public static String encryptByPublicKey(String data, String key) throws Exception {
byte[]byte_publicKey = Base64Util.decode(key);
byte[]byte_originAesPassword = data.getBytes();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(byte_publicKey);
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[]bytes = cipher.doFinal(byte_originAesPassword);
return Base64.encodeBase64String(bytes);
}
}
# AES + RSA解密描述
1、将加密的AES密钥使用合作方私钥解密
2、将加密的业务参数用AES密钥解密得到content
# Java版本代码示例:
public class Test{
public static void main(String[] args) throws Exception {
String encryptAesPassword = ""; //加密的AES密钥
String AESKey = AES.decryptByPrivateKey(encryptAesPassword,privateKey);
String encryptContent = ""; //加密的业务参数
String content = AES.AESDecode(AESKey, encryptContent);
System.out.println(content);
}
}
# 解密工具类
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.crypto.Data;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class AES {
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 String AESDecode(String aesPassword,String content) throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException, IOException {
try {
SecretKey original_key = null;
try {
KeyGenerator _generator = KeyGenerator.getInstance( "AES" );
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
secureRandom.setSeed(aesPassword.getBytes());
_generator.init(128,secureRandom);
original_key = _generator.generateKey();
} catch (Exception e) {
throw new RuntimeException( " 初始化密钥出现异常 " );
}
byte [] raw=original_key.getEncoded();
SecretKey key=new SecretKeySpec(raw, "AES");
Cipher cipher=Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte [] byte_content= new BASE64Decoder().decodeBuffer(content);
byte [] byte_decode=cipher.doFinal(byte_content);
String AES_decode=new String(byte_decode,"utf-8");
return AES_decode;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
public static String decryptByPrivateKey(String data, String key) throws Exception {
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(key));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = cipher.doFinal(Base64.decodeBase64(data));
String originStr = new String(bytes);
return originStr;
}
}
# 在线测试
参数 | 值 | 备注 |
---|---|---|
partnerNo | toB_common_test |
所需密钥:
参数 | 值 |
---|---|
爱奇艺公钥 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYfgx6oYHOOyb4b9Os9zgtKfUaSqFCJSFS1CGRk1YYzuaUioK46nI+FiMY6OI1xgNvgaVEuIKYgM9niYaODVbvLIj8hx/By4nj91VcIg5b5lTj3no+yDewiecoBp3rohAgpfGDYwLmzuB8lL74XMr3GJPA8MDxdNncvtcXeYpohwIDAQAB |
合作方私钥 | 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== |
← 内容定价浮层查询接口 单点内容关联查询接口 →