java对接IOS 内购

一、首先我们先简单理一下整个内购的核心流程:
①客户端发起支付订单
②客户端监听购买结果
③苹果回调订单购买成功时,客户端把苹果给的receipt_data和一些订单信息上报给服务器
④后台服务器拿receipt_data向苹果服务器校验
⑤苹果服务器向返回status结果,含义如下,其中为0时表示成功。
21000 App Store无法读取你提供的JSON数据
21002 收据数据不符合格式
21003 收据无法被验证
21004 你提供的共享密钥和账户的共享密钥不一致
21005 收据服务器当前不可用
21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
⑥服务器发现订单校验成功后,会把这笔订单存起来,transaction_id用MD5值映射下,保存到数据库,防止同一笔订单,多次发放内购商品。

苹果服务器返回给我们数据:

{
	"environment": "Sandbox",
	"receipt": {
		"adam_id": 0,
		"app_item_id": 0,
		"application_version": "2.0.6",
		"bundle_id": "com.appstoreMJB.mobao",
		"download_id": 0,
		"in_app": [{
			"is_trial_period": "false",
			"original_purchase_date": "2019-10-25 01:07:14 Etc/GMT",
			"original_purchase_date_ms": "1571965634000",
			"original_purchase_date_pst": "2019-10-24 18:07:14 America/Los_Angeles",
			"original_transaction_id": "1000000583857816",
			"product_id": "com.wha.***.6",
			"purchase_date": "2019-10-25 01:07:14 Etc/GMT",
			"purchase_date_ms": "1571965634000",
			"purchase_date_pst": "2019-10-24 18:07:14 America/Los_Angeles",
			"quantity": "1",
			"transaction_id": "1000000583857816"
		}],
		"original_application_version": "1.0",
		"original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
		"original_purchase_date_ms": "1375340400000",
		"original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
		"receipt_creation_date": "2019-10-25 01:07:14 Etc/GMT",
		"receipt_creation_date_ms": "1571965634000",
		"receipt_creation_date_pst": "2019-10-24 18:07:14 America/Los_Angeles",
		"receipt_type": "ProductionSandbox",
		"request_date": "2019-10-25 01:07:16 Etc/GMT",
		"request_date_ms": "1571965636457",
		"request_date_pst": "2019-10-24 18:07:16 America/Los_Angeles",
		"version_external_identifier": 0
	},
	"status": 0
}

验证代码:

package com.wha.controller;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.wha.dao.RecRechargeMapper;
import com.wha.model.RecRecharge;
import com.wha.model.sys.RequestLog;
import com.wha.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wha.model.ReturnData;
import com.wha.service.PayService;
import com.wha.util.CommonUtils;

@Controller
@RequestMapping("iap")
public class IapController {
    private Logger logger = Logger.getLogger(this.getClass());
    @Autowired
    private PayService payService;
    @Autowired
    private RecRechargeMapper recRechargeMapper;
    // 正式  购买凭证验证地址
    private static final String certificateUrl = "https://buy.itunes.apple.com/verifyReceipt";

    // 沙箱 购买凭证验证地址
    private static final String certificateUrlTest = "https://sandbox.itunes.apple.com/verifyReceipt"; 
    /**
     * 接收iOS端发过来的购买凭证
     *
     * @param userId
     * @param certificateCode
     * @param
     * @throws IOException
     */
    @RequestMapping("/setIapCertificate")
    public void setIapCertificate(String userId, String certificateCode , HttpServletResponse response, HttpServletRequest request) throws IOException {
        if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(certificateCode )) {
            CommonUtils.returnCode(response, new ReturnData(1, "缺少参数", null));
            return;
        }
        String url = certificateUrl;  
        try {
            String sendHttpsCoon = sendHttpsCoon(url, certificateCode);
            JSONObject json = JSONObject.parseObject(sendHttpsCoon);
            if ("21007".equals(json.get("status").toString())) {
                url = certificateUrlTest;
                sendHttpsCoon = sendHttpsCoon(url, certificateCode);  //发送请求
            }
            JSONObject jsonObject = JSONObject.parseObject(sendHttpsCoon); 
            if ("0".equals(jsonObject.get("status").toString())) {	//苹果服务器向返回status结果
                JSONArray inapp = (JSONArray) ((JSONObject) jsonObject.get("receipt")).get("in_app");
                if (inapp.size() > 0) { //如果订单状态成功在判断in_app这个字段有没有,没有直接就返回失败了。如果存在的话,遍历整个数组,通过客户端给的transaction_id 来比较
                    JSONObject parseObject = (JSONObject) inapp.get(0);
                    String product_id = parseObject.get("product_id").toString();
                    String transaction_id = parseObject.get("transaction_id").toString();
                    RecRecharge recRecharge = recRechargeMapper.selectByTransactionId(transaction_id);
                    if (!CommonUtils.isEmptyString(product_id) && !CommonUtils.isEmptyString(transaction_id) && null == recRecharge) {//判重,避免重复分发内购商品。收到客户端上报的transaction_id后,直接MD5后去数据库查,能查到说明是重复订单就不做处理
                        String[] split = product_id.split("com.wha.***."); //获取订单价格
                        payService.payReturn("A" + transaction_id, userId, split[1]);//处理自己的逻辑,将transaction_id存入数据库,完成订单
                    }
                }
            }
            CommonUtils.returnCode(response, new ReturnData(0, "ok", jsonObject));
        } catch (Exception e) {
            logger.error("购买凭证验证错误", e);
            CommonUtils.returnCode(response, new ReturnData(1, "购买凭证验证错误", null));
        } 
    }
   /**
     * 重写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;
        }
    } 
}

丢单:
引起内购丢单的主要操作其实是当用户点击内购商品时,苹果服务器太慢了,支付页面一直不出来。结果用户退出或者杀死App,这时候在Home页面,支付框又弹出来了,然后用户点击支付,成功后在打开App发现丢单。
一般这种只要你在Appdelegate的didFinishLaunchingWithOptions方法就开始对苹果内购回调做监听,然后把所有相关内购的东西抽出来做一个单例即可解决丢单

你可能感兴趣的:(java对接IOS 内购)