# 订单直充接口
# 简介
# 业务介绍
适用于ToB合作方,直充或单点,通过本文档的接口实现用户权益的开通
# 接口定义
# 应用场景
合作方订单完成后调用本接口,通知爱奇艺为用户发放会员权益
# 接口域名
| 环境 | 域名 |
|---|---|
| 生产 | openapi.vip.iqiyi.com |
| 测试 | test-openapi.vip.iqiyi.com |
# URL
/partner/subscribe.action
# 请求方式
| 参数 | 说明 |
|---|---|
| Method | GET/POST |
| Content-Type | application/x-www-form-urlencoded |
# 请求参数
正式参数由爱奇艺商务提供,测试参数参见在线测试
| 变量名 | 名称 | 是否必须 | |
|---|---|---|---|
| partnerNo | 合作方编码 | 是 | 合作方的唯一标识,由爱奇艺提供 |
| sign | 签名串 | 是 | MD5签名,签名方法见MD5加密描述 |
| orderNo | 订单号 | 是 | 合作方的订单号,字符串或数字或两者组合,必须能唯一标识每个订单 |
| item | 产品编码 | 是 | 产品编码,由爱奇艺提供 |
| contentId | 奇谱内容编号 | 产品编码为单点产品时必传 | 对应爱奇艺点播内容的奇谱id,只有当产品编码为单点产品时,此字段才有意义 |
| amount | 数量 | 是 | 购买产品数量 |
| sum | 金额 (整数) | 是 | 订单总金额=(合作方侧商品售卖单价*amount),单位为“分”。爱奇艺侧会根据这个金额以及双方合同约定的结算方式和分成比例等,计算出结算价,用于双方最终结算。 |
| mobile | 用户手机号 | 3选1 | 手机号,未注册的爱奇艺会自动注册,并下发短信通知用户账号信息 |
| encryptedMobile | 加密的手机号 | 3选1 | 加密格式的手机号(其他同上),采用RSA加密,加密方法见RSA加密,密钥由爱奇艺提供 |
| partnerUserId | 合作方用户id | 3选1 | 对于无法传手机号的合作方,在与爱奇艺passport完成对接后,可传此字段。如果同时传入mobile/ encryptedMobile字段,优先取此字段。 |
| areaCode | 手机号区号 | 否 | 不传默认为大陆手机区号86,海外手机号传国家代码 |
| behavior | 用户购买行为 | 否 | 爱奇艺侧用于统计用户的购买行为,并不影响订单的权益开通,取值:1-用户首购,2-用户续费,3-系统代为续费 |
| version | 接口版本号 | 否 | 当version≥2.0时,返回结果中才会包含startTime字段 |
| fc | 来源信息标识 | 否 | 非爱奇艺售卖渠道无需填写 |
| fv | 合作方为下游客户创建的FV | 否 |
# 返回参数
| 字段名 | 类型 | 说明 |
|---|---|---|
| code | String | 返回码,参考附录 |
| msg | String | 返回码说明 |
| startTime | String | version≥2.0后才会返回,会员开始时间,格式:yyyy-MM-dd HH:mm:ss |
| deadline | String | 会员结束时间,格式:yyyy-MM-dd HH:mm:ss |
| signPage | String | 自动续费签约页面地址URL(用户未签约时才返回) |
# 返回示例
{
"code": "A00000",
"msg": "成功",
"data": {
"startTime": "2016-11-11 12:00:00",
"deadline": "2016-11-11 12:00:00",
"signPage": "www.xxx.com/xxx"
}
}
# 回码定义
| 返回码 | msg | 常见原因 | 处理建议 | 问题处理人 | 备注 |
|---|---|---|---|---|---|
| A00000 | 处理成功 | ||||
| Q00009 | 已在其他渠道购买过优惠卡 | 该产品带有「连包/首单」等规则,要求用户在指定条件下已有订单,当前用户不满足。 | 核查用户身份是否满足;或由运营调整活动与产品配置。 | 销售/运营 | |
| Q00301 | 参数错误_partnerNo为空 | 请求里未传接口编号 partnerNo。 | 补上 partnerNo。 | 销售/运营 | |
| Q00301 | 参数错误_partnerNo无效 | partnerNo 填错、未开通本接口或未开通「通用直充」等销售方式。 | 联系对接销售/运营查后台获取合作方编号。 | 销售/运营 | |
| Q00301 | 参数错误_合作方产品item为空 | 未传接口产品编码 item。 | 传与合同一致的产品编码。 | 销售/运营 | |
| Q00301 | 参数错误_合作方产品无效 | 该接口产品未处于可售状态:例如已下架、超过有效期、状态异常、未挂在当前 partnerNo 下、或销售方式不匹配。 | 联系对接销售或运营,在后台检查该产品是否启用、有效期、是否与合作方接口绑定正确。 | 销售/运营 | |
| Q00301 | 参数错误_爱奇艺产品无效 | 接口产品在后台绑定的爱奇艺侧商品或 SKU 不存在、已停售,或产品映射配置错误。 | 请运营在合作后台核对「接口产品 → 爱奇艺商品/SKU」绑定是否完整、是否指向仍在线上售卖的产品;勿使用已下线商品对应编码。 | 销售/运营 | |
| Q00301 | 参数错误_订单号为空 | 未传合作方订单号 orderNo。 | 传全局唯一的订单号。 | 调用方 | |
| Q00301 | 参数错误_amount非法 | 购买数量为空、非数字或为负数。 | 按文档传非负整数。 | 调用方 | |
| Q00301 | 参数错误_sum非法 | 实付金额等字段为空、非数字或与约定格式不符。 | 确认字段含义与单位(常见为分);有价保时与价保金额对齐。 | 调用方 | |
| Q00301 | 参数错误_账号不可为空 | 手机号、加密手机号、partnerUserId 等至少应传一种的字段都未传。 | 按文档至少提供一种用户标识。 | 调用方 | |
| Q00301 | 参数错误_手机号非法 | 手机号格式不符合校验规则。 | 使用合规手机号 按文档使用加密手机号字段 秘钥不对或者未在爱奇艺配置秘钥信息 | 调用方/销售/运营 | |
| Q00301 | 参数错误_非爱奇艺手机号 | 该手机号尚未注册爱奇艺账号,且贵司侧未开通「用手机号自动注册爱奇艺」能力。 | 引导用户先注册爱奇艺;或向对接人申请开通自动注册后重试。(部分版本响应体可能带注册引导链接。) | 调用方 | |
| Q00301 | 参数错误_不允许通过邮箱直充 | 使用了邮箱作为充值账号,但当前产品或策略不允许走邮箱直充。 | 改用手机号或合同允许的账号类型。 | 调用方 | |
| Q00301 | 参数错误_contentId应该为数字 | 单点类产品要求传内容 ID,但为空或不是纯数字。 | 传平台提供的数字型内容 ID。 | 调用方 | |
| Q00301 | 参数错误_点播内容校验失败 | 内容不存在、已下架、不可售卖,或与当前单点商品不匹配。 | 核对内容 ID 是否正确、是否在可售范围内。 | 销售/运营 | |
| Q00301 | skuid为null | 升级类逻辑需要 SKU 信息,后台商品数据里 SKU 缺失。 | 检查该接口产品关联的爱奇艺商品是否维护完整 SKU。 | 销售/运营 | |
| Q00301 | 参数错误|源会员类型不能为空 | 升级商品需要「从哪种会员升级」的配置,后台未维护或商品本身不是升级类。 | 由运营确认该商品是否为升级商品,且规格里是否填写源会员类型。 | 销售/运营 | |
| Q00301 | 参数错误|会员卡单位非法 | 商品里配置的会员时长计费单位(例如按「月」「季」计一段)与交易侧要求不一致,或取值不在允许范围。 | 由运营核对商品配置中的单位字段 | 销售/运营 | |
| Q00301 | 会员卡单位不能是null | 与上一类相同,强调「计费单位」字段在后台为空。 | 由运营在商品配置中补全会员计费单位。 | 销售/运营 | |
| Q00304 | 账号转换失败 | 手机号、合作方用户编号等无法通过爱奇艺账号体系校验为有效用户,或校验过程失败、超时。 | 用户账号校验失败,合作方传过来的手机号等,我们需要去内部的账号系统校验合法性,如果在校验过程交互超时或校验失败,就会返回此错误码 | 建议重试或作为失败处理 | |
| Q00307 | 签名错误 | MD5 签名与平台计算结果不一致(密钥、参数排序、是否参与签名的空值等与约定不符)。 | 对照开放平台签名说明检查代码是否有问题。如果是秘钥不对联系销售 | 销售/运营 | |
| Q00308 | 请求超时 | 调用内部服务(例如学生套餐累计时长相关促销接口)超时。 | 稍后重试;不要只凭一次超时就认定失败或成功。 | 调用方/技术支持 | 建议重试或作为失败处理 |
| Q00332 | 系统错误 | 未单独分类的内部异常。 | 请联系技术支持 | 技术支持 | |
| Q00332 | 渲染对象结构错误 | HTTP 请求参数与接口约定模型不匹配(如缺参、类型错误),在框架绑定阶段失败。 | 对照文档检查参数名、类型、是否 application/x-www-form-urlencoded 等;仍不行请联系技术支持。 | 调用方 | |
| Q00332 | 系统错误_学生会员校验 | 学生会员累计购买等校验中,非超时类业务失败。 | 核对学生商品与促销配置;联系技术支持。 | 销售/运营/技术支持 | |
| Q00406 | 调交易下单失败,无法重试 | 交易同步返回明确失败,且不属于「可由系统异步再试」的一类。 | 不要对同一业务反复重试;核对用户、价格、商品状态后决定是否换单;可联系运营/技术支持。 | 销售/运营/技术支持 | |
| Q00406 | (交易或下游写入的其它说明) | 交易或下游会把简要原因写在 msg 里,与上条同属不可直接重试成功类。 | 结合完整 msg 与订单信息排查;不明确请联系技术支持。 | 技术支持 | |
| Q00407 | 交易下单失败_系统在异步重试_请稍后重新下单 | ① 交易侧判定可异步重试;② 订单号与库内未完成订单冲突,无法立即给出终态。两种情况都不能单靠本次响应当作最终成功或失败。 | 交易下单失败_系统在异步重试_请稍后重新下单 | 技术支持 | 已生单,建议重试或者等异步通知 (opens new window),结果未知,不可直接当做失败或成功处理 |
| Q00411 | 非有效价格信息 | 产品开启价保时,您传的成交价与后台价保规则不一致。 | 按运营提供的有效价格或价保规则更新金额后重试。 | 销售/运营 | |
| Q00411 | 云包场商品类型不存在 | 云包场商品在系统内的子类型与配置不一致,无法识别。 | 核对云包场活动与商品类型配置 | 销售/运营 | |
| Q00412 | 超过产品单次购买最大数量限制 | 大于接口产品配置的单次购买上限,或升级场景下允许购买件数不足。 | 减少购买数量;请运营调整上限或核对升级规则。 | 销售/运营 | |
| Q00412 | 预单号有误 | 云包场流程中预下单号缺失、过期或与活动不匹配。 | 按云包场对接说明重新取预单或核对活动参数。 | 技术支持 | |
| Q00413 | 查询会员信息失败 | 查询用户会员状态的服务失败或返回异常。 | 间隔重试;多次失败可按失败处理。 | 调用方/技术支持 | 建议重试或作为失败处理 |
| Q00413 | 求加坐与抽奖商品不允许第二次补充库存 | 云包场中「求加座/抽奖」类商品,不允许再次触发补库存流程。 | 严格按云包场规则调用;联系运营确认是否重复下单。 | 技术支持 | |
| Q00415 | 关联的第三方权益商品不可用 | 加价购等场景下,绑定的赠品或第三方权益未通过校验(配置错误、未生效等)。 | 核对赠品商品编码、活动是否在有效期内 | 销售/运营 | |
| Q00502 | 账号被风控 | 风控策略判定该笔请求或用户存在风险。 | 降低调用频率、核对真实用户操作环境 | 销售/运营 | |
| Q00504 | 产品库存不足 | 当前可售库存小于本次要买的数量。 | 需要确认是否需要补库存等 | 销售/运营 | |
| Q00505 | 用户已到限购次数 | 在限购规则规定的统计范围内(如自然月、活动期内),该用户购买次数已用尽。 | 下个周期再试,或按合同由运营调整限购策略。 | 销售/运营 | |
| Q00506 | 产品库存扣减失败,请重试 | 高并发等原因导致扣减库存未成功。 | 做有限次数、带间隔的重试。 | 技术支持 | 建议重试或作为失败处理 |
| Q00507 | 产品限购次数消耗失败,请重试 | 高并发等原因导致限购计数更新未成功。 | 同上,有限次重试。 | 技术支持 | 建议重试或作为失败处理 |
| Q00607 | 未签约自动续费 | 商品要求自动续费代扣,但用户尚未完成签约(响应里常有签约页地址等)。 | 引导用户签约后重新下单。 | 销售/运营 | |
| Q00608 | 请求自动续费接口错误 | 查询用户是否已签约自动续费等接口调用失败。 | 间隔重试;多次失败请联系技术支持。 | 技术支持 | 建议重试或作为失败处理 |
| Q00608 | (自动续费侧返回的补充说明) | 自动续费服务把具体原因附在 msg 中。 | 根据完整 msg 排查;必要时联系技术支持。 | 技术支持 | |
| Q00613 | 用户未购买过老版本学生会员套餐 | 用户未购买过老版本学生会员套餐(仅老版本学生会员 未购买过老版本学生会员的用户不再支持购买老版本的学生会员) | 暂无 | 销售/运营 | |
| Q00614 | 用户已经是黄金或钻石会员, 无法购买学生会员套餐 | 用户已经是黄金或星钻会员, 无法购买学生会员套餐。 | 更换商品,或待符合规则后再购买。 | 销售/运营 | |
| Q00615 | 用户学生会员套餐已达购买次数上限 | 用户学生会员套餐已达购买次数上限(仅学生会员 学生会员权益累计购买时长不能超过24个月) | 查询该用户之前购买时长情况,测试时更换账号进行测试。 | 销售/运营 | |
| Q00617 | 非学生身份 | 商品要求学生身份,校验未通过。 | 完成学生认证或改买非学生商品。 | 销售/运营 | |
| Q00711 | 调用规则引擎失败 | 人群、资格等规则服务调用失败或返回不可用。 | 间隔重试;持续失败请联系技术支持。 | 技术支持 | |
| Q00713 | 合作方用户身份不符合接口产品人群限制条件 | 规则引擎按后台配置校验用户标签/人群,当前用户不满足该接口产品要求。 | 核对活动人群配置与用户是否匹配。 | 销售/运营 | |
| Q00715 | 用户无联名会员购买资格 | 加价购、打包购等场景下,购买前校验认定用户不具备联名/加购资格。 | 核对资格规则与 SKU | 销售/运营 | |
| Q00717 | 用户不符合充值三方商品条件 | 部分「站外/三方」类商品有额外购买条件,前置校验未通过。 | 核对商品说明与用户条件 | 销售/运营 | |
| Q00903 | 参数错误_sku无效 | 云包场相关流程中,向活动侧拉取预单等失败,多与商品或活动配置有关。 | 核对云包场商品、活动编码与后台配置。 | 销售/运营 |
说明:上表中 Q00301、Q00332、Q00406、Q00411、Q00412、Q00413、Q00608 等同一代码出现多行时,每行对应一种 msg,请以实际返回的 msg 区分场景。
# 幂等机制
如果在同步订单过程中接口返回错误,且返回的错误码在上述回码定义中未备注已生单,则合作方可以使用原订单号再次发起同步请求,爱奇艺会重新生单
返回的错误码如果在上述回码定义中备注为已生单,合作方使用原订单号再次发起同步请求时,爱奇艺侧会按照订单号做幂等处理,不会重复产生订单
# 附录
# MD5加密描述
采用MD5计算签名,MD5密钥由爱奇艺提供,具体计算方法如下:
1、 假设共有三个参数 a=3、b=2、c=1;
2、 按参数名的字母正序排列,再用“&”连接后得到串A,即为“a=3&b=2&c=1”;
3、 将MD5密钥拼接到串A后面,假设MD5密钥为“qwer”,则拼接后为“a=3&b=2&c=1qwer”;
4、 计算拼接串的MD5即为最终签名值,注意使用UTF-8编码计算,MD5后的签名最后都转成小写。
举例:
参数:a=3、b=2、c=1;
MD5密钥:qwer
最终签名串:f80118ff523f25eda67cb799bdc9c52d
# JAVA版本示例代码如下:
public class Test{
/**
* MD5签名测试类
*
*/
private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public static void main(String[] args) {
TreeMap<String, Object> paramMap = new TreeMap<>();
paramMap.put("a", "3");
paramMap.put("b", "2");
paramMap.put("c", "1");
String md5key = "qwer";
String targetParam = Joiner.on("&").withKeyValueSeparator("=")
.useForNull("").join(paramMap);
// MD5签名工具类,可以自己实现, MD5实现参照
String targetSign = EncodeUtils.MD5(targetParam + md5key, "UTF-8");
System.out.println("targetSign: " + targetSign);
}
}
# 加密工具类
# MD5加密
import com.google.common.base.Joiner;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class EncodeUtils{
public static String MD5(String text, String charset) {
MessageDigest msgDigest = null;
try {
msgDigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("System doesn't support MD5 algorithm.");
}
try {
msgDigest.update(text.getBytes(charset)); //注意改接口是按照指定编码形式签名
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("System doesn't support your EncodingException.");
}
byte[] bytes = msgDigest.digest();
String md5Str = new String(encodeHex(bytes));
return md5Str;
}
public static char[] encodeHex(byte[] data) {
int l = data.length;
char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
out[j++] = DIGITS[0x0F & data[i]];
}
return out;
}
}
# RSA加密
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* Description: 安全加密RSA.
*/
public class RsaUtil{
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 公钥加密过程
*
* @param publicKey 公钥 base64转码串
* @param plainTextData 明文数据
* @return 加密结果Base64转码串
* @throws Exception 加密过程中的异常信息
*/
public static String encryptForBase64(String publicKey, byte[] plainTextData)
throws Exception {
RSAPublicKey rsaPublicKey = getPublicKeyByStr(publicKey);
BASE64Encoder base64 = new BASE64Encoder();
return base64.encode(encrypt(rsaPublicKey, plainTextData));
}
/**
* 公钥加密过程
*
* @param publicKey
* @param plainTextData
* @return
* @throws Exception
*/
public static byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData)
throws Exception {
if (publicKey == null) {
System.out.println("加密公钥为空, 请设置");
throw new Exception();
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(plainTextData);
return output;
} catch (Exception e) {
throw new Exception(e);
}
}
public static RSAPublicKey getPublicKeyByStr(String publicKeyStr)
throws Exception {
try {
BASE64Decoder Base64 = new BASE64Decoder();
byte[] buffer = Base64.decodeBuffer(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (Exception e) {
e.printStackTrace();
throw new Exception(e);
}
}
}
# 常见问题
# 签名错误
请仔细阅读按签名规则和示例代码,需要注意:参数值为null的,key也会参与计算签名,所以最好传参就去掉值为null的key。MD5后的签名最后都转成小写
# 下单未返回成功如何处理
可以重试发起下单、或接入回调接口等待我方成功后回调(如果接入了),我方重试间隔(回调时间点)如下:
{ "5s", "10s", "1m", "5m", "10m", "30m", "1h", "2h", "12h" }
即回调最晚时间是在12h后,如果下单后12h仍未回调,请联系我方人工处理
订单同步时需要考虑失败订单重试机制,使网络问题或其他问题导致同步失败的订单能够再次同步到爱奇艺服务器,防止两边订单不一致。 可先同步重试一到两次,仍失败改为异步重试,重试的间隔时间可以逐步扩大(例如最多重试9次,间隔时间依次为"5s", "10s", "1m", "5m", "10m", "30m", "1h", "2h", "12h"),直至订单最终同步成功; 如果重试多次后,订单同步依然失败,我方又未回调,就需要联系爱奇艺侧技术同学排查具体失败的原因。
# 在线测试
| 参数 | 值 | 备注 |
|---|---|---|
| partnerNo | toB_common_test | |
| item | 111 222 333 444 555 single | 111-天卡会员(1天) 222-月卡会员(1月) 333-季卡会员 444-年卡会员 555-周卡会员(7天) single-单点 |
所需密钥:
| 参数 | 值 |
|---|---|
| MD5密钥 | b0ee3c7f62760330 |
| 爱奇艺公钥 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYfgx6oYHOOyb4b9Os9zgtKfUaSqFCJSFS1CGRk1YYzuaUioK46nI+FiMY6OI1xgNvgaVEuIKYgM9niYaODVbvLIj8hx/By4nj91VcIg5b5lTj3no+yDewiecoBp3rohAgpfGDYwLmzuB8lL74XMr3GJPA8MDxdNncvtcXeYpohwIDAQAB |