zerosettle 1.7.0
zerosettle: ^1.7.0 copied to clipboard
ZeroSettle SDK for Flutter — Merchant of Record web checkout.
1.7.0 — 2026-06-10 #
Trial display facts + Apple external-purchase compliance flow-through:
Product.trial(TrialFacts) — trial mode, price, and duration display facts on products, mirroring the native iOS/Android SDKs.nullwhen the product has no trial or the mode is unknown.- iOS: ZeroSettleKit floor raised to 1.5.2, which auto-mints Apple external-purchase tokens (EU/EEA/JP) and sends the App Store storefront at checkout initiation — no Dart-side changes required. Apps in affected regions should also follow the native setup guide (Apple program enrollment, entitlements, Info.plist keys).
- Android: zerosettle-android floor raised to 1.2.1 (TrialFacts support).
Bumped #
- ZeroSettleKit (iOS)
~> 1.5.2 - zerosettle-android / zerosettle-android-ui
1.2.1
1.6.0 — 2026-06-01 #
On-screen offer-impression tracking for custom banners:
OfferImpression(child:)wrapper widget — reports a ≥50%-on-screen impression once per appearance; the SDK's built-inOfferTipViewalready self-tracks natively.ZeroSettle.reportOfferViewed(...)bridged to the native iOS/Android SDKs.- Requires ZeroSettleKit ≥ 1.5.0 (iOS) and zerosettle-android ≥ 1.2.0 (Android).
1.5.1 — 2026-05-25 #
Soft-deprecate the "Force ECL available" testing override in favor of the
single, end-to-end setSwitchAndSaveTestMode flag.
Deprecated #
ZeroSettle.instance.setEclAvailabilityOverride(bool?)— emits a deprecation warning. The method only ever affected the offer-gate visibility on Android; tapping the resulting "Switch Now" CTA still queried Play Billing for ECL availability and errored withSwitchAndSaveUnavailableon non-enrolled devices. UsesetSwitchAndSaveTestMode(true)instead — it surfaces the tip AND lets the entire flow run end-to-end on a non-ECL device. The bridge keeps forwarding so existing apps continue to work; the method will be removed in a future major.
Example app #
- Removed the "Force ECL available" toggle and its persisted
eclOverridepref. The remaining "Switch & Save full test mode" toggle is the single end-to-end Switch & Save testing switch.
Bumped #
io.zerosettle:zerosettle-androiddependency to1.1.1(carries the matching SDK-side soft-deprecation). Local Flutter Android builds needpublishToMavenLocalfrom theZeroSettle-Androidcheckout until1.1.1publishes to Maven Central.
1.5.0 — 2026-05-22 #
Android-parity sweep — closes the seven remaining bridge gaps so a Flutter app gets the same surfaces a native Android app gets. Everything is additive; no existing public API was renamed or removed.
Added #
ZeroSettle.instance.getSdkVersion()— returns the native SDK version string.ZeroSettle.instance.getIsUcbEnabled()+isUcbEnabledUpdatesstream — User Choice Billing state. Android-only; iOS returnsfalseand emits a singlefalse.ZeroSettle.instance.releasePendingCheckout()— cancels an in-flight web checkout. Android-only; iOS is a no-op.ZeroSettle.instance.getPendingActions(),pendingActionsUpdatesstream, anddismissPendingAction({transactionId})— backend-driven user prompts (post-migration info, manual Play cancel). Android-only; iOS returns empty lists / no-op.PendingActionDart sealed model withPendingActionMigrationCompletedInfoandPendingActionManualPlayCancelvariants.ZeroSettlePendingActionBannerwidget — zero-config Flutter mount point for the native Android pending-action banner; the native side self-renders the current top pending action from the SDK stream. iOS renders nothing.ZeroSettle.instance.fetchUserOffer()returning a typedUserOfferResponse— the server-canonical offer decision (migration / upgrade eligibility). The recommended offer API on both platforms.UserOffer*Dart model tree (UserOfferResponse,UserOfferSubscription,UserOfferData,UserOfferDisplay,UserOfferProration,UserOfferAppleSubscription) plusUserOfferActionTypeandUserOfferSourceStorefrontenums.ZeroSettle.instance.events—Stream<ZeroSettleEvent>of SDK analytics/lifecycle events (see the variant table below). NewZeroSettleEventsealed model with 10 typed variants +ZSEventUnknownforward-compat fallback.ZeroSettle.instance.setSwitchAndSaveTestMode(bool)— testing override that runs the entire Switch & Save (Play→web ECL migration) flow on a device/account not enrolled in Google's External Content Link program: the Play ECL plumbing is faked while the backend session mint and the web checkout run for real. Also implies ECL-available, so the offer tip surfaces. Android-only; iOS is a no-op. Requireszerosettle-android1.1.0.
Changed #
- The offer-tip Flutter widget is now
OfferTipView(wasMigrationTipView), matching the canonical name in iOSZeroSettleKit.MigrationTipViewandZSMigrateTipViewremain as@Deprecatedtypedef aliases, so existing code keeps compiling. The separateZeroSettleOfferTipwidget added earlier in this unreleased cycle (Android-only, no height-bridge) is removed —OfferTipViewis the single, unified, cross-platform offer tip.
Fixed #
- Android:
ZeroSettleMigrationManagerStaticsno longer throwsMissingPluginException. Thezerosettle/migration_manager_staticMethodChannel is now wired.isPermanentlyDismissed/setDismissedroute to the unifiedOfferDismissalStore;resetDismissedStateis a deliberate no-op (calling it would conflate the two iOS-distinct stores). - Android: the offer tip (
OfferTipView) now renders and the "Switch now" CTA works. Previously the embedded native offer tip never appeared on Android. Three stacked issues are fixed: a window-levelViewTree*lifecycle owner is installed on the activity content view so aComposeViewcan compose inside a Flutter PlatformView (FlutterViewprovides none); the AndroidAndroidViewis bootstrapped at 1px so Flutter instantiates the platform view; and the height bridge measures the tip's intrinsic height via an unbounded re-measure. TheComposeViewis now built against the hostActivity, so the "Switch now" CTA drives the SDK's Switch & Save checkout instead of silently no-op'ing (the SDK's offer-tip composable resolves the checkout Activity fromLocalContext).
ZeroSettle.instance.events — SDK analytics/lifecycle stream #
A new Stream<ZeroSettleEvent> is available at ZeroSettle.instance.events. Subscribe to receive discrete analytics and lifecycle events from the SDK without polling entitlements or wiring delegate callbacks.
ZeroSettle.instance.events.listen((event) {
switch (event) {
case ZSEventPurchaseSucceeded(:final productId, :final transactionId):
analytics.track('purchase', {
'product': productId,
'transaction': transactionId,
});
case ZSEventEntitlementsRefreshed(:final count):
print('$count active entitlements');
case ZSEventSyncFailed(:final terminal):
if (terminal) notifyUser('Sync failed — please restore purchases.');
default:
break;
}
});
Event variants
| Type | Fields | Platforms |
|---|---|---|
ZSEventPurchaseSucceeded |
productId, transactionId |
Android + iOS |
ZSEventPurchaseFailed |
productId, reason |
Android + iOS |
ZSEventEntitlementsRefreshed |
count (active-only) |
Android + iOS |
ZSEventSyncFailed |
purchaseToken, attempts, terminal |
Android (full); iOS (degraded: purchaseToken="", attempts=1) |
ZSEventOfferShown |
productId |
Android only |
ZSEventOfferAccepted |
productId |
Android only |
ZSEventOfferDismissed |
productId |
Android only |
ZSEventOfferEvaluationFailed |
reason |
Android only |
ZSEventMigrationCompleted |
productId |
Android only |
ZSEventPendingActionShown |
actionType |
Android only |
ZSEventUnknown |
type (raw string) |
Both (forward-compat fallback) |
Unknown event types decode to ZSEventUnknown rather than throwing, so apps built against an older SDK version remain compatible when new variants are introduced.
Android: backs directly off ZeroSettle.events (SharedFlow<ZeroSettleEvent>). All 10 variants are emitted natively.
iOS: backs off the ZeroSettleDelegate callbacks available in ZeroSettleKit. Events that have no delegate equivalent (offerShown, offerAccepted, offerDismissed, offerEvaluationFailed, migrationCompleted, pendingActionShown) are absent on iOS — they have no callback surface in the iOS SDK. iOS apps should observe offer-lifecycle events via OfferManager.stateUpdates instead.
Android: ZeroSettleMigrationManagerStatics no longer throws NotImplementedError #
The zerosettle/migration_manager_static MethodChannel is now wired on Android. Dart's deprecated ZeroSettleMigrationManagerStatics.isPermanentlyDismissed and setDismissed now resolve against the same OfferDismissalStore as ZeroSettleOfferManagerStatics. resetDismissedState is a deliberate no-op on Android (calling it would conflate the two iOS-distinct stores).
This is an internal fix — the MigrationManager Dart class is deprecated in favour of OfferManager. Existing apps using ZeroSettleMigrationManagerStatics calls will no longer crash on Android.
1.4.0 #
Tracks ZeroSettleKit 1.3.6. Two headline changes plus a Kit-level bug fix:
- Auto-bookkeeping for offer checkouts.
ZeroSettle.instance.presentPaymentSheet(...)andZeroSettle.instance.purchase(...)now run the offer state machine automatically when the productId matches the active offer'scheckoutProductId. Adopters usingOfferManagerno longer need to callmanager.present()ormanager.markCheckoutSucceeded()manually. - Swift Package Manager support. The plugin's iOS layer now supports SPM in addition to CocoaPods. Apps on Flutter 3.41+ with
flutter config --enable-swift-package-managerget the SPM resolution path, which pulls ZeroSettleKit directly fromgithub.com/zerosettle/ZeroSettleKit(bypassing the CocoaPods chain). CocoaPods adopters continue to work unchanged.
Auto-bookkeeping (the headline change) #
Adopters using ZeroSettle.instance.presentPaymentSheet(...) to accept a migration or upgrade offer no longer need to call manager.present() or manager.markCheckoutSucceeded(). The SDK detects active offer context and runs the state machine itself:
- Pre-checkout: state advances
.eligible → .presented. - Post-checkout success: state advances
.presented → .acceptedor.presented → .completeddepending onneedsAppleCancel. Migration conversion analytics fire automatically. - Failure / cancellation: state stays
.presented. User retries via the same CTA.
The OfferManager.stateUpdates stream surfaces every transition — reactive UI driven from this stream works identically before and after this release.
Swift Package Manager support #
Apps on Flutter 3.41+ can opt into SPM resolution with flutter config --enable-swift-package-manager (or via per-project pubspec config). The plugin's iOS layer at ios/zerosettle/Sources/zerosettle/ is now SPM-conventional, and a new ios/zerosettle/Package.swift declares ZeroSettleKit as an SPM dependency (~> 1.3.6).
Kit 1.3.6 fix: checkout sheet no longer shrinks mid-presentation #
CheckoutPreloaderPool.ensureReady (in ZeroSettleKit) previously returned the moment buttonsReady fired, before measureContentJS had completed and measuredContentHeight was set. CheckoutSheet's internal init then read measuredContentHeight = 0, the WebView fell back to a 300pt placeholder frame, and the live geometry observer later reported the real height — causing a visible sheet shrink during presentation. Kit 1.3.6 extends ensureReady to also wait for the isReady signal (which fires alongside measuredContentHeight). Flutter adopters using presentPaymentSheet benefit automatically by tracking Kit ~> 1.3.6.
CocoaPods adopters: nothing changes. The podspec stays as the fallback resolution path; iOS sources just live at a different path internally (transparent to consumers).
Adopter migration (auto-bookkeeping) #
If your app calls manager.present() and manager.markCheckoutSucceeded() around ZeroSettle.instance.presentPaymentSheet(...), delete both calls. The SDK now handles them. Compile-time deprecation warnings will guide you.
If your app uses manager.startCheckout() for raw URL flows, no change — that path stays manual and supported.
Deprecations #
OfferManager.present()—@Deprecated. Bookkeeping is automatic. Removed in 2.0.OfferManager.markCheckoutSucceeded({transactionId})—@Deprecated. Bookkeeping is automatic. Body retained through 1.x for adopters usingstartCheckout(raw URL escape hatch).MigrationManager(entire class) — class-level@Deprecated. Mirrors Kit's existing class-level deprecation onZSMigrationManager. UseOfferManagerinstead viaZeroSettle.instance.offerManager()— strict superset that handles migration + StoreKit→web upgrade + web→web upgrade.
Not deprecated #
OfferManager.startCheckout({stripeCustomerId})— remains the sanctioned path for adopters who need custom WebView/transport. Docstring rewritten to clarify scope.
Example app #
MigrationOfferCard renamed to OfferCard, switched to OfferManager, and updated to demonstrate the canonical 1-call _accept() flow. The example now has zero deprecation warnings.
Minimum requirements #
- Flutter:
>=3.41.0(required for SPM plugin tooling; apps on older Flutter pin to 1.3.4). - Dart SDK:
^3.11.0. - iOS deployment target:
18.0(matches ZeroSettleKit minimum).
Bumps #
- iOS pod dependency:
ZeroSettleKit ~> 1.3.6. - Plugin tracks Kit's 1.3.x line in lockstep.
1.3.4 #
Tracks ZeroSettleKit 1.3.4. Two adopter-visible changes plus a bridge cleanup.
ZSApplePaySetupRequiredException gains autoPresentedSetup field #
Mirrors Kit 1.3.4's payload addition on ZeroSettleError.applePaySetupRequired(autoPresentedSetup:). When the SDK auto-presented the system Wallet (because Configuration.applePaySetupBehavior == ApplePaySetupBehavior.presentBuiltInUI, the default), autoPresentedSetup is true; your app should NOT stack additional setup UI. When delegateToApp, the flag is false and your app owns the setup affordance — call ZeroSettle.instance.presentApplePaySetup() when ready.
try {
await ZeroSettle.instance.purchase(productId: 'pro_monthly');
} on ZSApplePaySetupRequiredException catch (e) {
if (e.autoPresentedSetup) {
// SDK already opened Wallet — no UI needed, maybe analytics
} else {
showCustomSetupSheet();
}
}
Source-compatible — the new field has a default of false, so existing catch (e) blocks keep working.
OfferManager factory: orphan-manager hazard fixed (Kit-side) #
Kit 1.3.4's offerManager(stripeCustomerId:) is now non-throwing and eager — it returns the canonical shared instance regardless of identify() state. The Flutter bridge no longer wraps the call in try/catch. Adopters using await ZeroSettle.instance.offerManager(...) benefit automatically: handles obtained before identify() runs now stay live and re-evaluate eligibility in place once the user identifies.
Internal: @Observable migration in ZeroSettleKit (no Flutter impact) #
Kit 1.3.4 migrated ApplePayAvailability from ObservableObject/@Published to Swift's modern Observation framework. The Flutter bridge subscribes to the deprecated-but-still-supported statePublisher (Combine AnyPublisher) — no Flutter-visible behavior change. Will be revisited before Kit 2.0 removes the Combine bridge.
Bumps #
- iOS pod dependency:
ZeroSettleKit ~> 1.3.4. - Plugin tracks Kit's 1.3.x line in lockstep.
1.3.3 #
Tracks ZeroSettleKit 1.3.3. No Flutter-visible API changes — the Kit's 1.3.3 work (OfferTipView and ZSOfferManager direct-init no longer require userId:) only affects native Swift call sites. Flutter already uses the canonical ZeroSettle.instance.offerManager(stripeCustomerId:) factory, so all 1.3.3 fixes are absorbed transparently.
- iOS pod dependency:
ZeroSettleKit ~> 1.3.3.
1.3.2 #
Tracks ZeroSettleKit 1.3.2. Pre-existing primitives that were missing in earlier 1.3.x Flutter releases are now bridged, plus the new Apple Pay availability + setup-behavior surface.
New purchase primitives #
purchase({productId, presentation?})— unified purchase entry point. Routes web vs. StoreKit per jurisdiction. Returns aCheckoutTransaction.purchaseViaStoreKit({productId})— explicit native StoreKit 2 purchase. Returns aCheckoutTransactionpopulated from the StoreKit 2Transaction(price/currency may benull).CheckoutTypeenum for the optionalpresentationargument onpurchase().
New state queries #
getCurrentUserId() → Future<String?>— observable identity state.getIsBootstrapped() → Future<bool>— SDK readiness flag adopters can poll/listen for before calling user-scoped APIs.recommendedAppAccountToken() → Future<String>— UUID derivation helper for non-UUID user IDs (Firebase, Privy, Auth0, etc.).
Pending claims (StoreKit ownership transfer UX) #
getPendingClaims() → Future<List<PendingClaim>>— read once.pendingClaimsUpdatesstream — emits whenever the SDK observes a StoreKit purchase that belongs to another ZeroSettle account on this device. Power your "transfer this purchase?" UX from this stream, then calltransferStoreKitOwnershipToCurrentUser({productId})when the user confirms.- New
PendingClaimmodel.
Apple Pay surface (new in Kit 1.3.2) #
Configuration.applePaySetupBehavior— new optionalconfigure()parameter.ApplePaySetupBehavior.presentBuiltInUI(default) opens system Wallet automatically when an Apple-Pay-only merchant has no Wallet card;ApplePaySetupBehavior.delegateToAppsurfacesZSApplePaySetupRequiredExceptionso your app can handle the flow.presentApplePaySetup()— opens system Wallet to add a card.getIsApplePayOnly() → Future<bool>— true when the active merchant config is Apple-Pay-only.getApplePayState() → Future<ApplePayAvailabilityState>— read the current.ready/.setupRequired/.unavailablestate.applePayStateUpdatesstream — observe state transitions to drive your own banner / pre-flight UI.- 2 new exceptions:
ZSApplePayUnavailableException,ZSApplePaySetupRequiredException. CheckoutConfig.paymentMethods+isApplePayOnlygetter on the remote config.
Bug fixes #
setBaseUrlOverridehad no concreteMethodChanneloverride (regression introduced in 0.4.0). Fixed; any environment usingbaseUrlOverride(staging, internal/ngrok) now configures correctly.- MIME-type cleanups in test mocks for
acceptSaveOffer/fetchUpgradeOfferConfigso facade round-trips don't fail on empty maps.
Bumps #
- iOS pod dependency:
ZeroSettleKit ~> 1.3.2. - Plugin tracks Kit's 1.3.x line in lockstep.
1.3.0 #
- Adds
identify()as the canonical entry point. Takes anIdentity(sealed class withIdentity.user,Identity.anonymous,Identity.deferred). Call once at launch (and again on sign-in/sign-out). Subsequent user-scoped calls read identity from internal state — no more passinguserId:everywhere. - Adds 10
userId-less method overloads.restoreEntitlements(),fetchTransactionHistory(),acceptSaveOffer({productId}),presentCancelFlow({productId}),pauseSubscription({productId, pauseDurationDays}),resumeSubscription({productId}),cancelSubscription({productId, immediate}),presentUpgradeOffer({productId}),fetchUpgradeOfferConfig({productId}),trackMigrationConversion(). Use these after callingidentify(). - Deprecates the
userId:parameter on every facade method, plusbootstrap(). They still compile and work through the 1.x line, but the parameter is annotated@Deprecatedand will be removed in2.0. Migrate by callingidentify()once and droppinguserId:from the rest. - Adds
appleMerchantId,preloadCheckout,maxPreloadedWebViewstoconfigure(). Configuration parity with iOS Kit 1.3.0. - Fixes
pauseSubscriptionsignature. The bridge now passespauseDurationDaysto ZeroSettleKit 1.3.0 (waspauseOptionId). DeprecatedpauseOptionIdparameter remains on the facade for source compatibility through 1.x. - Adds 5 new exception types mapped from ZeroSettleKit 1.3.0 errors:
ZSInvalidPublishableKeyException,ZSCheckoutConfigExpiredException,ZSTransactionVerificationFailedException,ZSPurchasePendingException,ZSUserNotIdentifiedException. - Fixes
CancelFlowOption.fromMapnull-safety fortriggersOffer/triggersPause(defaults tofalsewhen absent). - Bumps iOS pod dependency to
ZeroSettleKit ~> 1.3.0. - See
MIGRATING.mdin the iOS Kit for the full 1.3.0 migration guide; the same renames and deprecations apply to the Flutter SDK Dart facade.
0.4.0 #
- Aligns bridge with ZeroSettleKit 1.2.5.
- Adds
setCustomer({name, email})andlogout(). - Adds
transferStoreKitOwnershipToCurrentUser(replaces removedclaimEntitlement). - Adds
hasActiveEntitlement(productId),product(productId). - Maps
ZeroSettleError.checkoutNotStartedtoZSCheckoutNotStartedException. - Surfaces previously-missing model fields:
Entitlement.storekitOriginalTransactionId,originalPurchaseDateProduct.subscriptionGroupId,billingInterval,freeTrialDuration,isTrialEligibleCheckoutTransaction.storekitStatus
- Stubs removed APIs (
openCustomerPortal,showManageSubscription,presentSaveTheSaleSheet) withnot_implementederrors; Dart facade kept for source-compat. - Fixes bridge drift:
ZeroSettleKit.Product → ZSProduct,Price.cents → Price.amountCents,Entitlement.statusno longer optional.
0.3.4 #
- Update to ZeroSettleKit 1.2.3
0.3.3 #
- Maintenance release - no functional changes
0.3.2 #
- Add
ZSMigrateTipViewwidget for encouraging StoreKit users to migrate to web billing - Update to ZeroSettleKit 0.7.2
0.3.1 #
- Breaking: Add required
freeTrialDaysparameter tobootstrap(),warmUpPaymentSheet(),preloadPaymentSheet(), andpresentPaymentSheet()methods - Update to ZeroSettleKit 0.7.1+ with free trial support
- Update podspec to automatically use latest 0.7.x releases
0.3.0 #
- Add
remoteConfiganddetectedJurisdictiongetters for jurisdiction-specific checkout behavior - Add support for smart subscription management via
showManageSubscription() - Update to ZeroSettleKit 0.6.0+ API surface
0.2.0 #
- Update models and APIs to match ZeroSettleKit iOS SDK
- Add checkout events stream
- Improve error handling
0.1.0 #
- Initial release with core IAP functionality
- Support for product catalog, payment sheets, and entitlements
- iOS platform support via ZeroSettleKit