Post

Replies

Boosts

Views

Activity

Reply to Sandbox subscription test is triggering multiple transaction events for a single purchase attempt
Oof, good luck with ReactNative. I haven't used any IAP ReactNative library, so I'll speak in terms of native StoreKit. On a somewhat relieving note, I also have this issue in my Swift-native iOS app, and I made a post about it on the forums, too. It happens both in Sandbox and production, but NOT when using Local StoreKit Testing configuration files. There are multiple ways for you to observe StoreKit updates. You can observe transactions with Transaction.updates, and also just the subscription updates themselves with SubscriptionStatus.updates. Technically, these are different, but of course a new transaction means potential for a subscription status update. Basically, I had to implement logic to de-duplicate subscription updates that were redundant. For every Subscription update I get, I map it to an array for what I see as "active" subscriptions. The rest of my app will watch for if my array of "active" subscriptions changes. If it changes, then we will enabled/disable features. Duplicate transactions / updates should have no effect on my array and therefore no effect on the rest of my app. This is similar to Apple's SKDemo, which I highly suggest you look through carefully to get an idea of what I'm talking about.
1w
Reply to Transaction.updates sending me duplicated transactions after marking finished()
Thanks for the reply, @DTS Engineer! I've been down a rabbit hole since your initial reply. If processing takes even a few milliseconds, new updates can arrive and pile up behind the currently processing one, potentially leading to them being handled more than once if logic isn't airtight? This is kinda what I was thinking. If processing takes too long, then it would resurface somehow. I didn't rule this out, but I did rule out Actor reentrancy issues. I've also done some digging in the sample Projects, mainly SKDemo found here. As a reminder, my use case is solely focussed on tiered subscriptions. I do not support consumable transactions, so I'm really just looking at how subscriptions are handled. What I found, in my opinion, is not reflected well in documentation: 1. Transaction updates do not handle subscription enablements. When reading the StoreKit API docs, I saw that Transcation.updates tells you of incoming transactions. You must call transaction.finish(), but only after you've delivered the feature to the customer. This inferred to me that I had to unlock features for a subscription here, and also that this is a good place to send a purchase_success event. However, based on the SKDemo sample app... public func process(transaction: Transaction) async { // Only handle consumables and non consumables here. Check the subscription status each time // before unlocking a premium subscription feature. switch transaction.productType { case .nonConsumable: await processNonConsumableTransaction(transaction) case .consumable: await processConsumableTransaction(transaction) case _: // Finish the transaction. Grant access to the subscription based on the subscription status. await transaction.finish() } We simply finish the transaction for a subscription. My assumption has caused me to put a lot of logic into Transaction.updates. I think it's better to move my analytics and feature enablement out of this observer. encapsulating your logic within an actor and using a set for deduplication @DTS Engineer I think that what you said here lines up very well with the Demo. The Demo places work on the MainActor, and incoming transactions are stored in a Set in CustomerEntitlements.ownedNonConsumables Similarly, we also store active subscriptions in a dictionary (Not in a Set), but when we set this property, the rest of the app can respond accordingly to any changes made here. TLDR* Based on your comment and on the SKDemo app, I think I need to rewrite my code such that I'm deduplicating using Sets and storing activeSubscriptions in memory, handling analytics and feature enablement when my custom properties change. I should only finish() transactions in Transaction.updates and handle all of my other logic in another API, such as SubscriptionStatus.
Topic: App & System Services SubTopic: StoreKit Tags:
4w
Reply to Sandbox subscription test is triggering multiple transaction events for a single purchase attempt
Oof, good luck with ReactNative. I haven't used any IAP ReactNative library, so I'll speak in terms of native StoreKit. On a somewhat relieving note, I also have this issue in my Swift-native iOS app, and I made a post about it on the forums, too. It happens both in Sandbox and production, but NOT when using Local StoreKit Testing configuration files. There are multiple ways for you to observe StoreKit updates. You can observe transactions with Transaction.updates, and also just the subscription updates themselves with SubscriptionStatus.updates. Technically, these are different, but of course a new transaction means potential for a subscription status update. Basically, I had to implement logic to de-duplicate subscription updates that were redundant. For every Subscription update I get, I map it to an array for what I see as "active" subscriptions. The rest of my app will watch for if my array of "active" subscriptions changes. If it changes, then we will enabled/disable features. Duplicate transactions / updates should have no effect on my array and therefore no effect on the rest of my app. This is similar to Apple's SKDemo, which I highly suggest you look through carefully to get an idea of what I'm talking about.
Replies
Boosts
Views
Activity
1w
Reply to Transaction.updates sending me duplicated transactions after marking finished()
Thanks for the reply, @DTS Engineer! I've been down a rabbit hole since your initial reply. If processing takes even a few milliseconds, new updates can arrive and pile up behind the currently processing one, potentially leading to them being handled more than once if logic isn't airtight? This is kinda what I was thinking. If processing takes too long, then it would resurface somehow. I didn't rule this out, but I did rule out Actor reentrancy issues. I've also done some digging in the sample Projects, mainly SKDemo found here. As a reminder, my use case is solely focussed on tiered subscriptions. I do not support consumable transactions, so I'm really just looking at how subscriptions are handled. What I found, in my opinion, is not reflected well in documentation: 1. Transaction updates do not handle subscription enablements. When reading the StoreKit API docs, I saw that Transcation.updates tells you of incoming transactions. You must call transaction.finish(), but only after you've delivered the feature to the customer. This inferred to me that I had to unlock features for a subscription here, and also that this is a good place to send a purchase_success event. However, based on the SKDemo sample app... public func process(transaction: Transaction) async { // Only handle consumables and non consumables here. Check the subscription status each time // before unlocking a premium subscription feature. switch transaction.productType { case .nonConsumable: await processNonConsumableTransaction(transaction) case .consumable: await processConsumableTransaction(transaction) case _: // Finish the transaction. Grant access to the subscription based on the subscription status. await transaction.finish() } We simply finish the transaction for a subscription. My assumption has caused me to put a lot of logic into Transaction.updates. I think it's better to move my analytics and feature enablement out of this observer. encapsulating your logic within an actor and using a set for deduplication @DTS Engineer I think that what you said here lines up very well with the Demo. The Demo places work on the MainActor, and incoming transactions are stored in a Set in CustomerEntitlements.ownedNonConsumables Similarly, we also store active subscriptions in a dictionary (Not in a Set), but when we set this property, the rest of the app can respond accordingly to any changes made here. TLDR* Based on your comment and on the SKDemo app, I think I need to rewrite my code such that I'm deduplicating using Sets and storing activeSubscriptions in memory, handling analytics and feature enablement when my custom properties change. I should only finish() transactions in Transaction.updates and handle all of my other logic in another API, such as SubscriptionStatus.
Topic: App & System Services SubTopic: StoreKit Tags:
Replies
Boosts
Views
Activity
4w
Reply to Transaction.updates sending me duplicated transactions after marking finished()
@DTS Engineer Sorry! Did not mean to repost this. Any way we can delete this duplicated thread?
Topic: App & System Services SubTopic: StoreKit Tags:
Replies
Boosts
Views
Activity
4w