@appfunnel-dev/sdk 2.0.0-canary.1 → 2.0.0-canary.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/capabilities-Dq22RCr_.d.cts +180 -0
- package/dist/capabilities-Dq22RCr_.d.ts +180 -0
- package/dist/checkout-ClaO5IYV.d.ts +333 -0
- package/dist/checkout-DBp4bCpC.d.cts +333 -0
- package/dist/chunk-7JLOF6CJ.cjs +172 -0
- package/dist/chunk-7JLOF6CJ.cjs.map +1 -0
- package/dist/chunk-EMMSS5I5.cjs +37 -0
- package/dist/chunk-EMMSS5I5.cjs.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-JAO6AA4R.js +99 -0
- package/dist/chunk-JAO6AA4R.js.map +1 -0
- package/dist/chunk-OXQBEKZ5.js +157 -0
- package/dist/chunk-OXQBEKZ5.js.map +1 -0
- package/dist/chunk-RKPRMTSU.cjs +517 -0
- package/dist/chunk-RKPRMTSU.cjs.map +1 -0
- package/dist/chunk-SXENKZ4U.js +486 -0
- package/dist/chunk-SXENKZ4U.js.map +1 -0
- package/dist/chunk-VV7TFC64.cjs +106 -0
- package/dist/chunk-VV7TFC64.cjs.map +1 -0
- package/dist/chunk-WYUDL4FI.cjs +8 -0
- package/dist/chunk-WYUDL4FI.cjs.map +1 -0
- package/dist/chunk-ZZJG4EYL.js +6 -0
- package/dist/chunk-ZZJG4EYL.js.map +1 -0
- package/dist/driver-paddle.cjs +817 -0
- package/dist/driver-paddle.cjs.map +1 -0
- package/dist/driver-paddle.d.cts +10 -0
- package/dist/driver-paddle.d.ts +10 -0
- package/dist/driver-paddle.js +814 -0
- package/dist/driver-paddle.js.map +1 -0
- package/dist/driver-stripe.cjs +2312 -0
- package/dist/driver-stripe.cjs.map +1 -0
- package/dist/driver-stripe.d.cts +24 -0
- package/dist/driver-stripe.d.ts +24 -0
- package/dist/driver-stripe.js +2305 -0
- package/dist/driver-stripe.js.map +1 -0
- package/dist/index.cjs +2677 -846
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +358 -1005
- package/dist/index.d.ts +358 -1005
- package/dist/index.js +2395 -698
- package/dist/index.js.map +1 -1
- package/dist/manifest-C2mDXWNU.d.cts +996 -0
- package/dist/manifest-C2mDXWNU.d.ts +996 -0
- package/dist/manifest-entry.cjs +408 -0
- package/dist/manifest-entry.cjs.map +1 -0
- package/dist/manifest-entry.d.cts +192 -0
- package/dist/manifest-entry.d.ts +192 -0
- package/dist/manifest-entry.js +270 -0
- package/dist/manifest-entry.js.map +1 -0
- package/dist/protocol.cjs +13 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.cts +182 -0
- package/dist/protocol.d.ts +182 -0
- package/dist/protocol.js +4 -0
- package/dist/protocol.js.map +1 -0
- package/package.json +49 -4
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import { c as CheckoutProvider, b as CheckoutIntent, d as CheckoutSurface, U as UpsellKind, C as CheckoutError, a as CheckoutErrorCategory, f as SurfacePreference } from './capabilities-Dq22RCr_.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* v2 commerce primitives — the two intents from phase-3, made trivial.
|
|
6
|
+
*
|
|
7
|
+
* - `<Checkout offering surface>` — the **purchase** primitive. The `surface`
|
|
8
|
+
* prop picks presentation; some surfaces render **inline** (express wallets,
|
|
9
|
+
* inline card, provider iframe), some **open on click** (sheet/popup/redirect).
|
|
10
|
+
* `<Checkout>` covers both: it renders inline surfaces and renders a trigger
|
|
11
|
+
* button for the rest (`asChild` to use your own button).
|
|
12
|
+
* - `<Upsell offering>` — the **upsell** primitive. Off-session charge to the
|
|
13
|
+
* saved method, no card re-entry, one tap.
|
|
14
|
+
* - `useCheckout()` — the headless hook under both, always available for a
|
|
15
|
+
* fully-custom CTA.
|
|
16
|
+
*
|
|
17
|
+
* The SDK never talks to a provider. It owns the state machine + the analytics
|
|
18
|
+
* (`checkout.start` / `purchase.complete`) + post-charge navigation, and
|
|
19
|
+
* delegates the actual charge/render to an injected {@link CheckoutDriver}
|
|
20
|
+
* (the real one is platform-side; tests use {@link createMockDriver}).
|
|
21
|
+
*
|
|
22
|
+
* Which offering is charged is the author's bound variable, captured at render
|
|
23
|
+
* (inline surfaces) or at `open()` time (trigger surfaces).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
type CheckoutStatus = 'idle' | 'loading' | 'requires_action' | 'success' | 'error';
|
|
27
|
+
interface CheckoutRequest {
|
|
28
|
+
/** The offering SLOT the page named (`<Checkout offering="primary">`) — the display/analytics identity. Kept as the wire field `product` at the provider boundary. */
|
|
29
|
+
product: string;
|
|
30
|
+
/** The catalog charge KEY charged — the CHARGE identity, resolved from `product` by {@link driverWithCatalog}. */
|
|
31
|
+
catalogKey?: string;
|
|
32
|
+
intent: CheckoutIntent;
|
|
33
|
+
surface?: CheckoutSurface;
|
|
34
|
+
/** For `upsell` intent — which kind of off-session charge (default `subscription`). */
|
|
35
|
+
kind?: UpsellKind;
|
|
36
|
+
/**
|
|
37
|
+
* Buyer email at attempt time, read from the funnel's `user.email` namespace value
|
|
38
|
+
* (injected by `FunnelProvider` via {@link driverWithEmail} — no author wiring).
|
|
39
|
+
* Drivers forward it into the wire `CheckoutSessionRequest.email` (Stripe Customer
|
|
40
|
+
* creation / receipts / Paddle prefill).
|
|
41
|
+
*/
|
|
42
|
+
email?: string;
|
|
43
|
+
}
|
|
44
|
+
interface CheckoutResult {
|
|
45
|
+
ok: boolean;
|
|
46
|
+
error?: CheckoutError;
|
|
47
|
+
/** Charged amount in minor units (for the purchase event). */
|
|
48
|
+
amountMinor?: number;
|
|
49
|
+
currency?: string;
|
|
50
|
+
/**
|
|
51
|
+
* The **server-minted** dedup id for the purchase event — the platform creates it
|
|
52
|
+
* when the charge settles and fires CAPI with it; the SDK emits `purchase.complete`
|
|
53
|
+
* with the same id so the browser pixel dedupes against the server. (Server is the
|
|
54
|
+
* source of truth for payment events; the SDK doesn't mint these.)
|
|
55
|
+
*/
|
|
56
|
+
eventId?: string;
|
|
57
|
+
}
|
|
58
|
+
interface CheckoutCallbacks {
|
|
59
|
+
onSuccess: (result: CheckoutResult) => void;
|
|
60
|
+
onError: (error: CheckoutError) => void;
|
|
61
|
+
/**
|
|
62
|
+
* The buyer SUBMITTED an inline surface (Stripe confirm click / wallet
|
|
63
|
+
* authorization; Paddle inline checkout opening). This is when `checkout.start`
|
|
64
|
+
* fires and the status machine enters `loading` for inline attempts — trigger
|
|
65
|
+
* surfaces fire it at click time instead. It also marks fresh user intent, so
|
|
66
|
+
* the once-per-chain recovery guard resets. Optional: drivers that can't
|
|
67
|
+
* observe submit simply never call it, and the SHEET host deliberately doesn't
|
|
68
|
+
* forward it (the sheet's own open already began the attempt — re-firing would
|
|
69
|
+
* double-count starts and un-arm the recovery guard mid-chain).
|
|
70
|
+
*/
|
|
71
|
+
onStart?: () => void;
|
|
72
|
+
/**
|
|
73
|
+
* The inline surface reports a valid payment method was entered (e.g. Stripe's
|
|
74
|
+
* Payment Element became `complete`), before the charge. Drives the
|
|
75
|
+
* `checkout.payment_added` analytics step; optional — drivers that can't observe
|
|
76
|
+
* it (or off-session charges) simply never call it.
|
|
77
|
+
*/
|
|
78
|
+
onPaymentAdded?: () => void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* What a driver factory (`stripeCheckoutDriver` et al. from the
|
|
82
|
+
* `@appfunnel-dev/sdk/driver-*` entries) receives to construct a
|
|
83
|
+
* {@link CheckoutDriver}. Derived by `createFunnelTree` from the SAME
|
|
84
|
+
* renderer-injected mount data the platform tracker uses (tracking config +
|
|
85
|
+
* identity), so checkout wire calls land on the exact campaign/funnel/session
|
|
86
|
+
* the analytics do — there is no second config channel to drift.
|
|
87
|
+
*
|
|
88
|
+
* Serializable on purpose, and factories must be **construction-safe on the
|
|
89
|
+
* server too**: a factory only closes over this ctx (no provider SDK, no
|
|
90
|
+
* `window`) — all provider work belongs in effects/event handlers, so SSR can
|
|
91
|
+
* render the driver's stable inline placeholder and hydration matches.
|
|
92
|
+
*/
|
|
93
|
+
interface CheckoutDriverContext {
|
|
94
|
+
/** Public API origin, e.g. `https://api.appfunnel.net` (no trailing slash). */
|
|
95
|
+
apiBase: string;
|
|
96
|
+
/** Campaign DB id (the checkout route key, same as the ingestion route key). */
|
|
97
|
+
campaignId: string;
|
|
98
|
+
/** WebFunnel DB id (NOT the funnel.ts config id). */
|
|
99
|
+
funnelId: string;
|
|
100
|
+
/** Render-time mode — the server resolves the product binding + store credentials by it. */
|
|
101
|
+
mode: 'test' | 'live';
|
|
102
|
+
/** Funnel session id when one exists (analytics + customer continuity). */
|
|
103
|
+
sessionId?: string | null;
|
|
104
|
+
/** Signed-cookie-verified bare visitor id, when known. */
|
|
105
|
+
visitorId?: string | null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The provider seam. The platform injects the real one (Stripe/Paddle/Whop/…);
|
|
109
|
+
* tests use {@link createMockDriver}.
|
|
110
|
+
*
|
|
111
|
+
* Most providers ship their **own React SDK** (`@stripe/react-stripe-js`,
|
|
112
|
+
* `@paddle/paddle-js`, Primer's React components, …). A driver's `renderInline`
|
|
113
|
+
* returns exactly that: the provider's React element mounted in our chrome (inline,
|
|
114
|
+
* or inside the `<Sheet>`). Those provider SDKs live in the **driver/platform**
|
|
115
|
+
* package, not our core funnel SDK — which is why the driver is injected: the core
|
|
116
|
+
* stays lean and provider-agnostic, and you only pull the SDK for the provider you use.
|
|
117
|
+
*/
|
|
118
|
+
interface CheckoutDriver {
|
|
119
|
+
provider: CheckoutProvider;
|
|
120
|
+
/** Trigger a charge (trigger surfaces + programmatic). Resolves when settled. */
|
|
121
|
+
start(req: CheckoutRequest): Promise<CheckoutResult>;
|
|
122
|
+
/** Render an inline surface (e.g. the provider's React payment element); report via callbacks. */
|
|
123
|
+
renderInline?(req: CheckoutRequest, cb: CheckoutCallbacks): ReactNode;
|
|
124
|
+
/**
|
|
125
|
+
* Verify an EXISTING attempt with the server (`POST /complete`, bounded re-poll on
|
|
126
|
+
* `pending`) — the redirect-return resume leg: hosted-checkout success URLs carry
|
|
127
|
+
* `?af_checkout={correlationId}` back into the funnel, and {@link CheckoutResume}
|
|
128
|
+
* hands the id here so the settlement (server-verified) resolves into the normal
|
|
129
|
+
* success/failure paths. Never charges; only verifies.
|
|
130
|
+
*/
|
|
131
|
+
resume?(correlationId: string): Promise<CheckoutResult>;
|
|
132
|
+
}
|
|
133
|
+
interface MockDriverOptions {
|
|
134
|
+
/** The result a successful charge resolves with. */
|
|
135
|
+
result?: CheckoutResult;
|
|
136
|
+
/** Make **off-session** charges (no surface) fail — to exercise the upsell fallback. */
|
|
137
|
+
failOffSession?: CheckoutError | boolean;
|
|
138
|
+
}
|
|
139
|
+
/** A console/no-op driver — tests, preview, local dev. Succeeds unless configured to fail. */
|
|
140
|
+
declare function createMockDriver(provider?: CheckoutProvider, options?: MockDriverOptions): CheckoutDriver;
|
|
141
|
+
/**
|
|
142
|
+
* Wrap a driver so every attempt carries the buyer email the funnel has collected
|
|
143
|
+
* (`user.email`), read at ATTEMPT time — `start()` for trigger surfaces, the
|
|
144
|
+
* `renderInline` request for mounted surfaces. Applied by `FunnelProvider` around
|
|
145
|
+
* the injected driver, so `useCheckout`, `<Checkout>` inline branches and the
|
|
146
|
+
* checkout sheet all forward it without wiring; drivers pass it into the wire's
|
|
147
|
+
* `CheckoutSessionRequest.email`. An explicit `req.email` always wins.
|
|
148
|
+
*/
|
|
149
|
+
declare function driverWithEmail(driver: CheckoutDriver, getEmail: () => string | undefined): CheckoutDriver;
|
|
150
|
+
/** One declarative recovery route for a failure category. */
|
|
151
|
+
type FailureRoute = {
|
|
152
|
+
go: string;
|
|
153
|
+
} | {
|
|
154
|
+
modal: string | ComponentType<any>;
|
|
155
|
+
} | {
|
|
156
|
+
fallback: CheckoutSurface;
|
|
157
|
+
} | ((error: CheckoutError) => void);
|
|
158
|
+
/**
|
|
159
|
+
* What a `{ modal }` failure route hands the modal — the error AND the actions,
|
|
160
|
+
* because registry modals mount outside the flow's nav context and a recovery
|
|
161
|
+
* modal that can't act is just an error toast. Type your modal with this:
|
|
162
|
+
*
|
|
163
|
+
* ```tsx
|
|
164
|
+
* const Declined = defineModal<FailureModalProps>(({ error, go, retry }) => (
|
|
165
|
+
* <Sheet>
|
|
166
|
+
* <p>{error.category === 'prepaid_card' ? 'Prepaid cards can't start a trial.' : 'Payment didn't go through.'}</p>
|
|
167
|
+
* <button onClick={() => retry('sheet')}>Try another card</button>
|
|
168
|
+
* <button onClick={() => go('downsell')}>See the lite plan</button>
|
|
169
|
+
* </Sheet>
|
|
170
|
+
* ))
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* A modal can also host a whole `<Checkout offering="…" surface="element" />` for
|
|
174
|
+
* an inline downsell — the driver context is app-level, so checkout works inside
|
|
175
|
+
* modals as-is.
|
|
176
|
+
*/
|
|
177
|
+
interface FailureModalProps {
|
|
178
|
+
error: CheckoutError;
|
|
179
|
+
/** The product whose charge failed (absent only for product-less failures). */
|
|
180
|
+
product?: string;
|
|
181
|
+
/** Navigate the flow to a page key (modals can't call useNavigation themselves). */
|
|
182
|
+
go: (pageKey: string) => void;
|
|
183
|
+
/**
|
|
184
|
+
* Re-attempt the SAME product. `retry()` re-runs an upsell off-session; for a
|
|
185
|
+
* purchase (which needs UI to re-collect) it opens the payment sheet.
|
|
186
|
+
* `retry('sheet')` re-collects on the SDK sheet; an inline surface
|
|
187
|
+
* (`element`/`express`/`embedded`) is COERCED to the sheet when the provider
|
|
188
|
+
* supports it — a retry has no page slot to mount into, so the sheet hosts the
|
|
189
|
+
* element. On sheet-less MoR providers (Paddle) inline retries run through the
|
|
190
|
+
* driver, whose provider checkout opens its own overlay. `retry('popup')` /
|
|
191
|
+
* `retry('redirect')` hand off to the provider. A failed retry routes through
|
|
192
|
+
* `onFailed` again (the modal reopens) — the loop is user-driven, never
|
|
193
|
+
* automatic. Hide the modal first (`useModal().hide()`) when retrying into a
|
|
194
|
+
* surface that opens its own overlay.
|
|
195
|
+
*/
|
|
196
|
+
retry: (surface?: CheckoutSurface) => void;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Declarative failure recovery — route each failure **category** to what happens
|
|
200
|
+
* next, instead of hand-wiring `onError`:
|
|
201
|
+
*
|
|
202
|
+
* ```tsx
|
|
203
|
+
* <Checkout offering="monthly" surface="element" onFailed={{
|
|
204
|
+
* prepaid_card: { go: 'real-card' },
|
|
205
|
+
* insufficient_funds: { modal: DownsellModal },
|
|
206
|
+
* default: { modal: TryAgainModal },
|
|
207
|
+
* }} />
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* Resolution: exact category → `default` → nothing (the error state + `checkout.
|
|
211
|
+
* failed` event stand, as today). Two built-in judgments:
|
|
212
|
+
* - `canceled` only routes when mapped EXPLICITLY — a deliberately dismissed
|
|
213
|
+
* sheet/popup is not a failure to recover from, so `default` never catches it;
|
|
214
|
+
* - a `{ fallback }` recovery runs at most once per attempt chain (its own
|
|
215
|
+
* failure routes through the non-fallback routes), so declines can't loop.
|
|
216
|
+
*
|
|
217
|
+
* `checkout.failed` fires with category + declineCode on every failure regardless
|
|
218
|
+
* of routing — recovery pages are measurable, and being ordinary flow pages they
|
|
219
|
+
* are A/B-able with page experiments like anything else.
|
|
220
|
+
*/
|
|
221
|
+
type OnFailedMap = Partial<Record<CheckoutErrorCategory | 'default', FailureRoute>>;
|
|
222
|
+
interface UseCheckoutOptions {
|
|
223
|
+
intent?: CheckoutIntent;
|
|
224
|
+
/** For `upsell` intent — which kind of off-session charge (default `subscription`). */
|
|
225
|
+
kind?: UpsellKind;
|
|
226
|
+
/** Advance to the next page after a successful charge (default: true). */
|
|
227
|
+
advanceOnSuccess?: boolean;
|
|
228
|
+
onSuccess?: (result: CheckoutResult) => void;
|
|
229
|
+
onError?: (error: CheckoutError) => void;
|
|
230
|
+
/** Declarative failure recovery — see {@link OnFailedMap}. Runs AFTER `onError`. */
|
|
231
|
+
onFailed?: OnFailedMap;
|
|
232
|
+
/**
|
|
233
|
+
* Try this surface ON-SESSION once on any first failure, before `onFailed`
|
|
234
|
+
* routing — the generalized `<Upsell fallback>`: the off-session (or first)
|
|
235
|
+
* charge fails, the surface re-collects a card, the retry's failure then goes
|
|
236
|
+
* through `onFailed`. Never re-triggered by its own failure; never triggered
|
|
237
|
+
* by a user-`canceled` checkout.
|
|
238
|
+
*/
|
|
239
|
+
recoverySurface?: CheckoutSurface;
|
|
240
|
+
}
|
|
241
|
+
interface CheckoutHandle {
|
|
242
|
+
/** Start the charge for a product; resolves with the result (no surface = off-session). */
|
|
243
|
+
open: (product: string, surface?: CheckoutSurface) => Promise<CheckoutResult>;
|
|
244
|
+
status: CheckoutStatus;
|
|
245
|
+
/** The last failure, structured so you can route/recover by `category`. */
|
|
246
|
+
error: CheckoutError | null;
|
|
247
|
+
isLoading: boolean;
|
|
248
|
+
reset: () => void;
|
|
249
|
+
/** Build the callbacks an inline surface reports through (used by `<Checkout>`). */
|
|
250
|
+
callbacksFor: (product?: string, surface?: CheckoutSurface) => CheckoutCallbacks & {
|
|
251
|
+
onStart: () => void;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* The headless checkout hook. Owns the state machine + analytics + post-charge
|
|
256
|
+
* navigation; delegates the charge to the injected {@link CheckoutDriver}.
|
|
257
|
+
*/
|
|
258
|
+
declare function useCheckout(opts?: UseCheckoutOptions): CheckoutHandle;
|
|
259
|
+
interface CheckoutProps extends UseCheckoutOptions {
|
|
260
|
+
/** Logical offering slot (the author's bound selection). */
|
|
261
|
+
offering: string;
|
|
262
|
+
/**
|
|
263
|
+
* Presentation preference. A concrete {@link CheckoutSurface} pins it; `'auto'` lets the platform
|
|
264
|
+
* pick the best surface for the store's provider and the buyer's device — wallet-first on mobile
|
|
265
|
+
* (Stripe → ExpressCheckoutElement, Paddle → its Express Checkout variant), inline card/embedded
|
|
266
|
+
* on desktop (see {@link resolveSurface}). NOTE: `surface` has no default (kept required) so pinned
|
|
267
|
+
* funnels don't change behavior this canary; `'auto'` is opt-in. New authoring (editor + AI) should
|
|
268
|
+
* emit `surface="auto"` going forward so wallet-first is a funnel intent, not a provider prop.
|
|
269
|
+
*/
|
|
270
|
+
surface: SurfacePreference;
|
|
271
|
+
/** Render your own trigger element instead of the default button (trigger surfaces). */
|
|
272
|
+
asChild?: boolean;
|
|
273
|
+
/** Trigger label / your trigger element. */
|
|
274
|
+
children?: ReactNode;
|
|
275
|
+
className?: string;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The purchase primitive. Three presentation modes by the RESOLVED `surface`:
|
|
279
|
+
* - **inline** (`express`/`element`/`embedded`) — the driver renders the
|
|
280
|
+
* provider UI in place (keyed by product so a selection change remounts it);
|
|
281
|
+
* - **SDK sheet** (`sheet`) — a trigger opens the Appfunnel-owned {@link Sheet}
|
|
282
|
+
* and the driver renders the provider element inside it;
|
|
283
|
+
* - **provider-hosted** (`popup`/`redirect`) — a trigger hands off to the
|
|
284
|
+
* provider's own overlay/page.
|
|
285
|
+
*
|
|
286
|
+
* The `surface` prop is a {@link SurfacePreference}: a concrete surface (pinned) or `'auto'`, which
|
|
287
|
+
* {@link resolveSurface} maps to the best surface for the driver's provider + this device — ONCE per
|
|
288
|
+
* mount (frozen so the surface can't change mid-attempt and remount the provider element into a new
|
|
289
|
+
* charge). The resolved literal is what flows onto the wire; the server re-validates it.
|
|
290
|
+
*/
|
|
291
|
+
declare function Checkout({ offering, surface: surfacePreference, asChild, children, className, ...options }: CheckoutProps): ReactNode;
|
|
292
|
+
interface UpsellProps extends Omit<UseCheckoutOptions, 'intent'> {
|
|
293
|
+
offering: string;
|
|
294
|
+
/**
|
|
295
|
+
* If the off-session charge fails, present this surface **on-session** to
|
|
296
|
+
* re-collect a card and charge again — e.g. `fallback="sheet"` opens the
|
|
297
|
+
* payment sheet, `fallback="popup"` the provider overlay. Omit to just report
|
|
298
|
+
* the failure (via `onError` / the `checkout.failed` event).
|
|
299
|
+
*/
|
|
300
|
+
fallback?: CheckoutSurface;
|
|
301
|
+
asChild?: boolean;
|
|
302
|
+
children?: ReactNode;
|
|
303
|
+
className?: string;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* The upsell primitive — an off-session, one-tap charge to the saved method (no
|
|
307
|
+
* surface, no card re-entry). On failure, optionally `fallback` to an on-session
|
|
308
|
+
* surface to re-collect and retry — `fallback` IS the hook's `recoverySurface`,
|
|
309
|
+
* so the retry's own failure routes through `onFailed` (e.g. prepaid_card → a
|
|
310
|
+
* recapture page) instead of dead-ending.
|
|
311
|
+
*/
|
|
312
|
+
declare function Upsell({ offering, fallback, asChild, children, className, ...options }: UpsellProps): ReactNode;
|
|
313
|
+
/**
|
|
314
|
+
* The dangling settle leg of the `redirect` surface. The server appends
|
|
315
|
+
* `af_checkout={correlationId}` to hosted-checkout success URLs; when the buyer
|
|
316
|
+
* lands back in the funnel this component detects the param, has the server
|
|
317
|
+
* VERIFY the attempt (`driver.resume` → `POST /complete`, bounded re-poll while
|
|
318
|
+
* `pending`), and resolves it into the normal machinery:
|
|
319
|
+
*
|
|
320
|
+
* - `settled` → `purchase.complete` fires with the settlement's server-minted
|
|
321
|
+
* `eventId` (pixel↔CAPI dedup), the flow advances, and the param is stripped
|
|
322
|
+
* (`history.replaceState`) so a reload can't re-run the leg;
|
|
323
|
+
* - `failed` → the normal failure path (`checkout.failed` + status `error`;
|
|
324
|
+
* any `onFailed` maps passed as props apply — product-less, so only
|
|
325
|
+
* `go`/handler routes can act).
|
|
326
|
+
*
|
|
327
|
+
* SSR-safe (effect-only) and runs once per correlationId per page load. Mounted
|
|
328
|
+
* automatically by `createFunnelTree` inside the nav context; needs a driver
|
|
329
|
+
* with `resume` (both platform drivers ship it; the mock resolves its result).
|
|
330
|
+
*/
|
|
331
|
+
declare function CheckoutResume(options?: UseCheckoutOptions): ReactNode;
|
|
332
|
+
|
|
333
|
+
export { type CheckoutDriverContext as C, type FailureModalProps as F, type MockDriverOptions as M, type OnFailedMap as O, Upsell as U, type CheckoutDriver as a, Checkout as b, type CheckoutCallbacks as c, type CheckoutHandle as d, type CheckoutProps as e, type CheckoutRequest as f, type CheckoutResult as g, CheckoutResume as h, type CheckoutStatus as i, type FailureRoute as j, type UpsellProps as k, type UseCheckoutOptions as l, createMockDriver as m, driverWithEmail as n, useCheckout as u };
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import { c as CheckoutProvider, b as CheckoutIntent, d as CheckoutSurface, U as UpsellKind, C as CheckoutError, a as CheckoutErrorCategory, f as SurfacePreference } from './capabilities-Dq22RCr_.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* v2 commerce primitives — the two intents from phase-3, made trivial.
|
|
6
|
+
*
|
|
7
|
+
* - `<Checkout offering surface>` — the **purchase** primitive. The `surface`
|
|
8
|
+
* prop picks presentation; some surfaces render **inline** (express wallets,
|
|
9
|
+
* inline card, provider iframe), some **open on click** (sheet/popup/redirect).
|
|
10
|
+
* `<Checkout>` covers both: it renders inline surfaces and renders a trigger
|
|
11
|
+
* button for the rest (`asChild` to use your own button).
|
|
12
|
+
* - `<Upsell offering>` — the **upsell** primitive. Off-session charge to the
|
|
13
|
+
* saved method, no card re-entry, one tap.
|
|
14
|
+
* - `useCheckout()` — the headless hook under both, always available for a
|
|
15
|
+
* fully-custom CTA.
|
|
16
|
+
*
|
|
17
|
+
* The SDK never talks to a provider. It owns the state machine + the analytics
|
|
18
|
+
* (`checkout.start` / `purchase.complete`) + post-charge navigation, and
|
|
19
|
+
* delegates the actual charge/render to an injected {@link CheckoutDriver}
|
|
20
|
+
* (the real one is platform-side; tests use {@link createMockDriver}).
|
|
21
|
+
*
|
|
22
|
+
* Which offering is charged is the author's bound variable, captured at render
|
|
23
|
+
* (inline surfaces) or at `open()` time (trigger surfaces).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
type CheckoutStatus = 'idle' | 'loading' | 'requires_action' | 'success' | 'error';
|
|
27
|
+
interface CheckoutRequest {
|
|
28
|
+
/** The offering SLOT the page named (`<Checkout offering="primary">`) — the display/analytics identity. Kept as the wire field `product` at the provider boundary. */
|
|
29
|
+
product: string;
|
|
30
|
+
/** The catalog charge KEY charged — the CHARGE identity, resolved from `product` by {@link driverWithCatalog}. */
|
|
31
|
+
catalogKey?: string;
|
|
32
|
+
intent: CheckoutIntent;
|
|
33
|
+
surface?: CheckoutSurface;
|
|
34
|
+
/** For `upsell` intent — which kind of off-session charge (default `subscription`). */
|
|
35
|
+
kind?: UpsellKind;
|
|
36
|
+
/**
|
|
37
|
+
* Buyer email at attempt time, read from the funnel's `user.email` namespace value
|
|
38
|
+
* (injected by `FunnelProvider` via {@link driverWithEmail} — no author wiring).
|
|
39
|
+
* Drivers forward it into the wire `CheckoutSessionRequest.email` (Stripe Customer
|
|
40
|
+
* creation / receipts / Paddle prefill).
|
|
41
|
+
*/
|
|
42
|
+
email?: string;
|
|
43
|
+
}
|
|
44
|
+
interface CheckoutResult {
|
|
45
|
+
ok: boolean;
|
|
46
|
+
error?: CheckoutError;
|
|
47
|
+
/** Charged amount in minor units (for the purchase event). */
|
|
48
|
+
amountMinor?: number;
|
|
49
|
+
currency?: string;
|
|
50
|
+
/**
|
|
51
|
+
* The **server-minted** dedup id for the purchase event — the platform creates it
|
|
52
|
+
* when the charge settles and fires CAPI with it; the SDK emits `purchase.complete`
|
|
53
|
+
* with the same id so the browser pixel dedupes against the server. (Server is the
|
|
54
|
+
* source of truth for payment events; the SDK doesn't mint these.)
|
|
55
|
+
*/
|
|
56
|
+
eventId?: string;
|
|
57
|
+
}
|
|
58
|
+
interface CheckoutCallbacks {
|
|
59
|
+
onSuccess: (result: CheckoutResult) => void;
|
|
60
|
+
onError: (error: CheckoutError) => void;
|
|
61
|
+
/**
|
|
62
|
+
* The buyer SUBMITTED an inline surface (Stripe confirm click / wallet
|
|
63
|
+
* authorization; Paddle inline checkout opening). This is when `checkout.start`
|
|
64
|
+
* fires and the status machine enters `loading` for inline attempts — trigger
|
|
65
|
+
* surfaces fire it at click time instead. It also marks fresh user intent, so
|
|
66
|
+
* the once-per-chain recovery guard resets. Optional: drivers that can't
|
|
67
|
+
* observe submit simply never call it, and the SHEET host deliberately doesn't
|
|
68
|
+
* forward it (the sheet's own open already began the attempt — re-firing would
|
|
69
|
+
* double-count starts and un-arm the recovery guard mid-chain).
|
|
70
|
+
*/
|
|
71
|
+
onStart?: () => void;
|
|
72
|
+
/**
|
|
73
|
+
* The inline surface reports a valid payment method was entered (e.g. Stripe's
|
|
74
|
+
* Payment Element became `complete`), before the charge. Drives the
|
|
75
|
+
* `checkout.payment_added` analytics step; optional — drivers that can't observe
|
|
76
|
+
* it (or off-session charges) simply never call it.
|
|
77
|
+
*/
|
|
78
|
+
onPaymentAdded?: () => void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* What a driver factory (`stripeCheckoutDriver` et al. from the
|
|
82
|
+
* `@appfunnel-dev/sdk/driver-*` entries) receives to construct a
|
|
83
|
+
* {@link CheckoutDriver}. Derived by `createFunnelTree` from the SAME
|
|
84
|
+
* renderer-injected mount data the platform tracker uses (tracking config +
|
|
85
|
+
* identity), so checkout wire calls land on the exact campaign/funnel/session
|
|
86
|
+
* the analytics do — there is no second config channel to drift.
|
|
87
|
+
*
|
|
88
|
+
* Serializable on purpose, and factories must be **construction-safe on the
|
|
89
|
+
* server too**: a factory only closes over this ctx (no provider SDK, no
|
|
90
|
+
* `window`) — all provider work belongs in effects/event handlers, so SSR can
|
|
91
|
+
* render the driver's stable inline placeholder and hydration matches.
|
|
92
|
+
*/
|
|
93
|
+
interface CheckoutDriverContext {
|
|
94
|
+
/** Public API origin, e.g. `https://api.appfunnel.net` (no trailing slash). */
|
|
95
|
+
apiBase: string;
|
|
96
|
+
/** Campaign DB id (the checkout route key, same as the ingestion route key). */
|
|
97
|
+
campaignId: string;
|
|
98
|
+
/** WebFunnel DB id (NOT the funnel.ts config id). */
|
|
99
|
+
funnelId: string;
|
|
100
|
+
/** Render-time mode — the server resolves the product binding + store credentials by it. */
|
|
101
|
+
mode: 'test' | 'live';
|
|
102
|
+
/** Funnel session id when one exists (analytics + customer continuity). */
|
|
103
|
+
sessionId?: string | null;
|
|
104
|
+
/** Signed-cookie-verified bare visitor id, when known. */
|
|
105
|
+
visitorId?: string | null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The provider seam. The platform injects the real one (Stripe/Paddle/Whop/…);
|
|
109
|
+
* tests use {@link createMockDriver}.
|
|
110
|
+
*
|
|
111
|
+
* Most providers ship their **own React SDK** (`@stripe/react-stripe-js`,
|
|
112
|
+
* `@paddle/paddle-js`, Primer's React components, …). A driver's `renderInline`
|
|
113
|
+
* returns exactly that: the provider's React element mounted in our chrome (inline,
|
|
114
|
+
* or inside the `<Sheet>`). Those provider SDKs live in the **driver/platform**
|
|
115
|
+
* package, not our core funnel SDK — which is why the driver is injected: the core
|
|
116
|
+
* stays lean and provider-agnostic, and you only pull the SDK for the provider you use.
|
|
117
|
+
*/
|
|
118
|
+
interface CheckoutDriver {
|
|
119
|
+
provider: CheckoutProvider;
|
|
120
|
+
/** Trigger a charge (trigger surfaces + programmatic). Resolves when settled. */
|
|
121
|
+
start(req: CheckoutRequest): Promise<CheckoutResult>;
|
|
122
|
+
/** Render an inline surface (e.g. the provider's React payment element); report via callbacks. */
|
|
123
|
+
renderInline?(req: CheckoutRequest, cb: CheckoutCallbacks): ReactNode;
|
|
124
|
+
/**
|
|
125
|
+
* Verify an EXISTING attempt with the server (`POST /complete`, bounded re-poll on
|
|
126
|
+
* `pending`) — the redirect-return resume leg: hosted-checkout success URLs carry
|
|
127
|
+
* `?af_checkout={correlationId}` back into the funnel, and {@link CheckoutResume}
|
|
128
|
+
* hands the id here so the settlement (server-verified) resolves into the normal
|
|
129
|
+
* success/failure paths. Never charges; only verifies.
|
|
130
|
+
*/
|
|
131
|
+
resume?(correlationId: string): Promise<CheckoutResult>;
|
|
132
|
+
}
|
|
133
|
+
interface MockDriverOptions {
|
|
134
|
+
/** The result a successful charge resolves with. */
|
|
135
|
+
result?: CheckoutResult;
|
|
136
|
+
/** Make **off-session** charges (no surface) fail — to exercise the upsell fallback. */
|
|
137
|
+
failOffSession?: CheckoutError | boolean;
|
|
138
|
+
}
|
|
139
|
+
/** A console/no-op driver — tests, preview, local dev. Succeeds unless configured to fail. */
|
|
140
|
+
declare function createMockDriver(provider?: CheckoutProvider, options?: MockDriverOptions): CheckoutDriver;
|
|
141
|
+
/**
|
|
142
|
+
* Wrap a driver so every attempt carries the buyer email the funnel has collected
|
|
143
|
+
* (`user.email`), read at ATTEMPT time — `start()` for trigger surfaces, the
|
|
144
|
+
* `renderInline` request for mounted surfaces. Applied by `FunnelProvider` around
|
|
145
|
+
* the injected driver, so `useCheckout`, `<Checkout>` inline branches and the
|
|
146
|
+
* checkout sheet all forward it without wiring; drivers pass it into the wire's
|
|
147
|
+
* `CheckoutSessionRequest.email`. An explicit `req.email` always wins.
|
|
148
|
+
*/
|
|
149
|
+
declare function driverWithEmail(driver: CheckoutDriver, getEmail: () => string | undefined): CheckoutDriver;
|
|
150
|
+
/** One declarative recovery route for a failure category. */
|
|
151
|
+
type FailureRoute = {
|
|
152
|
+
go: string;
|
|
153
|
+
} | {
|
|
154
|
+
modal: string | ComponentType<any>;
|
|
155
|
+
} | {
|
|
156
|
+
fallback: CheckoutSurface;
|
|
157
|
+
} | ((error: CheckoutError) => void);
|
|
158
|
+
/**
|
|
159
|
+
* What a `{ modal }` failure route hands the modal — the error AND the actions,
|
|
160
|
+
* because registry modals mount outside the flow's nav context and a recovery
|
|
161
|
+
* modal that can't act is just an error toast. Type your modal with this:
|
|
162
|
+
*
|
|
163
|
+
* ```tsx
|
|
164
|
+
* const Declined = defineModal<FailureModalProps>(({ error, go, retry }) => (
|
|
165
|
+
* <Sheet>
|
|
166
|
+
* <p>{error.category === 'prepaid_card' ? 'Prepaid cards can't start a trial.' : 'Payment didn't go through.'}</p>
|
|
167
|
+
* <button onClick={() => retry('sheet')}>Try another card</button>
|
|
168
|
+
* <button onClick={() => go('downsell')}>See the lite plan</button>
|
|
169
|
+
* </Sheet>
|
|
170
|
+
* ))
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* A modal can also host a whole `<Checkout offering="…" surface="element" />` for
|
|
174
|
+
* an inline downsell — the driver context is app-level, so checkout works inside
|
|
175
|
+
* modals as-is.
|
|
176
|
+
*/
|
|
177
|
+
interface FailureModalProps {
|
|
178
|
+
error: CheckoutError;
|
|
179
|
+
/** The product whose charge failed (absent only for product-less failures). */
|
|
180
|
+
product?: string;
|
|
181
|
+
/** Navigate the flow to a page key (modals can't call useNavigation themselves). */
|
|
182
|
+
go: (pageKey: string) => void;
|
|
183
|
+
/**
|
|
184
|
+
* Re-attempt the SAME product. `retry()` re-runs an upsell off-session; for a
|
|
185
|
+
* purchase (which needs UI to re-collect) it opens the payment sheet.
|
|
186
|
+
* `retry('sheet')` re-collects on the SDK sheet; an inline surface
|
|
187
|
+
* (`element`/`express`/`embedded`) is COERCED to the sheet when the provider
|
|
188
|
+
* supports it — a retry has no page slot to mount into, so the sheet hosts the
|
|
189
|
+
* element. On sheet-less MoR providers (Paddle) inline retries run through the
|
|
190
|
+
* driver, whose provider checkout opens its own overlay. `retry('popup')` /
|
|
191
|
+
* `retry('redirect')` hand off to the provider. A failed retry routes through
|
|
192
|
+
* `onFailed` again (the modal reopens) — the loop is user-driven, never
|
|
193
|
+
* automatic. Hide the modal first (`useModal().hide()`) when retrying into a
|
|
194
|
+
* surface that opens its own overlay.
|
|
195
|
+
*/
|
|
196
|
+
retry: (surface?: CheckoutSurface) => void;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Declarative failure recovery — route each failure **category** to what happens
|
|
200
|
+
* next, instead of hand-wiring `onError`:
|
|
201
|
+
*
|
|
202
|
+
* ```tsx
|
|
203
|
+
* <Checkout offering="monthly" surface="element" onFailed={{
|
|
204
|
+
* prepaid_card: { go: 'real-card' },
|
|
205
|
+
* insufficient_funds: { modal: DownsellModal },
|
|
206
|
+
* default: { modal: TryAgainModal },
|
|
207
|
+
* }} />
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* Resolution: exact category → `default` → nothing (the error state + `checkout.
|
|
211
|
+
* failed` event stand, as today). Two built-in judgments:
|
|
212
|
+
* - `canceled` only routes when mapped EXPLICITLY — a deliberately dismissed
|
|
213
|
+
* sheet/popup is not a failure to recover from, so `default` never catches it;
|
|
214
|
+
* - a `{ fallback }` recovery runs at most once per attempt chain (its own
|
|
215
|
+
* failure routes through the non-fallback routes), so declines can't loop.
|
|
216
|
+
*
|
|
217
|
+
* `checkout.failed` fires with category + declineCode on every failure regardless
|
|
218
|
+
* of routing — recovery pages are measurable, and being ordinary flow pages they
|
|
219
|
+
* are A/B-able with page experiments like anything else.
|
|
220
|
+
*/
|
|
221
|
+
type OnFailedMap = Partial<Record<CheckoutErrorCategory | 'default', FailureRoute>>;
|
|
222
|
+
interface UseCheckoutOptions {
|
|
223
|
+
intent?: CheckoutIntent;
|
|
224
|
+
/** For `upsell` intent — which kind of off-session charge (default `subscription`). */
|
|
225
|
+
kind?: UpsellKind;
|
|
226
|
+
/** Advance to the next page after a successful charge (default: true). */
|
|
227
|
+
advanceOnSuccess?: boolean;
|
|
228
|
+
onSuccess?: (result: CheckoutResult) => void;
|
|
229
|
+
onError?: (error: CheckoutError) => void;
|
|
230
|
+
/** Declarative failure recovery — see {@link OnFailedMap}. Runs AFTER `onError`. */
|
|
231
|
+
onFailed?: OnFailedMap;
|
|
232
|
+
/**
|
|
233
|
+
* Try this surface ON-SESSION once on any first failure, before `onFailed`
|
|
234
|
+
* routing — the generalized `<Upsell fallback>`: the off-session (or first)
|
|
235
|
+
* charge fails, the surface re-collects a card, the retry's failure then goes
|
|
236
|
+
* through `onFailed`. Never re-triggered by its own failure; never triggered
|
|
237
|
+
* by a user-`canceled` checkout.
|
|
238
|
+
*/
|
|
239
|
+
recoverySurface?: CheckoutSurface;
|
|
240
|
+
}
|
|
241
|
+
interface CheckoutHandle {
|
|
242
|
+
/** Start the charge for a product; resolves with the result (no surface = off-session). */
|
|
243
|
+
open: (product: string, surface?: CheckoutSurface) => Promise<CheckoutResult>;
|
|
244
|
+
status: CheckoutStatus;
|
|
245
|
+
/** The last failure, structured so you can route/recover by `category`. */
|
|
246
|
+
error: CheckoutError | null;
|
|
247
|
+
isLoading: boolean;
|
|
248
|
+
reset: () => void;
|
|
249
|
+
/** Build the callbacks an inline surface reports through (used by `<Checkout>`). */
|
|
250
|
+
callbacksFor: (product?: string, surface?: CheckoutSurface) => CheckoutCallbacks & {
|
|
251
|
+
onStart: () => void;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* The headless checkout hook. Owns the state machine + analytics + post-charge
|
|
256
|
+
* navigation; delegates the charge to the injected {@link CheckoutDriver}.
|
|
257
|
+
*/
|
|
258
|
+
declare function useCheckout(opts?: UseCheckoutOptions): CheckoutHandle;
|
|
259
|
+
interface CheckoutProps extends UseCheckoutOptions {
|
|
260
|
+
/** Logical offering slot (the author's bound selection). */
|
|
261
|
+
offering: string;
|
|
262
|
+
/**
|
|
263
|
+
* Presentation preference. A concrete {@link CheckoutSurface} pins it; `'auto'` lets the platform
|
|
264
|
+
* pick the best surface for the store's provider and the buyer's device — wallet-first on mobile
|
|
265
|
+
* (Stripe → ExpressCheckoutElement, Paddle → its Express Checkout variant), inline card/embedded
|
|
266
|
+
* on desktop (see {@link resolveSurface}). NOTE: `surface` has no default (kept required) so pinned
|
|
267
|
+
* funnels don't change behavior this canary; `'auto'` is opt-in. New authoring (editor + AI) should
|
|
268
|
+
* emit `surface="auto"` going forward so wallet-first is a funnel intent, not a provider prop.
|
|
269
|
+
*/
|
|
270
|
+
surface: SurfacePreference;
|
|
271
|
+
/** Render your own trigger element instead of the default button (trigger surfaces). */
|
|
272
|
+
asChild?: boolean;
|
|
273
|
+
/** Trigger label / your trigger element. */
|
|
274
|
+
children?: ReactNode;
|
|
275
|
+
className?: string;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The purchase primitive. Three presentation modes by the RESOLVED `surface`:
|
|
279
|
+
* - **inline** (`express`/`element`/`embedded`) — the driver renders the
|
|
280
|
+
* provider UI in place (keyed by product so a selection change remounts it);
|
|
281
|
+
* - **SDK sheet** (`sheet`) — a trigger opens the Appfunnel-owned {@link Sheet}
|
|
282
|
+
* and the driver renders the provider element inside it;
|
|
283
|
+
* - **provider-hosted** (`popup`/`redirect`) — a trigger hands off to the
|
|
284
|
+
* provider's own overlay/page.
|
|
285
|
+
*
|
|
286
|
+
* The `surface` prop is a {@link SurfacePreference}: a concrete surface (pinned) or `'auto'`, which
|
|
287
|
+
* {@link resolveSurface} maps to the best surface for the driver's provider + this device — ONCE per
|
|
288
|
+
* mount (frozen so the surface can't change mid-attempt and remount the provider element into a new
|
|
289
|
+
* charge). The resolved literal is what flows onto the wire; the server re-validates it.
|
|
290
|
+
*/
|
|
291
|
+
declare function Checkout({ offering, surface: surfacePreference, asChild, children, className, ...options }: CheckoutProps): ReactNode;
|
|
292
|
+
interface UpsellProps extends Omit<UseCheckoutOptions, 'intent'> {
|
|
293
|
+
offering: string;
|
|
294
|
+
/**
|
|
295
|
+
* If the off-session charge fails, present this surface **on-session** to
|
|
296
|
+
* re-collect a card and charge again — e.g. `fallback="sheet"` opens the
|
|
297
|
+
* payment sheet, `fallback="popup"` the provider overlay. Omit to just report
|
|
298
|
+
* the failure (via `onError` / the `checkout.failed` event).
|
|
299
|
+
*/
|
|
300
|
+
fallback?: CheckoutSurface;
|
|
301
|
+
asChild?: boolean;
|
|
302
|
+
children?: ReactNode;
|
|
303
|
+
className?: string;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* The upsell primitive — an off-session, one-tap charge to the saved method (no
|
|
307
|
+
* surface, no card re-entry). On failure, optionally `fallback` to an on-session
|
|
308
|
+
* surface to re-collect and retry — `fallback` IS the hook's `recoverySurface`,
|
|
309
|
+
* so the retry's own failure routes through `onFailed` (e.g. prepaid_card → a
|
|
310
|
+
* recapture page) instead of dead-ending.
|
|
311
|
+
*/
|
|
312
|
+
declare function Upsell({ offering, fallback, asChild, children, className, ...options }: UpsellProps): ReactNode;
|
|
313
|
+
/**
|
|
314
|
+
* The dangling settle leg of the `redirect` surface. The server appends
|
|
315
|
+
* `af_checkout={correlationId}` to hosted-checkout success URLs; when the buyer
|
|
316
|
+
* lands back in the funnel this component detects the param, has the server
|
|
317
|
+
* VERIFY the attempt (`driver.resume` → `POST /complete`, bounded re-poll while
|
|
318
|
+
* `pending`), and resolves it into the normal machinery:
|
|
319
|
+
*
|
|
320
|
+
* - `settled` → `purchase.complete` fires with the settlement's server-minted
|
|
321
|
+
* `eventId` (pixel↔CAPI dedup), the flow advances, and the param is stripped
|
|
322
|
+
* (`history.replaceState`) so a reload can't re-run the leg;
|
|
323
|
+
* - `failed` → the normal failure path (`checkout.failed` + status `error`;
|
|
324
|
+
* any `onFailed` maps passed as props apply — product-less, so only
|
|
325
|
+
* `go`/handler routes can act).
|
|
326
|
+
*
|
|
327
|
+
* SSR-safe (effect-only) and runs once per correlationId per page load. Mounted
|
|
328
|
+
* automatically by `createFunnelTree` inside the nav context; needs a driver
|
|
329
|
+
* with `resume` (both platform drivers ship it; the mock resolves its result).
|
|
330
|
+
*/
|
|
331
|
+
declare function CheckoutResume(options?: UseCheckoutOptions): ReactNode;
|
|
332
|
+
|
|
333
|
+
export { type CheckoutDriverContext as C, type FailureModalProps as F, type MockDriverOptions as M, type OnFailedMap as O, Upsell as U, type CheckoutDriver as a, Checkout as b, type CheckoutCallbacks as c, type CheckoutHandle as d, type CheckoutProps as e, type CheckoutRequest as f, type CheckoutResult as g, CheckoutResume as h, type CheckoutStatus as i, type FailureRoute as j, type UpsellProps as k, type UseCheckoutOptions as l, createMockDriver as m, driverWithEmail as n, useCheckout as u };
|