ZSMigrationManager is the state machine behind ZSMigrateTipView. If the built-in view doesn’t fit your design, use the manager directly to build a fully custom switch and save experience while reusing all the eligibility logic, checkout orchestration, and state tracking.
Quick Start
ObservableObject — SwiftUI re-renders automatically when the state changes.
State Machine
The switch and save flow is a linear state machine. Each state represents a step in the user journey.| State | Meaning | offerData |
|---|---|---|
loading | Waiting for ZeroSettle.bootstrap() to finish. | nil |
ineligible | User doesn’t qualify (no StoreKit subscription, already switched, no campaign configured). | nil |
eligible | User qualifies. Offer data is available — show your UI. | Available |
presented | Offer is on screen or checkout is in progress. | Available |
accepted | Web checkout succeeded. Prompt the user to cancel Apple billing. | Available |
completed | User opened Apple subscription management. Switch and save flow is done. | Available |
dismissed | User closed the offer. Persisted in UserDefaults across launches. | nil |
presented, accepted, completed) so that background re-evaluations (e.g., entitlement refreshes) don’t disrupt an active checkout.
Published Properties
| Property | Type | Description |
|---|---|---|
state | MigrationOffer.State | Current state of the switch and save flow. |
offerData | MigrationOffer.OfferData? | Offer details (prompt text, discount, free trial days). Available from .eligible onward. |
isLoading | Bool | true while startCheckout() is creating a payment intent. |
checkoutError | Error? | The last error from startCheckout(), if any. |
userId | String | The user identifier this manager was created with. |
stripeCustomerId | String? | Optional Stripe Customer ID passed at init. When set, checkouts are attached to this existing customer. |
Offer Data
Whenstate is .eligible or later, offerData contains everything you need to render the offer.
MigrationOffer.OfferData
| Field | Type | Description |
|---|---|---|
prompt | MigrationPrompt | Title, message, CTA text, discount, and product ID from your dashboard campaign. |
freeTrialDays | Int | Days remaining on the user’s current StoreKit subscription (bridged as a free trial on web). |
activeStoreKitProductId | String | The product ID of the user’s active StoreKit subscription. |
MigrationPrompt
| Field | Type | Description |
|---|---|---|
productId | String | The web product ID to offer for switch and save. |
discountPercent | Int | The discount percentage (e.g., 15 for 15% off). |
title | String | Heading text for the offer. |
message | String | Body text explaining the offer. |
ctaText | String | Button label (e.g., “Save 15% Forever”). |
Methods
startCheckout() async -> URL?
Creates a payment intent and returns a checkout URL. Transitions state from .eligible to .presented.
nil on failure — check manager.checkoutError for details.
present()
Manually transition from .eligible to .presented without creating a checkout URL. Use this when you handle checkout via ZeroSettle.shared.purchase() (Safari / SFSafariViewController flow) instead of an inline webview.
markCheckoutSucceeded()
Call this after a successful web checkout to transition from .presented to .accepted. Fires conversion tracking automatically.
showAppleSubscriptionManagement() async
Opens the system subscription management sheet (StoreKit AppStore.showManageSubscriptions). Transitions from .accepted to .completed when the sheet dismisses.
dismiss()
Dismisses the offer from any state. Sets state to .dismissed and persists the dismissal in UserDefaults so it won’t reappear.
Static Methods
resetDismissedState()
Clears the persisted dismissal, allowing the offer to reappear. Useful for debugging.
Full Example
A complete custom switch and save card with loading, error, checkout, and completion states:Relationship to ZSMigrateTipView
ZSMigrateTipView uses ZSMigrationManager internally. Choose based on your needs:
ZSMigrateTipView | ZSMigrationManager | |
|---|---|---|
| Effort | Drop-in, zero UI code | You build the UI |
| Customization | Background color, fonts, border | Full control over layout, animations, copy |
| Checkout | Built-in inline webview | You choose: webview, Safari, SFSafariViewController |
| State tracking | Handled internally | You observe and react to state changes |
| Platform | SwiftUI, React Native, Flutter | SwiftUI only |

