美文网首页
java后台接入IOS内购

java后台接入IOS内购

作者: 打不死的小强8号 | 来源:发表于2020-05-15 17:51 被阅读0次

参考文档

说明

后台处理:将购买凭证(接收IOS端)发送到苹果的服务器验证,并将验证结果返回给客户端。

代码

工具类


import javax.net.ssl.*;
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;

/**
 * 苹果IAP内购验证工具类
 *
 * 官网:https://developer.apple.com/documentation/storekit/in-app_purchase
 * 参考:https://blog.csdn.net/lbd_123/article/details/87276204
 * @author xiaoqiang
 * @date 2020-05-11 14:37
 */
public class IosVerifyUtil {
    private static class TrustAnyTrustManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[] {};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    /**
     * 苹果服务器验证
     *
     * @param receipt
     *            账单
     * @url 要验证的地址
     * @return null 或返回结果 沙盒 https://sandbox.itunes.apple.com/verifyReceipt
     *
     *  21000App Store无法读取你提供的JSON数据
     *  21002 订单数据不符合格式
     *  21003 订单无法被验证
     *  21004 你提供的共享密钥和账户的共享密钥不一致
     *  21005 订单服务器当前不可用
     *  21006 订单是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
     *  21007 订单信息是测试用(sandbox),但却被发送到产品环境中验证
     *  21008 订单信息是产品环境中使用,但却被发送到测试环境中验证
     */
    public static String buyAppVerify(String receipt,int type) {
        String url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";
        String url_verify = "https://buy.itunes.apple.com/verifyReceipt";
        // 环境判断 线上/开发环境用不同的请求链接
        String url = "";
        if(type==0){
            // 沙盒测试
            url = url_sandbox;
        }else{
            // 线上测试
            url = url_verify;
        }

        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","application/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 + "\"}");
            hurlBufOus.write(str.getBytes());
            hurlBufOus.flush();

            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.printStackTrace();
        }
        return null;
    }

}

业务代码片段
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;// 日志用了lombok,报错可以将log相关删除
/**
*@param productId 商品ID
*@param receipt 购买凭证
*/
public Result iosInAppPurchase(String productId, String receipt) {
        if (StringUtils.isBlank(receipt)){
            return Result.fail("receipt不能为空");
        }
        if (StringUtils.isBlank(productId)){
            return Result.fail("productId不能为空");
        }
        // 0沙盒 非0即线上
        String verifyResult = IosVerifyUtil.buyAppVerify(receipt, 0);
        if (StringUtils.isBlank(verifyResult)) {// 苹果服务器没有返回验证结果
            log.info("IOS内购(充值)=>苹果服务器没有返回验证结果");
            return Result.error("苹果服务器没有返回验证结果");

        } else {// 苹果验证有返回结果
            JSONObject job = JSONObject.parseObject(verifyResult);
            log.info("IOS内购(充值)=>苹果服务器返回验证结果:{}", job);
            String states = job.getString("status");
            //是沙盒环境,应沙盒测试,否则执行下面
            if ("21007".equals(states)) {
                //2.在沙盒测试  发送平台验证
                verifyResult = IosVerifyUtil.buyAppVerify(receipt, 0);
                job = JSONObject.parseObject(verifyResult);
                states = job.getString("status");
            }
            if (states.equals("0")) { // 前端所提供的收据是有效的    验证成功
                String r_receipt = job.getString("receipt");
                JSONObject returnJson = JSONObject.parseObject(r_receipt);
                String in_app = returnJson.getString("in_app");
                JSONArray jsonArray = JSONArray.parseArray(in_app);
                log.info("IOS内购(充值)=>苹果服务器返回验证结果订单数量:{}", jsonArray.size());
                for (int i = 0; i < jsonArray.size(); i++) {
                    if (rechargeBallCoinIosVo.getBallCoinRechargeId().equals(jsonArray.getJSONObject(i).get("product_id") == null ? null : jsonArray.getJSONObject(i).get("product_id").toString())) {
                        // 订单号
                        String transactionId = jsonArray.getJSONObject(i).get("transaction_id") == null ? null : jsonArray.getJSONObject(i).get("transaction_id").toString();
                     
                        /* 自己的业务处理 */
                        // 1.创建订单信息
                        // 2. 增加用户虚拟币数量
                        // 3. 添加用户消费记录
                    }
                }
            } else {
                return Result.error(job);
            }
        }
        return Result.success();
    }

