Introduce a,

  • There are four types of iOS in-app purchase:
    1. Consumable goods: Products that can only be used once, then expire and must be purchased again. Example: fish food in a fishing App.
    1. Non-consumable goods: Products that can be purchased only once and do not expire or decrease with use. Example: game App track.
    1. Automatic renewal subscriptions: Products that allow users to buy dynamic content for a fixed period of time. Such subscriptions are automatically renewed unless the user chooses to cancel. Example: Monthly subscription to an App that provides streaming media services.
    1. Non-renewed subscriptions: Products that allow users to buy services for a limited time. The content of this in-app purchase item can be static. Such subscriptions are not automatically renewed. Example: a one-year subscription to the catalog of archived articles.

After finishing the project, I found that the most troublesome one was the automatic renewal subscription type. Because the other categories are one-time in-app purchases, and only the self-renewal category is continuous, which also has the concept of free trial period, promotion period, users can cancel the renewal, resume the renewal, etc. The background also needs to have a lot of corresponding logical operations. Here is a summary of the process of automatic renewal subscription type encountered problems and some pits, hope to help you.

Two, need to pay attention to when creating automatic renewal type

1. App private shared key:

You need to create an “App private shared key” that is a unique code to receive receipts for automatic subscription renewals from this App. This key is used to verify receipt to The Apple server. Not only do you need to upload receipt, but you also need to upload the secret key. If you need to transfer the App to another developer, or if you need to set the master shared key to private, you may need to use the App private shared key.

2. Subscription Group:

  • For detailed advice on subscribing to groups, see the official documentation for this group.

When creating an automatic renewal type, you need to create a subscription group if one does not already exist to provide users with a set of content offerings, service levels, or timelines. The name can be arbitrary, is to see their own, on behalf of the meaning of the line, a group can have more than one automatic renewal subscription. If you have a promotional offer, each customer can enjoy one promotional offer per subscribed group.

Subscriptions within a subscription group are mutually exclusive, meaning that users can only subscribe to one option in a group at a time. If you want users to be able to buy multiple subscriptions at once, you can put those in-app purchases into different subscription groups.

3. Subscription status URL:

Automatic subscription renewal also requires a subscription status URL. So when you configure this URL in the App message, you can actually get the server to server notification in the background. At the end of the article, we will talk about the operation of background in detail.

4. Promotion and promotion:

  • For detailed advice on promotional offers, see the official documentation for this price and sales range.

Promotional offers can be set up, such as our company’s program is to take the first seven days of free trial, of course, you can also set the first two months of half price, etc.

attribute describe
Along with it If you choose pay-as-you-go, the customer will pay the discounted price for each billing cycle of the selected period (for example, $9.99 for the standard subscription and $1.99 per month for the first three months).
Payment in advance If you choose “Prepay,” the customer will pay a one-time discount for the selected period of time (for example, $9.99 for the standard subscription and $1.99 for the first two months).
free If you select “Free”, the customer will have free access to the subscription for a selected period of time. The time limit can be 3 days, 1 week, 2 weeks, 1 month, 2 months, 3 months, 6 months, or 1 year. The one-month free trial runs between 28 and 31 days.

⚠️ Note: this price is open to new customers. Promotional offers can be used to attract new customers. This means that the user has enjoyed the free trial for seven days, and will not be able to enjoy the free trial again, which is up to Apple to determine. Here’s what Apple said to keep in mind:

  • Customers can enjoy a referral promotion for each subscribed group
  • You can set up one current promotional offer and one future promotional offer for each region
  • You can manage regional sales ranges, start and end dates in App Store Connect
  • If you have promoted your in-app purchase, the promotional offer will appear on your App Store product page
  • The promotional offer is available to customers running iOS 10, Apple TVOS 10 and macOS 10.12.6 and later

2. Internal purchase process

1. Process description

Let’s take a look at the general process for in-app purchases on iOS:

    1. The user initiates a purchase request to the Apple server and receives a callback when the purchase is completed (after the purchase is completed, the money will be transferred to the bank card applying for in-app purchase).
    1. After the completion of the successful purchase process, the authentication certificate is sent to the server (the APP side can also independently verify without relying on the server).
    1. My server work is divided into four steps: 3.1 Receive the purchase certificate sent by the iOS terminal. 3.2 Determine whether the credential exists or has been verified, and then store the credential. 3.3 Send the certificate to apple’s server (sandbox environment or formal environment) for verification, and return the verification result to the client. 3.4 Modify the user’s membership rights or issue virtual goods.

Basically, you encode the purchase certificate in Base64 and POST it to Apple’s verification server, which returns the validation result as JSON.

2. Concrete implementation

One caveat to automatic subscription types: Make sure to add listeners when your app starts running

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
Copy the code

