这个估计是我踩过的最大的坑,当时做微信支付的时候也没这么坑爹,当然他俩也半斤八两。。。

苹果官方明确表示:验证支付时,可能会有一定的延迟。第一次处理的时间就专注的解决这个问题了,忽略了掉单的问题(稍后再说),让我多次更新支付代码才降低了掉单率。

常识:

1,返回状态码含义

2、正常返回结果格式

{
"environment": "Sandbox",
"receipt": {
"in_app": [
{
"transaction_id": "10000004111119001",
"original_purchase_date": "2018-07-06 03:16:41 Etc/GMT",
"quantity": "1",
"original_transaction_id": "1000000414619001",
"purchase_date_pst": "2018-07-05 20:16:41 America/Los_Angeles",
"original_purchase_date_ms": "1530847001000",
"purchase_date_ms": "1530847001000",
"product_id": "com.Beixxxxxxxxxon.fourc",
"original_purchase_date_pst": "2018-07-05 20:16:41 America/Los_Angeles",
"is_trial_period": "false",
"purchase_date": "2018-07-06 03:16:41 Etc/GMT"
}
],
"adam_id": 0,
"receipt_creation_date": "2018-07-06 03:16:41 Etc/GMT",
"original_application_version": "1.0",
"app_item_id": 0,
"original_purchase_date_ms": "1375340400000",
"request_date_ms": "1530847558058",
"original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
"original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
"receipt_creation_date_pst": "2018-07-05 20:16:41 America/Los_Angeles",
"receipt_type": "ProductionSandbox",
"bundle_id": "com.jiaxxxxxmei.www.Gxxxxxxxrooms",
"receipt_creation_date_ms": "1530847001000",
"request_date": "2018-07-06 03:25:58 Etc/GMT",
"version_external_identifier": 0,
"request_date_pst": "2018-07-05 20:25:58 America/Los_Angeles",
"download_id": 0,
"application_version": "3"
},
"status": 0
}

3、凭证(此处提供的是测试环境的成功支付凭证)

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

4、测试|正式验证地址

//购买凭证验证地址  
private static final String certificateUrl = "https://buy.itunes.apple.com/verifyReceipt"; //测试的购买凭证验证地址
private static final String certificateUrlTest = "https://sandbox.itunes.apple.com/verifyReceipt";

开始JAVA开发接口验证:

初步代码,第一版,实现正常的支付功能(其中充值金额最好从返回结果中获取,不过我是让前端传过来的):

     /**
* 接收iOS端发过来的购买凭证
* @param
*/
@RequestMapping("/setIapCertificate")
@ResponseBody
public R setIapCertificate(HttpServletRequest req){
String certificateCode = req.getParameter("certificateCode");
String money = req.getParameter("money");//充值金额
String type = req.getParameter("type");//1、正式 2、测试
String url = null;
url = type.equals("1")?certificateUrl : certificateUrlTest; // if(StringUtils.isNotEmpty(certificateCode)){
String r = sendHttpsCoon(url, certificateCode); //向苹果服务器发送凭证并接受结果
JSONObject params = JSONObject.parseObject(r);
System.out.println("*****************"+params);
String code = "";
String environment = "";
String transaction_id = "";
final String orderNum = DateUtil.getRechargeOrderNum();//根据当前时间生成订单号,yyMMddHHmmssSSS + 6位随机数
if(params != null) {
code = String.valueOf(params.get("status"));
if("0".equals(code)) {//成功
environment = String.valueOf(params.get("environment"));
String r_receipt = params.getString("receipt");
JSONObject returnJson = JSONObject.parseObject(r_receipt);
System.out.println("*****************"+returnJson);
String in_app = returnJson.getString("in_app"); if (in_app.endsWith("]")) {
JSONObject in_appJson = JSONObject.parseObject(in_app.substring(1, in_app.length()-1));
transaction_id = in_appJson.getString("transaction_id"); // 订单号
}
//保存充值记录
saveRecharge(orderNum, money, environment, transaction_id);
return R.ok();
}
}else{
return R.error();
}
}else{
return R.error();
}
}

后来发现大部分可以支付成功,但部分充值不成功,考虑可能是网络的问题,然后利用定时任务实现多次请求。若第一次请求结果不是成功状态则保存第一次请求的结果,并且每隔10秒请求一次,最多请求6次(一分钟)。

