Technical Note TN2413

In-App Purchase FAQ

This document provides answers to frequently asked questions about in-app purchase.

Configuration
Error Messages
Localization
Receipt
Subscriptions
Troubleshooting
Additional Resources
Document Revision History

Configuration

Must I upload a binary to test In-App Purchase?

No. Testing in-app purchase does not require uploading a binary.

What can I do to help combat fraud during purchase transactions?

SKPayment provides the applicationUsername property, which allows you to help Apple detect irregular activity when requesting payment. it is an opaque identifier whose recommended value is a one-way hash of the user’s account name on your server. To help Apple combat fraud during purchase transactions, create a payment object, then set its applicationUsername property to an opaque identifier associated with the user’s account name on your server before making a purchase.

See Detecting Irregular Activity, which provides a hashedValueForAccountName: method that demonstrates how to create a one-way hash of the user's account. hashedValueForAccountName: assumes that the current user is associated with a username on your server. It takes the username as a parameter and returns its hash value as shown in Listing 1.

Listing 1  Providing an application username.

// Custom method to calculate the SHA-256 hash using Common Crypto.
-(NSString *)hashedValueForAccountName:(NSString *)userAccountName
{
    const int HASH_SIZE = 32;
    unsigned char hashedChars[HASH_SIZE];
    const char *accountName = [userAccountName UTF8String];
    size_t accountNameLen = strlen(accountName);
 
    // Confirm that the length of the user name is small enough
    // to be recast when calling the hash function.
    if (accountNameLen > UINT32_MAX) {
        NSLog(@"Account name too long to hash: %@", userAccountName);
        return nil;
    }
    CC_SHA256(accountName, (CC_LONG)accountNameLen, hashedChars);
 
    // Convert the array of bytes into a string showing its hex representation.
    NSMutableString *userAccountHash = [[NSMutableString alloc] init];
    for (int i = 0; i < HASH_SIZE; i++) {
        // Add a dash every four bytes, for readability.
        if (i != 0 && i%4 == 0) {
            [userAccountHash appendString:@"-"];
        }
        [userAccountHash appendFormat:@"%02x", hashedChars[i]];
    }
    return userAccountHash;
}

Use the above returned hash value of your user's account to populate the applicationUsername property of your payment object before calling addPayment: as shown in Listing 2.

Listing 2  Populate the applicationUsername property of an SKMutablePayment object.

// product is an SKProduct object.
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
 
//Populate applicationUsername with your customer's username on your server.
payment.applicationUsername = [self hashedValueForAccountName:@"userNameOnYourServer"];
 
// Submit payment request.
[[SKPaymentQueue defaultQueue] addPayment:payment];

What is the minimum version for supporting auto-renewable subscriptions?

  • iOS 4.2 is the minimum iOS version for supporting auto-renewable subscriptions.

  • macOS 10.9 is the minimum macOS version for supporting auto-renewable subscriptions.

When should I use the restoreCompletedTransactions method of SKPaymentQueue?

You should only use restoreCompletedTransactions to restore your auto-renewable subscription or non-consumable products in both of these cases:

  • To install them on additional devices owned by your customers.

  • To reinstall them on devices where their associated application was deleted.

How many In-App Purchase product IDs can we create per application in iTunes Connect?

Read Workflow for configuring in-app purchases to find out how many in-app purchase product IDs you can create across all apps in the account.

Error Messages

Your account info has changed

The "Your account info has changed" message indicates that you are signed in the App Store with your test user account on your device. Sandbox test user accounts become unusable once you use them to log in to a production environment. To resolve this error, sign out of iTunes & App Stores in the Settings application on your device, create a new test user account in iTunes Connect, and use it when prompted by StoreKit to confirm a purchase from within your app.

Cannot connect to iTunes Store

The "Cannot connect to iTunes Store" issue may be due to one or more of the following reasons:

This Apple ID has not yet been used in this iTunes Store

The "This Apple ID has not yet been used in this iTunes Store" message indicates that you are signed in the iTunes Store with your test user account on your device. See Your account info has changed for more information on how to resolve this issue.

You've already purchased this. Tap OK to download it again for free

The "You've already purchased this. Tap OK to download it again for free." message is a notification rather than an error. It indicates that you are attempting to purchase a non-consumable product that you have already bought. You are not charged when purchasing an already bought non-consumable product.

You've already purchased this. Would you like to get it again for free?

The "You've already purchased this. Would you like to get it again for free?" message indicates that you are attempting to purchase a non-consumable product that you have already bought. You are not charged when purchasing an already bought non-consumable product.

This In-App Purchase has already been bought. It will be restored for free.

The "This In-App Purchase has already been bought. It will be restored for free." message indicates that you did not call SKPaymentQueue's finishTransaction: in your application. Calling finishTransaction: allows you to remove a transaction from the payment queue. It notifies the App Store that the application acknowledges the transaction so that the App Store can mark it as completed.

You've already purchased this In-App Purchase but it hasn't been downloaded

You are getting the "You've already purchased this In-App Purchase but it hasn't been downloaded." message because you did not call SKPaymentQueue's finishTransaction: in your application. Calling finishTransaction: allows you to remove a transaction from the payment queue. It notifies the App Store that the application acknowledges the transaction so that the App Store can mark it as completed.

This is not a test user account. Please create a new account in the Sandbox environment

You are getting the "This is not a test user account. Please create a new account in the Sandbox environment." message because you signed in with your iTunes user account when prompted by StoreKit to confirm a purchase. To resolve this error, sign out of iTunes & App Stores in the Settings application on your device and use your sandbox test user account when prompted by StoreKit to confirm a purchase.

Localization

My In-App Purchase has localized information for various languages on iTunes Connect. However, the localizedDescription and localizedTitle properties always return information in English even though my test device language is not set to English

localizedDescription and localizedTitle return localized information whose language is based on the current iTunes Store rather than the current device language setting. For instance, if your in-app purchase is localized for German in iTunes Connect and you are logged with an English test user account, then localizedDescription and localizedTitle will return information localized in English. To have localizedDescription and localizedTitle return information localized in German, login with a German test user account on your test device.

Receipt

How do I use the cancellation_date field?

The Cancellation Date (cancellation_date) field is designed for use with auto-renewable subscription, non-consumable and non-renewing subscription products. This field is set when a customer contacts Apple customer support for a refund and the transaction is canceled. The utility of the cancellation_date field is most useful with auto-renewing subscription products. After the refund is issued and the cancellation_date field is added to the iTunes Server records for the user, there is no automatic update process for the application copy of the appStoreReceipt to be automatically updated. In the case of a receipt with an auto-renewing subscription product, validating the appStoreReceipt results in the iTunes Store verifyReceipt server providing the most current information about the auto-renewing subscription product in the latest_receipt_info field of the validated receipt.

At present this support does not exist for validating appStoreReceipts which have non-consumable and non-renewing subscription products. After the refund event, validating the appStoreReceipt in the app will not reflect a refund for non-consumable and non-renewing subscription purchases. The iTunes Server can update the appStoreReceipt to show the cancellation_date, but an update event must occur. The appStoreReceipt is refreshed only in the following cases after the refund has occurred - when there is a successful purchase of an in-app purchase item, when the SKReceiptRefreshRequest call is used and when the restoreCompletedTransactions is used to restore previously purchased products.

What url should I use to verify my receipt?

  • Use the sandbox URL https://sandbox.itunes.apple.com/verifyReceipt while testing your application in the sandbox and while your application is in review.

  • Use the production URL https://buy.itunes.apple.com/verifyReceipt once your application is live in the App Store.

    Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store.

    The 21007 status code indicates that this receipt is a sandbox receipt, but it was sent to the production service for verification. A status of 0 indicates that the receipt was properly verified. See for WWDC 2012: Managing Subscriptions with In-App Purchase more information.

Current receipt invalid or mismatched ds person id

You are getting this message because your application does not contain a macOS App Store receipt. See Receipt Validation Programming Guide for more infomation on how to obtain a receipt for your application.

Verifying my receipt fails with a status of <string of numbers>

Verifying your receipt may fail with a status of <string of numbers> for one or more of the following reasons:

  • You did not encode your receipt data using base64 encoding in your iOS application.

  • Your receipt contains characters that were not properly encoded or covered by your base-64 algorithm. See Send the Receipt Data to the App Store for an example that shows how to properly encode a receipt using base-64.

  • The object being posted to the App Store is not formatted as JSON. See Listing 3 for a proper JSON object for an auto-renewable subscription.



    Listing 3  Valid sample receipt for verifying an auto-renewable subscription.

    {
        "exclude-old-transactions": true/false,
        "receipt-data" : "...",
            "password" : "..."
    }

App Review cannot see my content despite a successful purchase.

If your app validates its receipt with the App Store after a successful purchase, check that your app is using the correct App Store URL to validate the receipt. See What url should I use to verify my receipt? for more information.

My app validates its receipt with the App Store via paymentQueue:updatedTransactions: after a successful purchase. However, the returned receipt contains an empty in_app array rather than the expected products.

An empty in_app array indicates that the App Store has not recorded any transactions for the user yet. It may be that the application receipt has not yet been updated. When this happens, your app can inform the user that the receipt does not appear current and ask whether to refresh it. Upon user agreement, your app should use the SKReceiptRefreshRequest class to update the receipt. At this point, if the App Store has recorded a purchase for the user, your app receipt will show it in in_app. See Refreshing the App Receipt for more information on how to update a receipt.

Information about consumable products is added to the receipt when they are paid for and remains in the receipt until you finish the transaction. After you finish the transaction, this information is removed the next time the receipt is updated. Thus resulting into an empty in_app array if your app only sells consumable products.

After I install the development app with Xcode, the appStoreReceiptURL is nil. What is the proper way to handle this situation?

When an application checks appStoreReceiptURL and finds that it is nil, start with the assumption that the current user has no access to premium content. The application can advise the user that the appStoreReceipt appears to be outdated and may require refreshing. If the user agrees that that appStoreReceipt requires refreshing, then the application can make the SKReceiptRefreshRequest call. Using SKReceiptRefreshRequest will have iOS present the iTunes User Authentication dialog. Once the user enters their password, the App Store will attempt to refresh the appStoreReceipt and the application will be called via requestDidFinish or didFailWithError. The application must allow the user to cancel the authentication dialog presented by SKReceiptRefreshRequest and continue on with the assumption that the user has no access to premium content.

The absence of the appStoreReceipt is typical when the application is installed using Xcode or TestFlight. When the application is installed from the App Store or restored from iCloud, the appStoreReceipt will always be present. However there are some unusual production cases where the appStoreReceipt may be missing from the application.

Subscriptions

How do I create and upload a hosted non-consumable product?

See Upload in-app purchase content for more information on how to create and upload a hosted non-consumable product in Xcode and iTunes Connect.

How do I handle auto-renewable subscriptions of multiple lengths for the same product?

You should let your customers manage auto-renewable subscriptions via the Manage Subscription page on their device. Use the following URL to open this page from within your app:

https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/manageSubscriptions

How do I migrate from an auto-renewable subscription to another In-App Purchase product?

Follow these steps to migrate from an auto-renewable subscription to another type of in-app purchase product:

  1. Remove your current auto-renewable subscription from sale in iTunes Connect by turning off their Clear for Sale flag, then remove it from your code. As a result, auto-renewal will be disabled for your product and an email will be sent to your customer. Note that you must still provide your customer with the paid product until the end of the subscription. Furthermore, all previously auto-renewable subscriptions are still restorable.

    For instance, if your customer has purchased a monthly subscription on April 1st and this subscription was removed from sale on April 19th, then you must provide the purchased provide content until May 1st.

  2. Create a new in-app purchase product of a type of your choosing, then update your binary to use it. See Create an in-app purchase for more information.

  3. Validate your app's receipt with the App Store, then parse its response to determine whether to provide its associated functionality to your customer. See Receipt Validation Programming Guide for more information.

My app does not receive any renewal notices even though it is running in the foreground.

If your app has a persistent transaction observer, then it will receive all its renewal notices when launching or resuming from the background. See Add a transaction queue observer at application launch for more information.

My server process rarely receives RENEWAL notices when the auto-renewing subscription renews.

