大致流程:
1、ios端进行支付,然后收到苹果的一串数据(也叫收据),然后ios端将其转码为BASE64编码的字符串。
2、ios端请求服务端接口,将数据传给服务端,服务端拿到数据后,通过一系列处理后,请求苹果服务器,验证此收据是否为正。
3、验证成功后,验证当前支付的交易是否成功,成功则处理相关业务。

关于苹果内购的文章,本人也是参考了网上的一些资料,在这里主要的参考文章贴一下:
https://www.jianshu.com/p/976fc6090cfa
https://www.cnblogs.com/zxtceq/p/10237643.html

但是以上的文章,主要写的是非订阅版的处理方式,导致我在处理连续包月订阅的时候,返回了21004错误,搞了半天,以为是我的数据问题,但是一直找不到解决办法。

后来在公司总监的帮助和指引下,先让前端同事,在前端请求苹果验证一下。结果发现同样返回21004错误。

原来,连续包月订阅,ios端需要拿到把在app-store上的定义共享秘钥传给我,然后拼接成json格式的字符串一起请求苹果服务器。

下面说一下我这边的处理步骤:
1、出了一个下单接口,让前端在调用ios支付之前,先下单(未完成支付),得到订单id。
2、调用苹果内购支付接口,把苹果的收据receipt,订单id,苹果交易流水号传过来服务端。
3、服务端拼接好数据后,请求苹果服务器验证是否支付成功,成功则更新订单的状态。

下面贴一下我这边的关键代码:

