magic_payments 0.0.7 copy "magic_payments: ^0.0.7" to clipboard
magic_payments: ^0.0.7 copied to clipboard

Multi-rail billing for the Magic Framework. One entitlement contract over Stripe on the web and store in-app purchase on mobile.

Changelog #

Unreleased #

0.0.7 #

Changed #

  • Every sibling floor names this batch's release. magic moves ^0.0.22 to ^0.0.24 and fluttersdk_artisan ^0.0.16 to ^0.0.17. The old ranges already admitted the new versions, so a fresh pub get resolves nothing differently; what changes is that the floors name the releases this package is verified against. magic 0.0.24 removes MagicController.onRefreshUI (BREAKING); this package calls it nowhere in lib/ or test/, so nothing here moves with it. test/pubspec_floors_test.dart asserts the new floors. (pubspec.yaml, test/pubspec_floors_test.dart)

0.0.6 #

Fixed #

  • An idle StoreIdentitySync.syncNow() runs in the caller's zone instead of waiting on another zone's microtask queue. 0.0.5 chained every sync onto a stored completed future, and a completed future runs its listeners in the zone it was created in: a sync started inside a widget test's fake-async zone (a team switch in magic_starter, which awaits the sync) never ran and the switch never returned, once an earlier test had created that future. An idle sync now starts in the caller's own turn; only a sync queued behind one in flight chains, and a detach() during an identify still keeps the next sync behind it. (lib/src/support/store_identity_sync.dart)

0.0.5 #

Added #

  • StoreIdentitySync keeps the store rail identified as the paying subject. Set StoreIdentitySync.billableId to a resolver answering the subject's id (a team or a user; the consumer decides), call attach() once, and every Auth.stateNotifier change identifies Payments.store with it; syncNow() identifies on demand (after a switch of the paying subject) and detach() stops. It skips a build without a store rail and a session without a subject, runs syncs one at a time in call order, each reading the subject when its turn comes (so a switch during an identify leaves the rail on the newer subject whatever order the vendor SDK finishes in), identifies a repeated id once, identifies again after a sign-out, and logs a BillingException from the rail at error level instead of throwing, retrying that id on the next sync. An unset resolver identifies nothing and logs once at debug level. (lib/src/support/store_identity_sync.dart, doc/basics/rails.md)

Changed #

  • Every sibling floor names this batch's release. magic moves ^0.0.16 to ^0.0.22; fluttersdk_artisan stays at ^0.0.16, still the newest. The old ranges already admitted the new versions, so a fresh pub get resolves nothing differently; what changes is that the floors name the release this package is verified against. StoreIdentitySync needs nothing newer than 0.0.16; of magic 0.0.22's BREAKING changes, only Auth.fake() dispatching through the real Event facade reaches it, from one test, and the suite passes unchanged. (pubspec.yaml, test/pubspec_floors_test.dart)

0.0.4 #

Dependency floors only; the package code is identical to 0.0.3.

Changed #

  • Every sibling floor names this batch's release. magic moves ^0.0.15 to ^0.0.16; fluttersdk_artisan stays at ^0.0.16, still the newest. The old ranges already admitted the new versions, so a fresh pub get resolves nothing differently; what changes is that the floors name the releases this package is verified against. magic 0.0.16 widens file_picker to admit 13, where PlatformFile.length() answers null for an unreadable file; this package does not call Pick. test/pubspec_floors_test.dart pins the new magic floor. (pubspec.yaml, test/pubspec_floors_test.dart, README.md, doc/getting-started/installation.md)

0.0.3 #

Dependency floors only; the package code is identical to 0.0.2.

Changed #

  • Every sibling floor names this batch's release. magic moves ^0.0.6 to ^0.0.15 and fluttersdk_artisan ^0.0.13 to ^0.0.16. The old ranges already admitted the new versions, so a fresh pub get resolves nothing differently; what changes is that the floors name the releases this package is verified against. magic 0.0.15 is breaking in its database layer (a migration may no longer manage its own transaction, and DB.transaction refuses a callback that closes the transaction itself); nothing in this package calls either, so no code here changes, but an app below magic 0.0.15 no longer resolves this release. (pubspec.yaml, test/pubspec_floors_test.dart)

0.0.2 #

Documentation only; the package code is identical to 0.0.1.

Changed #

  • The README described a package that no longer existed. It opened with a warning that 0.0.1 was a scaffold whose "public API is not implemented yet" and whose "exports are empty", which was true of the first commit and of nothing since: the barrels carry the three contracts, the drivers behind them, the entitlement model and the five enums, and 206 tests run against them. That warning was the first thing anyone read on pub.dev, so the package described itself as unusable while being usable. It now carries the ordinary pre-0.1.0 caution instead.
  • The one code sample in the README called a method that does not exist. Payments.entitlement() is Payments.currentEntitlement(). A reader copying the snippet did not compile.
  • The README gained the three-role table (which rail exists where, and why a rail is checked rather than assumed) and the actual install commands, rather than a sentence pointing at "your app's config".

0.0.1 #

First release of the package. Everything below is new, so this entry describes the shape rather than a diff.