就有了优化后的第二版

 /**
* 接收iOS端发过来的购买凭证(方法已过时,被替代)
* @param
*/
@RequestMapping("/setIapCertificate1")
@ResponseBody
public R setIapCertificate1(HttpServletRequest req){
String certificateCode = req.getParameter("certificateCode");
String money = req.getParameter("money");//充值金额
String type = req.getParameter("type");//1、正式 2、测试
String url = null;
url = type.equals("1")?certificateUrl : certificateUrlTest; // if(StringUtils.isNotEmpty(certificateCode)){
String r = sendHttpsCoon(url, certificateCode);
JSONObject params = JSONObject.parseObject(r);
System.out.println("*****************"+params);
String code = "";
String environment = "";
String transaction_id = "";
final String orderNum = DateUtil.getRechargeOrderNum();//根据当前时间生成订单号,yyMMddHHmmssSSS + 0~1000000随机数
if(params != null) { code = String.valueOf(params.get("status")); payError = new WfIospayErrorEntity();
payError.setCertificatecode(certificateCode);
payError.setMoney(money);
payError.setRebate(rebate);
payError.setResultcode(code);
payError.setType(type);
payError.setUpdatetime(new Date());
payError.setRemark(params.toString());
iospayErrorService.save(payError); if("0".equals(code)) {//成功
environment = String.valueOf(params.get("environment"));
String r_receipt = params.getString("receipt");
JSONObject returnJson = JSONObject.parseObject(r_receipt);
System.out.println("*****************"+returnJson);
String in_app = returnJson.getString("in_app"); if (in_app.endsWith("]")) {
JSONObject in_appJson = JSONObject.parseObject(in_app.substring(1, in_app.length()-1));
transaction_id = in_appJson.getString("transaction_id"); // 订单号
}
//保存充值记录
saveRecharge(rebate, orderNum, uid, money, environment, transaction_id);
return R.ok();
}else{//失败 final String url1 = url;
final String certificateCode1 = certificateCode;
final String environment1 = environment;
final String transaction_id1 = transaction_id;
final String money1 = money;//充值金额
final Timer timer = new Timer();
timer.schedule(new TimerTask() {//若失败,则每10请求一次 @Override
public void run() {
if (map.get(certificateCode1) != null) {
map.put(certificateCode1, map.get(certificateCode1) + 1);
} else {
map.put(certificateCode1, 1);
}
if (map.get(certificateCode1) >= 60) {
map.remove(certificateCode1);
timer.cancel();
}
String r1 = sendHttpsCoon(url1, certificateCode1);
if (JSONObject.parseObject(r1).getString("status").equals("0")) {
saveRecharge( orderNum, money1, environment1, transaction_id1);//当成功时处理充值业务
map.remove(certificateCode1);
timer.cancel();
}
//System.out.println("设定指定任务task在指定延迟delay后执行");
}
},1000, 10000); return R.error("68");
}
}else{//保存失败凭证
payError = new WfIospayErrorEntity();
payError.setCertificatecode(certificateCode);
payError.setMoney(money);
payError.setRebate(rebate);
payError.setResultcode(code);
payError.setType(type);
payError.setUpdatetime(new Date());
if(params != null){
payError.setRemark("params为空");
}else {
payError.setRemark(params.toString());
}
iospayErrorService.save(payError);
return R.error();
} }else{
return R.error();
}
}

but,还是有掉单的问题,此时我崩溃了,想不明白怎么回事。直到我查了苹果返回的结果发现,根据一个凭证查出了两次支付的订单信息