Your application process implements support for the server-to-server notification service for auto-renewing subscriptions and expects to receive the RENEWAL status update notification when a renewal occurs. The description for the RENEWAL notification type in Table 6-4 Status Update Notification Types needs clarification. The description states "Automatic renewal was successful for an expired subscription. Check Subscription Expiration Date to determine the next renewal date and time.

The App Store attempts to charge the user account 24 hours before an auto-renewing subscription expires. If the renewal is successful, there is no server-to server notification because the auto-renewing subscription did not enter into the expired state. However, in the few cases that iTunes is unable to renew the subscription (generally there was a connection problem with the credit card server) and the auto-renewing subscription is not renewed before the expiration_date passes, the auto-renewing subscription is technically considered expired. iTunes may continue to attempt to renew the subscription. If iTunes is successful, then the RENEWAL event is sent. For this reason, the advice is presented - Check Subscription Expiration Date to determine the next renewal date and time.

For a normal renewal where the user account is successfully charged before the subscription expires, validate the appStoreReceipt. Check the contents of the latest_receipt_info field to verify that there is an auto-renewing subscription in-app purchase item where the expires-date is later than the current date.

Troubleshooting

Why are my product identifiers being returned in the invalidProductIdentifiers array?

Your product identifiers may be returned in the invalidProductIdentifiers array for one or more of the following reasons:

  • You did not use an explicit App ID.

  • If you or App Review rejected your most recent binary in iTunes Connect.

  • You did not clear your in-app purchase products for sale in iTunes Connect.

  • You did not sign your app with the Provisioning Profile associated with your explicit App ID.

  • You might have modified your products, but these changes are not yet available to all the App Store servers.

  • You did not complete all the financial requirements. See Agreements, Tax, and Banking Information for more information.

  • Your product is marked as Apple-hosted whose content has not yet been uploaded to iTunes Connect. See Upload in-app purchase content for more information on how to upload hosted content.

  • Your product identifier specified in iTunes Connect does not match the identifier used by the SKProductsRequest object in your app. See QA1329: In-App Purchase Product Identifiers for more information about product identifiers.

App Review has recently approved my application, but my In-App Purchase identifiers in the production version of the application are being returned in the invalidProductIdentifiers array.

When an application is approved, the developer must also approve the application for release to the App Store. On approval, the application ID is activated to the App Store. The same activation is required for the in-app purchase identifiers and can only take place once the application is activated. In some cases, the activation of the In-App Purchase identifiers may lag up to 48 hours following the activation of the application.

If the developer does not approve the release of the production application to the App Store, then any new in-app purchase identifiers will not be activated. This is an issue when a developer wants to verify the application prior to activating it on the App Store. If the desire is to test the in-app purchase process for the new items, the application must be activated to the App Store. This is only an issue for new in-app purchase identifiers in a corresponding application submission. Once these in-app purchase identifiers have been activated, application updates to the submission will find that these in-app purchase identifiers are validated, even if the update is not activated.

Calling the payment queue’s restoreCompletedTransactions method does not restore any products in my application

Calling the payment queue’s restoreCompletedTransactions method may not restore any products in your application for one or more of the following reasons:

  • Your products have unfinished transactions. The restore process does not return a product if it has an unfinished transaction in the payment queue. See Finish the transaction for more information on how to finish transactions.

  • You did not have any previously bought non-consumable, auto-renewable subscriptions, or free subscriptions.

  • You were trying to restore non-renewing subscription or consumable products, which are not restorable. The restoreCompletedTransactions method only restores non-consumable, auto-renewable subscriptions, and free subscriptions.

  • Your app's build version number (CFBundleVersion) does not follow guidelines for build version numbers. CFBundleVersion is a string made of three unsigned integers separated by a period. See Set the version and build for more information.

My app does nothing or crashes when App Review attempts to purchase an In-App Purchase product

  • Your app called SKPaymentQueue'saddPayment: with an SKPayment object that is either nil or is associated with an invalid product identifier.

    Consider the case where tapping the "Buy" button results in the serial call sequence in an app: The app first sends a product request to the App Store to validate an in-app purchase product identifier, then calls addPayment: to make a purchase. If for some reasons (such as a network failure), the product request fails and the app does not verify that the response.product array contains the SKProduct object associated with the product identifier, then the app should not call addPayment:. If however, the app makes the addPayment: call with an SKPayment object thats uses an invalid product identifier as seen in Listing 4, at best, nothing happens. At worst, the app crashes for using a nil identifier. To App Review, the issue was triggered by tapping the "Buy" button and nothing happened (or the app crashed).



    Listing 4  App implements the serial call sequence. Does not follow best practices for presenting in-app purchase products.

    @property SKProductsRequest *request;
    @property SKProduct *product;
     
    - (IBAction)purchase:(id)sender
    {
        NSSet *productID = [NSSet setWithObject:@"product_identifier"];
        // Create a product request.
        self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:productID];
        self.request.delegate = self;
     
        // Send the product request to the App Store.
        [self.request start];
    }
     
     
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
    {
        // product is an instance of SKProduct. The app assumes that response.products is
        // not empty by getting its first element without any checks.
        self.product = [response.products firstObject];
     
        NSLog(@"Name: %@", self.product.localizedTitle);
     
        // The app creates a payment request for a product whose value could be nil.
        SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:self.product];
     
       // If the product was nil, the app will crash when executing addPayment:.
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }

    As such, be sure to follow the Query the App Store for product information before presenting your app’s store UI best practice, to check that response.product contains the desired in-app purchase identifiers before using them, and call addPayment: only with payment requests associated with valid products as seen in Listing 5.



    Listing 5  Follows best practices for presenting and purchasing in-app purchase products.

    @property SKProduct *product;
    @property SKProductsRequest *request;
     
    -(void)fetchProductInformation
    {
         NSSet *productID = [NSSet setWithObject:@"product_identifier"];
         // Create a product request.
         self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:productID];
         self.request.delegate = self;
     
        // Send the product request to the App Store.
        [self.request start];
    }
     
     
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
    {
        // Be sure that the products array is not empty before fetching its content.
        if ([response.products count] > 0)
        {
           // product is an instance of SKProduct.
           self.product = [response.products firstObject];
           NSLog(@"Name: %@", self.product.localizedTitle);
        }
    }
     
     
    - (IBAction)purchase:(id)sender
     {
        if (self.product != nil)
        {
            SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:self.product];
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
    }
  • If your app uses receipt validation to determine whether to provide its features, then it may be sending its receipt to the wrong verification environment.

    App Review reviews apps in the sandbox. If you assume that submitting your app for review means that your app must be set to work in the production environment, then your app will be sending a Sandbox receipt to the production environment for verification. This will result in validation failure, which prevents your app from delivering content.To App Review, your app does nothing when making a purchase. See What url should I use to verify my receipt? for more information about receipt URLs.

Additional Resources



Document Revision History


DateNotes
2018-05-01

Updated SKProductsRequest reason for failure to validate In-App Purchase identifiers. Added QA about missing appStoreReceipt in sandbox environment. Added clarification on the RENEWAL server-to-server Status Update Notification type. Added QA about testing a production app with new In-App Purchase items and the failure to validate the new In-App Purchase identifiers. Removed ref to status 21009 - now outdated. Fixed outdated URL references. Clarified the use of the "cancellation_date" field in the appStoreReceipt. Removed outdated QA on customer sharing of information.

2017-06-28

Fixed typos.

2017-02-09

Added the "How do I create a hosted non-consumable product?" and "When validating my receipt, the App Store returns a status code of 21009" questions.

2015-12-07

Updated the "What url should I use to verify my receipt?" faq and Additional Reading section. Added the "How do I use the cancellation_date field?", "My app does nothing or crashes when App Review attempts to purchase an In-App Purchase product", "You've already purchased this. Would you like to get it again for free?", "This In-App Purchase has already been bought. it will be restored for free.", and "Are there any guidelines I need to follow to help protect against potential fraudulent activities?" faqs.

2015-08-18

Added the "How do I handle auto-renewable subscriptions of multiple lengths for the same product?" question. Updated the "Verifying my receipt fails with a status of <string of numbers>" question.

2015-05-29

New document that provides answers to frequently asked questions about in-app purchase.