SwiftyStoreKit: purchaseProduct Fail

Platform

  • iOS
  • macOS
  • tvOS

In app purchase type

  • Consumable
  • Non-consumable
  • Auto-Renewable Subscription
  • Non-Renewing Subscription

Environment

  • Sandbox
  • Production

Not working in production but working in sandbox

When purchasing a subscription, it verifies the receipt correctly. Once it asks the user to buy the subscription I am getting a “Purchase Fail” error. Below is my code:

func verifySubscription(){
        let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "**********")
        SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
            switch result {
            case .success(let receipt):
                let purchaseResult = SwiftyStoreKit.verifySubscription(
                    type: .autoRenewable,
                    productId: "**********",
                    inReceipt: receipt)
                switch purchaseResult {
                case .purchased(let expiryDate, let receiptItems):
                    print("******************** Expiration Date: \(expiryDate) ********************")
                    print("******************** Receipt: \(receiptItems) ********************")
                case .expired(let expiryDate, let receiptItems):
                    print("******************** Expiration Date: \(expiryDate) ********************")
                    print("******************** Receipt: \(receiptItems) ********************")
                    self.purchaseSubscription()
                case .notPurchased:
                    print("******************** User never purchased ********************")
                    self.purchaseSubscription()
                }
                
            case .error(let error):
                print("******************** Receipt Verification Error: \(error) ********************")
            }
        }
    }

func purchaseSubscription(){
        SwiftyStoreKit.purchaseProduct("**********", atomically: true) { result in
            if case .success(let purchase) = result {
                if purchase.needsFinishTransaction {
                    SwiftyStoreKit.finishTransaction(purchase.transaction)
                }
            } else {
**I AM GETTING THIS ERROR AND ITS NOT LETTING USER BUY SUBSCRIPTION**
                print("******************** Purchase Error ********************")
                let alertController = UIAlertController(title: "Error", message: "Cannot purchase content.\nPlease try again later", preferredStyle: .alert)
                let yesAction = UIAlertAction(title: "Ok", style: .default) { (action) -> Void in }
                alertController.addAction(yesAction)
                self.present(alertController, animated: true, completion: nil)
            }
        }
    }

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 1
  • Comments: 21 (3 by maintainers)

Most upvoted comments

Apologies for the late reply everyone.

SwiftyStoreKit returns “Unknown error” when a transaction is .failed, but there is no transaction error. According to the docs, this shouldn’t happen though:

open class SKPaymentTransaction : NSObject {
    // Only set if state is SKPaymentTransactionFailed
    @available(iOS 3.0, *)
    open var error: Error? { get }
}

SwiftyStoreKit only returns an error if a failed transaction shows up in the payment queue. So I’m inclined to think this is a problem in StoreKit or in your IAP setup in iTunes connect.

Also:

Are you calling completeTransactions() on app startup?

If you don’t you could get mixed up failed transactions that were pending before you started the new purchase (this typically results in the completion block called immediately after calling purchaseProduct()).

@shi-rudo I only check the error result directly after receiving the notification and when the error object is nil I replace it with [NSNull null]:

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
    [[ATLogger shared] logDebug:[NSString stringWithFormat:@"Running transactions: %lu", (unsigned long)queue.transactions.count]];
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:
                [self purchaseTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failTransaction:transaction];
                break;
            case SKPaymentTransactionStatePurchasing:
                break;
            case SKPaymentTransactionStateDeferred:
                break;
            default:
                break;
        }
    }
}

- (void)failTransaction:(SKPaymentTransaction *)transaction {
    [[ATLogger shared] logError:[NSString stringWithFormat:@"ATStoreKitController fail transaction: %@", transaction.transactionIdentifier] withError:transaction.error];
    [self postPurchaseFailureNotificationForIdentifier:transaction.payment.productIdentifier andError:transaction.error];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)postPurchaseFailureNotificationForIdentifier:(NSString *)identifier andError:(NSError *)error {
    NSDictionary *userInfo = @{kUserInfoProduct:[self productForIdentifier:identifier],
                               kUserInfoError:(error ? error : [NSNull null])}; // <--- CHECK FOR NIL HERE
    [[NSNotificationCenter defaultCenter] postNotificationName:ATPurchaseFailureNotification object:identifier userInfo:userInfo];
}

I recommend to use logs for purchase process debugging.