Annuaire d'articles
Introduction
Enregistrez l'accès au serveur Java pour l'achat intégré d'Apple.
1. Type de produit
Apple stipule que les applications sur l'APP Store utilisent le propre mode de paiement d'Apple (achat intégré IAP) et Apple facturera une taxe de 30 %.
Les produits répertoriés incluent : consommable, non consommable, abonnement auto-renouvelable, abonnement non renouvelable. Les produits sur les étagères peuvent être configurés en arrière-plan de l'APP Store .
2. Obtenir le reçu de paiement
Une fois que l'utilisateur a terminé l'opération de paiement, Apple renvoie le reçu au client IOS, puis le client le renvoie au serveur pour traitement commercial. Le serveur doit transmettre les informations du ticket à Apple pour une deuxième vérification du ticket Une fois la vérification réussie, le reste de la logique métier peut continuer.
3. Vérification des billets
La facture est l'information pertinente qu'Apple paiera, l'organisera dans un json et nous la renverra. Certains des segments de données les plus couramment utilisés sont l'ID de produit, le délai de paiement, l'ID de commande d'Apple (transactionId) et les politiques préférentielles pour les produits d'abonnement automatique, le délai d'expiration, le délai de renouvellement, etc.
Apple dispose de deux interfaces pour la vérification des tickets, l'une est l'environnement sandbox et l'autre est l'environnement officiel. Différentes interfaces doivent être utilisées pour la vérification pendant la phase de test et après la mise en service. Le code d'erreur (21007) sera signalé lorsque le ticket officiel sera vérifié dans l'environnement sandbox .
Remarque : Le mode d'abonnement automatique nécessite de transmettre le paramètre " clé partagée ", qui peut être obtenu dans l'APP Store.
Documentation officielle de l'interface :
https://developer.apple.com/documentation/appstorereceipts/verifyreceipt
https://developer.apple.com/documentation/appstorereceipts/requestbody
4. Le serveur vérifie le ticket
4.1 Logique du serveur
public void verifyReceipt(AppleRequestProtocol request) {
// 票据
String receipt = request.getReceipt();
// 服务端自己的订单号,可用做后续业务逻辑
String orderId = request.getOrderNumber();
// 注意,有的票据在客户端接收时 加号 可能会被转换为 空格
String data = receipt.replace(" ", "+");
// 请求苹果服务器进行票据验证
String result = AppleVerifyUtil.verifyApple(data, 1, orderId);
JSONObject receiptData = JSONObject.parseObject(result);
// 解析票据
if(result == null){
// 解析票据失败 或 网络问题
log.error("[ verify receipt error]");
return ;
}else {
// 支付环境是否正确
int status = receiptData.getInteger("status");
if(21007 == status){
// 验证失败21007 走沙箱环境
result = AppleVerifyUtil.verifyApple(data, 0);
if(result == null){
// 解析票据失败
log.error("[ verify receipt error]");
return ;
}
receiptData = JSONObject.parseObject(result);
status = receiptData.getInteger("status");
}
if(0 == status){
JSONObject receiptInfo = receiptData.getJSONObject("receipt");
JSONArray inAppList = receiptInfo.getJSONArray("in_app");
if(!CollectionUtils.isEmpty(inAppList)){
JSONObject inApp = inAppList.getJSONObject(inAppList.size() - 1);
// 票据ID
String transactionId = inApp.getString("transaction_id");
// 购买时间
Long purchaseDateMs = inApp.getLong("purchase_date_ms");
// 商品ID 与在APP Store 后台配置的一致
String productId = inApp.getString("product_id");
// 剩余业务逻辑
}else{
// 获取in_app支付列表失败
log.error("[receipt error]");
}
}
}
}
4.2 Outils de vérification des achats intégrés Apple
/**
* 苹果内购验证工具类
*/
@Slf4j
public class AppleVerifyUtil {
/**
* 苹果内购沙盒环境
*/
private static final String url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";
/**
* 苹果内购正式环境
*/
private static final String url_verify = "https://buy.itunes.apple.com/verifyReceipt";
/**
* 秘钥 (自动订阅服务需要秘钥)
*/
private static final String KEY = "需要到APP Store后台获取";
/**
* 苹果服务器内购验证票据
* @param receipt 验证收据
* @param type 环境 (0 开发)
* @return
*/
public static String verifyApple(String receipt, int type) {
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);
JSONObject jsonObject = new JSONObject();
jsonObject.put("receipt-data", receipt);
jsonObject.put("password", KEY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
MediaType mediaType=MediaType.Companion.parse("application/json;charset=utf-8");
RequestBody stringBody=RequestBody.Companion.create(jsonObject.toString(),mediaType);
Request request=new Request
.Builder()
.url(console)
.post(stringBody)
.build();
String result = okHttpClient.newCall(request).execute().body().string();
return result;
} catch (Exception e) {
log.error("[ios verify error]");
return null;
}
}
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[] {
};
}
}
}
4.3 Informations sur les billets
Une fois que le serveur a demandé à Apple de vérifier l'interface, Apple renvoie les informations de ticket analysées. L'important est dans le ticket de caisse
"receipt": {
"in_app": [
{
"product_id": "202201", // 商品ID
"quantity": "1", // 购买商品数量
"transaction_id": "2000000026612777", //票据ID
"original_transaction_id": "2000000026612777", //原始购买票据ID
"purchase_date": "2022-04-06 01:54:59 Etc/GMT", //购买时间
"purchase_date_ms": "1649210099000", //购买时间戳
"purchase_date_pst": "2022-04-05 18:54:59 America/Los_Angeles", // 购买时间(美国)no
"original_purchase_date": "2022-04-06 01:55:00 Etc/GMT", //原始购买时间
"original_purchase_date_ms": "1649210100000", //原始购买时间戳
"original_purchase_date_pst": "2022-04-05 18:55:00 America/Los_Angeles", //原始购买时间(美国)no
"expires_date": "2022-04-06 01:59:59 Etc/GMT", //订阅到期时间
"expires_date_ms": "1649210399000", //订阅到期时间戳
"expires_date_pst": "2022-04-05 18:59:59 America/Los_Angeles", //订阅到期时间(美国) no
"is_in_intro_offer_period": "false", //是否在享受优惠价格期间
"is_trial_period": "false", //是否享受免费试用
"web_order_line_item_id": "2000000002007193", //跨设备购买事件(包括订阅更新事件)的唯一标识符。此值是识别订阅购买的主键
"in_app_ownership_type": "PURCHASED",
}
],
4.4 Codes d'erreur
code d'état - | détails |
---|---|
0 | Validation réussie |
21000 | La demande à l'App Store n'a pas été envoyée à l'aide de la méthode de demande HTTP POST. |
21001 | Ce code de statut n'est plus envoyé par l'App Store. |
21002 | Les données de l'attribut reception-data sont incorrectes ou manquantes. |
21003 | Le reçu n'a pas pu être authentifié. |
21004 | Le mot de passe de partage que vous avez fourni ne correspond pas au mot de passe de partage de fichiers de votre compte. |
21005 | Le serveur de réception est actuellement indisponible. |
21006 | Le reçu est valide, mais l'abonnement a expiré. Lorsque ce code d'état est renvoyé à votre serveur, les données de réception sont également décodées et renvoyées dans le cadre de la réponse. Renvoyé uniquement pour les reçus de transaction de style iOS 6 à renouvellement automatique. |
21007 | Ce reçu provient de l'environnement de test, mais a été envoyé à l'environnement de production pour vérification. |
21008 | Le reçu provient de l'environnement de production, mais est envoyé à l'environnement de test pour vérification. |
21009 | Erreur d'accès aux données internes. Réessayez plus tard. |
21010 | Le compte utilisateur est introuvable ou a été supprimé. |
5. Renouvellement
Pour les types d'abonnements à renouvellement automatique, l'App Store déduira automatiquement les frais pour aider les utilisateurs à renouveler le service avant l'expiration de la durée d'abonnement.
La méthode de vérification de serveur à serveur est également la méthode de vérification recommandée par Apple, et Apple nous informera activement de l'état. Le serveur doit recevoir le message de rappel envoyé par le serveur Apple et effectuer des opérations telles que le renouvellement, la désinscription et la désinscription en fonction du type de message.
5.1 Configurer l'adresse pour recevoir les notifications
Vous devez configurer l'URL d'état de l'abonnement en arrière-plan de l'App Store connect, qui est utilisée pour recevoir la notification de rappel du serveur App Store
Document officiel : https://help.apple.com/app-store-connect/#/ dev0067a330b
5.2 Recevoir des notifications
Le serveur Apple transmet l'objet JSON à votre serveur via HTTP POST, analyse le JSON pour obtenir le corps de réponse et effectue différentes opérations en fonction du paramètre notification_type type de notification.
Document officiel
responsebody : https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv1
notification_type : https://developer.apple.com/documentation/appstoreservernotifications/notification_type
public void renewal(JSONObject object) {
// 原始transaction_id
String originalTransactionId = object.getString("original_transaction_id");
// 获取订阅通知类型
String notification_type = object.getString("notification_type");
log.info("renewal notify: [ original_transaction_id: {} ], [ notification_type: {} ]", originalTransactionId, notification_type);
// 回调收据信息
JSONObject unifiedReceipt = object.getJSONObject("unified_receipt");
JSONArray latestReceiptInfo = unifiedReceipt.getJSONArray("latest_receipt_info");
if(!CollectionUtils.isEmpty(latestReceiptInfo)) {
JSONObject receipt = latestReceiptInfo.getJSONObject(0);
String productId = receipt.getString("product_id");
String transactionId = receipt.getString("transaction_id");
// 处理自动续订成功
if("DID_RENEW".equals(notification_type)){
// 业务逻辑
}
// 退款
if("CANCEL".equals(notification_type)){
if(!CollectionUtils.isEmpty(latestReceiptInfo)){
// 业务逻辑
}
}
}
}
renouveler
2022-10-16
En pratique, si vous rencontrez le problème d'acquisition erronée de factures de consommables, enregistrez-le.
La liste des reçus (in_app) dans le reçu de paiement (reçu) conservera tous les produits d'abonnement et les informations sur les produits non consommables , et entrera dans la liste dans l'ordre (le dernier est le dernier enregistrement d'achat). Les informations sur le produit consommable n'existent que lorsqu'elles ne sont pas vérifiées sur le serveur Apple et n'existent que dans le premier élément de la liste (le fait d'acheter à nouveau le produit consommable remplacera les informations sur la facture).
documents de référence
https://juejin.cn/post/7046969127205863438Documentation
en chinois simplifié Apple : https://developer.apple.com/cn/documentation/