接入Apple Pay流程

接入Apple Pay流程

最近在做IOS内购的后端事项,所以总结下整个流程,都是参考网上大佬的。

首先我们要搞清楚两个概念:苹果支付(Apple Pay)和IOS内购(IAP)

苹果支付:是一种支付的方式。和微信支付、支付宝等一样。

内购:是只要在iPhone App上购买的不是实物产品(也就是虚拟产品如qq币、皮肤、英雄......) 都需要走内购流程,苹果这里面抽走30%(真想说一句,太黑了)。

服务端

iOS内购充值,是通过客户端接入iOS的IAP模块(In-App Purchase)后,由客户端发起充值,然后再把充值数据(receipt)发给服务端,最后由服务端远程调用AppStore服务器验证。

 

接入Apple Pay流程_第1张图片

 

 

我们会发现这里和平时开发微信支付、支付宝支付的流程是不太一样的,微信、支付宝会提供后端的接口,由业务的服务端和支付宝的服务端交互,用户支付成功后,支付宝的服务器会异步给我们的后端通知,而在这里却是苹果客户端直接请求了苹果的服务端,成功后,向业务服务端再二次验证进行业务上扣款等操作的处理。

附:

支付宝支付流程

 

接入Apple Pay流程_第2张图片

 

 

 

接入Apple Pay流程_第3张图片

 

 

苹果审核App时,是在沙盒环境下测试。所以,当App提交苹果审核时,服务端需换成沙盒环境,否则就无法通过苹果审核。通常游戏开发商都会搞一个审核服来给苹果审核,这样,审核服用沙盒环境,正式服用正式环境。

但对于很多App应用开发商来说,专门搞一个服务器显然增加了不少成本。其实还是有办法处理的,方法如下:

根据验单返回的 status 字段:

 

接入Apple Pay流程_第4张图片

 

 

当 status = 21007 时,把请求地址换成沙盒测试地址,再次请求验单。

[iOS内购充值 服务器端处理 – 没有开花的树]

 

接入Apple Pay流程_第5张图片

 

 

[php处理苹果支付接口回调 - xijieyuan2qi的博客 - CSDN博客]

官方:

[苹果的支付回调的接口文档地址]

客户端内购流程:

[干货 | 关于Apple Pay接入和开发,看这一篇就够了]

[iOS开发支付篇-内购(IAP) - 梁飞宇 - 博客园]

[ios 内购详解(2019) - 简书]

[苹果支付流程]

[iOS 内购(In-App Purchase)总结 | 笑忘书店]

[iOS内购掉单问题 | bomo的开发随笔]

[苹果 IAP 开发中的那些坑和掉单问题 - iOS - 掘金]

[苹果IAP开发中的那些坑和掉单问题 - 铁蕾的个人博客]

