ios内购开发的坑
如果你的ios需要有虚拟付费功能,那么你必须需要使用ios的内购,才能审核通过。
在对接ios内购的时候,还是遇到了很多的坑,经过一番努力,最终解决上线了。
需要在苹果上面申请产品包,这里产品包申请有个坑(还有可能就是对苹果的产品不了解):
1、消耗型产品
ios返回的in_app数据只有一个;
2、订阅型产品
ios返回的in_app数据是一个购买历史列表;
坑1:刚开始的时候,我们申请了消耗型产品,这个类型的产品在in_app里只返回当前购买的数据。后来申请被拒了,说充值会员不能用这个类型的产品,就换成订阅型产品,结果验证收据时in_app返回购买历史列表。因为之前看网上的文档,没有提到in_app里面是列表,然后代码是获取in_app列表里面的第一个数据,在做订单价格比较的时候,一直匹配不上。为什么要做价格比较?因为苹果的支付成功结果是返回给客户端的,为了安全起见,需要验证苹果的收据信息里面的产品id跟我们的订单表的产品id是否一样。本来想比较收据里的流水号跟我们订单的流水号是否相同,但是发现苹果压根没返回我们提交的流水号。
坑2:in_app数据变成列表之后,后台要验证订单信息,发现不太好验证,后来发现客户端可以获取到苹果收据返回的transactionId,in_app数据里面也有这个transactionId字段,所以在验证收据的时候,让客户端把这个值传过来,这样就可以匹配上是哪个数据了。
坑3:因为支付成功是返回给客户端的,客户端的网络不稳定,有可能不能及时把这个状态发给服务端,可能会导致用户付钱了,但是后台迟迟没有收到信息,导致后续的操作没办法进行。解决:客户端把数据保存在本地,状态为未验证,用户下次启动的时候,把这个数据提交给服务端,走完整个流程。
整个苹果支付的流程图
服务端把客户端提交上来的收据保存在数据库,如果验证失败,可以搞个定时任务继续做验证。为什么会失败?1、调用苹果网络异常;2、苹果服务不可用;3、调用苹果验证超时;4、我们调用苹果出现bug;
当时还担心线上环境和沙盒环境返回的数据不一样,上线后支付了一笔,发现是一样的。
以下是一些伪代码,如果要用要稍微调整下。
验证收据方法
private static final String URL_SANDBOX = "https://sandbox.itunes.apple.com/verifyReceipt";
private static final String URL_VERIFY = "https://buy.itunes.apple.com/verifyReceipt";
1、使用redis对并发进行处理
String key = orderNo的key 全局唯一;
if (rt.opsForValue().setIfAbsent(key, ip地址)) {
rt.expire(key, 5, TimeUnit.SECONDS);
//2、检验订单是否存在
//3、修改订单状态为付款中
//4、拼成固定的格式传给平台
Map<String, Object> receiptDataMap = new HashMap<String, Object>(1);
receiptDataMap.put("receipt-data", ios传上来的base64的数据);
//5、先线上测试 发送平台验证
String verifyResult = HttpRequest.postJson(URL_VERIFY, receiptDataMap, null);
//可以把ios返回的数据保存到订单表里
if (verifyResult == null) {
// 苹果服务器没有返回验证结果
throw new Exception(”订单不存在“);
} else {
// 苹果验证有返回结果
log.info("【ios内购-notifyForPay】正式环境,苹果返回数据:[{}]", verifyResult);
JSONObject job = JSONObject.parseObject(verifyResult);
String states = job.getString("status");
//是沙盒环境,应沙盒测试,否则执行下面 是否开启沙盒环境测试
String openSandbox = 后台搞个参数管理,进行配置;
if (Boolean.valueOf(openSandbox)) {
if ("21007".equals(states)) {
//再沙盒测试 发送平台验证
verifyResult = HttpRequest.postJson(URL_SANDBOX, receiptDataMap, null);
log.info("【ios内购-notifyForPay】沙盒环境,苹果返回数据:[{}]", verifyResult);
//可以把ios返回的数据保存到订单表里
job = JSONObject.parseObject(verifyResult);
states = job.getString("status");
}
}
log.info("【ios内购-notifyForPay】苹果平台返回值:states=[{}]", states);
// 前端所提供的收据是有效的 验证成功
if ("0".equals(states)) {
String receiptJson = job.getString("receipt");
JSONObject returnJson = JSONObject.parseObject(receiptJson);
String inApp = returnJson.getString("in_app");
JSONObject inAppJson = getInApp(JSONObject.parseArray(inApp, JSONObject.class),
generateOrderDto.getTransactionId());
String productId = inAppJson.getString("product_id");
//检查产品包id在我们系统是否可以查到
// 订单号 transaction_id
String originalTransactionId = inAppJson.getString("original_transaction_id");
BigDecimal price = 我们系统的价格;
boolean isPriceEquals = !(订单表的金额.compareTo(price) == 0);
//判断金额是否相等
if (isPriceEquals) {
//验证平台与ios平台金额是否一致
//保存错误订单
} else {
//保存到数据库 修改订单状态为成功
}
} else {
if (!StringUtils.equals("21005", states)) {
//修改订单状态为失败
}
}
}
return 返回值;
}else{
throw new Exception(“请求太频繁”);
}
工具类
private JSONObject getInApp(List<JSONObject> inAppList,String transactionId){
for(JSONObject jsonObject : inAppList){
String iosTransactionId = jsonObject.getString("transaction_id");
if(transactionId.equals(iosTransactionId)){
return jsonObject;
}
}
throw new Exception(“异常);
}
苹果收据返回的数据格式:
{
"status": 0,
"environment": "Sandbox",
"receipt": {
"receipt_type": "ProductionSandbox",
"adam_id": 0,
"app_item_id": 0,
"bundle_id": "xxxx",
"application_version": "0",
"download_id": 0,
"version_external_identifier": 0,
"receipt_creation_date": "2018-10-05 10:06:12 Etc/GMT",
"receipt_creation_date_ms": "1515146772000",
"receipt_creation_date_pst": "2018-10-05 02:06:12 America/Los_Angeles",
"request_date": "2018-10-05 10:06:14 Etc/GMT",
"request_date_ms": "1515146774645",
"request_date_pst": "2018-10-05 02:06:14 America/Los_Angeles",
"original_purchase_date": "2013-08-10 07:00:00 Etc/GMT",
"original_purchase_date_ms": "1375340400000",
"original_purchase_date_pst": "2013-08-10 00:00:00 America/Los_Angeles",
"original_application_version": "1.0",
"in_app": [
{
"quantity": "1",
"product_id": "xxxxxx",
"transaction_id": "1000000364151455",
"original_transaction_id": "1000000364151455",
"purchase_date": "2018-10-05 10:06:11 Etc/GMT",
"purchase_date_ms": "1515146771000",
"purchase_date_pst": "2018-10-05 02:06:11 America/Los_Angeles",
"original_purchase_date": "2018-10-05 10:06:11 Etc/GMT",
"original_purchase_date_ms": "1515146771000",
"original_purchase_date_pst": "2018-10-05 02:06:11 America/Los_Angeles",
"is_trial_period": "false"
}
]
}
}
关注公众号,更快获取文章