​
/*** 下单接口的业务处理层
*/
public RetKit createOpenVipOrder(String amount, int payType, User user) {User realUser = User.dao.findById(user.getId());if (realUser.getPhone() == null || StringUtils.isEmpty(realUser.getPhone())) {return RetKit.fail("请先绑定手机!");}if (payType != VipOrder.PAY_TYPE_IOS) {return RetKit.fail("参数有误!");}BigDecimal money = null;try {money = new BigDecimal(amount);} catch (Exception e) {logger.error("金额参数有误");return RetKit.fail("金额参数有误!");}BigDecimal realPayMoney = BigDecimal.ZERO;VipSet vs = VipSet.dao.findFirst("select * from vip_set limit 1");if (realUser.getAlreadyOpenedVip() == User.OPENED_VIP) {if (money.compareTo(vs.getVipMoney()) != 0) {return RetKit.fail("vip金额有误!");}} else {if (money.compareTo(vs.getFirstVipMoney()) != 0) {return RetKit.fail("首充金额有误!");}}realPayMoney = money;String orderNo = OrderNumberKit.dao.createVipOrderNumber();VipOrder model = new VipOrder();model.setVipOrderNumber(orderNo);model.setUserId(realUser.getId());model.setOpenType(VipOrder.OPEN_TYPE_RECHARGE);model.setPayMoney(realPayMoney);model.setStatus(VipOrder.STATUS_NO_PAY);model.setPayType(payType);model.setCreateTime(new Date());boolean succ = model.save();return succ ? RetKit.ok("下单成功").set("vipOrderId", model.getId()) :RetKit.fail("下单失败");}​
/***  苹果内购支付接口业务处理层
*/
public RetKit openVipIosPay(String receipt, String sandbox, Integer vipOrderId, String transactionId) {if (receipt == null || StrKit.isBlank(receipt)) {return RetKit.fail("参数有误!");}if (!sandbox.equals("0") && !sandbox.equals("1")) {return RetKit.fail("参数有误!");}VipOrder vipOrder = VipOrder.dao.findById(vipOrderId);if (vipOrder == null) {return RetKit.fail("订单参数有误!");}String verifyResult = IosVerify.buyAppVerify(receipt, sandbox);if (verifyResult == null) {LogKit.info("无订单信息:" + verifyResult);return RetKit.fail("无订单信息!");} else {LogKit.info("苹果平台返回Json:" + verifyResult);JSONObject job = JSONObject.parseObject(verifyResult);String status = job.getString("status");// 验证成功if (status.equals("0")) {String r_receipt = job.getString("receipt");JSONObject returnJson = JSONObject.parseObject(r_receipt);String in_app = returnJson.getString("in_app");JSONArray arr = JSONArray.parseArray(in_app);//此处需要遍历处理,因为ios端调用苹果内购支付时候,会有同时产生多张订单,只要其中一张订单匹配,即代表支付成功。boolean flag = false;for (int i = 0; i < arr.size(); i++) {JSONObject obj = (JSONObject) arr.get(i);// 交易中有当前交易,则认为交易成功if (transactionId.equals((String) obj.get("transaction_id"))) {flag = true;break;}}if (flag) {// 处理业务逻辑boolean succ = vipOrder.setStatus(VipOrder.STATUS_IS_PAY).setTradeNo(transactionId).update();return succ ? RetKit.ok("充值成功!") : RetKit.fail("充值失败");}return RetKit.fail("当前交易不在交易列表中");} else {LogKit.error("苹果内购错误码:" + status);return RetKit.fail("支付失败!");}}}
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Locale;import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;import com.jfinal.kit.LogKit;/*** 苹果IAP内购验证工具类*/
public class IosVerify {private static class TrustAnyTrustManager implements X509TrustManager {@Overridepublic void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {// TODO Auto-generated method stub}@Overridepublic void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {// TODO Auto-generated method stub}@Overridepublic X509Certificate[] getAcceptedIssuers() {// TODO Auto-generated method stubreturn new X509Certificate[] {};}}private static class TrustAnyHostnameVerifier implements HostnameVerifier {@Overridepublic boolean verify(String arg0, SSLSession arg1) {// TODO Auto-generated method stubreturn true;}}// 沙箱urlprivate static final String url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";// 线上urlprivate static final String url_verify = "https://buy.itunes.apple.com/verifyReceipt";// 苹果连续订阅共享秘钥,ios在app-store添加后提供private static final String IOS_SHARED_SECRET_PASSWORD = "088c1088ef404f6c9978b932e61483f5";public static String buyAppVerify(String receipt, String sandbox) {System.out.println(receipt);// 环境判断 线上/开发环境用不同的请求链接String url = "";if (sandbox.equals("1")) {url = url_verify;} else {url = url_sandbox;}try {SSLContext sc = SSLContext.getInstance("SSL");sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());URL console = new URL(url);HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();conn.setSSLSocketFactory(sc.getSocketFactory());conn.setHostnameVerifier(new TrustAnyHostnameVerifier());conn.setRequestMethod("POST");conn.setRequestProperty("content-type", "text/json");conn.setRequestProperty("Proxy-Connection", "Keep-Alive");conn.setDoInput(true);conn.setDoOutput(true);BufferedOutputStream hurlBufOus = new BufferedOutputStream(conn.getOutputStream());//普通支付
//          String str = String.format(Locale.CHINA, "{\"receipt-data\":\"" + receipt + "\"}");//连续包月订阅需要加上共享密钥String str = String.format(Locale.CHINA,"{\"receipt-data\":\"" + receipt + "\",\"password\":\"" + IOS_SHARED_SECRET_PASSWORD + "\"}");hurlBufOus.write(str.getBytes());hurlBufOus.flush();hurlBufOus.close();InputStream is = conn.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(is));String line = null;StringBuffer sb = new StringBuffer();while ((line = reader.readLine()) != null) {sb.append(line);}return sb.toString();} catch (Exception ex) {System.out.println(ex.getMessage());LogKit.error("------苹果服务器异常------");ex.printStackTrace();}return null;}}

ps:在请求苹果服务器过程中有时候会报21002状态,请检查几个方面:
1、ios端传过来的数据有误。
2、检查ios传过来的数据转json格式字符串是否有误。
3、base64转码是否正确(本人后台调用网上方法转码时候,遇到过此问题,有可能是base64转码的方法有问题)。

以上代码除了业务代码是本人自己写的外,苹果工具类的代码是参考网上文章复制下来的。

java(jfinal) 接入ios苹果内购(连续包月订阅),服务端将二次验证。相关推荐

  1. uni app ios 苹果内购

    app ios 苹果内购 的步骤 1,准备工作先要uniapp 开发ios 内购需要准备的沙盒 测试账号,在苹果手机登录沙盒账号 也就是把自己的Apple ID退出登录沙盒账号,manifest.js ...

  2. 哔哩哔哩android课程,哔哩哔哩连续包月大会员怎么取消?苹果安卓取消连续包月方法...

    哔哩哔哩是国内知名的弹幕视频网站,用户也是有很多的,当你在开通会员服务时,可以开月卡.季卡.年卡,有一些小伙伴开通的是连续包月优惠套餐,那么哔哩哔哩连续包月大会员怎么取消呢?下面智能手机网就来和大家介 ...

  3. iOS苹果内购流程(Apple Pay)

    好文:https://github.com/lintide/ApplePayDemo 苹果内购流程 字数631 阅读133 评论1 喜欢6 1.登录开发者中心[开发者中心链接](https://dev ...

  4. iOS 苹果内购 In-App Purchase 踩过的坑

    项目里面要接苹果支付,结果我研究了一个小时的apple pay,结果经理说是苹果内购,当时就感觉被耍了!好了,那就说说In-App Purchase这个吧,前面所有的准备工作经理做完了,我只是码代码, ...

  5. PHP处理苹果APP内购后到服务端的二次验证(项目经验)

    一.苹果APP支付到服务端验证流程 1. 用户在app中点击购买: 2. app调用服务端接口生成订单: 3. app获取到服务端订单生成成功后弹出支付窗口: 4. 用户输入密码支付: 5. app接 ...

  6. 苹果怎么取消连续包月服务

    产品型号:iPhone 12 系统版本:iOS 14.7 苹果取消连续包月服务总共分为3个步骤,具体的操作步骤如下: 1.点击手机上的App store,进入主界面. 2.点击右上方的头像,进入个人主 ...

  7. iOS苹果内购详细步骤

    一.设置协议等相关 1.点击协议.税务和银行业务. 点击协议.税务和银行业务.png 2.点击 Request Contracts 下面的 Request,一直点击直到主协议界面. Request.p ...

  8. iOS 苹果内购详细步骤

    一.设置协议等相关 1.点击协议.税务和银行业务. 2.点击 Request Contracts 下面的 Request,一直点击直到主协议界面. 主协议界面 Contact info :联系人信息 ...

  9. IOS苹果内购 PHP后端验证票据

    大体流程: 1.IOS端需要在iTunes Connect上面添加配置一些内购商品,并审核通过,每个内购商品有自己的唯一标识product_id. 2.PHP后端要有一套与之对应的内购商品.IOS应用 ...

最新文章

  1. error: No resource identifier found for attribute 'showAsAction' in package 'com.ymeng.项目名字'
  2. RobotFramework操作API
  3. AJAX中的请求方式以及同步异步的区别
  4. mysql 介绍 知乎_SQL介绍——Mysql
  5. 在centos 7 下安装图形界面
  6. 如何成为一名Web前端开发人员?入行学习完整指南
  7. CentOS 7安装Gnome GUI 图形界面
  8. 现代电力系统分析王锡凡pdf_一对一论文定制 | 电子工程:电力系统潮流及最优潮流算法研究...
  9. 如何注册网站域名?申请域名详细教程
  10. 苹果官方mfi认证名单_苹果入驻抖音,完成官方认证
  11. asp毕业设计——基于asp+sqlserver的英语网络考试系统设计与实现(毕业论文+程序源码)——网络考试系统
  12. 公共关系礼仪实务章节测试题——公共关系的类型(六)
  13. 高斯投影坐标正算公式详解
  14. 复星打造的外滩新地标--BFC外滩金融中心将于12月12日正式开业
  15. (Scikit-Learn)线性回归 基函数的含义详解
  16. 如何评价微擎?怎么看待微擎模块应用?
  17. android点赞取消赞功能吗,Android 仿微博的点赞上报功能,持续点赞再取消
  18. 国产实时操作系统+intel x86/龙芯平台超边缘计算机方案
  19. PCB:FPC原材料,设计,加工,组装终极解决方案
  20. 中国移动宽带测试用哪个软件,中国移动评测四大类手机应用APP 看完你就知道5G和4G网络差距在哪儿...

热门文章

  1. 网上订餐叫外卖的发展优势
  2. vim光速开发,你值得拥有
  3. 【自动驾驶】高级驾驶辅助系统(ADAS)
  4. 中国首台超级计算机“天河一号,中国首台千万亿次超级计算机天河一号安装完毕...
  5. js 只准输入数字_javascript 限制只允许输入数字的几种方法
  6. ipv6 无状态地址管理
  7. python识别屏幕图像后点击操作_Python学习笔记——用GUI自动化控制键盘和鼠标
  8. JIRA 6.3.6 详细安装图解
  9. 开发APP的java工具_app傻瓜式开发工具_app开发工具
  10. 计算机软件水平辅导,计算机软件水平辅导:软件的社交能力