{
"environment": "Production",
"receipt": {
"in_app": [
{
"transaction_id": "470000347480489",
"original_purchase_date": "2018-07-13 06:01:00 Etc/GMT",
"quantity": "1",
"original_transaction_id": "470000347480489",
"purchase_date_pst": "2018-07-12 23:01:00 America/Los_Angeles",
"original_purchase_date_ms": "1531461660000",
"purchase_date_ms": "1531461660000",
"product_id": "com.Beijxxxxxxxxation.fourc",
"original_purchase_date_pst": "2018-07-12 23:01:00 America/Los_Angeles",
"is_trial_period": "false",
"purchase_date": "2018-07-13 06:01:00 Etc/GMT"
},
{
"transaction_id": "470000347479507",
"original_purchase_date": "2018-07-13 05:56:51 Etc/GMT",
"quantity": "1",
"original_transaction_id": "470000347479507",
"purchase_date_pst": "2018-07-12 22:56:51 America/Los_Angeles",
"original_purchase_date_ms": "1531461411000",
"purchase_date_ms": "1531461411000",
"product_id": "com.Beixxxxxxxxxxxcation.fourb",
"original_purchase_date_pst": "2018-07-12 22:56:51 America/Los_Angeles",
"is_trial_period": "false",
"purchase_date": "2018-07-13 05:56:51 Etc/GMT"
}
],
"adam_id": 1375992347,
"receipt_creation_date": "2018-07-13 06:01:00 Etc/GMT",
"original_application_version": "3",
"app_item_id": 1375992347,
"original_purchase_date_ms": "1531016326000",
"request_date_ms": "1531461663282",
"original_purchase_date_pst": "2018-07-07 19:18:46 America/Los_Angeles",
"original_purchase_date": "2018-07-08 02:18:46 Etc/GMT",
"receipt_creation_date_pst": "2018-07-12 23:01:00 America/Los_Angeles",
"receipt_type": "Production",
"bundle_id": "com.jxxxxxxxnmei.www.Goxxxxxxsrooms",
"receipt_creation_date_ms": "1531461660000",
"request_date": "2018-07-13 06:01:03 Etc/GMT",
"version_external_identifier": 827702473,
"request_date_pst": "2018-07-12 23:01:03 America/Los_Angeles",
"download_id": 87033802813938,
"application_version": "3"
},
"status": 0
}

知道原因后,想解决办法。读取苹果服务器返回的订单数组,循环充值。但这样的话,需要避免重复充值,所以现在需要用缓存给每次充值做个记录(我选择的redis),在充值之前判断苹果服务器返回的订单中有没有已经充过值的。

然后有了第三版

 /**
* 接收iOS端发过来的购买凭证
*
* @param
*/
@RequestMapping("/setIapCertificate")
@ResponseBody
public R setIapCertificate(HttpServletRequest req) {
final Integer uid = WebUtils.getUid(req);
String certificateCode = req.getParameter("certificateCode");
String money = req.getParameter("money");//充值金额
String type = req.getParameter("type");//1、正式 2、测试
String url = null;
url = type.equals("1") ? certificateUrl : certificateUrlTest; // WfIospayErrorEntity payError = new WfIospayErrorEntity();
payError.setCertificatecode(certificateCode);
payError.setMoney(money);
payError.setRebate(rebate);
payError.setResultcode("110");
payError.setType(type);
payError.setUid(String.valueOf(uid));
payError.setUpdatetime(new Date());
payError.setRemark("params为空");
iospayErrorService.save(payError); if (StringUtils.isNotEmpty(certificateCode)) {
String r = sendHttpsCoon(url, certificateCode);
JSONObject params = JSONObject.parseObject(r);
System.out.println("*****************" + params);
String code = "";
String environment = "";
String transaction_id = "";
final String orderNum = DateUtil.getRechargeOrderNum();//根据当前时间生成订单号,yyMMddHHmmssSSS + 0~1000000随机数
if (params != null) { code = String.valueOf(params.get("status")); payError = new WfIospayErrorEntity();
payError.setCertificatecode(certificateCode);
payError.setMoney(money);
payError.setRebate(rebate);
payError.setResultcode(code);
payError.setType(type);
payError.setUid(String.valueOf(uid));
payError.setUpdatetime(new Date());
payError.setRemark(params.toString());
iospayErrorService.save(payError); if ("0".equals(code)) {//成功
environment = String.valueOf(params.get("environment"));
String r_receipt = params.getString("receipt");
JSONObject returnJson = JSONObject.parseObject(r_receipt);
System.out.println("*****************" + returnJson);
//String in_app = returnJson.getString("in_app");
JSONArray jsonArray = (JSONArray) returnJson.get("in_app");// 获取返回结果中的订单列表
JSONObject targetOrder = null;
String product_id = null;
for (int i = 0; i < jsonArray.size(); i++) { targetOrder = jsonArray.getJSONObject(i);//获取订单信息对象
product_id = targetOrder.getString("product_id");//获取产品信息
transaction_id = targetOrder.getString("transaction_id");// transaction_id交易号 if (!ISsoLoginHelper.confirmPay(uid, transaction_id)) {//redis验证订单是否已经充值
//保存充值记录
ISsoLoginHelper.savePay(uid, transaction_id, money);//用redis保存记录
//进行充值
saveRecharge(rebate, orderNum, uid, money, environment, transaction_id);
}
} return R.ok();
} else {//失败 final String url1 = url;
final String certificateCode1 = certificateCode;
final String environment1 = environment;
final String transaction_id1 = transaction_id;
final String money1 = money;//充值金额
final Timer timer = new Timer();
timer.schedule(new TimerTask() { @Override
public void run() {
if (map.get(certificateCode1) != null) {
map.put(certificateCode1, map.get(certificateCode1) + 1);
} else {
map.put(certificateCode1, 1);
}
if (map.get(certificateCode1) >= 60) {
map.remove(certificateCode1);
timer.cancel();
}
String r1 = sendHttpsCoon(url1, certificateCode1);
if (JSONObject.parseObject(r1).getString("status").equals("0")) {
if (!ISsoLoginHelper.confirmPay(uid, transaction_id1)) {//redis验证订单是否已经充值
//保存充值记录
ISsoLoginHelper.savePay(uid, transaction_id1, money1);
//进行充值
saveRecharge(rebate, orderNum, uid, money1, environment1, transaction_id1);
}
map.remove(certificateCode1);
timer.cancel();
}
//System.out.println("设定指定任务task在指定延迟delay后执行");
}
}, 1000, 10000); return R.error("68");
}
} else { payError = new WfIospayErrorEntity();
payError.setCertificatecode(certificateCode);
payError.setMoney(money);
payError.setRebate(rebate);
payError.setResultcode("111");
payError.setType(type);
payError.setUid(String.valueOf(uid));
payError.setUpdatetime(new Date());
payError.setRemark("params再次为空");
iospayErrorService.save(payError); return R.error();
} } else {
return R.error();
}
}

