iphone下单成功->ios返回recepit凭据->iphone获取凭据请求java服务端->java服务端请求苹果进行二次验证->验证通过返回产品和交易号->java服务器存储并返回给前端金额提示消费成功
直接上代码
// 正式 购买凭证验证地址
private static final String certificateUrl = "https://buy.itunes.apple.com/verifyReceipt";
// 沙箱 购买凭证验证地址
private static final String certificateUrlTest = "https://sandbox.itunes.apple.com/verifyReceipt";
===========================================核心代码块=====================================================
if (StringUtils.isEmpty(certificateCode )) {
return GeneralResponse.error("缺少参数");
}
String url = certificateUrl;
try {
String sendHttpsCoon = sendHttpsCoon(url, certificateCode);
if(StringUtils.isBlank(sendHttpsCoon)){
for(int i=0; i<5; i++){
sendHttpsCoon = sendHttpsCoon(url, certificateCode);
if(StringUtils.isNotBlank(sendHttpsCoon)){
break;
}
}
}
JSONObject json = JSONObject.parseObject(sendHttpsCoon);
if ("21007".equals(json.get("status").toString())) {
url = certificateUrlTest;
sendHttpsCoon = sendHttpsCoon(url, certificateCode); //发送请求
if(StringUtils.isBlank(sendHttpsCoon)){
for(int i=0; i<5; i++){
sendHttpsCoon = sendHttpsCoon(url, certificateCode);
if(StringUtils.isNotBlank(sendHttpsCoon)){
break;
}
}
}
}
JSONObject jsonObject = JSONObject.parseObject(sendHttpsCoon);
log.info("凭证:{},certificateCode:{},returnJson:{}", jsonObject.toJSONString(), certificateCode, jsonObject.toJSONString());
JifenLogEntity jifenLogEntity = new JifenLogEntity();
if ("0".equals(jsonObject.get("status").toString())) { //苹果服务器向返回status结果
JSONObject parseObject =jsonObject.getJSONObject("receipt");
String inApp = parseObject.getString("in_app");
List inApps = JSONObject.parseArray(inApp, HashMap.class);
if (CollectionUtils.isNotEmpty(inApps)) { //如果订单状态成功在判断in_app这个字段有没有,没有直接就返回失败了。如果存在的话,遍历整个数组,通过客户端给的transaction_id 来比较
for (HashMap app : inApps) {
String productId = app.get("product_id").toString();
String transactionId = app.get("transaction_id").toString();
List recRecharge = recRechargeRepository.findByTransactionId("A" + transactionId);
if (StringUtils.isNotBlank(productId) && StringUtils.isNotBlank(transactionId) && CollectionUtils.isEmpty(recRecharge)) {//判重,避免重复分发内购商品。收到客户端上报的transaction_id后,直接MD5后去数据库查,能查到说明是重复订单就不做处理
//处理自己的逻辑,将transaction_id存入数据库,完成订单
RecRechargeEntity recRechargeEntity = new RecRechargeEntity();
recRechargeEntity.setTransactionId("A" + transactionId);
recRechargeEntity.setCertificateCode(certificateCode);
recRechargeEntity.setUsername(user.getUsername());
recRechargeEntity.setProductId(productId);
if("com.DuduTravel.duduShuxue10".equals(productId)){
recRechargeEntity.setPrice("12");
user.setJifen(user.getJifen() + 100);
jifenLogEntity.setJifen(100);
} else if("com.DuduTravel.duduShuxue30".equals(productId)){
recRechargeEntity.setPrice("111");
user.setJifen(user.getJifen() + 200);
jifenLogEntity.setJifen(200);
} else if("com.DuduTravel.duduShuxue60".equals(productId)){
recRechargeEntity.setPrice("222");
user.setJifen(user.getJifen() + 300);
jifenLogEntity.setJifen(300);
} else {
recRechargeEntity.setPrice("0");
}
recRechargeEntity.setJson(jsonObject.toJSONString());
recRechargeRepository.save(recRechargeEntity);
userRepository.saveAndFlush(user);
jifenLogEntity.setTotalJifen(user.getJifen());
jifenLogEntity.setUserId(user.getId());
jifenLogEntity.setUsername(user.getUsername());
jifenLogEntity.setReason("用户购买,时间:"
+ app.get("purchase_date_pst").toString() +",金额"
+ recRechargeEntity.getPrice());
jifenLogRepository.save(jifenLogEntity);
} else {
log.error("A{}重复消费", transactionId);
}
}
}
} else {
return GeneralResponse.error(jsonObject.toJSONString());
}
return GeneralResponse.success(user.getJifen());
} catch (Exception e) {
log.error("购买凭证验证错误", e);
return GeneralResponse.error("购买凭证验证错误");
}
=====================================工具代码块==========================================
/**
* 重写X509TrustManager
*/
private static TrustManager myX509TrustManager = 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 {
}
};
/**
* 发送请求
*
* @param url
* @param
* @return
*/
private String sendHttpsCoon(String url, String code) {
if (url.isEmpty()) {
return null;
}
try {
// 设置SSLContext
SSLContext ssl = SSLContext.getInstance("SSL");
ssl.init(null, new TrustManager[]{myX509TrustManager}, null);
// 打开连接
HttpsURLConnection conn = (HttpsURLConnection) new URL(url).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", code.replace(" ", "+"));
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 = null;
StringBuffer sb = new StringBuffer();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
} catch (Exception e) {
return null;
}
}
问题:
1.java传输是+号会转换为空格
使用URLEncoder.encoder(recept,"IOS8859-1")请求苹果无效
2.使用replace("+", "%2B")替换所有加号请求苹果无效
3.使用replace(" ","+")替换所有空格有效
4.其他博客贡献者总会默默地给你埋坑,增加开发时间,请主动标记他进入黑名单,谢谢!
返回格式:
{
"environment": "Sandbox",
"receipt": {
"adam_id": 0,
"app_item_id": 0,
"application_version": "3",
"bundle_id": "com.xx.xx.xx",
"download_id": 0,
"in_app": [
{
"is_trial_period": "false",
"original_purchase_date": "2020-06-10 03:43:15 Etc/GMT",
"original_purchase_date_ms": "1591760595000",
"original_purchase_date_pst": "2020-06-09 20:43:15 America/Los_Angeles",
"original_transaction_id": "100002923929292",
"product_id": "com.xx.xx.xx",
"purchase_date": "2020-06-10 03:43:15 Etc/GMT",
"purchase_date_ms": "1591760595000",
"purchase_date_pst": "2020-06-09 20:43:15 America/Los_Angeles",
"quantity": "1",
"transaction_id": "1010101010101010"
},
{
"is_trial_period": "false",
"original_purchase_date": "2020-06-11 09:29:44 Etc/GMT",
"original_purchase_date_ms": "1591867784000",
"original_purchase_date_pst": "2020-06-11 02:29:44 America/Los_Angeles",
"original_transaction_id": "100002923929292",
"product_id": "com.xx.xx.xx",
"purchase_date": "2020-06-11 09:29:44 Etc/GMT",
"purchase_date_ms": "1591867784000",
"purchase_date_pst": "2020-06-11 02:29:44 America/Los_Angeles",
"quantity": "1",
"transaction_id": "18i18181811111"
}
],
"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": "2020-06-11 09:41:10 Etc/GMT",
"receipt_creation_date_ms": "1591868470000",
"receipt_creation_date_pst": "2020-06-11 02:41:10 America/Los_Angeles",
"receipt_type": "ProductionSandbox",
"request_date": "2020-06-12 07:09:52 Etc/GMT",
"request_date_ms": "1591945792375",
"request_date_pst": "2020-06-12 00:09:52 America/Los_Angeles",
"version_external_identifier": 0
},
"status": 0
}
productId:产品id,用这个可以获取对应的价格套餐
transaction_id:单笔消费唯一主键,用于重复消费判断
产品和id和金额需要存储到DB或者枚举类,目的是为了判断用户购买了那个套餐以及花了多少钱