iosin-app-purchasereceipt-validation

Receipt validation before showing IAP-page


I have a question about our in-app purchase flow because it was rejected by Apple.

Because we don’t know if the user has a valid subscription, we have to fetch the receipt first which leads to the iTunes Store password prompt (which is the normal behavior as we understand correctly in the not production environment). Then the validation comes and, depending on the result, we show the auto-renewing subscription page or we pass the requested action.

Our flow is:

  1. Fetching receipt
  2. Validating receipt
  3. If
    • valid: pass requested action;
    • not valid: show auto-renewing subscription page where the user can make the purchase.

Now Apple has commented in their rejection that they don't see the auto-renewing subscription page. Instead, they got the password prompt from iTunes Store.

As we understand correctly, the password prompt is the normal behavior in a not production environment but Apple seems to not expect this behavior. We changed nothing in the way how we fetch the receipt.

We're using SwiftyStoreKit for an easy handling. This is our code:

SwiftyStoreKit.fetchReceipt(forceRefresh: false) { result in
    switch result {
    case .success(let receiptData):
        let encryptedReceipt = receiptData.base64EncodedString(options: [])
        Log.info("Fetch receipt success")
        //further code to send the receipt to our server
    case .error(let error):
        observer.send(error: error.localizedDescription)
    }
}

Is our flow incorrect or how can we validate if the user has a valid subscription without fetching the receipt? We're a bit confused here. Can someone can give us any advice here?


Solution

  • Your current flow doesn't give a very good user experience; they start your app and see an iTunes account password prompt without any context as to why it is appearing.

    I would suggest you adopt a process similar to the following:

    1. When you validate a subscription on this device, add a boolean to UserDefaults
    2. When your app starts, check for this value.
    3. If the value is true then refresh the receipt and validate the subscription
    4. If the value is false/not present then display your store interface. Make sure that your store interface includes a "Restore" button.
    5. Now, if the user knows that they have subscription they can tap restore and you can validate their subscription; If they are prompted for their iTunes account password at this time then they understand why.
    6. If the user doesn't have a subscription then they can purchase one. Even if they do have one and initiate a purchase, StoreKit will handle that and tell them they already have an active subscription
    7. Whether the subscription is purchased or restored, you will get a transaction for the subscription. You can then update UserDefaults and proceed as per step 3.

    Alternatively, check to see if the local receipt exists before attempting to refresh it. If the receipt doesn't exist then it is likely that the user will be prompted for an iTunes password:

    if let receiptDataURL = appStoreReceiptURL,
       let _ = try? Data(contentsOf: receiptDataURL)  {
           SwiftyStoreKit.fetchReceipt(forceRefresh: false) { result in
    
           switch result {
               case .success(let receiptData):
    
                   let encryptedReceipt = receiptData.base64EncodedString(options: [])
    
                   Log.info("Fetch receipt success")
    
        //further code to send the receipt to our server
    
               case .error(let error):
                   observer.send(error: error.localizedDescription)
           }
       }
    }  else {
        // Display purchase/restore interface
    }