Skip to content

feat: free trial and store intro offer on the billing screen - #176

Merged
anilcancakir merged 3 commits into
mainfrom
feature/trial-ui
Oct 10, 2026
Merged

anilcancakir merged 3 commits into
mainfrom
feature/trial-ui

Conversation

@anilcancakir

Copy link
Copy Markdown
Member

Shows a free trial on the billing screen and discloses a store introductory offer only to a customer who may take it.

What changes

  • MagicStarterProduct.trialDays: decoded from each plans product row's trial_days (magic-starter-laravel 0.0.23 answers it per caller, 0 when the caller is ineligible).
  • MagicStarterBillingController.trialEnd: trialEndsAt ?? currentPeriodEnd, only while the entitlement is trialing (a store trial carries no trial_ends_at).
  • Billing screen:
    • Current plan card: a "Trial" badge, and a trial line in place of the renewal line: "Free trial ends (N days left), then per ", the store sentence on a store trial, and "Free trial ends . It will not renew." for a cancelled trial.
    • Web plan card for a product with trialDays > 0: "Free for N days. Card required, then per ." above the button, and the button reads "Start free trial". Checkout is unchanged.
    • Store disclosure: "Free for , then per " only when StoreProductOffer.introEligible is true and the intro price is 0. A paid intro keeps the plain price line (the wire has no cycle count, so a period alone would understate a multi-period offer).
    • The billed price stays the large figure in every case (App Store guideline: the billed amount is the most prominent pricing element).
  • New en.stub keys (listed in the CHANGELOG and doc/basics/teams.md) for hosts that publish en.json.
  • Requires magic_payments ^0.0.9 (introEligible) and magic-starter-laravel 0.0.23 for trial_days (an older backend reads as no trial).

Verification

  • flutter analyze: no issues. flutter test: 1925 pass (trial and intro states at 390 and 1440 px, days-left plurals, cancelled trial, ineligible and paid intros, long plan name with both badges).
  • dart format --set-exit-if-changed: clean.

@codecov

codecov Bot commented Oct 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@kodizm

kodizm Bot commented Oct 10, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

Looks correct to merge: the trial and intro-offer copy only shows behind the right gates, and checkout behaviour is unchanged.

I read the controller, model, view, pubspec, en.stub and fixture diffs, and checked them against callers and against CLAUDE.md and .claude/rules/views.md. Here is why I think it is correct:

  • trialEnd only returns a value while planStatus == trialing. _trialEndsAt is cleared on reset and set from the same entitlement read as the snapshot, so a stale date cannot show up after the trial converts.
  • _webTrial uses the same gates as _selectPlan's web branch (storeRail == null, _canPurchaseViaWeb, _saleProduct). It sits inside the _cycleOverride builder, so the line and the CTA label follow the cycle toggle while onPressed stays the same. The floor and custom tiers have no sale product, so they never get trial copy.
  • The store intro line needs introEligible, a single-unit ISO period and introPrice == 0. Any other case falls back to the existing price sentence.
  • A cancelled trial (renews == false) is handled first, so it never names a price or the store renewal sentence. The days-left count rounds up and is clamped at zero.
  • Every new trans key is in en.stub. common.unknown already exists. The CHANGELOG.md and doc/ updates required by CLAUDE.md's post-change checklist are both in the diff.

I could not inspect magic_payments 0.0.9 (StoreProductOffer.introEligible / introPrice / introPeriod) locally. CI's "Published graph" job resolving and analysing against pub.dev is the evidence that those members exist with the types used here.

Tests

Controller tests cover trialEnd, including the fallback to currentPeriodEnd and the non-trialing case. Model and wire fixture tests cover trial_days decoding. View tests cover the trial, cancelled-trial, store-trial, eligible/ineligible/paid-intro and plural states at both widths.

CI

  • Lint & Test: success
  • Published graph: success
  • codecov/patch: success
  • Auto-merge low-risk Dependabot PRs: skipped

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.

1 participant