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.

How do I enable my wildcard App ID to support In-App Purchase?

Follow these steps to enable your App ID to support in-app purchase:

  1. Identify your app’s current bundle ID in Xcode or iTunes Connect. See About Bundle IDs for more information.

  2. Follow steps 1-6 and 8-10 of Registering App IDs to update your wildcard App ID to one that supports in-app purchase.

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 Configuring an In-App Purchase Product to find out how many in-app purchase product IDs you can create per application.

Error Messages

Your account info has changed

The "Your account info has changed" message indicates that you are signed in the iTunes 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.

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.

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

When validating my receipt, the App Store returns a status code of 21009.

The status code of 21009 indicates that there was a problem with the receipt validation process. For most cases, this was a temporary issue with the server and you should retry validating your receipt with the App Store.

In other cases, 21009 will be returned when the user account associated with the receipt was deleted or when the receipt contains some invalid information. As these cases are extremely rare, please file a bug at https://bugreport.apple.com when encountering them.

How do I use the cancellation_date field?

The Cancellation Date (cancellation_date) field is designed for use with auto-renewable subscription and non-consumable products. This field is set when a customer contacts Apple customer support for a refund and the transaction is canceled. cancellation_date is not automatically added to your app's receipt when set. It appears in the latest_receipt section of your app's receipt when the receipt is updated (for instance, when a new payment transaction occurs or when you restore purchases using SKPaymentQueue’s restoreCompletedTransactions), when your app calls SKReceiptRefreshRequest to refresh it, or when your app performs receipt validation with the App Store (https://buy.itunes.apple.com/verifyReceipt).

Your should validate your app's receipt with the App Store, then parse its response to determine whether an auto-renewable subscription was canceled.

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.

    {
        "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 StoreKit has not recorded any transactions for that 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 StoreKit 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.

Subscriptions

How do I create a hosted non-consumable product?

See Hosting Non-Consumable Products with Apple for more information on how to create a hosted non-consumable product in Xcode and iTunes Connect.

I am unable to upload my hosted content to iTunes Connect with Xcode 6 or later

There is a current bug that prevents Xcode 6 or later from uploading hosted content to iTunes Connect. To workaround this issue, use Application Loader to upload a package containing your hosted content. See below for steps on how to do so:

  1. In the Xcode Archives Organizer, select the archive containing your hosted content.

  2. Click Export.

  3. In the dialog that appears, choose Export as an Installer Package.

  4. Click Next to start generating your package, then select Export to save your package (a file with a .pkg filename extension).

  5. Use Application Loader to upload the above package. See Using Application Loader for more information about it.

Figure 1  Generating a package for a hosted content. The numbers in this figure correspond to the steps above.

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 Creating In-App Purchase Products 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.

How do I find out whether my customers have opted to share their contact information with me?

Use the Subscription Expiration Date (expires_date) field in your receipt to determine whether your customers have opted to share their information.

Let's say you decide to offer a 7-day free trial to customers who agree to share their information when purchasing your 1 month subscription. StoreKit will provide you with a receipt whose expires_date accounts for both the 7 days and 1 month lengths. Your subscription will expire 1 month and 7 days after it was first purchased, then every month thereafter while auto-renewal is turned on for it.

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.

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 an Apple-hosted one whose content has not yet been uploaded to iTunes Connect. See Hosting Non-Consumable Products with Apple for more information on 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.

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 Setting the Version Number and Build String 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. Thus; resulting 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
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.