Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for interrupted transactions #615

Closed

Conversation

maciesielka
Copy link

@maciesielka maciesielka commented Jan 12, 2021

Summary

Seems related to #593 and #606.

Per documentation provided by https://developer.apple.com/documentation/storekit/in-app_purchase/testing_in-app_purchases_with_sandbox, interrupted purchases will send two transaction notifications for a given purchase:

  1. a .failed state as the interruption begins
  2. a .success state afterwards with matching product identifier and quantity

This diff attempts to account for this flow, which would end tracking for a particular purchase attempt on its first failure

Notes

I haven't been able to test this flow to completion using the Sandbox environment, since "Accept"-ing terms doesn't seem to trigger a successful transaction in the payment queue. I'd appreciate any assistance in understanding how to test this flow, or if a Feedback to Apple may be in order. (Update: See note below)

Update: Use a relatively new Sandbox tester account for testing the "Interrupted Purchases" flow, as it seems older accounts run into issues where the purchase is never made successful after accepting terms.

@kambala-decapitator
Copy link

I haven't been able to test this flow to completion using the Sandbox environment, since "Accept"-ing terms doesn't seem to trigger a successful transaction in the payment queue.

For me it works fine: the success branch of SwiftyStoreKit.purchaseProduct() is called after accepting terms.

@vapor-pawelw
Copy link

I also tried testing this on sandbox and didn't get any updates on the queue observer after accepting these fake terms Apple is providing, so I'm not sure if this is not a bug on their side. I will release an app with the change from this PR to the store shortly and see if there's any difference in production environment.

Also not sure if this library now complies with what Apple described about Apple Pay here
https://developer.apple.com/support/psd2/

@vapor-pawelw
Copy link

I haven't been able to test this flow to completion using the Sandbox environment, since "Accept"-ing terms doesn't seem to trigger a successful transaction in the payment queue.

For me it works fine: the success branch of SwiftyStoreKit.purchaseProduct() is called after accepting terms.

What environment did you test it on? For me there were no updates on the queue and did not get a handler call on sandbox

@kambala-decapitator
Copy link

@vapor-pawelw was testing in sandbox envrionment on iOS 14.3

@maciesielka
Copy link
Author

@vapor-pawelw was testing in sandbox envrionment on iOS 14.3

Hmm. I can confirm I'm testing on iOS 14.3 as well, but still experience the unexpected behavior on Sandbox.

@vapor-pawelw
Copy link

I'm on 14.3 also, just tried running payment on my app again and I'm still not getting any follow-up transaction on the queue after accepting the terms. Only getting failure when the terms show up. @kambala-decapitator are you using the official repo + the commits from @maciesielka , or some other/own fork repo? I have no idea why this would be different for us. Maybe it is something related to the sandbox account?

@kambala-decapitator
Copy link

kambala-decapitator commented Jan 13, 2021

I've just retested on another 14.3 device and had success again...

Yes, I use fork from this PR:

pod 'SwiftyStoreKit', :git => 'https://github.com/maciesielka/SwiftyStoreKit.git', :branch => 'feature/interrupted-transactions'

Here's my simplified code and output I receive after accepting terms:

SwiftyStoreKit.purchaseProduct(...) {
    switch $0 {
    case .success(let purchase):
        print("[iap] purchase success:", purchase.productId)
    case .error(let error):
        print("[iap] purchase error:", error)
    }
}

[iap] purchase error: related decl 'e' for SKErrorCode(_nsError: Error Domain=SKErrorDomain Code=0 "Ein unbekannter Fehler ist aufgetreten" UserInfo={NSLocalizedDescription=Ein unbekannter Fehler ist aufgetreten, NSUnderlyingError=0x2805ff630 {Error Domain=ASDServerErrorDomain Code=3038 "Die allgemeinen Geschäftsbedingungen der Apple-Mediendienste haben sich geändert." UserInfo={NSLocalizedDescription=Die allgemeinen Geschäftsbedingungen der Apple-Mediendienste haben sich geändert.}}})

[iap] purchase success: my.product.id

My sandbox tester was created for Switzerland in June 2020.

Maybe I'll ask you a stupid question: did you make sure that the library got rebuilt after changing its source?

@maciesielka
Copy link
Author

I've just retested on another 14.3 device and had success again

🎉

Maybe I'll ask you a stupid question: did you make sure that the library got rebuilt after changing its source?

I've tested this further upstream as well -- just breakpointing within the SKPaymentTransactionObserver in the SwiftyStoreKit lib in debug. And I see the first failure upon the presentation of the terms sheet, and then no subsequent updates after accepting.

@kambala-decapitator
Copy link

And I see the first failure upon the presentation of the terms sheet, and then no subsequent updates after accepting.

I faced this in the latest release as well. But completion block of SwiftyStoreKit.completeTransactions() does get called.