Because of the automatic subscription type, except for the first purchase behavior is triggered by the user. Apple automatically completes the subsequent fees, usually starting 24 hours before the expiration date. Apple will try to deduct the fees. If the fees are successfully deducted, they will be actively pushed to the APP when the APP is started next time. So, be sure to add the above sentence when the APP launches.

⚠️ Be sure to perform the finishTransaction operation after the order is completed

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
Copy the code

Let’s take a look at a few important proxy implementations: First, introduce StoreKit, a library that is required for In-app purchases

#import <StoreKit/StoreKit.h>
Copy the code
(1) began to adjust the payment process, the request product information, need to use SKProductsRequestDelegate here, it is goods request correction, can tell you have the goods
@param productId @param productId productId */
- (void)payWithAppleProductID:(NSString *)productId {
    
    if ([SKPaymentQueue canMakePayments]) {
        // If in-app purchases are allowed
        // Put the product ID information into a collection
        NSArray *productIdentifiers = [[NSArray alloc] initWithObjects: productId, nil];
        NSSet * set = [NSSet setWithArray:productIdentifiers];
        // Request in-purchase product information, only return the requested product (mainly used to verify the validity of the product)
        SKProductsRequest * request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
        request.delegate = self;
        [request start];
        
    } else {
        // If in-app purchases are prohibited on the user's phone.
        // A prompt is displayed to enable the purchase permission switch...}}#pragma mark - SKProductRequestDelegate 
SKProductsRequest is an object encapsulated by Apple. This object has two properties. Products is an array that represents all the items you have obtained. Each item is an array element. InvalidProductIdentifiers is invalid array of commodity id, this id is corresponding your backend build commodity id in apple. * /
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{

    NSLog(@"-------------- received product feedback ---------------------");
    NSArray *product = response.products;
    if([product count] == 0){
        [SVProgressHUD dismiss];
        NSLog(@"-------------- no merchandise ------------------");
        return;
    }

    NSLog(@"productID:%@", response.invalidProductIdentifiers);
    NSLog(@" Product paid quantity :% LU", (unsigned long)[product count]);

    SKProduct *requestProduct = nil;
    for (SKProduct *pro in product) {
        NSLog(@ "% @", [pro description]);
        NSLog(@ "% @", [pro localizedTitle]);
        NSLog(@ "% @", [pro localizedDescription]);
        NSLog(@ "% @", [pro price]);
        NSLog(@ "% @", [pro productIdentifier]);
        // If the ID of the backend consumption item is the same as what I need to request here (to ensure the correctness of the order)
        if([pro.productIdentifier isEqualToString:_currentProId]){ requestProduct = pro; }}// Send a purchase request
    //SKPayment *payment = [SKPayment paymentWithProduct:requestProduct]; // Immutable
    SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:requestProduct];/ / variable
    payment.applicationUsername = @ "123456";// Specify the userId of the user when the payment is initiated
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

// The request failed
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
    NSLog(@ "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- wrong -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- : % @", error);
}

- (void)requestDidFinish:(SKRequest *)request{
    NSLog(@"------------ End of feedback -----------------");
}
Copy the code

When I send the purchase request, I bind the ID of the currently logged in user

payment.applicationUsername = [Global sharedGlobal].loginInfo.userId;
Copy the code

When I receive the transaction callback later, I can determine whether the current user is the same user based on the applicationUsername I carry, and if so, I can validate the ticket. However, do not rely on this parameter completely, because some people say that this parameter is sometimes empty on the Internet, so we should first check whether it is empty when verifying, if not, then compare it with the current user ID. If not, just go through the validation process as usual.

  • SKProductsRequest is an object encapsulated by Apple that has two properties.
  • The products attribute is an array that represents all the items you have obtained. Each item is an array element.
  • Attribute invalidProductIdentifiers is invalid id an array of goods, this id is corresponding your backend build commodity id in apple.
// Array of SKProduct instances.
@property(nonatomic.readonly) NSArray<SKProduct *> *products NS_AVAILABLE(10_7, 3_0);

// Array of invalid product identifiers.
@property(nonatomic.readonly) NSArray<NSString *> *invalidProductIdentifiers NS_AVAILABLE(10_7, 3_0);
Copy the code
(2) Judge the purchase result, which is needed hereSKPaymentTransactionObserver.SKPaymentTransactionObserverIt’s a trade observer that tells you where you are in a trade.
// 13. Monitor the purchase result
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction {
    
    for (SKPaymentTransaction *tran in transaction){
        
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased:
                NSLog(@" Transaction Completed");
                // Subscribe to special processing
                if (tran.originalTransaction) {
                    // If the order is automatically renewed, the originalTransaction will have the content
                    NSLog(@" Automatic renewal order,originalTransaction = %@",tran.originalTransaction);
                } else {
                    // Regular purchase, and automatic subscription for the first purchase
                    NSLog(@" Regular purchase, and automatic subscription for first purchase.");
                }
                 if ([Global sharedGlobal].loginInfo.logined) {
                    // Process the ticket and finish only after you have logged in
                    NSString *orderUserId = [[tran payment] applicationUsername];// Get the user Id for the order
                    if ((orderUserId && orderUserId.length > 0 && [[Global sharedGlobal].loginInfo.userId isEqualToString:orderUserId]) || (nil == orderUserId || orderUserId.length == 0)) {
                        // When the order userId is the same as the current userId or the order userId is empty, the ticket is processed and the finish operation is executed
                        [self completeTransaction:tran];
                        [[SKPaymentQueue defaultQueue] finishTransaction:tran];// Destroy this operation. The local database will record and restore the operation}}break;
            case SKPaymentTransactionStatePurchasing:
                NSLog(@" Item added to list");
                break;
            case SKPaymentTransactionStateRestored:
                NSLog(@" Already purchased goods");
                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                break;
            case SKPaymentTransactionStateFailed:
                NSLog(@" Deal failed");
                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                break;
            default:
                break; }}}// The transaction is over. When the transaction is over, we need to verify that the payment information is correct in the Appstore. After all the information is correct, we can give the user the method of our virtual items.
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
    
    NSString * str = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
    NSString *environment = [self environmentForReceipt:str];
    NSLog(@"----- completeTransaction method called completeTransaction 1--------%@",environment);
    // Verify the credential and get the transaction credential returned by Apple
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];// appStoreReceiptURL added in iOS7.0 to store credentials at this address after a purchase transaction is completed
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];// Obtain the purchase credentials from the sandbox
    NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];// BASE64 is a common encoding scheme, which is usually used for data transmission and the basic algorithm of encryption algorithm. During transmission, it can guarantee the stability of data transmission. BASE64 can be encoded and decoded
    
    if(! [UserOrderInfo isHasReceiptDate:encodeStr]) {// If the local database does not have this ticket record
        NSString *environmentStr;
        if ([environment isEqualToString:@"environment=Sandbox"]) {
            environmentStr = @"sandbox";
        } else {
            environmentStr = @"product";
        }
        // POST the ticket to your server for verification...}}Copy the code

The length of the transaction credentials to be sent to colleagues in the background is very large. At the beginning, it was more than 7,000, so the background limited the length of the transaction credentials to 10,000. As a result, with the increase of subscriptions, the transaction credentials also got bigger and bigger, finally reaching more than 30,000, and the background had to change the length to 300,000.

  • On the basis of the above, I added the order record of local data to prevent order drop. Before verifying the ticket, I inserted all the data including the ticket into the local database. And perform the Objc [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; That is to tell Apple that my payment process is over. In this way, if the program blinks or other conditions occur, the next time the app is started, it will first query the local database for any unfinished orders and continue the in-app purchase process. You don’t have to rely on apple’s automatic notifications to continue with in-app purchases, because apple in-app purchases are tied to an appleId, whereas most companies need to be tied to their own app’s user id. It’s better to have your own local record response, but not if the user changes devices.

3. Various situations

For the first time you are upgrading and Plan Changes

Users can manage their subscriptions in the App Store or in account Settings in the interface of your App. For each subscription, the App Store displays all the renewal options offered by the subscription group. Users can easily change their service levels and choose to upgrade, downgrade or cross-rank as needed at any time. Any demotions of duration or cross ranks with different durations will take effect on the next renewal date.

You can view the Subscription Automatic Renewal Preferences field of the receipt to see any schedule changes that the user has selected that will take effect on the next renewal date.

The Expiration and Renewal of the contract are due

The subscription renewal process begins ten days prior to the expiration date. During these 10 days, the App Store will check for any billing issues that may delay or prevent automatic subscription renewals, such as:

  • The customer’s payment method is no longer available,
  • The price of the product has gone up since the user bought the subscription,
  • This product is no longer available.

The App Store can notify users of any problems so that they can fix it before their subscription expires and avoid an outage to their subscription service. The App Store starts experimenting with automatic renewals 24 hours before subscriptions expire. The App Store will try multiple times to automatically renew a subscription over a period of time, but if the number of failed attempts is too many, it will eventually stop.

  • Note: For billing related issues, the App Store may try to renew a subscription for up to 60 days. You can check the subscription retry mark in your receipt to see if the App Store is still trying to renew your subscription.

3. The Cancellation

Subscriptions are paid in full at purchase. Customers can only get a refund by contacting Apple Customer Service. For example, if a user accidentally buys the wrong product, customer support can unsubscribe and issue a full or partial refund. Customers can unsubscribe during the subscription period, but the subscription is still paid at the end of the same period.

To check if Apple Customer Support has canceled the purchase, look for the “cancellation date” field in the receipt. If this field contains a date, the purchase has been cancelled regardless of the expiration date of the subscription. With respect to the provision of content or services, the cancelled transaction is treated as if no purchase has been made.

Depending on the type of product your application offers, you may need to check the current subscription period in effect, or you may need to check all past subscription periods. For example, a magazine application needs to examine all past subscription periods to determine which issues the user should access. Applications with streaming services only need to check the currently active subscription to determine whether users should have access to their services.

Server authentication

In fact, in-app purchases can be verified by the client itself, but for the sake of security, most companies will choose to verify the validity of the order on the server. Our project is no exception. First configure automatic renewal subscriptions in iTunes Connection. See apple’s official documentation, Enabling Server Notifications for Automatic Renewal subscriptions, below. The difference between automatic renewal and other types of subscriptions is that we must generate a shared key in App Store Connect, send this key to a backroom colleague, and fill in the subscription status URL.

If server to server notifications are configured in this way, the background will receive the following types of status update notifications:

NOTIFICATION_TYPE describe
INITIAL_BUY Initial purchase subscription. Latest_receipt is verified in the App Store and can be stored on your server at any time to verify the user’s subscription status.
CANCEL Apple Customer Support cancelled the subscription. Check the Cancellation Date for the Date and time of subscription Cancellation.
RENEWAL The expired subscription was automatically renewed successfully. Check the Subscription Expiration Date to determine the next renewal Date and time.
INTERACTIVE_RENEWAL Customers renew their subscriptions interactively by using the App interface or in the App Store within the App Store. The service is available immediately.
DID_CHANGE_RENEWAL_PREF The customer has changed the plan that will be in effect at the next renewal. Current effective programs will not be affected.

From this we can see that there is no user normal renewal notification, this is different from Android, Android will have a renewal notification. Apple renewals by default, and cancellations are notified.

Started back side also is met a lot of don’t understand the problem, finally found the same orders credentials can be used, no matter how many times you renew the behind, just one of these credentials to apple, can get all the order information and subscribe to the state, so that the end of each cycle () at the end of probation period or the last day of, According to the ticket information, we can get the information of whether the user still renewed the subscription, so that we can decide whether to continue to give the VIP of the next month.

4. Sandbox test

Because our project requirement for the first time to buy automatic renewal of seven day free trial, and an apple sandbox account can only enjoy a free trial, so lead to my application once every self-test will be a new sandbox account and submitted to the testing department test and when to apply for a bunch of accounts, the last applied for 47 sandbox account… When we tested auto-renewal subscriptions, the time limit was shortened. In addition, test subscriptions can only be automatically renewed up to six times.

The actual time limit The test time
1 week 3 minutes
1 month 5 minutes
2 months Ten minutes
3 months 15 minutes
6 months 30 minutes
1 year 1 hour

Compared to Android, Apple is less friendly to test, especially since there is no way to simulate a user’s manual unsubscription because sandbox accounts have no way to manage subscriptions. Android can test this scenario.

In addition, if the sandbox account is renewed with the app open all the time, you may not receive the notification after 5 minutes of the renewal cycle. It is better to kill the app and restart it after 5 minutes, so that you will receive the notification of the renewal.

  • In the case of a non-sandbox account, the project can be set to open this URL to launch iTunes or the iTunes Store and display the “Manage Subscriptions” page. Buy.itunes.apple.com/WebObjects/…

⚠️ Note: If you are using TestFlight, you do not need to use a sandbox account to purchase. You will need to use a real account to purchase. Of course, you will not be charged money, but the popover instructions have changed, as shown below:

V. Audit

1. Automatic subscription renewal instructions must be included.

Automatically renew your subscription. Make sure you have a detailed explanation in your app, like the one below:

app
app
iTunes Connect

2. Do not force the user to log in to purchase

Because Apple stipulates that all the accounts bound to in-app purchase should be Apple accounts, so you should be able to buy without logging in your app with your own account, that is, you should be able to buy even when you are a tourist. Otherwise, Apple will be delayed to make money.

There are two solutions to this problem:

(1) It can be purchased as a tourist (the device is bound if you have not logged in, and the account will be bound after login with the next account)

(2) You must log in to use the APP. Of course, you can also make an audit interface to deal with.

The above summary refers to and extracts the following articles, thank you very much for sharing the following authors! :

1. “iOS In-App Purchase: Automatic Renewal Subscription Summary” by Author Guangcai Film

2. Apple’s official help document

3. IOS Auto-subscription Development by Qiyer

Reprint please note the original source, shall not be used for commercial dissemination – fan how much