Affiliate Flow#8885
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds referral attach V2 support, coupon-code propagation, and affiliate discount UI updates across native, Go, Flutter, and purchase flows. Also adjusts dialog sizing, semantic colors, and one plans feature tile. ChangesReferral V2 and coupon code feature
Estimated code review effort: 4 (Complex) | ~75 minutes Dialog and semantic color updates
Estimated code review effort: 1 (Trivial) | ~5 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR adds end-to-end promo/referral (coupon) code support across the subscription/payment flows (Flutter → platform channels → Go core/FFI), introduces a V2 referral attachment API that returns discounted plan data, and updates the plans UI to reflect affiliate discounts. It also refactors purchase acknowledgment into testable components, updates localizations, and bumps Go dependencies.
Changes:
- Thread
couponCodethrough IAP start + acknowledgment, Stripe flows, and payment redirects on Android/iOS/macOS/FFI/Go core. - Add referral attachment V2 (
attachReferralCodeV2) returning structured JSON (plans/providers/discount) and update plans UI to show affiliate pricing + removal flow. - Extract and test purchase acknowledgment + pending purchase persistence, and centralize plans sorting.
Reviewed changes
Copilot reviewed 35 out of 41 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| test/core/services/purchase/purchase_acknowledger_test.dart | New unit tests for acknowledgment state machine (retry/dedup/error handling). |
| test/core/services/purchase/pending_purchase_store_test.dart | New unit tests for persisted plan/coupon attribution metadata. |
| macos/Runner/Handlers/MethodHandler.swift | Pass couponCode into Apple purchase acknowledgment; add attachReferralCodeV2 handler. |
| ios/Runner/Handlers/MethodHandler.swift | Pass couponCode into Apple purchase acknowledgment; add attachReferralCodeV2 handler. |
| android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt | Thread couponCode into mobile calls; add AttachReferralCodeV2 method implementation. |
| lib/lantern/lantern_service.dart | Add couponCode parameters to core service methods; expose attachReferralCodeV2. |
| lib/lantern/lantern_platform_service.dart | Forward couponCode through method-channel payment APIs; add platform attachReferralCodeV2. |
| lib/lantern/lantern_ffi_service.dart | Forward couponCode for redirect flows; add FFI-backed attachReferralCodeV2. |
| lib/lantern/lantern_core_service.dart | Extend core service interface with couponCode and V2 referral attachment response type. |
| lib/features/plans/provider/referral_notifier.dart | Switch referral state to hold V2 response and update plans on success. |
| lib/features/plans/provider/referral_notifier.g.dart | Generated Riverpod updates for new referral notifier state type. |
| lib/features/plans/provider/plans_notifier.dart | Add updatePlans and publish state after fetch to unblock callers. |
| lib/features/plans/provider/plans_notifier.g.dart | Generated hash update after notifier changes. |
| lib/features/plans/provider/payment_notifier.dart | Thread couponCode through purchase/redirect/subscription calls; simplify store-build detection. |
| lib/features/plans/provider/payment_notifier.g.dart | Generated hash update after payment notifier changes. |
| lib/features/plans/plans.dart | Add affiliate banner + removal flow; apply referral V2; forward applied code into IAP flow. |
| lib/features/plans/plans_list.dart | Update plan selection logic and pass affiliate discount data to plan items. |
| lib/features/plans/plan_item.dart | Display original (strikethrough) price when affiliate discount applies. |
| lib/features/plans/feature_list.dart | Minor feature list content/formatting changes. |
| lib/features/auth/choose_payment_method.dart | Forward affiliate coupons into Stripe flows; update checkout line items and labels. |
| lib/core/services/purchase/purchase_acknowledger.dart | New acknowledgment state machine (dedupe + retry) with injected collaborators. |
| lib/core/services/purchase/pending_purchase_store.dart | New persistence layer for pending plan/coupon attribution keyed by txn/product. |
| lib/core/services/app_purchase.dart | Integrate pending-store + acknowledger; support offer SKU loading; thread coupon attribution. |
| lib/core/models/referral_attach_response.dart | New V2 referral attachment response model + null-safe state extension. |
| lib/core/models/plan_data.dart | Add shared sortPlansAndProviders; add original-price fields for discounted display. |
| lib/core/extensions/plan.dart | Add formatting helpers for original price + discount amount based on backend fields. |
| lib/core/extensions/error.dart | Map new backend invalid-referral signal to localized invalid referral message. |
| lib/core/common/app_theme.dart | Constrain dialog width globally per Material guidelines. |
| lib/core/common/app_semantic_colors.dart | Add semantic “attention” colors used for affiliate promo UI. |
| lib/core/common/app_dialog.dart | Prevent custom dialog content from collapsing by forcing max width usage. |
| assets/locales/en.po | Add promo/affiliate UI strings; update renewal info and new order total label. |
| lantern-core/core.go | Add couponCode to payment APIs; add ReferralAttachmentV2 returning JSON bytes. |
| lantern-core/mobile/mobile.go | Propagate couponCode into mobile-exposed Go functions; add referral attachment V2. |
| lantern-core/ffi/ffi.go | Extend exported C ABI to accept couponCode and add referralAttachmentV2. |
| go.mod | Update replace directives (incl. radiance) and dependency versions. |
| go.sum | Dependency checksum updates. |
| lib/features/vpn/provider/available_servers_notifier.g.dart | Generated hash update. |
| lib/features/report_issue/provider/report_issue_draft_notifier.g.dart | Generated hash update. |
| lib/features/home/provider/country_code_notifier.g.dart | Generated hash update. |
| lib/features/home/provider/app_event_notifier.g.dart | Generated hash update. |
Files not reviewed (4)
- lib/features/home/provider/app_event_notifier.g.dart: Generated file
- lib/features/home/provider/country_code_notifier.g.dart: Generated file
- lib/features/plans/provider/payment_notifier.g.dart: Generated file
- lib/features/plans/provider/plans_notifier.g.dart: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
lib/core/models/plan_data.dart (1)
36-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove unused
iconsparameter fromcopyWith.
copyWithacceptsMap<String, List<String>>? iconsbut never uses it, andPlansDatahas noiconsfield. This is dead code that could mislead callers into thinking they can update icons viacopyWith.♻️ Proposed fix
PlansData copyWith({ Providers? providers, List<Plan>? plans, - Map<String, List<String>>? icons, }) { return PlansData( providers: providers ?? this.providers, plans: plans ?? this.plans, ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/core/models/plan_data.dart` around lines 36 - 45, Remove the unused icons argument from PlansData.copyWith in plan_data.dart. The method currently accepts Map<String, List<String>>? icons but never applies it, and PlansData has no icons field, so update the copyWith signature to only include the actual supported fields (like providers and plans) and keep the constructor call aligned with those fields.lib/features/plans/provider/plans_notifier.dart (1)
91-98: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winBackground refresh can overwrite referral-discounted plans.
_refreshInBackgroundcallsfetchPlans(fromBackground: true)and then unconditionally setsstate = AsyncData(remotePlans). If a referral code is applied (viaupdatePlans) while a background refresh is in flight, the background completion will overwrite the discounted plans with the original server plans, silently reverting the discount.Consider guarding against this — e.g., skipping the state assignment if a referral is active, or cancelling the background refresh when
updatePlansis called.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/features/plans/provider/plans_notifier.dart` around lines 91 - 98, The background refresh in PlansNotifier can overwrite referral-discounted plans because _refreshInBackground() always assigns the fetched result to state after fetchPlans(fromBackground: true). Update the PlansNotifier flow so that when updatePlans applies a referral code, any in-flight background refresh is ignored or cancelled, or the state assignment in _refreshInBackground is skipped whenever a referral is currently active. Use the _refreshInBackground and updatePlans methods as the key places to add this guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/core/models/referral_attach_response.dart`:
- Around line 13-14: `ReferralType.fromString` is mapping every non-`'referral'`
value to `affiliate`, so `null`, empty, `"none"`, and unknown backend values are
misclassified. Update the `fromString` logic in `ReferralType` to return
`ReferralType.none` for `null`/unrecognized values, keep `'referral'` mapping to
`referral`, and only map the explicit affiliate case when the backend actually
provides it.
In `@lib/core/services/app_purchase.dart`:
- Around line 500-515: The `_handlePurchase` catch block in `AppPurchase` leaves
stale session callbacks behind after an unexpected exception, which can cause
re-delivered StoreKit transactions to trigger an abandoned flow. Update the
catch handling to follow the same capture-clear-fire pattern used in the restore
paths: clear `_session.onSuccess` and `_session.onError` before invoking any
callback, and then report the error through the captured callback or logger as
appropriate. Use the `_handlePurchase` method and `_session` callbacks as the
key touchpoints.
In `@lib/core/services/purchase/purchase_acknowledger.dart`:
- Around line 222-259: The retry loop in _scheduleRetry() for PurchaseAck can
run forever because state.attempts is never capped, so add a maximum retry limit
before scheduling the Timer. Update the retry flow in
PurchaseAckRetryState/_scheduleRetry to stop rescheduling once the cap is
reached, log a warning when retries are exhausted, and ensure the final failure
path still goes through _handleFailure() or equivalent for visibility.
In `@lib/features/auth/choose_payment_method.dart`:
- Around line 547-574: The applied promo code text in the discount summary is
using the wrong i18n key, so the referral code is not rendered. Update the
`showDiscountDeduction` UI in `choose_payment_method.dart` to use
`promo_code_with` instead of `promo_code` in the `Text` widget that formats
`referral.code`, so the placeholder is actually filled and the code appears to
the user.
In `@lib/features/plans/provider/referral_notifier.dart`:
- Around line 33-35: The referral plan lookup in `ReferralNotifier` uses
`plans.plans.firstWhere` to find the `bestValue` plan, which can crash with a
runtime `StateError` when no match exists. Update the `defaultPlan` selection to
use a safe fallback such as `firstWhereOrNull` from `package:collection` or an
`orElse` branch, and handle the null/missing-plan case before continuing the
referral flow.
---
Nitpick comments:
In `@lib/core/models/plan_data.dart`:
- Around line 36-45: Remove the unused icons argument from PlansData.copyWith in
plan_data.dart. The method currently accepts Map<String, List<String>>? icons
but never applies it, and PlansData has no icons field, so update the copyWith
signature to only include the actual supported fields (like providers and plans)
and keep the constructor call aligned with those fields.
In `@lib/features/plans/provider/plans_notifier.dart`:
- Around line 91-98: The background refresh in PlansNotifier can overwrite
referral-discounted plans because _refreshInBackground() always assigns the
fetched result to state after fetchPlans(fromBackground: true). Update the
PlansNotifier flow so that when updatePlans applies a referral code, any
in-flight background refresh is ignored or cancelled, or the state assignment in
_refreshInBackground is skipped whenever a referral is currently active. Use the
_refreshInBackground and updatePlans methods as the key places to add this
guard.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 751fd42f-e728-44ce-8c04-176ca6c06c5a
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (40)
android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.ktassets/locales/en.pogo.modios/Runner/Handlers/MethodHandler.swiftlantern-core/core.golantern-core/ffi/ffi.golantern-core/mobile/mobile.golib/core/common/app_dialog.dartlib/core/common/app_semantic_colors.dartlib/core/common/app_theme.dartlib/core/extensions/error.dartlib/core/extensions/plan.dartlib/core/models/plan_data.dartlib/core/models/referral_attach_response.dartlib/core/services/app_purchase.dartlib/core/services/purchase/pending_purchase_store.dartlib/core/services/purchase/purchase_acknowledger.dartlib/features/auth/choose_payment_method.dartlib/features/home/provider/app_event_notifier.g.dartlib/features/home/provider/country_code_notifier.g.dartlib/features/plans/feature_list.dartlib/features/plans/plan_item.dartlib/features/plans/plans.dartlib/features/plans/plans_list.dartlib/features/plans/provider/payment_notifier.dartlib/features/plans/provider/payment_notifier.g.dartlib/features/plans/provider/plans_notifier.dartlib/features/plans/provider/plans_notifier.g.dartlib/features/plans/provider/referral_notifier.dartlib/features/plans/provider/referral_notifier.g.dartlib/features/report_issue/provider/report_issue_draft_notifier.g.dartlib/features/vpn/provider/available_servers_notifier.g.dartlib/lantern/lantern_core_service.dartlib/lantern/lantern_ffi_service.dartlib/lantern/lantern_generated_bindings.dartlib/lantern/lantern_platform_service.dartlib/lantern/lantern_service.dartmacos/Runner/Handlers/MethodHandler.swifttest/core/services/purchase/pending_purchase_store_test.darttest/core/services/purchase/purchase_acknowledger_test.dart
This pull request introduces support for promo/referral codes during the subscription and payment flows, and adds a new version of the referral code attachment API. It updates both Android and iOS code to pass coupon codes through the payment and purchase acknowledgment processes, updates the core Go interfaces and FFI bindings to support these changes, and adds new localization strings for promo code UI elements. Dependency versions are also updated.
Promo/Referral Code Support in Payments and Subscriptions:
MethodHandler.kt): Modified payment and purchase acknowledgment methods to accept and forward acouponCodeparameter. [1] [2] [3]MethodHandler.swift): UpdatedacknowledgeInAppPurchaseto accept and forward acouponCode. [1] [2]core.go,ffi.go,mobile.go): Updated payment and subscription methods to accept and forwardcouponCodeparameters throughout the stack, including Stripe and payment redirect flows. [1] [2] [3] [4] [5] [6] [7] [8] [9]Referral Code Attachment V2:
AttachReferralCodeV2to theMethodsenum and implemented its handler, which calls the newMobile.referralAttachmentV2. [1] [2]"attachReferralCodeV2"and implementedreferralAttachV2to call the new mobile core method. [1] [2]ReferralAttachmentV2methods to the core interface and FFI, returning detailed JSON about the referral attachment. [1] [2] [3]Localization and UI Strings:
Dependency Updates:
domainfront,kindling, andlantern-box.These changes collectively enable promo/referral code entry and attribution throughout the payment flows, improve referral code handling, and prepare the UI for these new features.
Summary by CodeRabbit