其中涉及的工具或方法:

发送请求:

    /**
* 重写X509TrustManager
*/
private static TrustManager myX509TrustManager = new X509TrustManager() { @Override
public X509Certificate[] getAcceptedIssuers() {
return null;
} @Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
}; /**
* 发送请求
* @param url
* @param
* @return
*/
private String sendHttpsCoon(String url, String code){
if(url.isEmpty()){
return null;
}
try {
//设置SSLContext
SSLContext ssl = SSLContext.getInstance("SSL");
ssl.init(null, new TrustManager[]{myX509TrustManager}, null); //打开连接
HttpsURLConnection conn = (HttpsURLConnection) new URL(url).openConnection();
//设置套接工厂
conn.setSSLSocketFactory(ssl.getSocketFactory());
//加入数据
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-type","application/json"); JSONObject obj = new JSONObject();
obj.put("receipt-data", code); BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());
buffOutStr.write(obj.toString().getBytes());
buffOutStr.flush();
buffOutStr.close(); //获取输入流
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null;
StringBuffer sb = new StringBuffer();
while((line = reader.readLine())!= null){
sb.append(line);
}
return sb.toString(); } catch (Exception e) {
return null;
}
}

redis保存|判断充值记录(安装配置我就不多说了,不懂的请自行百度)

    /**
* 保存ios订单支付信息
*
* @param userid
* @return
*/
public static void savePay(Integer userid,String transaction_id,String money) {
Jedis jedis = myJedisPool.getResource(); try {
jedis.setnx(userid +"_"+ transaction_id , money);
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
} finally {
jedis.close();
}
} /**
* 判断订单是否已充值成功
*
* @param userid
* @return
*/
public static boolean confirmPay(Integer userid,String transaction_id) {
Jedis jedis = myJedisPool.getResource();
boolean b = false;
try {
b = jedis.exists(userid +"_"+ transaction_id);
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
} finally {
jedis.close();
}
return b;
}

【JAVA】IOS内购二次验证及掉单问题解决的更多相关文章

  1. 苹果IOS内购二次验证返回state为21002的坑

    项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...

  2. IOS内购支付server验证模式

    IOS 内购支付两种模式: 内置模式 server模式 内置模式的流程: app从app store 获取产品信息 用户选择须要购买的产品 app发送支付请求到app store app store ...

  3. IOS内购支付服务器验证模式

    IOS 内购支付两种模式: 内置模式 服务器模式 内置模式的流程: app从app store 获取产品信息 用户选择需要购买的产品 app发送支付请求到app store app store 处理支 ...

  4. iOS内购的订单对应和补单

    内购的关键类: 1.SKPayment(SKMutablePayment可将自己的参数一对一与苹果产生的payment对应起来) 2.TransactionObserver:交易状态更新时执行此方法, ...

  5. iOS 内购讲解

    一.总说内购的内容 1.协议.税务和银行业务 信息填写 2.内购商品的添加 3.添加沙盒测试账号 4.内购代码的具体实现 5.内购的注意事项 二.协议.税务和银行业务 信息填写 2.1.协议.税务和银 ...

  6. iOS - 内购总结

        如果有人以后要在做内购这一块.希望可以好好的阅读这篇文章,虽然不是字字珠玑.但是也是本人亲人趟过了无数的坑,希望可以对大家有所帮助!  下面是在研究工程中遇到的问题(iOS 内购的流程如下 1 ...

  7. iOS 内购遇到的坑

    一.内购沙盒测试账号在支付成功后,再次购买相同 ID 的物品,会提示如下内容的弹窗.您以购买过此APP内购项目,此项目将免费恢复 原因: 当使用内购购买过商品后没有把这个交易事件关,所以当我们再次去购 ...

  8. iOS内购(IAP)中的那些坑

    公司的公共库原来并没有这部分的代码,以前做内购是用两个比较有名的github上的第三方库.一个叫MKStoreKit,另一个叫IAPManager,我看了一下写的都很辣鸡,使用起来很不方便,而且写的还 ...

  9. Unity苹果(iOS)内购接入(Unity内置IAP)

    https://www.jianshu.com/p/4045ebf81a1c Unity苹果(iOS)内购接入(Unity内置IAP) Kakarottog                       ...

随机推荐

  1. django的项目创建简明流程

    个人理解,不妥之处请指出 创建项目:django-admin startproject user_sys 创建APP:python manage.py startapp auth 测试项目创建是否成功 ...

  2. MongoDB-4: 查询(二-数组、内嵌文档)

    一.简介 我们上一篇介绍了db.collection.find()可以实现根据条件查询和指定使用投影运算符返回的字段省略此参数返回匹配文档中的所有字段,我们今天介绍了对数组和内嵌文档的查询操作,尤其是 ...

  3. Andrew Ng机器学习编程作业:Anomaly Detection and Recommender Systems

    作业文件 machine-learning-ex8 在本次练习,第一节我们将实现异常检测算法,并把它应用到检测网络故障服务器上.在第二部分,我们将使用协同过滤来构建电影推荐系统. 1. 异常检测 在这 ...

  4. 【TensorFlow】tf.nn.conv2d是怎样实现卷积的?

    tf.nn.conv2d是TensorFlow里面实现卷积的函数,参考文档对它的介绍并不是很详细,实际上这是搭建卷积神经网络比较核心的一个方法,非常重要 tf.nn.conv2d(input, fil ...

  5. Redis六(管道)

    管道 为什么使用管道? Redis是一个TCP服务器,支持请求/响应协议. 在Redis中,请求通过以下步骤完成: 客户端向服务器发送查询,并从套接字读取,通常以阻塞的方式,用于服务器响应. 服务器处 ...

  6. go——基本构成要素

    Go的语言符号又称为词法元素,共包括5类内容: 标识符(identifier) 关键字(keyword) 字面量(literal) 分隔符(delimiter) 操作符(operator)它们可以组成 ...

  7. Delphi 正则表达式之TPerlRegEx 类的属性与方法(3): Start、Stop

    Delphi 正则表达式之TPerlRegEx 类的属性与方法(3): Start.Stop //设定搜索范围: Start.Stop var   reg: TPerlRegEx; begin   r ...

  8. javascript Date对象 之 时间转字符串

    javascript Date对象 --> 时间转字符串: 测试代码: <!DOCTYPE html> <html lang="en"> <he ...

  9. js防抖

    那我们设置个 options 作为第三个参数,然后根据传的值判断到底哪种效果,我们约定: leading:false 表示禁用第一次执行trailing: false 表示禁用停止触发的回调 我们来改 ...

  10. C/C++中0xcccccccc...

    * 0xABABABAB : Used by Microsoft's HeapAlloc() to mark "no man's land" guard bytes after a ...