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

TransactionPoster: don't finish transactions for non-subscriptions if they're not processed #2841

Conversation

NachoSoto
Copy link
Contributor

@NachoSoto NachoSoto commented Jul 19, 2023

This ensures that if the server returns a 200, but didn't actually process the transaction, we don't lose it forever.

When this happens, a warning will be logged:

[purchases] DEBUG: ℹ️ TransactionPoster: handling transaction for product 'BA92DCD8-74C2-45FF-AEE4-2FFD036AF827'
[purchases] WARN: ⚠️ Transaction '19B3BD23-181B-442A-A5B8-05D686ED98E2' will not be finished: it's a non-subscription and it's missing in CustomerInfo list: []

Dependencies:

@NachoSoto NachoSoto added the pr:fix A bug fix label Jul 19, 2023
@NachoSoto NachoSoto requested review from tonycosentini and a team July 19, 2023 13:37

func containsNonSubscription(_ transation: StoreTransactionType) -> Bool {
return self.nonSubscriptions.contains {
$0.transactionIdentifier == transation.transactionIdentifier
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tonycosentini this is ready. Just gotta change to use the real store transaction ID not the RC ID.
Also I wonder if we should restrict this by sandbox and store too /cc @aboedo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does non subscription here mean both a consumable and a non-consumable? 🤔

Can the answer to this be added as a comment to this function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NachoSoto is your comment here still true? Or is this ready to go? I think the khepri PR has been merged, not sure whether it's been deployed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also...
We might wanna do it for non-renewing subscriptions as well, since they have the same characteristics as consumables in that they disappear from the receipt forever.

And lastly, non-consumables don't actually need this, do they get the same treatment?

Copy link
Contributor Author

@NachoSoto NachoSoto Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: we discovered that they're included, so test those too:

  • Integration test
  • Sandbox test.

Copy link
Contributor Author

@NachoSoto NachoSoto Aug 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the PR, logs, and code to make this more clear. I was mixing consumables with non-subscriptions everywhere.

We might wanna do it for non-renewing subscriptions as well, since they have the same characteristics as consumables in that they disappear from the receipt forever.

And lastly, non-consumables don't actually need this, do they get the same treatment?

See TransactionPoster.shouldFinish(transaction:for:customerInfo:). That new logic is based on StoreProduct.productCategory (subscription / non-subscriptions).
We can't use ProductType because we can't determine that for SK1 products (see SK1StoreProduct.productType).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note that we could in theory distinguish between non-renewing and auto-renewing subs by mixing in the subs vs non subs distinction + expiration date != nil, but it's a non-trivial change that feels a bit more risky, and it isn't worth the effort.

@NachoSoto
Copy link
Contributor Author

Waiting on @tonycosentini to change transaction identifiers for StoreKit tests so we can verify this in integration tests.

@NachoSoto NachoSoto force-pushed the nacho/sdk-3220-dont-finish-ios-consumable-transactions-until-we-see-them-in branch from 1def557 to 1e0ad6e Compare July 21, 2023 06:40
@NachoSoto NachoSoto requested a review from aboedo August 3, 2023 20:31
@NachoSoto NachoSoto force-pushed the nacho/sdk-3220-dont-finish-ios-consumable-transactions-until-we-see-them-in branch 3 times, most recently from 70ee846 to b6c08ab Compare August 3, 2023 21:21
@NachoSoto
Copy link
Contributor Author

This should be ready.

@NachoSoto NachoSoto force-pushed the nacho/sdk-3220-dont-finish-ios-consumable-transactions-until-we-see-them-in branch from b6c08ab to 00215b7 Compare August 3, 2023 21:45
@codecov
Copy link

codecov bot commented Aug 3, 2023

Codecov Report

Merging #2841 (3940482) into main (022cdea) will increase coverage by 0.04%.
Report is 2 commits behind head on main.
The diff coverage is 90.62%.

@@            Coverage Diff             @@
##             main    #2841      +/-   ##
==========================================
+ Coverage   86.74%   86.79%   +0.04%     
==========================================
  Files         219      220       +1     
  Lines       15650    15701      +51     
==========================================
+ Hits        13576    13627      +51     
  Misses       2074     2074              
Files Changed Coverage Δ
...urces/Identity/CustomerInfo+NonSubscriptions.swift 0.00% <0.00%> (ø)
Sources/Misc/Deprecations.swift 37.85% <0.00%> (-0.28%) ⬇️
Sources/Logging/Strings/PurchaseStrings.swift 89.69% <100.00%> (+0.17%) ⬆️
...urces/Purchasing/Purchases/TransactionPoster.swift 100.00% <100.00%> (ø)
...ing/StoreKitAbstractions/SK1StoreTransaction.swift 100.00% <100.00%> (ø)
...ing/StoreKitAbstractions/SK2StoreTransaction.swift 100.00% <100.00%> (ø)
...hasing/StoreKitAbstractions/StoreTransaction.swift 98.14% <100.00%> (-0.04%) ⬇️

... and 3 files with indirect coverage changes

@tonycosentini tonycosentini removed their request for review August 4, 2023 03:33
@NachoSoto NachoSoto force-pushed the nacho/sdk-3220-dont-finish-ios-consumable-transactions-until-we-see-them-in branch from 00215b7 to 556d11e Compare August 7, 2023 23:15
Copy link
Member

@joshdholtz joshdholtz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense to me! I think I really have questions about if and how consumables and non-consumables are being treated here 🤷‍♂️ The title of the PR mentions consumables but the code refers to non-subscriptions.


func containsNonSubscription(_ transation: StoreTransactionType) -> Bool {
return self.nonSubscriptions.contains {
$0.transactionIdentifier == transation.transactionIdentifier
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does non subscription here mean both a consumable and a non-consumable? 🤔

Can the answer to this be added as a comment to this function?

Sources/Logging/Strings/PurchaseStrings.swift Outdated Show resolved Hide resolved
Comment on lines +142 to +151
case .nonSubscription:
// Only finish consumables if the server actually processed it.
let shouldFinish = (
!transaction.hasKnownTransactionIdentifier ||
customerInfo.nonSubscriptions.contains {
$0.storeTransactionIdentifier == transaction.transactionIdentifier
}
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My head is still getting stuck here if nonSubscription means consumable or non-consumable and if they should be treated the same 🤷‍♂️

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah... I think ideal behavior is:

  • auto-renewing subscriptions and non-consumables: business as usual
  • consumables and non-renewing subscriptions: finish only if part of response

Current is:

  • auto-renewing subscriptions and non-renewing subscriptions: business as usual
  • consumables and non-consumables: finish only if part of the response

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we should make sure the backend behaves in the same way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answer to this (will test to make sure), non_subscriptions include:

  • Consumables
  • Non-consumables
  • Non-renewing subscriptions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating on this comment for folks who haven't been following as closely - I believe we do now have integration tests with non-consumables and non-subscriptions, and have used them to confirm that the backend indeed sends them as the same thing, and they're safe to treat the same way.

@objc public let transactionIdentifier: String

/// The unique identifier for the transaction created by the store.
@objc public let storeTransactionIdentifier: String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add this to the PR description as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I extracted this to #3009 so we can make that a feature and keep this as a fix.

Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit worried about the storeTransactionIdentifier vs transactionIdentifier nomenclature and their differences between subscriptions and non-subscriptions... Other than that, I think this is good

@NachoSoto NachoSoto force-pushed the nacho/sdk-3220-dont-finish-ios-consumable-transactions-until-we-see-them-in branch from 556d11e to 008002b Compare August 14, 2023 15:39
@NachoSoto NachoSoto force-pushed the nacho/sdk-3220-dont-finish-ios-consumable-transactions-until-we-see-them-in branch from b744267 to d867701 Compare August 14, 2023 16:43
@NachoSoto
Copy link
Contributor Author

I think I really have questions about if and how consumables and non-consumables are being treated here 🤷‍♂️ The title of the PR mentions consumables but the code refers to non-subscriptions.

@joshdholtz I updated the log, PR description, and code to make that more clear.

Base automatically changed from integration-test-consumable-non-renewing to main August 14, 2023 22:09
NachoSoto added a commit that referenced this pull request Aug 14, 2023
…packages (#3008)

There were some questions about these in #2841, and realized we had no
test coverage for them.
This also improves the documentation of `CustomerInfo.nonSubscriptions`
and `NonSubscriptionTransaction`.
NachoSoto added a commit that referenced this pull request Aug 14, 2023
This will be used for #2841, but it can be useful for users as well.
@NachoSoto NachoSoto force-pushed the nacho/sdk-3220-dont-finish-ios-consumable-transactions-until-we-see-them-in branch from d867701 to 49ed141 Compare August 14, 2023 22:33
@NachoSoto
Copy link
Contributor Author

This is ready now.

NachoSoto added a commit that referenced this pull request Aug 14, 2023
@NachoSoto NachoSoto force-pushed the nacho/sdk-3220-dont-finish-ios-consumable-transactions-until-we-see-them-in branch from 49ed141 to 3940482 Compare August 14, 2023 23:15
@NachoSoto NachoSoto merged commit 5d3263c into main Aug 15, 2023
2 checks passed
@NachoSoto NachoSoto deleted the nacho/sdk-3220-dont-finish-ios-consumable-transactions-until-we-see-them-in branch August 15, 2023 14:05
NachoSoto added a commit that referenced this pull request Aug 15, 2023
This test was added in #2841, but iOS 12 can't detect offline `CustomerInfo`s because those aren't available, so this test isn't relevant there.

Fixes https://app.circleci.com/pipelines/github/RevenueCat/purchases-ios/14193/workflows/a3836667-878c-4bdd-939a-e809e9f0ac5a/jobs/107291/tests
This was referenced Aug 16, 2023
NachoSoto pushed a commit that referenced this pull request Aug 17, 2023
**This is an automatic release.**

### Bugfixes
* `PurchasesOrchestrator`: fixed callback not invoked regression during
downgrades (#3028) via NachoSoto (@NachoSoto)
* `TransactionPoster`: don't finish transactions for non-subscriptions
if they're not processed (#2841) via NachoSoto (@NachoSoto)
### Performance Improvements
* `StoreKit 2`: only listen to `StoreKit.Transaction.updates` when SK2
is enabled (#3032) via NachoSoto (@NachoSoto)
* `CustomerInfoManager`: post transactions in parallel to POST receipts
only once (#2954) via NachoSoto (@NachoSoto)
### Other Changes
* `PostedTransactionCache`: remove implementation (#3030) via NachoSoto
(@NachoSoto)
* `Integration Tests`: improved `testCanPurchaseMultipleSubscriptions`
(#3025) via NachoSoto (@NachoSoto)
* `GitHub`: improved `ISSUE_TEMPLATE` (#3022) via NachoSoto (@NachoSoto)
* `TransactionPoster`: added transaction ID and Date to log (#3026) via
NachoSoto (@NachoSoto)
* `TransactionPoster`: fix iOS 12 test (#3018) via NachoSoto
(@NachoSoto)
* `SystemInfo`: added `ClockType` (#3014) via NachoSoto (@NachoSoto)
* `Integration Tests`: begin tests with
`UIApplication.willEnterForegroundNotification` to simulate a real app
(#3015) via NachoSoto (@NachoSoto)
* `Integration Tests`: add tests to verify `CustomerInfo`+`Offerings`
request de-dupping (#3013) via NachoSoto (@NachoSoto)
* `SwiftLint`: disable `unneeded_synthesized_initializer` (#3010) via
NachoSoto (@NachoSoto)
* Added `internal`
`NonSubscriptionTransaction.storeTransactionIdentifier` (#3009) via
NachoSoto (@NachoSoto)
* `Integration Tests`: added tests for non-renewing and non-consumable
packages (#3008) via NachoSoto (@NachoSoto)
* Expanded `EnsureNonEmptyArrayDecodable` to
`EnsureNonEmptyCollectionDecodable` (#3002) via NachoSoto (@NachoSoto)
MarkVillacampa pushed a commit that referenced this pull request Sep 6, 2023
…packages (#3008)

There were some questions about these in #2841, and realized we had no
test coverage for them.
This also improves the documentation of `CustomerInfo.nonSubscriptions`
and `NonSubscriptionTransaction`.
MarkVillacampa pushed a commit that referenced this pull request Sep 6, 2023
MarkVillacampa pushed a commit that referenced this pull request Sep 6, 2023
MarkVillacampa pushed a commit that referenced this pull request Sep 6, 2023
**This is an automatic release.**

### Bugfixes
* `PurchasesOrchestrator`: fixed callback not invoked regression during
downgrades (#3028) via NachoSoto (@NachoSoto)
* `TransactionPoster`: don't finish transactions for non-subscriptions
if they're not processed (#2841) via NachoSoto (@NachoSoto)
### Performance Improvements
* `StoreKit 2`: only listen to `StoreKit.Transaction.updates` when SK2
is enabled (#3032) via NachoSoto (@NachoSoto)
* `CustomerInfoManager`: post transactions in parallel to POST receipts
only once (#2954) via NachoSoto (@NachoSoto)
### Other Changes
* `PostedTransactionCache`: remove implementation (#3030) via NachoSoto
(@NachoSoto)
* `Integration Tests`: improved `testCanPurchaseMultipleSubscriptions`
(#3025) via NachoSoto (@NachoSoto)
* `GitHub`: improved `ISSUE_TEMPLATE` (#3022) via NachoSoto (@NachoSoto)
* `TransactionPoster`: added transaction ID and Date to log (#3026) via
NachoSoto (@NachoSoto)
* `TransactionPoster`: fix iOS 12 test (#3018) via NachoSoto
(@NachoSoto)
* `SystemInfo`: added `ClockType` (#3014) via NachoSoto (@NachoSoto)
* `Integration Tests`: begin tests with
`UIApplication.willEnterForegroundNotification` to simulate a real app
(#3015) via NachoSoto (@NachoSoto)
* `Integration Tests`: add tests to verify `CustomerInfo`+`Offerings`
request de-dupping (#3013) via NachoSoto (@NachoSoto)
* `SwiftLint`: disable `unneeded_synthesized_initializer` (#3010) via
NachoSoto (@NachoSoto)
* Added `internal`
`NonSubscriptionTransaction.storeTransactionIdentifier` (#3009) via
NachoSoto (@NachoSoto)
* `Integration Tests`: added tests for non-renewing and non-consumable
packages (#3008) via NachoSoto (@NachoSoto)
* Expanded `EnsureNonEmptyArrayDecodable` to
`EnsureNonEmptyCollectionDecodable` (#3002) via NachoSoto (@NachoSoto)
NachoSoto added a commit that referenced this pull request Feb 6, 2024
Fixes #3636.

This was added in #3009.
It was not made `public` because it was only used for #2841, but as explained in #3636 it can be useful as `public`.
NachoSoto added a commit that referenced this pull request Feb 6, 2024
)

Fixes #3636.

This was added in #3009.
It was not made `public` because it was only used for #2841, but as
explained in #3636 it can be useful as `public`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants