background

Recently, I found some problems in the automatic renewal subscription model of iOS in-app purchase, so I hereby sort them out.

The problem

The APP I developed has no login function, that is to say, no user system. There are two problems as follows:

  1. Let’s use the first oneApple ID(hereinafter referred to asA) bought an auto-renewal subscription on a mobile phone. User uninstallsAPPAfter reinstallation, or user useAThe account was logged in on another phone. How to continueAAccount subscription permission?
  2. On the same phone,AThe account bought an automatic renewal subscription, which he then changed in his phone SettingsApple ID(hereinafter referred to asB), how inAPPPermission to unsubscribe within? Since account B did not purchase an automatic renewal subscription, he must not be given permission. Otherwise, you can use account A to log in on countless mobile phones, and then switch accounts. All accounts after switching enjoy automatic renewal subscription, which shows that it is oneBug.

Define a few concepts

After the APP is installed, a receipt_data receipt is generated. The receipt_data receipt information is not empty after the APP is installed. Receipt_data is an encrypted receipt containing the purchase information. Receipt_data is associated with an Apple ID, that is, purchased with an Apple ID.

The solution

Solution to Problem 1:

When users uninstall the APP and reinstall it, or log in with account A on other mobile phones. Obviously, this receipt_data does not contain the purchase certificate of account A at that time. Take this Receipt_data to Apple verification interface for verification, it is obvious that you cannot get the purchase certificate at that time. However, the user has purchased, so it is necessary to provide a “resume purchase” button on the page, so that the user takes the initiative to resume purchase. Call StoreKit’s Restore Purchase API, and the callback to the payment status will be called back again, returning the new Receipt_DATA. In this case, take this Receipt_data to Apple’s authentication interface to verify that you can see the purchase certificate at that time. So you can give users subscription privileges. If the user clicks the “Buy” button instead of clicking the “Restore Purchase” button, StoreKit will pop up the system box you purchased and return an error code in the payment status callback, allowing developers to determine whether the user has purchased.

Solution to Problem 2:

After user B logs in to the system, the system does nothing but print receipt_data. The system displays the receipt_data used when user A logs in to the system. Receipt_data (through the refresh interface of StoreKit). After refreshing the receipt_data, obtain the new receipt_data and verify it with Apple’s verification interface. Then we can see that account B has no purchase record and prompt him to purchase it. However, we cannot tell whether the user has switched accounts or not. Therefore, every time you enter the APP, you should check receipt_data and verify it. If the user does not kill the APP but changes the Apple ID in the phone system, it will not be refreshed when the APP starts. We can refresh receipt_data and verify it when the user views the subscription.

Pay attention to the point

The client is advised not to directly call Apple’s authentication interface to verify Receipt_data. Instead, it can use its own backend to make a transfer. Java background call Apple authentication interface part of the code is as follows:

private static final String url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt"; / / sand box test private static final String url_verify = "https://buy.itunes.apple.com/verifyReceipt"; Private static final String IOS_SHARED_SECRET_PASSWORD = "private static final String IOS_SHARED_SECRET_PASSWORD =" "; / / apple continuous subscribe to the Shared key / * * * * * @ apple server validation param * @ the return receipt bill null or returns the result Sandbox at https://sandbox.itunes.apple.com/verifyReceipt Public static String buyAppVerify(String receipt, int type, Int status) throws Exception {// Environment check line/development environment use different request link String URL = ""; if (type == 0) { url = url_sandbox; // sandbox test} else {url = url_verify; 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", "text/json"); conn.setRequestProperty("Proxy-Connection", "Keep-Alive"); conn.setDoInput(true); conn.setDoOutput(true); BufferedOutputStream hurlBufOus = new BufferedOutputStream(conn.getOutputStream()); String str = null; If (status = = 0) {/ / / / consumption Mosaic fixed format to platform STR = the String. Format (Locale. CHINA, "{" receipt - data" : "" + receipt +" "} "); }else if (status == 1) {STR = string. format(locale.china, "{"receipt-data":"" + receipt + "","password":"" + IOS_SHARED_SECRET_PASSWORD + ""}"); } //} 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 e) {system.out.println (" apple server Exception "); e.printStackTrace(); throw e; }}Copy the code