Le serveur Java accède aux achats intégrés d'Apple. Réaliser la vérification secondaire des factures et le renouvellement automatique de l'abonnement

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

Veuillez ajouter une description de l'image
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/

Guess you like

Origin blog.csdn.net/Peanutfight/article/details/125668096
Recommended