Swiftystorekit: purchaseProduct Fail

Created on 21 Nov 2017  路  21Comments  路  Source: bizz84/SwiftyStoreKit

Platform

  • [X] iOS
  • [ ] macOS
  • [ ] tvOS

In app purchase type

  • [ ] Consumable
  • [ ] Non-consumable
  • [X] Auto-Renewable Subscription
  • [ ] Non-Renewing Subscription

Environment

  • [ ] Sandbox
  • [X] 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)
            }
        }
    }
purchase flows question

All 21 comments

Can anyone help me? I need this resolved asap

@WillMays1 did you find a solution for this? I'm stumbling upon this as well

@haemi No I have not found a solution. I have contacted Apple and they said it is an issue with SwiftyStoreKit and to contact the developer. I have tried to get ahold of him but have had no luck

@WillMays1 Apologies for the late reply.

What error do you get when you make the purchase?

You can use this code to find out:

SwiftyStoreKit.purchaseProduct("your-product-id", quantity: 1, atomically: true) { result in
    switch result {
    case .success(let purchase):
        print("Purchase Success: \(purchase.productId)")
    case .error(let error):
        switch error.code {
        case .unknown: print("Unknown error. Please contact support")
        case .clientInvalid: print("Not allowed to make the payment")
        case .paymentCancelled: break
        case .paymentInvalid: print("The purchase identifier was invalid")
        case .paymentNotAllowed: print("The device is not allowed to make the payment")
        case .storeProductNotAvailable: print("The product is not available in the current storefront")
        case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
        case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
        case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
        }
    }
}

(if you need to test in production you can replace the print statements with analytics calls).

Also make sure to double check all your iTunes Connect settings, and read all relevant information.

Do I need to add the quantity: 1 when purchasing an auto renewable subscription?

@WillMays1 no, you don't. Quantity is an optional parameter, it defaults to 1 if you omit it.

I have this issue as well -- it works when I use a sandbox ID, but not when I try it with a real apple ID. I get the 'unknown error.' Is there any way to fix this?

Any ideas? My purchase is not working when I use a real Apple ID, and I suspect that is the reason why my build got rejected. Is this because of Swifty?

@bizz84 I have the same issue. And the error return in result is .unkown. But the users are billed by apple/ It doesn't occur on every purchaseProduct but only sometimes.

@bizz84 Any news about this issue?

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()).

I am experiencing the same issue. It works most of the time, but occasionally we get a user who is gets both a payment confirmation alert.
img_5772
and then immediately gets the unknown purchase error alert.
img_5773

They insist that they are subscribed, are able to view the subscription in their phone settings, and have been billed.

Calling completeTransactions() on app start up.

What might be the issue in IAP setup in Itunes Connect? Especially if this is only an occasional problem?

I don't understand how both alerts could be shown.
Where does that first success alert come from?
It must be generated before verifying the receipt, because that happens inside the result from SwiftyStoreKit.purchaseProduct returned as .success.

So how am I getting both? How is it both successfully making the purchase, while also getting an error, before even verifying the receipt from the purchase?

The second is generated only if the result from SwiftyStoreKit.purchaseProduct returns as .error

SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in
NetworkActivityIndicatorManager.networkOperationFinished()

                switch result {
                case .success(let product):
                    // fetch content from your server, then:
                    if product.needsFinishTransaction {
                        SwiftyStoreKit.finishTransaction(product.transaction)
                    }
                    self.verifyPurchaseOfSubscription(purchase, completion: { result in
                        purchaseSuccessHandler(result)
                    }, verifyErrorHandler: { error in
                        verifyErrorHandler(error)
                    })
                    print("Purchase Success: \(product.productId)")
                case .error(let error):
                    purchaseErrorHandler(error)
                }
            }




func alertForPurchaseError(error: SKError) -> UIAlertController {
    switch error.code {

    case .unknown:
        Mixpanel.mainInstance().track(event: "Unknown Purchase Error", properties: ["userID" : self.userID])
        return alertWithTitle(title: "Purchase failed", message: "Unknown error. Please contact support or try again in a moment.")

Also getting this error. Maybe it's a new bug introduced from Apple because the error delegate from StoreKit fires along with a success delegate like described in this post.. Just weird it work in test but not in production.

First: I've nothing to do with this project.
Second: I've found this issue on google and I've noticed that the last post here is only 11 hours ago and right now I'm also facing an issue with the missing error object of a failed transaction object in a production environment...

So I guess its a new bug from Apple.

@RaimundWege did you discovered if it is a bug from apple?

Hi please if you got it working kindly assist with my issue https://github.com/bizz84/SwiftyStoreKit/issues/412

To solve that issue in my own store implementation I had to add a check for nil. I thought that the transaction in the case of SKPaymentTransactionStateFailed always provides an error object. But in some cases the error can be nil and in my implementation I've added this nilvalue to a dictionary... the solution was to add NSNull in that case: error ? error : [NSNull null].

I guess this happens when there are some old zombie transactions which were never finished correctly.

i am have the same error at the moment. the problem occurred suddenly.

@RaimundWege could you give as a more detailed explanation how you handled nil case?

It seems there are some problems at the Apple side. This issue happens all over the world

@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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Bizzi-Body picture Bizzi-Body  路  3Comments

astrokin picture astrokin  路  4Comments

odedharth picture odedharth  路  8Comments

Sam-Spencer picture Sam-Spencer  路  5Comments

andresmontelpare picture andresmontelpare  路  9Comments