@vapor-pawelw
Copy link

@kambala-decapitator yes, tried rebuilding but that didn't help :( I am using latest master branch of the original repo and branched out from that then cherry-picked @maciesielka changes. Also not getting completeTransactions upon accepting or restarting app. I don't know why this is different for us. Might try with another sandbox account

@maciesielka
Copy link
Author

Making a new Sandbox account allowed it to work for me! The one i'd been testing with was quite old even tho it allowed for "Interrupted Purchases" to be enabled in AppStoreConnect

@vapor-pawelw
Copy link

Yes, same for me :) Newer sandbox account did work. Btw. I don't like the delay it takes since failed to getting purchased on the observer. I looked at the .failed underlying error and getting some ASDServerErrorDomain error 3038 (as mentioned by @kambala-decapitator above) but I have no idea if this is something that won't change on the production when it won't be the agreement but something else. Would be nice if I could tell that the payment has been deferred (there's even a status for that, but it seems its not used in this case) so I could show the loader but there's no such thing

@maciesielka
Copy link
Author

@vapor-pawelw Agree that we can't really expect ASDServerErrorDomain code 3038 for each scenario where this occurs; the new SCA requirements likely require authorization through a bank, rather than a new terms sheet for instance. Also was shocked that this flow didn't use .deferred, but it feels like that's just a decision Apple made.

Any more changes to be made here?

@kambala-decapitator
Copy link

Any more changes to be made here?

seems fine to me

@vapor-pawelw
Copy link

I guess there's nothing more we can do about it, and the code seems fine to me. I'd say ready for merge, also this is quite urgent issue so I think this PR shouldn't be kept too long. If someone feels like this needs some minor refactoring, this can always be done later if needed.

@vapor-pawelw
Copy link

Also tested on production on several apps released since yesterday and I can confirm this solution is working

@anta-semenov
Copy link

anta-semenov commented Jan 15, 2021

I'm also currently working on interrupted flow for SCA. And there is another way to try SCA flow, using ask to buy (it could be done with storekit xcode testing or with putting simulatesAskToBuyInSandbox in purchaseProduct call).
And in this case it actually use deferred transaction state and library doesn't work as I would expect.
The purchaseProduct call doesn't end with completion, instead completeTransactions callback is called.
The thing is that PaymentsController.swift -> processTransaction function doesn't handle deferred state. For me it looks like it should treat deferred state the same way as it does with purchased, meaning it should finish transaction and call completion block with success result. In result we can check transactionState and act accordingly. And after ask to buy (or sca) will be completed completeTransactions callback will be called.

And for me with interrupted purchases flow library already works as expected, meaning completeTransactions callback is called after I accept new terms, so handling specific error is more a dev responsibility than a library, imho.

@vapor-pawelw
Copy link

I noticed that too when inspecting the code and checking if code from this PR should work when my sandbox flow didn't work and came to the same conclusion, but I think another PR could be opened for that because of urgency of this one & I think it would be good if some people could test if treating deferred the same way failed is treated here will work properly with Ask to Buy

@maciesielka
Copy link
Author

And for me with interrupted purchases flow library already works as expected, meaning completeTransactions callback is called after I accept new terms, so handling specific error is more a dev responsibility than a library, imho.

@anta-semenov You mean that the library works as expected before the changes in this PR?

And there is another way to try SCA flow, using ask to buy (it could be done with storekit xcode testing or with putting simulatesAskToBuyInSandbox in purchaseProduct call). And in this case it actually use deferred transaction state and library doesn't work as I would expect.

@anta-semenov Thanks for calling out these testing flows. I'm happy to add .deferred support here, assuming the existing changes are still required per my first question for ya

@maciesielka
Copy link
Author

Looking at the changeset closer, I'm not sure we can merge this as is. Existing consumers of the library may expect the interrupted purchases (and sure enough the deferred purchases as well) to be handled in the completeTransaction handler, even if that's not expected by us in this thread. That makes this and any subsequent changes to handle the deferred state within purchaseProduct all breaking changes.

@Sam-Spencer
Copy link
Collaborator

@maciesielka That is a bit of a quandary. I think, in the long term (seeing as this type of situation may become increasingly common across jurisdictions and as Apple adds new Privacy Controls, Parental Restrictions, etc.) that the library may need to head in that direction --> towards more specificity in the types returned from completeTransaction.

There's a good amount of community support for this and some good feedback as well. However, I'd like to avoid breaking any existing payment flows people may have implemented -- maybe there's a better way to design this? 😕

@maciesielka
Copy link
Author

maybe there's a better way to design this? 😕

Yup agree, and sorta what I was hinting at with my comment. Hopefully I'll get around to reworking some of this soon.

@maciesielka
Copy link
Author

Going to close this for now, will reopen if there is any movement on my end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants