参考文档
- 官方文档
- https://www.cnblogs.com/TheYouth/p/6847014.html?utm_source=itdadao&utm_medium=referral
- https://www.cnblogs.com/lazydays/p/12078006.html
说明
后台处理:将购买凭证(接收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
购买凭证解析后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
}