Added #

  • Billing for a Magic app over more than one rail, behind three contracts instead of one. BillingService carries the five entitlement READS, which are honourable on every platform because the backend is the authority on an entitlement no matter which rail sold the subscription. WebBillingService carries the four web writes (checkout, swap, cancel, portal). StoreBillingService carries the four store methods (identify, purchase, restore, openStoreManagement). Nine methods in, nine out, none dropped.

    The split is the point. A single interface forces a build that cannot serve a method to declare it anyway, so the shape it replaces threw UnsupportedPlatformException from four methods on mobile and a billing screen rendered an Upgrade button whose only behaviour was to fail. A caller now asks whether a rail EXISTS (Payments.store != null) and does not render the affordance, instead of rendering one and catching a refusal.

  • One compile-time platform seam, and exactly one runtime device check. The drivers resolve through a three-arm conditional import (a stub default, a web arm, an io arm), each arm exposing the same three factory functions because a conditional import resolves a whole FILE. The io arm asks one runtime question of its own, and it is not a smell: its guard is also satisfied on macOS, Windows and Linux, none of which has StoreKit or Play Billing, so "which rails can this BUILD serve" and "does this DEVICE have a store" are two different questions with one mechanism each. Nothing above the factory branches on a platform.

  • A RevenueCat store driver, RevenueCatStoreService, on purchases_flutter. It reads one config key, payments.revenuecat.public_sdk_key, and refuses a blank one at purchase time by logging and throwing rather than letting the SDK's own failure surface far from its cause. RevenueCat issues a separate public key per store, and lib/config/payments.dart is Dart rather than JSON, so the published stub resolves it with a switch (defaultTargetPlatform).

  • PaymentsManager, the Payments facade and a service provider. The manager holds one resolved instance per role and extend() swaps any of them, which is how a consumer replaces the store rail with a mediator this package does not ship, and how a test stands in for a driver without mocking a third-party SDK.

  • A CLI on fluttersdk_artisan: payments:install, payments:configure and payments:doctor, in a lib/cli.dart entry point separate from the runtime library so an app that never runs a command does not carry the command tree. Only doctor is exposed as an MCP tool, because the other two mutate a consumer's files.

    doctor reports the store rail's key as absent, blank or declared WITHOUT failing on it. A web-only or desktop-only app is correct without the key, so failing would turn a sound project red; but the driver throws under a customer's finger when it is missing, and passing in silence was measured on a real consumer and was worse. It reports, with the consequence attached.

  • PaymentMethod.available, so a consumer stops guessing why a card is missing. Reading a card is the one billing call that dials the rail live, so the producer soft-fails a rail outage into a 200 with every field null, which is byte-identical to a customer who genuinely has no card. The field is the producer's own answer to which of the two it was: false means the rail could not be asked, true with a null last4 means there is genuinely no card. It decodes as bool? and an ABSENT key is null, never false, because a backend too old to send it must not be reported as a rail that is down.

  • BillingCycle, because a tier is not a price. A vendor selling pro at a monthly rate and again at a discounted annual rate has one tier and two prices, and three places have to agree which is in play: the catalogue shows a figure, the checkout charges one, the renewal line names one. With no cycle on the wire those answers come from three sources and disagree. Measured on a consumer app against a live Stripe test account: the screen offered "Annual, save ~15%" at $29/mo and Stripe charged $34.00 monthly, with the invoice and the renewal date siding with Stripe.

    So WebBillingService.checkout and swap both take a REQUIRED cycle, with no default. A default would be the same defect wearing a type: the caller showing an annual figure has to say annual, and the compiler is what makes every call site say which. BillingEntitlement.cycle reports what the customer actually bought, resolved server-side from the price their subscription sits on, which is a different fact from whichever column a catalogue toggle happens to be displaying.

    It is the ONE vocabulary in this package with no fallback member: BillingCycle.fromWire answers null rather than picking a side. Every other enum here degrades to a none case because "no rail has said" is a state it can express; monthly and annual are the only two cycles there are, so a default is a claim about what somebody is being charged. Null means unknown and a caller has to render it as unknown.

  • PlanStatus.isDunning, the question no field on the wire answered. Both pastDue and grace still GRANT, so a screen reading subscribed alone cannot tell a paying customer from one whose card has just bounced, and it showed them the same healthy renewal sentence. Measured on a live Stripe test clock: a failed renewal put the subscription in past_due and the billing page still read "renews Nov 24, 2026" with no warning anywhere.

    Deliberately NOT a grants() mirror, which this enum does not carry: whether a status entitles is the producer's answer and arrives as BillingEntitlement.subscribed. This asks a different question, is the customer's money late, and a client that re-derived entitlement from the status word would be answering the first one twice.

  • Seven documentation pages under doc/, covering installation, configuration, the rails, the drivers, the manager, the service provider and the CLI.

Notes for anyone reading the source #

  • The five reads live in one place, BillingReadsOverHttp, mixed into both the web and io arms. They were duplicated byte for byte until a review found them, and the duplication was invisible to every gate this package has: only ONE arm compiles per target, so no analyze run and no passing test could ever observe the two copies disagreeing. test/drivers/billing_reads_over_http_test.dart asserts that neither arm declares a read of its own, which is the part that survives a future refactor.

  • Neither store rail has processed a transaction. No RevenueCat project or store product exists yet, so the store path is exercised by tests and by nothing else. Treat it as code-complete and unproven.

0
likes
150
points
632
downloads

Documentation

Documentation
API reference

Publisher

verified publisherfluttersdk.com

Weekly Downloads

Multi-rail billing for the Magic Framework. One entitlement contract over Stripe on the web and store in-app purchase on mobile.

Homepage
Repository (GitHub)
View/report issues

Topics

#payments #billing #in-app-purchase #magic-framework #flutter-plugin

License

MIT (license)

Dependencies

flutter, fluttersdk_artisan, magic, purchases_flutter

More

Packages that depend on magic_payments