统一返回结果



import java.util.HashMap;
import java.util.Objects;

/**
 * @author xiaoqiang
 * @Description 返回结果信息
 * @date 2019/3/21 16:50
 */
public class Result extends HashMap<Object, Object> {

    public static String CODE_KEY = "code";
    public static String MSG_KEY = "msg";
    public static String DATA_KEY = "data";


    /**
     * 调用结果(-1 异常 0 失败 1 成功)
     */
    public enum CallResult {
        /**
         * 异常
         */
        ERROR(-1, "异常"),
        /**
         * 失败
         */
        FAIL(0, "失败"),
        /**
         * 成功
         */
        SUCCESS(1, "成功");
        /**
         * code码
         */
        private Integer code;
        /**
         * 提示信息
         */
        private String msg;

        private CallResult(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }

        public Integer getCode() {
            return code;
        }

        public String getMsg() {
            return msg;
        }
    }

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    /**
     * 成功
     *
     * @return
     */
    public static Result success() {
        Result result = new Result();
        result.put(CODE_KEY, Result.CallResult.SUCCESS.getCode());
        result.put(MSG_KEY, Result.CallResult.SUCCESS.getMsg());
        result.put(DATA_KEY, null);
        return result;
    }

    public static Result success(String msg) {
        Result result = success();
        result.put(MSG_KEY, msg);
        return result;
    }

    public static Result success(ResultCode code, Object obj) {
        Result result = success();
        result.put(CODE_KEY, code.code());
        result.put(MSG_KEY, code.msg());
        result.put(DATA_KEY, obj);
        return result;
    }

    public static Result success(Object obj) {
        Result result = success();
        result.put(DATA_KEY, obj);
        return result;
    }

    public static Result success(String msg, Object obj) {
        Result result = success();
        result.put(MSG_KEY, msg);
        result.put(DATA_KEY, obj);
        return result;
    }

    /**
     * 失败
     *
     * @return
     */
    public static Result fail() {
        Result result = new Result();
        result.put(CODE_KEY, Result.CallResult.FAIL.getCode());
        result.put(MSG_KEY, Result.CallResult.FAIL.getMsg());
        result.put(DATA_KEY, null);
        return result;
    }

    public static Result fail(String msg) {
        Result result = fail();
        result.put(MSG_KEY, msg);
        return result;
    }

    public static Result fail(ResultCode code, Object obj) {
        Result result = fail();
        result.put(CODE_KEY, code.code());
        result.put(MSG_KEY, code.msg());
        result.put(DATA_KEY, obj);
        return result;
    }

    public static Result fail(Object obj) {
        Result result = fail();
        result.put(DATA_KEY, obj);
        return result;
    }

    public static Result fail(String msg, Object obj) {
        Result result = fail();
        result.put(MSG_KEY, msg);
        result.put(DATA_KEY, obj);
        return result;
    }

    /**
     * 异常
     *
     * @return
     */
    public static Result error() {
        Result result = new Result();
        result.put(CODE_KEY, Result.CallResult.ERROR.getCode());
        result.put(MSG_KEY, Result.CallResult.ERROR.getMsg());
        result.put(DATA_KEY, null);
        return result;
    }

    public static Result error(String msg) {
        Result result = error();
        result.put(MSG_KEY, msg);
        return result;
    }

    public static Result error(Object obj) {
        Result result = error();
        result.put(DATA_KEY, obj);
        return result;
    }

    public static Result error(String msg, Object obj) {
        Result result = error();
        result.put(MSG_KEY, msg);
        result.put(DATA_KEY, obj);
        return result;
    }

    /**
     * 自定义
     *
     * @param code
     * @param msg
     * @param obj
     * @return
     */
    public static Result custom(Object code, Object msg, Object obj) {
        Result result = new Result();
        result.put(CODE_KEY, code);
        result.put(MSG_KEY, msg);
        result.put(DATA_KEY, obj);
        return result;
    }

    /**
     * 自定义返回
     * @param code
     * @param obj
     * @return
     */
    public static Result custom(ResultCode code, Object obj) {
        Result result = new Result();
        result.put(CODE_KEY, code.code());
        result.put(MSG_KEY, code.msg());
        result.put(DATA_KEY, obj);
        return result;
    }


    /**
     *
     * @return
     */
    public static Result notLogin(){
        Result result = new Result();
        result.put(CODE_KEY, ResultCode.NOT_LOGIN.code());
        result.put(MSG_KEY, ResultCode.NOT_LOGIN.msg());
        result.put(DATA_KEY, null);
        return result;
    }

    /**
     * 验证成功
     * @param result
     * @return
     */
    public static boolean isSuccess(Result result) {
        return !Objects.isNull(result.get(CODE_KEY)) && (Integer) result.get(CODE_KEY) == 1;
    }

}

购买凭证解析后JSON数据样列

{
    "environment": "Sandbox",
    "receipt": {
        "in_app": [
            {
                "transaction_id": "1000000665290365",
                "original_purchase_date": "2020-05-15 09:08:45 Etc/GMT",
                "quantity": "1",
                "original_transaction_id": "1000000665290365",
                "purchase_date_pst": "2020-05-15 02:08:45 America/Los_Angeles",
                "original_purchase_date_ms": "1589533725000",
                "purchase_date_ms": "1589533725000",
                "product_id": "1237265852472238082",
                "original_purchase_date_pst": "2020-05-15 02:08:45 America/Los_Angeles",
                "is_trial_period": "false",
                "purchase_date": "2020-05-15 09:08:45 Etc/GMT"
            }
        ],
        "adam_id": 0,
        "receipt_creation_date": "2020-05-15 09:08:46 Etc/GMT",
        "original_application_version": "1.0",
        "app_item_id": 0,
        "original_purchase_date_ms": "1375340400000",
        "request_date_ms": "1589533728625",
        "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": "2020-05-15 02:08:46 America/Los_Angeles",
        "receipt_type": "ProductionSandbox",
        "bundle_id": "com.bzsports.bz",
        "receipt_creation_date_ms": "1589533726000",
        "request_date": "2020-05-15 09:08:48 Etc/GMT",
        "version_external_identifier": 0,
        "request_date_pst": "2020-05-15 02:08:48 America/Los_Angeles",
        "download_id": 0,
        "application_version": "6338"
    },
    "status": 0
}

相关文章

  • java后台接入IOS内购

    参考文档 官方文档 https://www.cnblogs.com/TheYouth/p/6847014.html...

  • 苹果内购

    苹果内购大至分三部分:(注:接入苹果内购,应用需要提供游客登录方式,不然审核不过) 苹果后台配置 获取商品列表 购...

  • iOS H5支付宝 微信支付.

    iOS开发中, 如果不想接入支付的SDK 或者 商品只能走内购, 但是又不想要走内购(不想给苹果手续费...), ...

  • iOS内购掉单处理教程

    1.前言 最近公司的项目需要接入苹果内购支付。看了下项目里面,内购这个模块的功能之前就已经写好了,然后就跟后台调试...

  • Unity 接入IAP(下)IOS篇

    接上篇,本篇着重介绍IOS接入后,如何验证内购是否成功。 接入IAP的方法,在上一篇已经介绍,方法都是一样,还是创...

  • iOS 苹果内购流程

    本文参考: iOS开发之内购完全笔记 iOS开发内购全套图文教程 iOS应用程序内购/内付费(一) 代码...

  • Apple内购相关

    最近项目中有接入apple内购相关的功能,在此记录下一些经验总结 key_1 iTunes Connect后台,创...

  • iOS 内购注意

    IOS 内购注意(项目加入内购后,发现正式环境支付正常,测试环境失败!!!原因是后台查询支付凭证无效!!) 提交给...

  • iOS内购流程文档-Lion

    iOS内购流程: iOS内购 什么时候用到呢? 虚拟产品就需要用到iOS内购;购买的商品,是在本app中...

  • iOS-内购

    前言:关于iOS内购,参考两篇博文 iOS-iOS内购流程(手把手图文教程)iOS内购你看我就够了(埋坑篇) 我自...

网友评论

      本文标题:java后台接入IOS内购

      本文链接:https://www.haomeiwen.com/subject/drqhohtx.html