Track free trial start for Stripe purchases#452
Merged
Conversation
…duling - Fire `FreeTrialStart` event when a Stripe purchase completes and `isFreeTrialAvailable` is true, so the delegate receives `.freeTrialStart` for Stripe purchases (previously only fired for App Store purchases) - Fix notification scheduling to check `paywallInfo.isFreeTrialAvailable` instead of `product.trialPeriodDays > 0`, so notifications respect the actual trial eligibility that was sent to Stripe Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
isFreeTrialAvailable is paywall-level, so also verify the specific purchased product has trialPeriodDays > 0 to avoid false positives when another product on the paywall has a trial. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes mismatch where productIds.first could refer to a different product than what the user actually purchased on a multi-product paywall. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix existing trial notification test to set isFreeTrialAvailable = true - Add test: trialPeriodDays > 0 but isFreeTrialAvailable = false → no notification - Add test: isFreeTrialAvailable = true but trialPeriodDays = 0 → no notification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that the freeTrialStart event is dispatched to the delegate in the positive case and not dispatched in the three negative cases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Test plan
Checklist
Greptile Summary
This PR extends free trial tracking to Stripe purchases by firing the
FreeTrialStartevent when a Stripe redemption completes with a trial-eligible product, and tightens the notification scheduling guard so bothtrialPeriodDays > 0(product-level) andisFreeTrialAvailable(user-level eligibility) must be true before any trial-related side effects fire.Key changes:
afterRedeem()inWebEntitlementRedeemer: Added a nestedisFreeTrialAvailableguard inside the existingtrialPeriodDays > 0check; both must be true to callsuperwall.track(FreeTrialStart(...))and schedule local notifications. Uses the injectedsuperwallparameter (notSuperwall.shared) andredemptionProduct.identifierdirectly, correctly addressing the dependency-injection and identifier-mismatch concerns raised previously.handleStripeCheckoutAbandon: Co-bundled fix passesproduct: productinstead ofproduct: nilto the abandonTransactionevent — the product was already embedded in thestateenum but was missing from the top-level field, which would have caused analytics to drop product information on Stripe checkout abandons.trialPeriodDays == 0, suppressed whenisFreeTrialAvailable == false, and suppressed whentrialPeriodDays == 0even ifisFreeTrialAvailable == true. TheisFreeTrialAvailable: truesetter was also retroactively added to the existing positive-path test to prevent false-positives after the new guard was introduced.5.60.0→5.61.0(minor version, no API surface changes expected).Confidence Score: 4/5
handleStripeCheckoutAbandonproduct: nil → product: productfix is not reflected in CHANGELOG.md, which is a documentation concern rather than a functional one.Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[afterRedeem called] --> B{codeResult == .success?} B -- No --> Z[Skip trial logic] B -- Yes --> C{redemptionInfo.paywallInfo?.product != nil?} C -- No --> Z C -- Yes --> D{redemptionProduct.trialPeriodDays > 0?} D -- No --> Z D -- Yes --> E{superwall.paywallViewController != nil?} E -- No --> Z E -- Yes --> F[Fetch paywallInfo from VC] F --> G{paywallInfo.isFreeTrialAvailable?} G -- No --> Z G -- Yes --> H[Create StoreProduct.blank with redemptionProduct.identifier] H --> I[await superwall.track FreeTrialStart event] I --> J[Filter localNotifications for .trialStarted] J --> K[scheduleNotifications]Comments Outside Diff (2)
Tests/SuperwallKitTests/Web/WebEntitlementRedeemerTests.swift, line 1253-1261 (link)Missing assertion for
freeTrialStartdelegate eventThe core new behaviour introduced in this PR — dispatching a
.freeTrialStartevent to the delegate viasuperwall.track(InternalSuperwallEvent.FreeTrialStart(...))— is never verified by any test.MockSuperwallDelegatealready records all dispatched events ineventsReceived, so checking for the event is straightforward.Positive test (
testRedeem_withFreeTrial_schedulesNotification): should additionally assert that.freeTrialStartwas received:Negative tests (
testRedeem_withTrialDaysButNotEligible_doesNotScheduleNotificationat line ~1611, andtestRedeem_withEligibleButNoTrialDays_doesNotScheduleNotificationat line ~1785): should assert the event was not fired to confirm the guard conditions are actually preventing the delegate call, not just the notification scheduling:Without these assertions, the tests only validate that
NotificationSchedulerMockwasn't called — but the actualtrack(FreeTrialStart(...))call could fire (or silently fail) without any test catching it.Sources/SuperwallKit/Web/WebEntitlementRedeemer.swift, line 250-253 (link)Abandon fix not reflected in CHANGELOG
The change from
product: niltoproduct: producton this line is a meaningful bug fix — previously theTransaction(.abandon)event sent to analytics was missing product information despite already embedding the product in thestateenum. This fix is bundled into the PR but isn't mentioned inCHANGELOG.md(the entry only references trial eligibility andfreeTrial_start). Worth adding a separate bullet so consumers can understand the full scope of the change.Last reviewed commit: 02acd97