Handling StoreKit Errors
Tags: tutorial, mobile, ios, storekit
Published: 2022-04-22 09:34:54

The error state is one of the five UI states. It is pretty important for users’ experience and satisfaction. If you don’t handle errors, it will be the main reason for users to lose control and as a consequence – a reduction in retention or even deletion of the app. 

Error handling is more important when you work with dependencies, especially when working with a library like StoreKit. In general, StoreKit is a black-box in which anything can happen. In this short article, we will explore how to handle failed transactions and give appropriate error messages to the user as a feedback.

##NSError Overview
I just want to remind you how NSError works. We will need it below. Any error that happens on iOS includes error domain, domain-specific error code, and additional information specific for the application. So, for us the most important fields are:
- domain
- code
- userInfo

##Variety of errors
Errors associated with payments, store products, and cloud services occur with the domain model `SKErrorDomain` and error codes `SKError.Code`. The entire list of errors includes more than 15 options, you can find them in this documentation. In addition to `SKErrorDomain` errors, there may be network problems that occur in the `NSURLErrorDomain` model. Let’s handle all of them. 

##Handling StoreKit errors
For error handling, we need to implement two methods of the `SKRequestDelegate` delegate.
```
optional func request(_ request: SKRequest, didFailWithError error: Error)
```
This is an optional method that receives errors from `SKRequest`. It is important to keep in mind that when these errors occur, `(void) requestDidFinish: (SKRequest *) request` will not be called.

The second point of failure is transaction processing, here errors are quite varied and can be associated with both the fact that the user canceled the operation, and with the restriction of rights under parental control, for example. To receive errors when working with transactions, it is necessary to separately process the transaction status `SKPaymentTransactionStateFailed`.

```
- (void)paymentQueue:(nonnull SKPaymentQueue *)queue
 updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {
  for (SKPaymentTransaction *transaction in transactions) {
	  switch (transaction.transactionState) {
	    case SKPaymentTransactionStatePurchasing:
	      break;
	    case SKPaymentTransactionStatePurchased:
	      // implement handling purchased state
	      // [self handlePurchasedTransaction:transaction];
	      break;
	    case SKPaymentTransactionStateFailed:
	      [self handleFailedTransaction:transaction];
	      break;
	    case SKPaymentTransactionStateRestored:
	      // implement handling restored state
	      // [self handlePurchasedTransaction:transaction];
	      break;
	    default:
	      break;
	  }
  }
}
```
 Method `handleFailedTransaction`:

```
- (void)handleFailedTransaction:(SKPaymentTransaction *)transaction {
  NSError *error = transaction.error;
  
  if (error && [[error domain] isEqualToString:SKErrorDomain]) {    
      switch (error.code) {
        case SKErrorUnknown:
          // Error code indicating that an unknown or unexpected error occurred.
          break;
        case SKErrorClientInvalid:
          // Error code indicating that the client is not allowed to perform the attempted action.
          break;
        case SKErrorPaymentCancelled:
          // Error code indicating that the user canceled a payment request.
          break;
        case SKErrorStoreProductNotAvailable:
          // Error code indicating that the requested product is not available in the store.
          break;
        case SKErrorPaymentNotAllowed:
          // Error code indicating that the user is not allowed to authorize payments.
          break;
        case SKErrorPaymentInvalid:
          // Error code indicating that one of the payment parameters was not recognized by the App Store.
          break;
        // Belowe codes available on different iOS
        case 6:
          // SKErrorCloudServicePermissionDenied
          // Error code indicating that the user has not allowed access to Cloud service information.
          break;
        case 7:
          // SKErrorCloudServiceNetworkConnectionFailed
          // Error code indicating that the device could not connect to the network.
          break;
        case 8:
          // SKErrorCloudServiceRevoked
          // Error code indicating that the user has revoked permission to use this cloud service.
          break;
        case 9:
          // SKErrorPrivacyAcknowledgementRequired
          // Error code indicating that the user has not yet acknowledged Apple’s privacy policy for Apple Music.
          break;
        case 10:
          // SKErrorUnauthorizedRequestData
          // Error code indicating that the app is attempting to use a property for which it does not have the required entitlement.
          break;
        case 11:
          // SKErrorInvalidOfferIdentifier
          // The specified subscription offer identifier is not valid
        case 12:
          // SKErrorInvalidSignature
          // The cryptographic signature provided is not valid
          break;
        case 13:
          // SKErrorMissingOfferParams
          // One or more parameters from SKPaymentDiscount is missing
          break;
        case 14:
          // SKErrorInvalidOfferPrice
          // The price of the selected offer is not valid (e.g. lower than the current base subscription price)
          break;
      }
  } else if (error && [[error domain] isEqualToString:NSURLErrorDomain]) {
    switch (error.code) {
      case NSURLErrorNotConnectedToInternet:
        // A network resource was requested, but an internet connection has not been established and can’t be established automatically.
        break;
      default:
       // Handle other network errors here
      break;
    }
  }
  
}
```
##Types of errors

Let’s take a look at all the possible cases of StoreKit errors.

###.unknownSKErrorUnknown code 0
This error [SKErrordomain=0](http://qonversion.io/blog/skerrordomain-code-0/?utm_source=devto&utm_medium=blog&utm_campaign=skerrordomain0) return usually happens when an unknown or unexpected error has occurred. Usually, there is no need to take any action on the developers side, but here are a few recommendations to check: 

1. Check the `LocalizedDescription` property of the error object.
2. If the error appeared during the testing, try logging out or try it with a new test user. 
3. The problem might be on the user’s account side, so it is worth asking the users to check and see if anyone gets back to you with more information.

###.SKErrorClientInvalid code 1
This error is displayed when someone without the required permissions tries to perform the action. No action is needed to be done on your side. However, it is possible to try and show an automated notification with a message related to the action such as: “due to …. you are not allowed to perform the action. Please, change your account or device”.

###.SKErrorPaymentCancelled code 2
This error code indicates that the user canceled a payment request. No action is needed from your side, however, you could try to look at the user’s behavior to find the right trigger to encourage the user to complete the payment in the future – such as an offer or triggered email that talks about the value of your app. The best reaction to this error would be to catch the event and send the user an automated notification with a discount offer or something else of value to try and encourage them to continue using your app.

###.SKErrorPaymentInvalid code 3
This error shows that the payment was not processed correctly due to an issue with the billing – Error code indicating that one of the payment parameters wasn’t recognized by the App Store, card expiration or not enough funds. Try to check whether the billing issue is related to card expiration and if so, the best idea is to catch this error, and send an automated notification or email with a reminder to your user. 

###.SKErrorPaymentNotAllowed code 4
An error code that indicates that the user is not allowed to authorize payments.

###.SKErrorStoreProductNotAvailable code 5
This error might happen when your user tries to purchase a product that is unavailable in their region. Check SKStorefront.

###.SKErrorCloudServicePermissionDenied code 6
Error code that indicates that the user has not allowed access to Cloud service information.

###.SKErrorCloudServiceNetworkConnectionFailed code 7
This error occurs when the user tries to complete an action without a proper internet connection. Your best bet is to show them a notification highlighting that their connection is unstable, such as “please make sure that you have a proper internet connection, and try again later”.

###.SKErrorCloudServiceRevoked code 8
Error code indicating that the user has revoked permission to use this cloud service.

###.SKErrorPrivacyAcknowledgementRequired code 9
Error code indicating that the user has not yet accepted Apple’s privacy policy for Apple Music.

###.SKErrorUnauthorizedRequestData code 10
Error code indicating that the app is attempting to access a property for which it does not have the required permissions.

###.SKErrorInvalidOfferIdentifier code 11
This error indicates that the specified subscription offer identifier is not valid. For instance, you have not set up an offer with that identifier in the App Store, or you have revoked the offer. The best way to do this is to show the user a screen with your support team’s contact details.

###.SKErrorInvalidSignature code 12
The cryptographic signature provided is not valid. This error indicates that the promo offer purchase has been created incorrectly. There is no reason to show this error to the user – it’s best to handle this error and fix this within your infrastructure.

###.SKErrorMissingOfferParams code 13
One or more parameters from SKPaymentDiscount are missing. Same as above.

###.SKErrorInvalidOfferPrice code 14
This error indicates that the price of the selected offer is not valid, for instance, the price with a discount applied is lower than  the current base subscription price.

###NSURLErrorNotConnectedToInternet
A network resource was requested, but an internet connection has not been established and can’t be established automatically.

##How to handle StoreKit errors?
There are a lot of types of errors, so I‘d recommend dividing them into several groups:

- external, which cannot be influenced in any way, for example, network problems
- request errors such as incorrect product ID
- user side errors such as canceling an operation or an unverified account

##External errors
For such errors, an alert about the error itself is enough, as well as a repeat button for starting purchasing again. For example, if there is no Internet or an error occurs when connecting to the App Store.

##Request errors
This is a group of errors that can cause the greatest damage to the product conversions, as they are system problems, for example, `SKErrorStoreProductNotAvailable` or `SKErrorInvalidOfferPrice`. Errors of this group must be covered with additional logging, with the ability to remotely catch them on the server and fix them.

User side errors 
This group of errors is directly related to the user’s state, for example, `SKErrorPrivacyAcknowledgmentRequired`. 

Correct error handling within the application is a very important part of the user experience. First of all, I would recommend implementing the error and (not too sure, would you say “implementing the error AT the loading state”, since both have to occur simultaneously)  the loading state and then start implementing the main (content) state.