验单返回数据格式

  • 老版本IOS返回
{
    "receipt": {
        "original_purchase_date_pst": "2016-12-03 01:11:01 America/Los_Angeles", 
        "purchase_date_ms": "1480756261254", 
        "unique_identifier": "96f51b28f628493709966f33a1fe7ba", 
        "original_transaction_id": "1000000255766", 
        "bvrs": "82", 
        "transaction_id": "1000000255766", 
        "quantity": "1", 
        "unique_vendor_identifier": "FE358-1362-40FD-870F-DF788AC5", 
        "item_id": "11822945", 
        "product_id": "rjkf_itemid_1", 
        "purchase_date": "2016-12-03 09:11:01 Etc/GMT", 
        "original_purchase_date": "2016-12-03 09:11:01 Etc/GMT", 
        "purchase_date_pst": "2016-12-03 01:11:01 America/Los_Angeles", 
        "bid": "com.xxx.xxx", 
        "original_purchase_date_ms": "1480756261254"
    }, 
    "status": 0
}
复制代码
  • 新版IOS返回(7.0以后)
{
    "status": 0, 
    "environment": "Sandbox", 
    "receipt": {
        "receipt_type": "ProductionSandbox", 
        "adam_id": 0, 
        "app_item_id": 0, 
        "bundle_id": "com.xxx.xxx", 
        "application_version": "84", 
        "download_id": 0, 
        "version_external_identifier": 0, 
        "receipt_creation_date": "2016-12-05 08:41:57 Etc/GMT", 
        "receipt_creation_date_ms": "1480927317000", 
        "receipt_creation_date_pst": "2016-12-05 00:41:57 America/Los_Angeles", 
        "request_date": "2016-12-05 08:41:59 Etc/GMT", 
        "request_date_ms": "1480927319441", 
        "request_date_pst": "2016-12-05 00:41:59 America/Los_Angeles", 
        "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", 
        "original_application_version": "1.0", 
        "in_app": [
            {
                "quantity": "1", 
                "product_id": "rjkf_itemid_1", 
                "transaction_id": "10000003970", 
                "original_transaction_id": "10000003970", 
                "purchase_date": "2016-12-05 08:41:57 Etc/GMT", 
                "purchase_date_ms": "1480927317000", 
                "purchase_date_pst": "2016-12-05 00:41:57 America/Los_Angeles", 
                "original_purchase_date": "2016-12-05 08:41:57 Etc/GMT", 
                "original_purchase_date_ms": "1480927317000", 
                "original_purchase_date_pst": "2016-12-05 00:41:57 America/Los_Angeles", 
                "is_trial_period": "false"
            }
        ]
    }
}
复制代码

 

一文中说到

这特么也太坑了,返回的数据格式还会不一样?查了一下资料,大概说是:ios7(这个数字可能不对,印象中的,大概就是这么个意思)以前的版本支付和现在的版本支付,去验证时会返回不同的数据结构。WTF,那么作为服务器端,只能先麻烦点,先判断结构中是否包含:in_app,这部分,如果包含就用下面的结构解析,反之则用第一种结构来处理。如果status=0,我们就可以根据客户端提交上来的订单号,将订单状态进行变更了,并将验证结果返回给客户端。

验单代码示例

// 苹果服务验证
package com.miracle9.animal.util;
 
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;  
  
/** 
 * 苹果IAP内购验证工具类
 * @ClassName: IosVerify 
 * @Description:Apple Pay 
 */  
public class IosVerifyUtil {  
  
    private static class TrustAnyTrustManager implements X509TrustManager {  
  
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
        }  
  
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
        }  
  
        public X509Certificate[] getAcceptedIssuers() {  
            return new X509Certificate[] {};  
        }  
    }  
  
    private static class TrustAnyHostnameVerifier implements HostnameVerifier {  
        public boolean verify(String hostname, SSLSession session) {  
            return true;  
        }  
    }  
  
    private static final String url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";  
    private static final String url_verify = "https://buy.itunes.apple.com/verifyReceipt";  
  
    /** 
     * 苹果服务器验证 
     *  
     * @param receipt 
     *            账单 
     * @url 要验证的地址 
     * @return null 或返回结果 沙盒 https://sandbox.itunes.apple.com/verifyReceipt 
     *  
     */  
    public static String buyAppVerify(String receipt,int type) {  
    	//环境判断 线上/开发环境用不同的请求链接  
    	String url = "";
    	if(type==0){
    		url = url_sandbox; //沙盒测试
    	}else{
    		url = url_verify; //线上测试
    	}
        //String url = EnvUtils.isOnline() ?url_verify : 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 + "\"}");//拼成固定的格式传给平台
            // 上面这个有问题(21002),改成下面这样返回正常。直接将receipt当参数发到苹果验证就行,不用拼格式
            String str = String.format(Locale.CHINA, 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;  
    }  
  
    /** 
     * 用BASE64加密 
     *  
     * @param str 
     * @return 
     */  
    public static String getBASE64(String str) {  
        byte[] b = str.getBytes();  
        String s = null;  
        if (b != null) {  
            s = new sun.misc.BASE64Encoder().encode(b);  
        }  
        return s;  
    }  
  
} 
复制代码

作者:何小H

转载地址:https://juejin.cn/post/6844904001897512967

你可能感兴趣的:(Java,java)