Skip to content

Instantly share code, notes, and snippets.

@donnywals
Created June 8, 2018 08:45
Show Gist options
  • Save donnywals/a8c140605ee8668657453bf748ffe4ab to your computer and use it in GitHub Desktop.
Save donnywals/a8c140605ee8668657453bf748ffe4ab to your computer and use it in GitHub Desktop.

Best Practices and What’s New with In-App Purchases

Introductory prices

SKProduct.introductoryPrice is an instance of SKProductDiscount. Just like a normal SKProduct, SKProductDiscount has a price and priceLocale. It also has a subscriptionPeriod. This period has a unit in the form of a month, week, month, year and a number of units. For instance 3 month units is possible. There is also a numberOfPeriods. Another property on SKProductDiscount is paymentMode.

If you have a 3 month cycle for your subscriptions, you can offer 6 months of access for a discount by setting numberOfPeriods to 2. If you set paymentMode to payAsYouGo, the user pays the second period after the first ends. You could also do a payUpFront setting so the user will pay the first two periods all at once. After the 6 month trial is up, the next renewal is for three months.

The last paymentMode is freeTrial, this gives the entire introductory period to the user for free.

Since iOS 11.2, subscriptionPeriod is part of SKProduct. New in iOS 12 is subscriptionGroupIdentifier. Intro price eligibility is determined based on the subscription group identifier. There can only be one introductory price per subscription group.

It is up to the developer to make sure the user’s eligibility is properly determined and the correct price is shown. More on that in the Engineering Subscription talk.

New in iOS 12 Free trials for non-subscription apps. Non-sub apps can now publish a free app with a non-consumable in-app purchase to unlock the app. To start the trial the user uses the none-consumable. Naming convention is 14-day Trial, very obvious that it’s a trial and for how long. It is important that users understand this. You must also inform the user about the costs once the trial is over and what features will be locked once the trial expires.

Requesting ratings and reviews

iOS limits how often you can ask for reviews. So ask for them with care. It is important to not interrupt the user while they are doing something in your app. Make sure that your user has spent some time with your app, and preferably has some positive experiences with your app.

iOS does not rate limit the prompt so if you ask for a review three times in a row, your user will get the prompt three times in a row. So make sure to add some of your own rate-limiting logic to your app to make sure the user isn’t flooded with review prompts by your app.

Requesting ratings and reviews will now also be available on macOS Mojave.

Sandbox testing

StoreKit uses the certificate that your app is signed with to figure out if it should go to the sandbox or the production environment. The sandbox does not charge users, ever. You must use a separate account for the sandbox. The sandbox also uses its own backend so all urls are different.

  • The SKReceiptRefreshRequest can be configured to return an expired receipt
  • SKMutablePayment.simulateAskToBuyInSandbox can be used to simulate a kid asking permission to their parents.
  • Auto-renewals use a condensed timeframe for renewing subscriptions. 1 year goes by in 1 hour.
  • Auto-Renewing in the sandbox is limited to 5 times.

Before testing in the sandbox you must configure your IAPs. You must also sign in with your sandbox account to use the sandbox IAPS.

New in iOS 12 You can now configure your sandbox account in settings. The app will then know that it should use your sandbox account when testing IAPs. No need to sign out of your production account and back in.

Observing transactions

You should observe transactions as soon as possible. Preferably in didFinishLaunchingWithOptions. It’s good practice to handle transactions as soon as possible. A buy can be interrupted by a user. For instance when they leave the app. You want to re-prompt them as soon as they return to your app.

Another reason to observe transaction as soon is possible is because renewals are also handled in the observer. Also, promo codes are redeemed in the App Store and then passed to your app.

Finishing transactions

  • In the purchasing state, you do nothing. Wait for a different state.
  • In the purchased state, you should validate the purchase in your app.
  • In the failed state, inspect the failure reason and act accordingly
  • The restored state is similar to the purchased state
  • The deferred state means the purchase is pending. For instance when a child must ask their parents before proceeding to buy.

Only finish transactions when you have finished honouring the purchase. For instance after you have downloaded contents, not during. If you validate receipts. Then validate it before you finish the transaction.

Ask to buy

The buying user sees a notification that their parent has been asked for the purchase. When the parent takes action, the app will receive either a purchased or failed transaction state depending on the parent’s choice. The parent must respond witting 24-hours. Multiple asks with 24-hours are all handled at once on the parent side.

Do not expect or wait for the ask to complete, it might never come back if the parent takes no action.

Receipts

Receipts are stored on the device, issued and signed by the App Store and can be validated. You can use on-device validation to unlock features within the app. You can also use server-to-server validation. You send the receipt to Apple and it will validate the device for you, making sure that the request comes from a trusted server.

Server-to-server validation is a good idea. However, device-to-server validation should not be used, it’s not secure. Make use of an intermediate server in this scenario.

Do not check the receipt expiration date against the on-device clock. Compare the dates within the receipt. If all transactions took place before the certificate expires, the receipt is valid.

If you verify that the receipt belongs to your app, prefer checking against hard-coded values. It’s too easy to change the app plist to match a malicious receipt. You can verify the device by checking type 4 and 5 from the device. Type 5 is a sha-1 hash that is unique for the device. You can use this to verify that the receipt belongs to the current device.

When you detect an invalid receipt you ask StoreKit for a new refresh, store sign-in is required at that point. You must avoid a continuous loop of validate-and-refresh so keep track of this.

Receipts and free-trials

The receipt’s type17 attributes contains information about the type of purchase. You can use the sub dictionaries here to figure out what the user has bought and whether the trial is still valid.

To find out if the user unlocked the app, look for your full-unlock identifier. If you find it, the app is unlocked. If not, look for the trial identifier. Since the device clock isn’t reliable you might want to use a back-end server to validate the trial.

If neither is found, then you should show your on boarding to allow the user a free trial.

In case you have an existing app in the store, you can use the type 19 attribute in the receipt to validate whether the user is a currently paying user. A user that has already payed for your app should not be offered the free trial and should not have to buy the app again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment