bizz84 / SwiftyStoreKit

Lightweight In App Purchases Swift framework for iOS 8.0+, tvOS 9.0+ and macOS 10.10+ ⛺
MIT License
6.56k stars 797 forks source link

User payment is successful, StoreKit return fails "paymentCancelled". #537

Open Techgps1 opened 4 years ago

Techgps1 commented 4 years ago

Platform

In-app purchase type

Environment

Version

0.15.0

Report

Issue summary

When the user tried to re-new subscription In the production environment sometimes it works properly but in some cases, user payment is successful but dramatically StoreKit return error "paymentCancelled" it the SKError class which means user canceled the request but user claims he did not cancel. Also customer proves that they paid.

It failed in production but working in the sandbox.

Here's the method I used for purchase single product :

SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: false) { result in
    switch result {
    case .success(let product):
        // fetch content from your server, then:
        if product.needsFinishTransaction {
            SwiftyStoreKit.finishTransaction(product.transaction)
        }
        print("Purchase Success: \(product.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")
        default: print((error as NSError).localizedDescription)
        }
    }
}

Please advise what I'm doing wrong.

Your thoughts and suggestions highly appreciated.

Thanks in advance.

goob0176 commented 4 years ago

I've got my app rejected several times because of that issue. In sandbox everything works just fine, but when App Store Review Team tries to purchase some product with production account, the successful message pops up, but still StoreKit returns failed result (iOS, Non-consumable In-App). I''ll appreciate if there will be any help with that. Thanks

yangfan1233 commented 4 years ago

I got the same problem

Sam-Spencer commented 4 years ago

Can you clarify if this issue is occurring in actual production or only during App Store Review?

Techgps1 commented 4 years ago

Hello @Sam-Spencer Thank you for reply. I've fetching this issue in actual production not during app store review.

wicheda commented 4 years ago

Dear @Sam-Spencer ,

I have had a very similar issue, which is potentially happening in both production and sandbox. My app has been rejected twice now, with Apple stating:

Specifically, a successful subscription purchase does not forward us to the app and we are stuck on the subscription screen.

When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code "Sandbox receipt used in production," you should validate against the test environment instead.

We didn't change anything in the purchase code. We also have a low but significant percentage of users who say that they can not get past our payment page (i.e. the receipt verification fails), despite payment being taken.

Do you have any thoughts on what this may be? Is it possible Apple have an issue, or is some code change required in the library?

Thanks,

Daniel.

Sam-Spencer commented 4 years ago

Thank you for the additional details @wicheda! Based on the number of reports about this and similar issues with subscription purchasing in app-review / production, it looks like this is an issue with the library. It is going to take a lot of testing to figure out what's going wrong and why...

Community help would be greatly appreciated in solving this problem!

Techgps1 commented 4 years ago

Hello @Sam-Spencer Some of my app users fetching these issues while he tried to use an expired credit card, the payment was rejected, then he updated his credit card information and the payment went through but we did not get the order(didn't get any status about the purchase).

I've done bit research about and here is a similar issue in the apple developer forum as well https://developer.apple.com/forums/thread/112099 https://developer.apple.com/forums/thread/130890

Not sure it useful or not.

Thanks!

Techgps1 commented 4 years ago

@Sam-Spencer This happens when the user is required to update their credit card expiration date information. updatedTransactions is called twice, once with a failed state and then immediately after with a purchased state (assuming the user updates their credit card information and completes the purchase)

Techgps1 commented 4 years ago

@Sam-Spencer I think this is "Interrupted purchase flow". Make sure SwiftyStoreKit handled the interrupted purchase flow situation. For more information about the Interrupted purchase flow "https://developer.apple.com/forums/thread/86505#:~:text=The%20interrupted%20purchase%20flow%20situation,app%20comes%20forward%20to%20either."

To replicate the interrupted purchase flow situation, install the production app, then enter the Settings app and clear out the credit card info for the current user. One can also launch the iTunes app on a macOS system, enter the Account settings and set the credit card information to “none”. Now launch the in-app purchase app and attempt a purchase

miwandn commented 4 years ago

I believe I found the bug related to it. So looks like the initial fail causes the payment to be removed from the payments property. So now when you go to find the paymentIndex it doesn't exist anymore. However the completion block on completeTransactions does get hit because it doesn't need to hit the index.

PaymentsController -> Line 126