一 Apple内购流程
- 先在Apple开发者后台创建产品,同时与自己系统的产品进行关联
- 用户在APP端选择购买的产品,在自己系统中进行下单并获取单号
- 根据Apple产品ID在Apple服务中创建订单并支付,获取到receipt
- 拿着自己系统的单号和支付后的receipt调用自己服务端验签接口
- 自己服务端调用Apple服务的验证接口(需要先调用沙盒验证,再调用生产验证;自己测试及APP审核时会走沙盒)
- 根据验证结果处理自身业务逻辑
在这个流程中客户端也需要考虑一些异常情况,由于我只是一个Javaer,客户端这里的逻辑不进行赘述(专业的事情要交给专业的人去处理)
了解了整个内购的流程之后我们也就能够知道作为后端研发我们要进行哪些工作了。
- 提供验证接口(如参为订单号和支付凭证)
- 调用Apple服务的验证接口
- 根据验证结果处理支付业务逻辑
二 代码展示
Talk is cheap, show me the code!
接口定义很简单,这里就不粘贴了,验证的逻辑需要先验证沙盒,然后根据返回的状态码判断是否需要再调用生产验证接口。因为客户端开发及APP提审时使用的是沙盒支付。伪代码如下:
java
...省略
AppleCertificateResultDTO certificateResultDTO = AppleUtil.checkCertificate(receipt, false);
if (Objects.equals(certificateResultDTO.getStatus(), 21007)) {
certificateResultDTO = AppleUtil.checkCertificate(receipt, true);
}
...省略
1 验签工具类
java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rise.adventure.trade.domain.dto.pay.AppleCertificateResultDTO;
import com.rise.common.enums.RiseCommonResultCode;
import com.rise.common.exception.RiseException;
import lombok.extern.slf4j.Slf4j;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@Slf4j
public class AppleUtil {
//购买凭证验证地址
private static final String CERTIFICATE_URL = "https://buy.itunes.apple.com/verifyReceipt";
//测试的购买凭证验证地址
private static final String TEST_CERTIFICATE_URL = "https://sandbox.itunes.apple.com/verifyReceipt";
/**
* 重写X509TrustManager
*/
private static TrustManager trustManager = 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 {
}
};
public static AppleCertificateResultDTO checkCertificate(String certificate, boolean qaEnv) {
String certificateUrl = CERTIFICATE_URL;
if (qaEnv) {
certificateUrl = TEST_CERTIFICATE_URL;
}
try {
SSLContext ssl = SSLContext.getInstance("SSL");
ssl.init(null, new TrustManager[]{trustManager}, null);
//打开连接
HttpsURLConnection conn = (HttpsURLConnection) new URL(certificateUrl).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", certificate);
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;
StringBuffer resBuilder = new StringBuffer();
while ((line = reader.readLine()) != null) {
resBuilder.append(line);
}
log.info("苹果支付验证结果为: 【{}】", resBuilder);
return JSON.parseObject(resBuilder.toString(), AppleCertificateResultDTO.class);
} catch (Exception e) {
log.error("苹果支付验证结果失败: ", e);
throw new RiseException(RiseCommonResultCode.UNKNOWN, "苹果支付结果验证失败");
}
}
}
2 receipt解析结果对象
java
@Setter
@Getter
public class AppleCertificateResultDTO implements Serializable {
private Integer status;
private String environment;
private AppleReceiptDTO receipt;
}
@Setter
@Getter
public class AppleReceiptDTO implements Serializable {
@JSONField(name = "adam_id")
private String adamId;
@JSONField(name = "app_item_id")
private String appItemId;
@JSONField(name = "application_version")
private String applicationVersion;
@JSONField(name = "bundle_id")
private String bundleId;
@JSONField(name = "download_id")
private String downloadId;
@JSONField(name = "original_application_version")
private String originalApplicationVersion;
@JSONField(name = "original_purchase_date")
private String originalPurchaseDate;
@JSONField(name = "original_purchase_date_ms")
private String originalPurchaseDateMs;
@JSONField(name = "original_purchase_date_pst")
private String originalPurchaseDatePst;
@JSONField(name = "receipt_creation_date")
private String receiptCreationDate;
@JSONField(name = "receipt_creation_date_ms")
private String receiptCreationDateMs;
@JSONField(name = "receipt_creation_date_pst")
private String receiptCreationDatePst;
@JSONField(name = "receipt_type")
private String receiptType;
@JSONField(name = "request_date")
private String requestDate;
@JSONField(name = "request_date_ms")
private String requestDateMs;
@JSONField(name = "request_date_pst")
private String requestDatePst;
@JSONField(name = "version_external_identifier")
private String versionExternalIdentifier;
@JSONField(name = "in_app")
private List<AppDTO> inApp;
@Setter
@Getter
public static class AppDTO implements Serializable {
@JSONField(name = "in_app_ownership_type")
private String inAppOwnershipType;
@JSONField(name = "is_trial_period")
private Boolean isTrialPeriod;
@JSONField(name = "original_purchase_date")
private String originalPurchaseDate;
@JSONField(name = "original_purchase_date_ms")
private String originalPurchaseDateMs;
@JSONField(name = "original_purchase_date_pst")
private String originalPurchaseDatePst;
@JSONField(name = "original_transaction_id")
private String originalTransactionId;
@JSONField(name = "product_id")
private String productId;
@JSONField(name = "purchase_date")
private String purchaseDate;
@JSONField(name = "purchase_date_ms")
private String purchaseDateMs;
@JSONField(name = "purchase_date_pst")
private String purchaseDatePst;
@JSONField(name = "quantity")
private Integer quantity;
@JSONField(name = "transaction_id")
private String transactionId;
}
}
三 解析结果status值说明
状态码 | 描述 |
---|---|
0 | 验证成功 |
21002 | 收据无法被验证 |
21003 | 收据有效,但不属于开发者 |
21005 | 收据已使用 |
21007 | 沙盒收据无法用于生产环境 |
21008 | 生产环境收据无法用于沙盒环境 |
Apple内购的流程在后端这块的逻辑应该还是比较简单的,只需要交验下客户端传递的支付凭证即可,然后处理下支付成功的逻辑即可。
如果本文对您有些许的帮助,请大方的关注作者吧。
欢迎大家关注我的公众号为我加油打气:【Bug搬运小能手】。文章会优先在公众号发布。