嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

周梦康 发表于 2016-03-29 14326 次浏览 标签 : Java

免费领取阿里云优惠券 我的直播 - 《PHP 进阶之路》

苹果支付的逻辑如下

  1. 首先客户端先请求苹果支付中心,支付中心返回给客户端一堆加密的数据。

  2. 然后客户端把这段加密的数据 base64之后传给后端。

  3. 最后由后端再去请求苹果支付中心来验证这次购买是否成功。验证通过,服务器端对业务逻辑进行处理(增加应用内的金币等)。

代码实现

服务器端请求验证通过之后的数据应该类似于

{
    environment = Sandbox;
    receipt =     {
        "adam_id" = 0;
        "app_item_id" = 0;
        "application_version" = 201512181924;
        "bundle_id" = "me.topit.awesome.t16.pk";
        "download_id" = 0;
        "in_app" =         (
                        {
                "is_trial_period" = false;
                "original_purchase_date" = "2016-03-29 07:55:05 Etc/GMT";
                "original_purchase_date_ms" = 1459238105000;
                "original_purchase_date_pst" = "2016-03-29 00:55:05 America/Los_Angeles";
                "original_transaction_id" = 1000000202075562;
                "product_id" = "me.topit.sixteenpk.6";
                "purchase_date" = "2016-03-29 07:55:05 Etc/GMT";
                "purchase_date_ms" = 1459238105000;
                "purchase_date_pst" = "2016-03-29 00:55:05 America/Los_Angeles";
                quantity = 1;
                "transaction_id" = 1000000202075562;

            }
        );
        "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" = "2016-03-29 07:55:06 Etc/GMT";
        "receipt_creation_date_ms" = 1459238106000;
        "receipt_creation_date_pst" = "2016-03-29 00:55:06 America/Los_Angeles";
        "receipt_type" = ProductionSandbox;
        "request_date" = "2016-03-29 07:55:08 Etc/GMT";
        "request_date_ms" = 1459238108417;
        "request_date_pst" = "2016-03-29 00:55:08 America/Los_Angeles";
        "version_external_identifier" = 0;
    };
    status = 0;
}

首先验证status是否为0,0表示正常。然后才是其他业务逻辑的判断。

String endpoint;
if(TopitConfig.getBoolean("app.debug")){
    endpoint = "https://sandbox.itunes.apple.com/verifyReceipt";
}else{
    endpoint = "https://buy.itunes.apple.com/verifyReceipt";
}

JSONObject jsonObject = new JSONObject();
jsonObject.put("receipt-data",data);

BasicHeader[] basicHeaders = new BasicHeader[1];
basicHeaders[0] = new BasicHeader("Content-Type","application/json");

String res = HttpRequestUtil.post(endpoint, jsonObject.toString(), basicHeaders, true);

JSONObject resObject = new JSONObject(res);

try{
    int status = Integer.parseInt(resObject.get("status").toString());

    if(status == 0){
        JSONObject inApp = (JSONObject) resObject.getJSONObject("receipt").getJSONArray("in_app").get(0);
        int ts = (int) (inApp.getLong("purchase_date_ms")/1000);
        rechargeService.applePay(loginId,inApp.get("product_id").toString(),inApp.get("transaction_id").toString(),ts);
        return success();
    }else{
        return error("充值失败");
    }
}catch (Exception e){
    e.printStackTrace();
    return error("充值失败");
}

上面用到HTTPS POST 直接发送实体内容的方法,如下

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import org.apache.http.conn.ssl.SSLSocketFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;

/**
 * Created by zhoumengkang on 19/1/16.
 */
public class HttpRequestUtil {
    public static String post(String url, String data, Header[] headers, boolean ssl) {
        HttpClient httpClient = getClient(ssl);
        String responseText = "";
        HttpResponse httpResponse = null;

        try {
            HttpPost httpPost = new HttpPost(url);
            RequestConfig requestConfig = RequestConfig.custom().
                    setSocketTimeout(5000).
                    setConnectTimeout(5000).
                    build();
            httpPost.setConfig(requestConfig);

            httpPost.setHeaders(headers);

            httpPost.setEntity(new StringEntity(data, "UTF-8"));

            httpResponse = httpClient.execute(httpPost);
            if (httpResponse.getStatusLine().getStatusCode() == 200) {
                responseText = EntityUtils.toString(httpResponse.getEntity());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return responseText;
    }

    public static HttpClient getClient(boolean isSSL) {

        HttpClient httpClient = new DefaultHttpClient();

        if (isSSL) {
            X509TrustManager xtm = new X509TrustManager() {
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

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

                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };

            try {
                SSLContext ctx = SSLContext.getInstance("TLS");

                ctx.init(null, new TrustManager[]{xtm}, null);

                SSLSocketFactory socketFactory = new SSLSocketFactory(ctx);

                httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));

            } catch (Exception e) {
                throw new RuntimeException();
            }
        }

        return httpClient;
    }
}

是不是苹果测试服务器不稳定?偶尔会返回错误,但是实际我请求都是购买同一个商品

{"status":21002, "exception":"java.lang.IllegalArgumentException"}

同样的请求多次操作,发现有成功有失败,不知道是什么原因。

苹果应用内支付,服务器端的实现

The data in the receipt-data property was malformed or missing.

https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1

嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

评论列表