@appfunnel-dev/sdk 2.0.0-canary.7 → 2.0.0-canary.9

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.
Files changed (47) hide show
  1. package/dist/{capabilities-7_hy5f5G.d.cts → capabilities-Dq22RCr_.d.cts} +67 -1
  2. package/dist/{capabilities-7_hy5f5G.d.ts → capabilities-Dq22RCr_.d.ts} +67 -1
  3. package/dist/{checkout-CMEdxpgo.d.cts → checkout-ClaO5IYV.d.ts} +17 -4
  4. package/dist/{checkout-D1NUPv4p.d.ts → checkout-DBp4bCpC.d.cts} +17 -4
  5. package/dist/{chunk-QMAZGLGV.cjs → chunk-7JLOF6CJ.cjs} +59 -7
  6. package/dist/chunk-7JLOF6CJ.cjs.map +1 -0
  7. package/dist/{chunk-CFFMZYPE.js → chunk-JAO6AA4R.js} +3 -3
  8. package/dist/{chunk-CFFMZYPE.js.map → chunk-JAO6AA4R.js.map} +1 -1
  9. package/dist/{chunk-TNLRQPVY.js → chunk-OXQBEKZ5.js} +55 -8
  10. package/dist/chunk-OXQBEKZ5.js.map +1 -0
  11. package/dist/{chunk-VAOR77NW.cjs → chunk-VV7TFC64.cjs} +7 -7
  12. package/dist/{chunk-VAOR77NW.cjs.map → chunk-VV7TFC64.cjs.map} +1 -1
  13. package/dist/{chunk-7YQLPPPG.js → chunk-VW2HVPR4.js} +7 -2
  14. package/dist/chunk-VW2HVPR4.js.map +1 -0
  15. package/dist/{chunk-BPOAWI4G.cjs → chunk-Y4YNJ2EX.cjs} +7 -2
  16. package/dist/chunk-Y4YNJ2EX.cjs.map +1 -0
  17. package/dist/driver-paddle.cjs +26 -24
  18. package/dist/driver-paddle.cjs.map +1 -1
  19. package/dist/driver-paddle.d.cts +2 -2
  20. package/dist/driver-paddle.d.ts +2 -2
  21. package/dist/driver-paddle.js +6 -4
  22. package/dist/driver-paddle.js.map +1 -1
  23. package/dist/driver-stripe.cjs +32 -26
  24. package/dist/driver-stripe.cjs.map +1 -1
  25. package/dist/driver-stripe.d.cts +2 -2
  26. package/dist/driver-stripe.d.ts +2 -2
  27. package/dist/driver-stripe.js +10 -4
  28. package/dist/driver-stripe.js.map +1 -1
  29. package/dist/index.cjs +88 -61
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +5 -5
  32. package/dist/index.d.ts +5 -5
  33. package/dist/index.js +20 -5
  34. package/dist/index.js.map +1 -1
  35. package/dist/{manifest-CMe8yVkr.d.cts → manifest-B3Tdab0M.d.cts} +64 -1
  36. package/dist/{manifest-CMe8yVkr.d.ts → manifest-B3Tdab0M.d.ts} +64 -1
  37. package/dist/manifest-entry.cjs +112 -31
  38. package/dist/manifest-entry.cjs.map +1 -1
  39. package/dist/manifest-entry.d.cts +9 -26
  40. package/dist/manifest-entry.d.ts +9 -26
  41. package/dist/manifest-entry.js +64 -3
  42. package/dist/manifest-entry.js.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-7YQLPPPG.js.map +0 -1
  45. package/dist/chunk-BPOAWI4G.cjs.map +0 -1
  46. package/dist/chunk-QMAZGLGV.cjs.map +0 -1
  47. package/dist/chunk-TNLRQPVY.js.map +0 -1
@@ -26,7 +26,26 @@ interface SurfaceCapability {
26
26
  wallets: boolean;
27
27
  /** Reliability of an off-session upsell after a purchase on this surface. */
28
28
  offSession: OffSessionReliability;
29
+ /**
30
+ * Per-surface DRIVER readiness. Absent = renderable now (the provider's driver mounts it).
31
+ * `'planned'` = the provider genuinely ships this surface (cite it in the profile) but OUR
32
+ * driver doesn't render it yet — publish validation FAILS CLOSED on it so we never promise a
33
+ * surface the runtime can't produce. Mirrors the provider-level {@link ProviderProfile.status}
34
+ * seam at surface granularity: implement the driver, drop the flag, and it flips on cleanly.
35
+ */
36
+ driver?: 'planned';
29
37
  }
38
+ /**
39
+ * How a provider composes a TRIAL — the scattered provider `if`s made a matrix dimension
40
+ * (§7 item 4). Stripe trials are **composed** (a separate fee/vault step precedes the sub);
41
+ * Paddle trials are **native** (the price itself carries the trial, MoR collects up front):
42
+ * - `paid_composed` — Stripe: trial-fee PaymentIntent → subscription with `trial_period_days`.
43
+ * - `free_vault` — Stripe: SetupIntent vaults the card (no charge) → trial subscription.
44
+ * - `paid_native` — Paddle: the price carries the paid trial; one item, collected up front.
45
+ * - `free_native` — Paddle: native free trial on the price.
46
+ * A provider with an EMPTY `trialShapes` can't run a trial offer (planned providers → fail closed).
47
+ */
48
+ type TrialShape = 'paid_composed' | 'free_vault' | 'paid_native' | 'free_native';
30
49
  interface ProviderProfile {
31
50
  /** Merchant-of-Record (handles tax/compliance) vs. a PSP you settle through. */
32
51
  isMoR: boolean;
@@ -44,6 +63,11 @@ interface ProviderProfile {
44
63
  * can express, so funnels written today stay valid when the driver lands.
45
64
  */
46
65
  status: 'full' | 'planned';
66
+ /**
67
+ * TRIAL shapes this provider can run (§7 item 4). Empty = no trial path (fail closed at
68
+ * publish — a paid/free-trial funnel on this provider can't be produced yet).
69
+ */
70
+ trialShapes: TrialShape[];
47
71
  /** Supported surfaces → per-surface capabilities. **Absent = not supported.** */
48
72
  surfaces: Partial<Record<CheckoutSurface, SurfaceCapability>>;
49
73
  }
@@ -71,6 +95,39 @@ interface ValidationResult {
71
95
  /** Non-fatal caveat (e.g. Paddle upgrade-only). */
72
96
  note?: string;
73
97
  }
98
+ /** Device class the surface resolver picks against — mobile gets the wallet-first surface. */
99
+ type DeviceClass = 'mobile' | 'desktop';
100
+ /**
101
+ * What an author writes on `<Checkout surface>`: either a concrete {@link CheckoutSurface} (pinned,
102
+ * validated as today) or `'auto'` — a declarative "give me the best surface for this store's
103
+ * provider and this device". The platform resolves `'auto'` to a concrete surface via
104
+ * {@link resolveSurface}; `'auto'` never crosses the checkout wire (the client resolves first, the
105
+ * server re-validates the concrete surface through {@link validateCheckout}).
106
+ */
107
+ type SurfacePreference = 'auto' | CheckoutSurface;
108
+ /** A surface the provider offers, can take a purchase on, AND our driver renders now (not `'planned'`). */
109
+ declare function isRenderableSurface(provider: CheckoutProvider, surface: CheckoutSurface): boolean;
110
+ /** The provider's renderable purchase surfaces (drives the `'auto'` fallbacks + the publish gate). */
111
+ declare function renderableSurfaces(provider: CheckoutProvider): CheckoutSurface[];
112
+ /**
113
+ * Resolve a {@link SurfacePreference} to the concrete surface the runtime will actually mount, for a
114
+ * given provider and device class (§7 item 3). PURE — the client resolves once per mount and puts the
115
+ * result on the wire (the wire stays surface-literal; the server re-validates via {@link
116
+ * validateCheckout}), and the publish gate resolves it the same way. Deterministic:
117
+ *
118
+ * - an EXPLICIT surface resolves to ITSELF — the author's pin is never silently rewritten (an
119
+ * unsupported pin, e.g. `express` on a provider that can't render it, is caught by
120
+ * `validateCheckout` at publish, exactly as today);
121
+ * - `'auto'` → mobile picks the provider's best wallet-first RENDERABLE surface (express where the
122
+ * driver ships it, else the best inline/embedded frame); desktop picks the inline card
123
+ * field/embedded frame. Only driver-ready surfaces are considered, so the result always renders.
124
+ *
125
+ * Totality guard: a provider with NO driver-ready surface (every surface `'planned'`, or unknown
126
+ * provider) falls back to its first declared surface (unknown → a nominal default) so the function is
127
+ * total; downstream `validateCheckout`/flow-select then fail that closed. In practice `'auto'` is only
128
+ * authored on a funnel's ONE resolved (full-status) provider, which always has a renderable surface.
129
+ */
130
+ declare function resolveSurface(provider: CheckoutProvider, preference: SurfacePreference, deviceClass: DeviceClass): CheckoutSurface;
74
131
  /** Surfaces a provider supports, for the editor/AI to steer authors. */
75
132
  declare function surfacesFor(provider: CheckoutProvider): CheckoutSurface[];
76
133
  /** Whether a provider is a Merchant of Record (it owns tax/compliance). */
@@ -83,6 +140,15 @@ declare function isOrchestrator(provider: CheckoutProvider): boolean;
83
140
  * (e.g. Paddle has no `element`).
84
141
  */
85
142
  declare function validateCheckout(provider: CheckoutProvider, surface: CheckoutSurface): ValidationResult;
143
+ /** Trial shapes a provider can compose (empty = it can't run a trial offer yet). */
144
+ declare function trialShapesFor(provider: CheckoutProvider): TrialShape[];
145
+ /**
146
+ * Validate that a provider can run a TRIAL (§7 item 4). Pass a specific {@link TrialShape} to
147
+ * check that exact shape; omit it to assert the provider supports *any* trial (the cheap gate
148
+ * check when only the offering's TRIAL-role binding existence is known). A provider with no
149
+ * trial shapes (every planned provider) fails closed.
150
+ */
151
+ declare function validateTrial(provider: CheckoutProvider, shape?: TrialShape): ValidationResult;
86
152
  /**
87
153
  * Validate an **off-session upsell** of `kind` after a purchase made on
88
154
  * `paywallSurface`. Combines provider support for the kind (Paddle can't stack a
@@ -111,4 +177,4 @@ declare function checkoutError(category: CheckoutErrorCategory, message: string,
111
177
  retryable?: boolean;
112
178
  }): CheckoutError;
113
179
 
114
- export { type CheckoutError as C, INLINE_SURFACES as I, type OffSessionReliability as O, PROVIDER_PROFILES as P, type SurfaceCapability as S, type UpsellKind as U, type ValidationResult as V, type CheckoutErrorCategory as a, type CheckoutIntent as b, type CheckoutProvider as c, type CheckoutSurface as d, type ProviderProfile as e, checkoutError as f, isMerchantOfRecord as g, isOrchestrator as h, isInlineSurface as i, validateUpsell as j, surfacesFor as s, validateCheckout as v };
180
+ export { type CheckoutError as C, type DeviceClass as D, INLINE_SURFACES as I, type OffSessionReliability as O, PROVIDER_PROFILES as P, type SurfaceCapability as S, type TrialShape as T, type UpsellKind as U, type ValidationResult as V, type CheckoutErrorCategory as a, type CheckoutIntent as b, type CheckoutProvider as c, type CheckoutSurface as d, type ProviderProfile as e, type SurfacePreference as f, checkoutError as g, isMerchantOfRecord as h, isInlineSurface as i, isOrchestrator as j, isRenderableSurface as k, resolveSurface as l, validateUpsell as m, validateTrial as n, renderableSurfaces as r, surfacesFor as s, trialShapesFor as t, validateCheckout as v };
@@ -26,7 +26,26 @@ interface SurfaceCapability {
26
26
  wallets: boolean;
27
27
  /** Reliability of an off-session upsell after a purchase on this surface. */
28
28
  offSession: OffSessionReliability;
29
+ /**
30
+ * Per-surface DRIVER readiness. Absent = renderable now (the provider's driver mounts it).
31
+ * `'planned'` = the provider genuinely ships this surface (cite it in the profile) but OUR
32
+ * driver doesn't render it yet — publish validation FAILS CLOSED on it so we never promise a
33
+ * surface the runtime can't produce. Mirrors the provider-level {@link ProviderProfile.status}
34
+ * seam at surface granularity: implement the driver, drop the flag, and it flips on cleanly.
35
+ */
36
+ driver?: 'planned';
29
37
  }
38
+ /**
39
+ * How a provider composes a TRIAL — the scattered provider `if`s made a matrix dimension
40
+ * (§7 item 4). Stripe trials are **composed** (a separate fee/vault step precedes the sub);
41
+ * Paddle trials are **native** (the price itself carries the trial, MoR collects up front):
42
+ * - `paid_composed` — Stripe: trial-fee PaymentIntent → subscription with `trial_period_days`.
43
+ * - `free_vault` — Stripe: SetupIntent vaults the card (no charge) → trial subscription.
44
+ * - `paid_native` — Paddle: the price carries the paid trial; one item, collected up front.
45
+ * - `free_native` — Paddle: native free trial on the price.
46
+ * A provider with an EMPTY `trialShapes` can't run a trial offer (planned providers → fail closed).
47
+ */
48
+ type TrialShape = 'paid_composed' | 'free_vault' | 'paid_native' | 'free_native';
30
49
  interface ProviderProfile {
31
50
  /** Merchant-of-Record (handles tax/compliance) vs. a PSP you settle through. */
32
51
  isMoR: boolean;
@@ -44,6 +63,11 @@ interface ProviderProfile {
44
63
  * can express, so funnels written today stay valid when the driver lands.
45
64
  */
46
65
  status: 'full' | 'planned';
66
+ /**
67
+ * TRIAL shapes this provider can run (§7 item 4). Empty = no trial path (fail closed at
68
+ * publish — a paid/free-trial funnel on this provider can't be produced yet).
69
+ */
70
+ trialShapes: TrialShape[];
47
71
  /** Supported surfaces → per-surface capabilities. **Absent = not supported.** */
48
72
  surfaces: Partial<Record<CheckoutSurface, SurfaceCapability>>;
49
73
  }
@@ -71,6 +95,39 @@ interface ValidationResult {
71
95
  /** Non-fatal caveat (e.g. Paddle upgrade-only). */
72
96
  note?: string;
73
97
  }
98
+ /** Device class the surface resolver picks against — mobile gets the wallet-first surface. */
99
+ type DeviceClass = 'mobile' | 'desktop';
100
+ /**
101
+ * What an author writes on `<Checkout surface>`: either a concrete {@link CheckoutSurface} (pinned,
102
+ * validated as today) or `'auto'` — a declarative "give me the best surface for this store's
103
+ * provider and this device". The platform resolves `'auto'` to a concrete surface via
104
+ * {@link resolveSurface}; `'auto'` never crosses the checkout wire (the client resolves first, the
105
+ * server re-validates the concrete surface through {@link validateCheckout}).
106
+ */
107
+ type SurfacePreference = 'auto' | CheckoutSurface;
108
+ /** A surface the provider offers, can take a purchase on, AND our driver renders now (not `'planned'`). */
109
+ declare function isRenderableSurface(provider: CheckoutProvider, surface: CheckoutSurface): boolean;
110
+ /** The provider's renderable purchase surfaces (drives the `'auto'` fallbacks + the publish gate). */
111
+ declare function renderableSurfaces(provider: CheckoutProvider): CheckoutSurface[];
112
+ /**
113
+ * Resolve a {@link SurfacePreference} to the concrete surface the runtime will actually mount, for a
114
+ * given provider and device class (§7 item 3). PURE — the client resolves once per mount and puts the
115
+ * result on the wire (the wire stays surface-literal; the server re-validates via {@link
116
+ * validateCheckout}), and the publish gate resolves it the same way. Deterministic:
117
+ *
118
+ * - an EXPLICIT surface resolves to ITSELF — the author's pin is never silently rewritten (an
119
+ * unsupported pin, e.g. `express` on a provider that can't render it, is caught by
120
+ * `validateCheckout` at publish, exactly as today);
121
+ * - `'auto'` → mobile picks the provider's best wallet-first RENDERABLE surface (express where the
122
+ * driver ships it, else the best inline/embedded frame); desktop picks the inline card
123
+ * field/embedded frame. Only driver-ready surfaces are considered, so the result always renders.
124
+ *
125
+ * Totality guard: a provider with NO driver-ready surface (every surface `'planned'`, or unknown
126
+ * provider) falls back to its first declared surface (unknown → a nominal default) so the function is
127
+ * total; downstream `validateCheckout`/flow-select then fail that closed. In practice `'auto'` is only
128
+ * authored on a funnel's ONE resolved (full-status) provider, which always has a renderable surface.
129
+ */
130
+ declare function resolveSurface(provider: CheckoutProvider, preference: SurfacePreference, deviceClass: DeviceClass): CheckoutSurface;
74
131
  /** Surfaces a provider supports, for the editor/AI to steer authors. */
75
132
  declare function surfacesFor(provider: CheckoutProvider): CheckoutSurface[];
76
133
  /** Whether a provider is a Merchant of Record (it owns tax/compliance). */
@@ -83,6 +140,15 @@ declare function isOrchestrator(provider: CheckoutProvider): boolean;
83
140
  * (e.g. Paddle has no `element`).
84
141
  */
85
142
  declare function validateCheckout(provider: CheckoutProvider, surface: CheckoutSurface): ValidationResult;
143
+ /** Trial shapes a provider can compose (empty = it can't run a trial offer yet). */
144
+ declare function trialShapesFor(provider: CheckoutProvider): TrialShape[];
145
+ /**
146
+ * Validate that a provider can run a TRIAL (§7 item 4). Pass a specific {@link TrialShape} to
147
+ * check that exact shape; omit it to assert the provider supports *any* trial (the cheap gate
148
+ * check when only the offering's TRIAL-role binding existence is known). A provider with no
149
+ * trial shapes (every planned provider) fails closed.
150
+ */
151
+ declare function validateTrial(provider: CheckoutProvider, shape?: TrialShape): ValidationResult;
86
152
  /**
87
153
  * Validate an **off-session upsell** of `kind` after a purchase made on
88
154
  * `paywallSurface`. Combines provider support for the kind (Paddle can't stack a
@@ -111,4 +177,4 @@ declare function checkoutError(category: CheckoutErrorCategory, message: string,
111
177
  retryable?: boolean;
112
178
  }): CheckoutError;
113
179
 
114
- export { type CheckoutError as C, INLINE_SURFACES as I, type OffSessionReliability as O, PROVIDER_PROFILES as P, type SurfaceCapability as S, type UpsellKind as U, type ValidationResult as V, type CheckoutErrorCategory as a, type CheckoutIntent as b, type CheckoutProvider as c, type CheckoutSurface as d, type ProviderProfile as e, checkoutError as f, isMerchantOfRecord as g, isOrchestrator as h, isInlineSurface as i, validateUpsell as j, surfacesFor as s, validateCheckout as v };
180
+ export { type CheckoutError as C, type DeviceClass as D, INLINE_SURFACES as I, type OffSessionReliability as O, PROVIDER_PROFILES as P, type SurfaceCapability as S, type TrialShape as T, type UpsellKind as U, type ValidationResult as V, type CheckoutErrorCategory as a, type CheckoutIntent as b, type CheckoutProvider as c, type CheckoutSurface as d, type ProviderProfile as e, type SurfacePreference as f, checkoutError as g, isMerchantOfRecord as h, isInlineSurface as i, isOrchestrator as j, isRenderableSurface as k, resolveSurface as l, validateUpsell as m, validateTrial as n, renderableSurfaces as r, surfacesFor as s, trialShapesFor as t, validateCheckout as v };
@@ -1,5 +1,5 @@
1
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 } from './capabilities-7_hy5f5G.cjs';
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
3
 
4
4
  /**
5
5
  * v2 commerce primitives — the two intents from phase-3, made trivial.
@@ -259,7 +259,15 @@ declare function useCheckout(opts?: UseCheckoutOptions): CheckoutHandle;
259
259
  interface CheckoutProps extends UseCheckoutOptions {
260
260
  /** Logical offering slot (the author's bound selection). */
261
261
  offering: string;
262
- surface: CheckoutSurface;
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;
263
271
  /** Render your own trigger element instead of the default button (trigger surfaces). */
264
272
  asChild?: boolean;
265
273
  /** Trigger label / your trigger element. */
@@ -267,15 +275,20 @@ interface CheckoutProps extends UseCheckoutOptions {
267
275
  className?: string;
268
276
  }
269
277
  /**
270
- * The purchase primitive. Three presentation modes by `surface`:
278
+ * The purchase primitive. Three presentation modes by the RESOLVED `surface`:
271
279
  * - **inline** (`express`/`element`/`embedded`) — the driver renders the
272
280
  * provider UI in place (keyed by product so a selection change remounts it);
273
281
  * - **SDK sheet** (`sheet`) — a trigger opens the Appfunnel-owned {@link Sheet}
274
282
  * and the driver renders the provider element inside it;
275
283
  * - **provider-hosted** (`popup`/`redirect`) — a trigger hands off to the
276
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.
277
290
  */
278
- declare function Checkout({ offering, surface, asChild, children, className, ...options }: CheckoutProps): ReactNode;
291
+ declare function Checkout({ offering, surface: surfacePreference, asChild, children, className, ...options }: CheckoutProps): ReactNode;
279
292
  interface UpsellProps extends Omit<UseCheckoutOptions, 'intent'> {
280
293
  offering: string;
281
294
  /**
@@ -1,5 +1,5 @@
1
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 } from './capabilities-7_hy5f5G.js';
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
3
 
4
4
  /**
5
5
  * v2 commerce primitives — the two intents from phase-3, made trivial.
@@ -259,7 +259,15 @@ declare function useCheckout(opts?: UseCheckoutOptions): CheckoutHandle;
259
259
  interface CheckoutProps extends UseCheckoutOptions {
260
260
  /** Logical offering slot (the author's bound selection). */
261
261
  offering: string;
262
- surface: CheckoutSurface;
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;
263
271
  /** Render your own trigger element instead of the default button (trigger surfaces). */
264
272
  asChild?: boolean;
265
273
  /** Trigger label / your trigger element. */
@@ -267,15 +275,20 @@ interface CheckoutProps extends UseCheckoutOptions {
267
275
  className?: string;
268
276
  }
269
277
  /**
270
- * The purchase primitive. Three presentation modes by `surface`:
278
+ * The purchase primitive. Three presentation modes by the RESOLVED `surface`:
271
279
  * - **inline** (`express`/`element`/`embedded`) — the driver renders the
272
280
  * provider UI in place (keyed by product so a selection change remounts it);
273
281
  * - **SDK sheet** (`sheet`) — a trigger opens the Appfunnel-owned {@link Sheet}
274
282
  * and the driver renders the provider element inside it;
275
283
  * - **provider-hosted** (`popup`/`redirect`) — a trigger hands off to the
276
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.
277
290
  */
278
- declare function Checkout({ offering, surface, asChild, children, className, ...options }: CheckoutProps): ReactNode;
291
+ declare function Checkout({ offering, surface: surfacePreference, asChild, children, className, ...options }: CheckoutProps): ReactNode;
279
292
  interface UpsellProps extends Omit<UseCheckoutOptions, 'intent'> {
280
293
  offering: string;
281
294
  /**
@@ -12,6 +12,7 @@ function isInlineSurface(surface) {
12
12
  var ON = { purchase: true, wallets: true, offSession: "reliable" };
13
13
  var MANAGED = { purchase: true, wallets: true, offSession: "conditional" };
14
14
  var ROUTED = { purchase: true, wallets: true, offSession: "conditional" };
15
+ var PADDLE_EXPRESS = { purchase: true, wallets: true, offSession: "reliable" };
15
16
  var ALL_UPSELLS = ["subscription", "one-time", "upgrade"];
16
17
  var PROVIDER_PROFILES = {
17
18
  // PSP; you hold the PaymentMethod and fire your own off-session PaymentIntent on
@@ -24,27 +25,37 @@ var PROVIDER_PROFILES = {
24
25
  tokenModel: "merchant",
25
26
  orchestrator: false,
26
27
  status: "full",
28
+ // Composed trials: paid = fee PaymentIntent + sub (trial_period_days); free = SetupIntent vault
29
+ // + trial sub (services/checkout/stripe-trial.ts).
30
+ trialShapes: ["paid_composed", "free_vault"],
27
31
  surfaces: { express: ON, element: ON, sheet: ON, embedded: MANAGED, redirect: MANAGED }
28
32
  },
29
- // Merchant-of-Record; card entry is always Paddle's frame → no own element/sheet/
30
- // express. Off-session via subscription one-time charge / collection_mode, but
31
- // **no 2nd concurrent subscription** (upgrade + one-time only), provider-initiated.
33
+ // Merchant-of-Record; raw card entry is always Paddle's frame → no own element/sheet.
34
+ // `express` is present AND renderable (Apple-Pay-first Express Checkout, shipped 2026-04-27 —
35
+ // PADDLE_EXPRESS; the driver opens the inline frame with `variant: 'express'`).
36
+ // Off-session via subscription one-time charge / collection_mode, but **no 2nd concurrent
37
+ // subscription** (upgrade + one-time only), provider-initiated.
32
38
  paddle: {
33
39
  isMoR: true,
34
40
  offSessionUpsells: ["one-time", "upgrade"],
35
41
  tokenModel: "provider",
36
42
  orchestrator: false,
37
43
  status: "full",
38
- surfaces: { embedded: ON, popup: ON, redirect: ON }
44
+ // Native trials: the price itself carries the trial, Paddle collects up front / vaults
45
+ // (services/checkout/paddle-checkout.ts — "Paddle paid trials are NATIVE").
46
+ trialShapes: ["paid_native", "free_native"],
47
+ surfaces: { embedded: ON, popup: ON, redirect: ON, express: PADDLE_EXPRESS }
39
48
  },
40
49
  // Merchant-of-Record; iframe-only (no raw card field). Off-session works
41
50
  // (provider-initiated, async via webhooks). `sheet` = our chrome hosting their embed.
51
+ // Trial shape not yet researched → empty (fail closed until the driver + profile land).
42
52
  whop: {
43
53
  isMoR: true,
44
54
  offSessionUpsells: ALL_UPSELLS,
45
55
  tokenModel: "provider",
46
56
  orchestrator: false,
47
57
  status: "planned",
58
+ trialShapes: [],
48
59
  surfaces: { embedded: ON, redirect: ON, sheet: ON }
49
60
  },
50
61
  // Pure orchestrator (PSP-side); Headless renders express/element/sheet/embedded.
@@ -57,6 +68,7 @@ var PROVIDER_PROFILES = {
57
68
  tokenModel: "merchant",
58
69
  orchestrator: true,
59
70
  status: "planned",
71
+ trialShapes: [],
60
72
  surfaces: { express: ROUTED, element: ROUTED, sheet: ROUTED, embedded: ROUTED }
61
73
  },
62
74
  // PSP **and** orchestrator (own acquiring + connectors routing across Stripe/Adyen/
@@ -68,9 +80,29 @@ var PROVIDER_PROFILES = {
68
80
  tokenModel: "merchant",
69
81
  orchestrator: true,
70
82
  status: "planned",
83
+ trialShapes: [],
71
84
  surfaces: { express: ON, element: ON, sheet: ON, embedded: ON, redirect: ON }
72
85
  }
73
86
  };
87
+ var AUTO_SURFACE_ORDER = {
88
+ mobile: ["express", "embedded", "element", "sheet", "popup", "redirect"],
89
+ desktop: ["element", "embedded", "express", "sheet", "redirect", "popup"]
90
+ };
91
+ function isRenderableSurface(provider, surface) {
92
+ const cap = PROVIDER_PROFILES[provider]?.surfaces[surface];
93
+ return !!cap && cap.purchase && cap.driver !== "planned";
94
+ }
95
+ function renderableSurfaces(provider) {
96
+ return surfacesFor(provider).filter((s) => isRenderableSurface(provider, s));
97
+ }
98
+ function resolveSurface(provider, preference, deviceClass) {
99
+ if (preference !== "auto") return preference;
100
+ for (const surface of AUTO_SURFACE_ORDER[deviceClass]) {
101
+ if (isRenderableSurface(provider, surface)) return surface;
102
+ }
103
+ const declared = surfacesFor(provider);
104
+ return declared[0] ?? (deviceClass === "mobile" ? "express" : "element");
105
+ }
74
106
  function surfacesFor(provider) {
75
107
  return Object.keys(PROVIDER_PROFILES[provider]?.surfaces ?? {});
76
108
  }
@@ -83,7 +115,22 @@ function isOrchestrator(provider) {
83
115
  function validateCheckout(provider, surface) {
84
116
  const cap = PROVIDER_PROFILES[provider]?.surfaces[surface];
85
117
  if (!cap) return { ok: false, reason: `${provider} has no '${surface}' surface` };
86
- return cap.purchase ? { ok: true } : { ok: false, reason: `'${surface}' can't take a purchase on ${provider}` };
118
+ if (!cap.purchase) return { ok: false, reason: `'${surface}' can't take a purchase on ${provider}` };
119
+ if (cap.driver === "planned") return { ok: false, reason: `${provider} '${surface}' checkout isn't renderable yet (the provider ships it, but our driver doesn't render it)` };
120
+ return { ok: true };
121
+ }
122
+ function trialShapesFor(provider) {
123
+ return PROVIDER_PROFILES[provider]?.trialShapes ?? [];
124
+ }
125
+ function validateTrial(provider, shape) {
126
+ const profile = PROVIDER_PROFILES[provider];
127
+ if (!profile) return { ok: false, reason: `unknown provider '${provider}'` };
128
+ const shapes = profile.trialShapes;
129
+ if (shapes.length === 0) return { ok: false, reason: `${provider} can't run a trial offer yet` };
130
+ if (shape && !shapes.includes(shape)) {
131
+ return { ok: false, reason: `${provider} doesn't support a '${shape}' trial (it supports: ${shapes.join(", ")})` };
132
+ }
133
+ return { ok: true };
87
134
  }
88
135
  function validateUpsell(provider, paywallSurface, kind = "subscription") {
89
136
  const profile = PROVIDER_PROFILES[provider];
@@ -113,8 +160,13 @@ exports.checkoutError = checkoutError;
113
160
  exports.isInlineSurface = isInlineSurface;
114
161
  exports.isMerchantOfRecord = isMerchantOfRecord;
115
162
  exports.isOrchestrator = isOrchestrator;
163
+ exports.isRenderableSurface = isRenderableSurface;
164
+ exports.renderableSurfaces = renderableSurfaces;
165
+ exports.resolveSurface = resolveSurface;
116
166
  exports.surfacesFor = surfacesFor;
167
+ exports.trialShapesFor = trialShapesFor;
117
168
  exports.validateCheckout = validateCheckout;
169
+ exports.validateTrial = validateTrial;
118
170
  exports.validateUpsell = validateUpsell;
119
- //# sourceMappingURL=chunk-QMAZGLGV.cjs.map
120
- //# sourceMappingURL=chunk-QMAZGLGV.cjs.map
171
+ //# sourceMappingURL=chunk-7JLOF6CJ.cjs.map
172
+ //# sourceMappingURL=chunk-7JLOF6CJ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commerce/capabilities.ts"],"names":[],"mappings":"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","file":"chunk-7JLOF6CJ.cjs","sourcesContent":["/**\n * The provider capability matrix + checkout error taxonomy — the PURE half of the\n * checkout seam, extracted from checkout.tsx so it ships through the **manifest\n * entry** (no React): the API's publish validation, the editor, AI authoring, and\n * local dev all run the SAME checks the runtime primitives use (phase-3 §3.1b —\n * \"the SDK ships this as data\"). The React half (driver seam, `useCheckout`,\n * `<Checkout>`/`<Upsell>`) stays in ./checkout, which re-exports everything here.\n */\n\nexport type CheckoutSurface =\n | 'express' // wallet quick-buttons (Apple/Google Pay) — inline\n | 'element' // inline card form — inline\n | 'embedded' // provider-managed inline checkout — inline\n | 'sheet' // bottom-sheet popup — opens on click\n | 'popup' // provider-hosted overlay — opens on click\n | 'redirect' // provider-hosted page — opens on click\n\nexport type CheckoutProvider = 'stripe' | 'paddle' | 'whop' | 'primer' | 'solidgate'\nexport type CheckoutIntent = 'purchase' | 'upsell'\n\n/** Surfaces that render inline (vs. those that open on click). */\nexport const INLINE_SURFACES: ReadonlySet<CheckoutSurface> = new Set<CheckoutSurface>([\n 'express',\n 'element',\n 'embedded',\n])\n\nexport function isInlineSurface(surface: CheckoutSurface): boolean {\n return INLINE_SURFACES.has(surface)\n}\n\n/**\n * The kind of off-session upsell (researched against Funnelfox's changelog, which\n * ships these as distinct features). The discriminating real-world constraint is\n * **stacking a 2nd concurrent subscription** — Paddle can't, SolidGate/Stripe can.\n */\nexport type UpsellKind =\n | 'subscription' // charge a NEW, 2nd concurrent subscription off-session (stacking)\n | 'one-time' // charge a one-time add-on off-session\n | 'upgrade' // change the existing subscription's plan\n\n/** Reliability of an off-session upsell charge AFTER a purchase on a surface. */\nexport type OffSessionReliability =\n | 'reliable' // we hold the saved method (Stripe Elements / SolidGate) or the provider charges it (Paddle / Whop API)\n | 'conditional' // possible but may decline — Stripe **managed** checkout (vault charge, SCA not guaranteed) or an orchestrator's PSP-dependent routing (Primer)\n | 'none'\n\nexport interface SurfaceCapability {\n purchase: boolean\n /** Apple/Google Pay available on this surface. */\n wallets: boolean\n /** Reliability of an off-session upsell after a purchase on this surface. */\n offSession: OffSessionReliability\n /**\n * Per-surface DRIVER readiness. Absent = renderable now (the provider's driver mounts it).\n * `'planned'` = the provider genuinely ships this surface (cite it in the profile) but OUR\n * driver doesn't render it yet — publish validation FAILS CLOSED on it so we never promise a\n * surface the runtime can't produce. Mirrors the provider-level {@link ProviderProfile.status}\n * seam at surface granularity: implement the driver, drop the flag, and it flips on cleanly.\n */\n driver?: 'planned'\n}\n\n/**\n * How a provider composes a TRIAL — the scattered provider `if`s made a matrix dimension\n * (§7 item 4). Stripe trials are **composed** (a separate fee/vault step precedes the sub);\n * Paddle trials are **native** (the price itself carries the trial, MoR collects up front):\n * - `paid_composed` — Stripe: trial-fee PaymentIntent → subscription with `trial_period_days`.\n * - `free_vault` — Stripe: SetupIntent vaults the card (no charge) → trial subscription.\n * - `paid_native` — Paddle: the price carries the paid trial; one item, collected up front.\n * - `free_native` — Paddle: native free trial on the price.\n * A provider with an EMPTY `trialShapes` can't run a trial offer (planned providers → fail closed).\n */\nexport type TrialShape = 'paid_composed' | 'free_vault' | 'paid_native' | 'free_native'\n\nexport interface ProviderProfile {\n /** Merchant-of-Record (handles tax/compliance) vs. a PSP you settle through. */\n isMoR: boolean\n /** Upsell kinds this provider can charge off-session (one-click, no re-entry). */\n offSessionUpsells: UpsellKind[]\n /** Who charges the saved method off-session: you (PSP) or the provider (MoR/managed). */\n tokenModel: 'merchant' | 'provider'\n /** Routes across multiple processors (Primer; SolidGate also offers this) vs. a single acquirer. */\n orchestrator: boolean\n /**\n * Driver tier (Wave-4 decision, Simon 2026-07-02): `full` = a real driver ships\n * (Stripe, Paddle); `planned` = profile only — authoring/publish validate against\n * it, but no charge path exists yet (Whop, Primer, SolidGate). A new provider\n * becomes buildable by writing a driver; its profile already gates what authors\n * can express, so funnels written today stay valid when the driver lands.\n */\n status: 'full' | 'planned'\n /**\n * TRIAL shapes this provider can run (§7 item 4). Empty = no trial path (fail closed at\n * publish — a paid/free-trial funnel on this provider can't be produced yet).\n */\n trialShapes: TrialShape[]\n /** Supported surfaces → per-surface capabilities. **Absent = not supported.** */\n surfaces: Partial<Record<CheckoutSurface, SurfaceCapability>>\n}\n\n/** Reliable off-session after this surface (we/the provider hold a chargeable method). */\nconst ON: SurfaceCapability = { purchase: true, wallets: true, offSession: 'reliable' }\n/** Stripe-managed checkout: a purchase works, but the off-session upsell may decline. */\nconst MANAGED: SurfaceCapability = { purchase: true, wallets: true, offSession: 'conditional' }\n/** Orchestrator-routed (Primer): off-session depends on which PSP vaulted the method. */\nconst ROUTED: SurfaceCapability = { purchase: true, wallets: true, offSession: 'conditional' }\n/**\n * Paddle **Express Checkout** (Apple-Pay-first mobile variant of Paddle.js checkout, shipped\n * 2026-04-27 — https://developer.paddle.com/changelog/2026/apple-pay-express-checkout/). Paddle\n * genuinely offers it (MoR, so it's their hosted/inline frame surfaced wallet-first, NOT a raw\n * wallet element in our UI), which is why it's a distinct `express` surface here rather than only\n * the `wallets` flag already true on Paddle's embedded/popup. RENDERABLE as of 2026-07-19:\n * drivers/paddle.tsx opens the inline frame with `settings.variant: 'express'` (verified against\n * `@paddle/paddle-js@1.6.4` — `CheckoutSettings.variant: 'multi-page' | 'one-page' | 'express'` +\n * the changelog's `displayMode: 'inline'` requirement), so the `driver: 'planned'` flag is dropped\n * and publish no longer fails it closed. Off-session stays reliable (provider-token, MoR).\n */\nconst PADDLE_EXPRESS: SurfaceCapability = { purchase: true, wallets: true, offSession: 'reliable' }\nconst ALL_UPSELLS: UpsellKind[] = ['subscription', 'one-time', 'upgrade']\n\n/**\n * Provider capabilities (researched 2026-06-23 against provider docs + Funnelfox;\n * trial mechanics re-verified 2026-07-02, doc 09 §4.4).\n *\n * Three real-world corrections to the naive model:\n * 1. **Off-session upsell support is per-provider by *kind*** (`offSessionUpsells`):\n * Paddle can't stack a 2nd concurrent subscription (upgrade + one-time only);\n * Stripe/SolidGate/Whop/Primer can do all kinds.\n * 2. **Off-session *reliability* varies by the paywall surface** (`SurfaceCapability.\n * offSession`): Stripe **managed** checkout (`embedded`/`redirect`) upsells as a\n * direct charge against Stripe's vault → `conditional` (may decline); Elements\n * surfaces are `reliable`. Primer is `conditional` everywhere (PSP-dependent).\n * 3. **Surface availability is a hard constraint.** MoR/managed providers can't\n * expose a raw card field to your own UI: Paddle has no `express`/`element`/your\n * `sheet`; Whop has no raw `element`/`express`; Primer has no hosted `redirect`;\n * Stripe/SolidGate have no `popup`. A missing entry means \"not possible.\"\n */\nexport const PROVIDER_PROFILES: Record<CheckoutProvider, ProviderProfile> = {\n // PSP; you hold the PaymentMethod and fire your own off-session PaymentIntent on\n // the Elements surfaces (reliable). **Managed** Checkout (`embedded`/`redirect`)\n // upsells too, but as a direct charge against Stripe's vaulted card → conditional.\n // No provider \"popup\" surface.\n stripe: {\n isMoR: false, offSessionUpsells: ALL_UPSELLS, tokenModel: 'merchant', orchestrator: false, status: 'full',\n // Composed trials: paid = fee PaymentIntent + sub (trial_period_days); free = SetupIntent vault\n // + trial sub (services/checkout/stripe-trial.ts).\n trialShapes: ['paid_composed', 'free_vault'],\n surfaces: { express: ON, element: ON, sheet: ON, embedded: MANAGED, redirect: MANAGED },\n },\n // Merchant-of-Record; raw card entry is always Paddle's frame → no own element/sheet.\n // `express` is present AND renderable (Apple-Pay-first Express Checkout, shipped 2026-04-27 —\n // PADDLE_EXPRESS; the driver opens the inline frame with `variant: 'express'`).\n // Off-session via subscription one-time charge / collection_mode, but **no 2nd concurrent\n // subscription** (upgrade + one-time only), provider-initiated.\n paddle: {\n isMoR: true, offSessionUpsells: ['one-time', 'upgrade'], tokenModel: 'provider', orchestrator: false, status: 'full',\n // Native trials: the price itself carries the trial, Paddle collects up front / vaults\n // (services/checkout/paddle-checkout.ts — \"Paddle paid trials are NATIVE\").\n trialShapes: ['paid_native', 'free_native'],\n surfaces: { embedded: ON, popup: ON, redirect: ON, express: PADDLE_EXPRESS },\n },\n // Merchant-of-Record; iframe-only (no raw card field). Off-session works\n // (provider-initiated, async via webhooks). `sheet` = our chrome hosting their embed.\n // Trial shape not yet researched → empty (fail closed until the driver + profile land).\n whop: {\n isMoR: true, offSessionUpsells: ALL_UPSELLS, tokenModel: 'provider', orchestrator: false, status: 'planned',\n trialShapes: [],\n surfaces: { embedded: ON, redirect: ON, sheet: ON },\n },\n // Pure orchestrator (PSP-side); Headless renders express/element/sheet/embedded.\n // No hosted redirect page, no popup checkout. Off-session is PSP-conditional —\n // the vault lives with whichever processor Primer routed to (data matches this\n // comment as of 2026-07-02; it previously said conditional but coded reliable).\n primer: {\n isMoR: false, offSessionUpsells: ALL_UPSELLS, tokenModel: 'merchant', orchestrator: true, status: 'planned',\n trialShapes: [],\n surfaces: { express: ROUTED, element: ROUTED, sheet: ROUTED, embedded: ROUTED },\n },\n // PSP **and** orchestrator (own acquiring + connectors routing across Stripe/Adyen/\n // PayPal). One Payment Form (element/sheet/embedded) + hosted page + wallet button.\n // Strong native off-session (Recurring API). No popup.\n solidgate: {\n isMoR: false, offSessionUpsells: ALL_UPSELLS, tokenModel: 'merchant', orchestrator: true, status: 'planned',\n trialShapes: [],\n surfaces: { express: ON, element: ON, sheet: ON, embedded: ON, redirect: ON },\n },\n}\n\nexport interface ValidationResult {\n ok: boolean\n reason?: string\n /** Non-fatal caveat (e.g. Paddle upgrade-only). */\n note?: string\n}\n\n// ── Surface preference resolution (§7 item 3 — wallet-first is a funnel INTENT) ──\n\n/** Device class the surface resolver picks against — mobile gets the wallet-first surface. */\nexport type DeviceClass = 'mobile' | 'desktop'\n\n/**\n * What an author writes on `<Checkout surface>`: either a concrete {@link CheckoutSurface} (pinned,\n * validated as today) or `'auto'` — a declarative \"give me the best surface for this store's\n * provider and this device\". The platform resolves `'auto'` to a concrete surface via\n * {@link resolveSurface}; `'auto'` never crosses the checkout wire (the client resolves first, the\n * server re-validates the concrete surface through {@link validateCheckout}).\n */\nexport type SurfacePreference = 'auto' | CheckoutSurface\n\n/**\n * Preference order for `surface=\"auto\"` per device class. MOBILE is wallet-FIRST (express, then the\n * wallet-bearing inline frame, then a raw card field); DESKTOP prefers the inline card\n * field/embedded frame (a wallet button is a phone affordance). Both are filtered to what the\n * provider offers AND our driver can render NOW (see {@link isRenderableSurface}), so `'auto'` never\n * resolves to a surface the runtime can't produce.\n */\nconst AUTO_SURFACE_ORDER: Record<DeviceClass, readonly CheckoutSurface[]> = {\n mobile: ['express', 'embedded', 'element', 'sheet', 'popup', 'redirect'],\n desktop: ['element', 'embedded', 'express', 'sheet', 'redirect', 'popup'],\n}\n\n/** A surface the provider offers, can take a purchase on, AND our driver renders now (not `'planned'`). */\nexport function isRenderableSurface(provider: CheckoutProvider, surface: CheckoutSurface): boolean {\n const cap = PROVIDER_PROFILES[provider]?.surfaces[surface]\n return !!cap && cap.purchase && cap.driver !== 'planned'\n}\n\n/** The provider's renderable purchase surfaces (drives the `'auto'` fallbacks + the publish gate). */\nexport function renderableSurfaces(provider: CheckoutProvider): CheckoutSurface[] {\n return surfacesFor(provider).filter((s) => isRenderableSurface(provider, s))\n}\n\n/**\n * Resolve a {@link SurfacePreference} to the concrete surface the runtime will actually mount, for a\n * given provider and device class (§7 item 3). PURE — the client resolves once per mount and puts the\n * result on the wire (the wire stays surface-literal; the server re-validates via {@link\n * validateCheckout}), and the publish gate resolves it the same way. Deterministic:\n *\n * - an EXPLICIT surface resolves to ITSELF — the author's pin is never silently rewritten (an\n * unsupported pin, e.g. `express` on a provider that can't render it, is caught by\n * `validateCheckout` at publish, exactly as today);\n * - `'auto'` → mobile picks the provider's best wallet-first RENDERABLE surface (express where the\n * driver ships it, else the best inline/embedded frame); desktop picks the inline card\n * field/embedded frame. Only driver-ready surfaces are considered, so the result always renders.\n *\n * Totality guard: a provider with NO driver-ready surface (every surface `'planned'`, or unknown\n * provider) falls back to its first declared surface (unknown → a nominal default) so the function is\n * total; downstream `validateCheckout`/flow-select then fail that closed. In practice `'auto'` is only\n * authored on a funnel's ONE resolved (full-status) provider, which always has a renderable surface.\n */\nexport function resolveSurface(\n provider: CheckoutProvider,\n preference: SurfacePreference,\n deviceClass: DeviceClass,\n): CheckoutSurface {\n // Explicit pin → itself (validated elsewhere; never rewritten).\n if (preference !== 'auto') return preference\n for (const surface of AUTO_SURFACE_ORDER[deviceClass]) {\n if (isRenderableSurface(provider, surface)) return surface\n }\n // No driver-ready surface — keep total; downstream validation fails it closed.\n const declared = surfacesFor(provider)\n return declared[0] ?? (deviceClass === 'mobile' ? 'express' : 'element')\n}\n\n/** Surfaces a provider supports, for the editor/AI to steer authors. */\nexport function surfacesFor(provider: CheckoutProvider): CheckoutSurface[] {\n return Object.keys(PROVIDER_PROFILES[provider]?.surfaces ?? {}) as CheckoutSurface[]\n}\n\n/** Whether a provider is a Merchant of Record (it owns tax/compliance). */\nexport function isMerchantOfRecord(provider: CheckoutProvider): boolean {\n return PROVIDER_PROFILES[provider]?.isMoR ?? false\n}\n\n/** Whether a provider routes across multiple processors (Primer, SolidGate). */\nexport function isOrchestrator(provider: CheckoutProvider): boolean {\n return PROVIDER_PROFILES[provider]?.orchestrator ?? false\n}\n\n/**\n * Validate a (provider, surface) for a **purchase**: the surface must exist for\n * the provider and accept a payment. Surface availability is the real constraint\n * (e.g. Paddle has no `element`).\n */\nexport function validateCheckout(provider: CheckoutProvider, surface: CheckoutSurface): ValidationResult {\n const cap = PROVIDER_PROFILES[provider]?.surfaces[surface]\n if (!cap) return { ok: false, reason: `${provider} has no '${surface}' surface` }\n if (!cap.purchase) return { ok: false, reason: `'${surface}' can't take a purchase on ${provider}` }\n // The provider offers this surface but our driver can't render it yet (e.g. Paddle Express\n // Checkout) → fail closed so publish never promises a surface the runtime can't produce.\n if (cap.driver === 'planned') return { ok: false, reason: `${provider} '${surface}' checkout isn't renderable yet (the provider ships it, but our driver doesn't render it)` }\n return { ok: true }\n}\n\n/** Trial shapes a provider can compose (empty = it can't run a trial offer yet). */\nexport function trialShapesFor(provider: CheckoutProvider): TrialShape[] {\n return PROVIDER_PROFILES[provider]?.trialShapes ?? []\n}\n\n/**\n * Validate that a provider can run a TRIAL (§7 item 4). Pass a specific {@link TrialShape} to\n * check that exact shape; omit it to assert the provider supports *any* trial (the cheap gate\n * check when only the offering's TRIAL-role binding existence is known). A provider with no\n * trial shapes (every planned provider) fails closed.\n */\nexport function validateTrial(provider: CheckoutProvider, shape?: TrialShape): ValidationResult {\n const profile = PROVIDER_PROFILES[provider]\n if (!profile) return { ok: false, reason: `unknown provider '${provider}'` }\n const shapes = profile.trialShapes\n if (shapes.length === 0) return { ok: false, reason: `${provider} can't run a trial offer yet` }\n if (shape && !shapes.includes(shape)) {\n return { ok: false, reason: `${provider} doesn't support a '${shape}' trial (it supports: ${shapes.join(', ')})` }\n }\n return { ok: true }\n}\n\n/**\n * Validate an **off-session upsell** of `kind` after a purchase made on\n * `paywallSurface`. Combines provider support for the kind (Paddle can't stack a\n * 2nd subscription) with the paywall surface's off-session reliability (Stripe\n * managed checkout is `conditional` — returned as a `note`, not a hard block).\n */\nexport function validateUpsell(\n provider: CheckoutProvider,\n paywallSurface: CheckoutSurface,\n kind: UpsellKind = 'subscription',\n): ValidationResult {\n const profile = PROVIDER_PROFILES[provider]\n if (!profile) return { ok: false, reason: `unknown provider '${provider}'` }\n if (!profile.offSessionUpsells.includes(kind)) {\n const can = profile.offSessionUpsells.length ? ` (it supports: ${profile.offSessionUpsells.join(', ')})` : ''\n return { ok: false, reason: `${provider} can't charge a '${kind}' upsell off-session${can}` }\n }\n const cap = profile.surfaces[paywallSurface]\n if (!cap) return { ok: false, reason: `${provider} has no '${paywallSurface}' surface to upsell after` }\n if (cap.offSession === 'none') {\n return { ok: false, reason: `a purchase on '${paywallSurface}' retains no chargeable method for an upsell` }\n }\n if (cap.offSession === 'conditional') {\n return { ok: true, note: `off-session upsell after ${provider} '${paywallSurface}' may decline — prefer a reliable surface or wire an on-session fallback` }\n }\n return { ok: true }\n}\n\n// ── Checkout error taxonomy (pure — drivers + server both normalize into it) ──\n\n/**\n * Normalized failure category — the driver/platform maps each provider's decline\n * codes into one of these so funnels can route/recover by reason. `authentication_\n * required` (3DS/SCA) and `requires_payment_method` are the cases an **off-session**\n * upsell can't satisfy → recover on-session (re-collect a card).\n */\nexport type CheckoutErrorCategory =\n | 'card_declined'\n | 'insufficient_funds'\n | 'expired_card'\n | 'prepaid_card' // card funding is prepaid — trials/subscriptions commonly decline; route to a real-card recapture page\n | 'authentication_required' // SCA/3DS needed — off-session can't challenge\n | 'requires_payment_method' // no usable saved method\n | 'processing_error'\n | 'canceled'\n | 'unknown'\n\nexport interface CheckoutError {\n category: CheckoutErrorCategory\n message: string\n /** Raw provider decline code, if any (e.g. Stripe `card_declined`/`insufficient_funds`). */\n declineCode?: string\n /** Whether re-attempting (e.g. an on-session re-collection) may succeed. */\n retryable: boolean\n}\n\n/** Build a {@link CheckoutError} (driver/test helper). */\nexport function checkoutError(\n category: CheckoutErrorCategory,\n message: string,\n extra: { declineCode?: string; retryable?: boolean } = {},\n): CheckoutError {\n const retryable = extra.retryable ?? (category === 'authentication_required' || category === 'requires_payment_method' || category === 'processing_error')\n return { category, message, declineCode: extra.declineCode, retryable }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { checkoutError } from './chunk-TNLRQPVY.js';
1
+ import { checkoutError } from './chunk-OXQBEKZ5.js';
2
2
 
3
3
  // src/drivers/wire.ts
4
4
  function endpoint(ctx, path = "") {
@@ -95,5 +95,5 @@ function leaveForRedirect(url) {
95
95
  }
96
96
 
97
97
  export { completeWithPoll, leaveForRedirect, postComplete, postSession, settlementToResult, toCheckoutError };
98
- //# sourceMappingURL=chunk-CFFMZYPE.js.map
99
- //# sourceMappingURL=chunk-CFFMZYPE.js.map
98
+ //# sourceMappingURL=chunk-JAO6AA4R.js.map
99
+ //# sourceMappingURL=chunk-JAO6AA4R.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/drivers/wire.ts"],"names":[],"mappings":"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","file":"chunk-CFFMZYPE.js","sourcesContent":["/**\n * Shared wire plumbing for the checkout drivers — the CLIENT half of the v2\n * checkout contract (commerce/checkout-api.ts). Lives under drivers/ (not\n * commerce/) on purpose: it ships only inside the `driver-*` subpath entries,\n * so the core funnel entry never bundles a checkout fetch — the core SDK talks\n * to a {@link CheckoutDriver}, never to the wire.\n *\n * Only `import type` from commerce/checkout(.tsx) here — a runtime import would\n * drag the React checkout primitives into every driver chunk. The one runtime\n * dependency is the pure error taxonomy (capabilities.ts).\n */\nimport type {\n CheckoutCompleteRequest,\n CheckoutCompleteResponse,\n CheckoutSessionRequest,\n CheckoutSessionResponse,\n CheckoutSettlement,\n} from '../commerce/checkout-api'\nimport type { CheckoutDriverContext, CheckoutRequest, CheckoutResult } from '../commerce/checkout'\nimport { checkoutError, type CheckoutError } from '../commerce/capabilities'\n\nfunction endpoint(ctx: CheckoutDriverContext, path = ''): string {\n const base = ctx.apiBase.replace(/\\/+$/, '')\n return `${base}/campaign/${encodeURIComponent(ctx.campaignId)}/v2/checkout${path}`\n}\n\nasync function postJson<T>(url: string, body: unknown): Promise<T> {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n // The contract speaks in `step` responses, not HTTP errors — a non-2xx means the\n // request never reached the checkout service properly (proxy, deploy, outage).\n if (!res.ok) throw checkoutError('processing_error', `checkout request failed (HTTP ${res.status})`)\n return (await res.json()) as T\n}\n\n/** Normalize any thrown value into the taxonomy (network drops → `processing_error`). */\nexport function toCheckoutError(e: unknown): CheckoutError {\n if (e && typeof e === 'object' && 'category' in e && 'message' in e && 'retryable' in e) {\n return e as CheckoutError\n }\n return checkoutError('processing_error', e instanceof Error ? e.message : 'Checkout request failed')\n}\n\n/**\n * Open a checkout attempt: `POST {apiBase}/campaign/{campaignId}/v2/checkout`.\n * The correlationId is client-minted per ATTEMPT (idempotency key + the id the\n * server stamps into provider metadata) — mint it once, reuse it on retries.\n */\nexport function postSession(\n ctx: CheckoutDriverContext,\n req: CheckoutRequest,\n correlationId: string,\n): Promise<CheckoutSessionResponse> {\n const body: CheckoutSessionRequest = {\n funnelId: ctx.funnelId,\n mode: ctx.mode,\n // The CHARGE identity is the catalog key (resolved from the slot by driverWithCatalog); fall back\n // to `product` for legacy builds where the page named the catalog key directly.\n productKey: req.catalogKey ?? req.product,\n intent: req.intent,\n kind: req.kind,\n // null (not absent) = explicit off-session — the server validates surfaces\n // against the capability matrix, so never let one default in transit.\n surface: req.surface ?? null,\n sessionId: ctx.sessionId ?? undefined,\n visitorId: ctx.visitorId ?? undefined,\n // Buyer email collected by the funnel (user.email) — Stripe Customer creation,\n // receipts, Paddle prefill. Injected at attempt time by the FunnelProvider seam.\n email: req.email,\n correlationId,\n }\n return postJson<CheckoutSessionResponse>(endpoint(ctx), body)\n}\n\n/** Ask the server to VERIFY with the provider and write the money facts (idempotent). */\nexport function postComplete(\n ctx: CheckoutDriverContext,\n correlationId: string,\n providerRef?: string,\n): Promise<CheckoutCompleteResponse> {\n const body: CheckoutCompleteRequest = { providerRef }\n return postJson<CheckoutCompleteResponse>(\n endpoint(ctx, `/${encodeURIComponent(correlationId)}/complete`),\n body,\n )\n}\n\n/** Server settlement → the SDK's CheckoutResult (eventId passes through for pixel dedup). */\nexport function settlementToResult(s: CheckoutSettlement): CheckoutResult {\n return { ok: true, amountMinor: s.amountMinor, currency: s.currency, eventId: s.eventId }\n}\n\nexport interface CompletePollOptions {\n providerRef?: string\n /** Total complete attempts before giving up (default 5). */\n attempts?: number\n /** Delay between attempts (default 2000ms). */\n intervalMs?: number\n /** Error message when the provider hasn't confirmed inside the window (MoR/async). */\n timeoutMessage?: string\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/**\n * Complete with a bounded poll. Sync providers (Stripe) settle on the first\n * attempt — the server verifies against the provider inline. MoR/async providers\n * (Paddle) settle via webhook, so `pending` is normal: re-poll a few times, then\n * stop holding the UI hostage — the charge may still land server-side, which is\n * why the timeout error is `retryable` and the message should say the receipt\n * can still arrive (the server's money facts don't depend on this browser).\n */\nexport async function completeWithPoll(\n ctx: CheckoutDriverContext,\n correlationId: string,\n opts: CompletePollOptions = {},\n): Promise<CheckoutResult> {\n const attempts = opts.attempts ?? 5\n const intervalMs = opts.intervalMs ?? 2000\n try {\n for (let i = 0; i < attempts; i++) {\n const res = await postComplete(ctx, correlationId, opts.providerRef)\n if (res.step === 'settled') return settlementToResult(res.result)\n if (res.step === 'failed') return { ok: false, error: res.error }\n if (i < attempts - 1) await sleep(intervalMs)\n }\n } catch (e) {\n return { ok: false, error: toCheckoutError(e) }\n }\n return {\n ok: false,\n error: checkoutError(\n 'processing_error',\n opts.timeoutMessage ?? 'The payment has not been confirmed yet — please check back shortly.',\n // `completion_timeout` marks \"the poll ran out while the attempt was still\n // pending\" — drivers key double-charge guards on it (the attempt may still\n // settle server-side; it was never refused).\n { retryable: true, declineCode: 'completion_timeout' },\n ),\n }\n}\n\n/**\n * The `redirect` step hand-off: navigate to the provider-hosted page and return a\n * promise that settles ONLY if the buyer comes back to this very page. The page\n * is leaving — resolving early would fire `purchase.complete`/navigation for a\n * charge that hasn't happened, and rejecting would flash an error during the\n * (successful) hand-off — so while the navigation proceeds the promise stays\n * pending and `useCheckout` stays in `loading`, the truthful UI for a page\n * mid-navigation. The hosted page's SUCCESS return URL re-enters the funnel with\n * `af_checkout={correlationId}` (a fresh load — this promise is gone; the SDK's\n * resume leg settles the attempt).\n *\n * The one way THIS page comes back to life is a bfcache restore (browser Back\n * from the hosted page): `pageshow` with `persisted: true`. That is a buyer who\n * abandoned the hosted checkout — resolve `canceled` so the trigger re-enables\n * and inline redirect placeholders recover instead of spinning forever.\n */\nexport function leaveForRedirect(url: string): Promise<CheckoutResult> {\n return new Promise<CheckoutResult>((resolve) => {\n if (typeof window !== 'undefined') {\n const onPageshow = (event: PageTransitionEvent) => {\n if (!event.persisted) return\n window.removeEventListener('pageshow', onPageshow)\n resolve({\n ok: false,\n error: checkoutError('canceled', 'Returned from the hosted checkout without completing'),\n })\n }\n window.addEventListener('pageshow', onPageshow)\n }\n window.location.assign(url)\n })\n}\n"]}
1
+ {"version":3,"sources":["../src/drivers/wire.ts"],"names":[],"mappings":"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","file":"chunk-JAO6AA4R.js","sourcesContent":["/**\n * Shared wire plumbing for the checkout drivers — the CLIENT half of the v2\n * checkout contract (commerce/checkout-api.ts). Lives under drivers/ (not\n * commerce/) on purpose: it ships only inside the `driver-*` subpath entries,\n * so the core funnel entry never bundles a checkout fetch — the core SDK talks\n * to a {@link CheckoutDriver}, never to the wire.\n *\n * Only `import type` from commerce/checkout(.tsx) here — a runtime import would\n * drag the React checkout primitives into every driver chunk. The one runtime\n * dependency is the pure error taxonomy (capabilities.ts).\n */\nimport type {\n CheckoutCompleteRequest,\n CheckoutCompleteResponse,\n CheckoutSessionRequest,\n CheckoutSessionResponse,\n CheckoutSettlement,\n} from '../commerce/checkout-api'\nimport type { CheckoutDriverContext, CheckoutRequest, CheckoutResult } from '../commerce/checkout'\nimport { checkoutError, type CheckoutError } from '../commerce/capabilities'\n\nfunction endpoint(ctx: CheckoutDriverContext, path = ''): string {\n const base = ctx.apiBase.replace(/\\/+$/, '')\n return `${base}/campaign/${encodeURIComponent(ctx.campaignId)}/v2/checkout${path}`\n}\n\nasync function postJson<T>(url: string, body: unknown): Promise<T> {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n // The contract speaks in `step` responses, not HTTP errors — a non-2xx means the\n // request never reached the checkout service properly (proxy, deploy, outage).\n if (!res.ok) throw checkoutError('processing_error', `checkout request failed (HTTP ${res.status})`)\n return (await res.json()) as T\n}\n\n/** Normalize any thrown value into the taxonomy (network drops → `processing_error`). */\nexport function toCheckoutError(e: unknown): CheckoutError {\n if (e && typeof e === 'object' && 'category' in e && 'message' in e && 'retryable' in e) {\n return e as CheckoutError\n }\n return checkoutError('processing_error', e instanceof Error ? e.message : 'Checkout request failed')\n}\n\n/**\n * Open a checkout attempt: `POST {apiBase}/campaign/{campaignId}/v2/checkout`.\n * The correlationId is client-minted per ATTEMPT (idempotency key + the id the\n * server stamps into provider metadata) — mint it once, reuse it on retries.\n */\nexport function postSession(\n ctx: CheckoutDriverContext,\n req: CheckoutRequest,\n correlationId: string,\n): Promise<CheckoutSessionResponse> {\n const body: CheckoutSessionRequest = {\n funnelId: ctx.funnelId,\n mode: ctx.mode,\n // The CHARGE identity is the catalog key (resolved from the slot by driverWithCatalog); fall back\n // to `product` for legacy builds where the page named the catalog key directly.\n productKey: req.catalogKey ?? req.product,\n intent: req.intent,\n kind: req.kind,\n // null (not absent) = explicit off-session — the server validates surfaces\n // against the capability matrix, so never let one default in transit.\n surface: req.surface ?? null,\n sessionId: ctx.sessionId ?? undefined,\n visitorId: ctx.visitorId ?? undefined,\n // Buyer email collected by the funnel (user.email) — Stripe Customer creation,\n // receipts, Paddle prefill. Injected at attempt time by the FunnelProvider seam.\n email: req.email,\n correlationId,\n }\n return postJson<CheckoutSessionResponse>(endpoint(ctx), body)\n}\n\n/** Ask the server to VERIFY with the provider and write the money facts (idempotent). */\nexport function postComplete(\n ctx: CheckoutDriverContext,\n correlationId: string,\n providerRef?: string,\n): Promise<CheckoutCompleteResponse> {\n const body: CheckoutCompleteRequest = { providerRef }\n return postJson<CheckoutCompleteResponse>(\n endpoint(ctx, `/${encodeURIComponent(correlationId)}/complete`),\n body,\n )\n}\n\n/** Server settlement → the SDK's CheckoutResult (eventId passes through for pixel dedup). */\nexport function settlementToResult(s: CheckoutSettlement): CheckoutResult {\n return { ok: true, amountMinor: s.amountMinor, currency: s.currency, eventId: s.eventId }\n}\n\nexport interface CompletePollOptions {\n providerRef?: string\n /** Total complete attempts before giving up (default 5). */\n attempts?: number\n /** Delay between attempts (default 2000ms). */\n intervalMs?: number\n /** Error message when the provider hasn't confirmed inside the window (MoR/async). */\n timeoutMessage?: string\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/**\n * Complete with a bounded poll. Sync providers (Stripe) settle on the first\n * attempt — the server verifies against the provider inline. MoR/async providers\n * (Paddle) settle via webhook, so `pending` is normal: re-poll a few times, then\n * stop holding the UI hostage — the charge may still land server-side, which is\n * why the timeout error is `retryable` and the message should say the receipt\n * can still arrive (the server's money facts don't depend on this browser).\n */\nexport async function completeWithPoll(\n ctx: CheckoutDriverContext,\n correlationId: string,\n opts: CompletePollOptions = {},\n): Promise<CheckoutResult> {\n const attempts = opts.attempts ?? 5\n const intervalMs = opts.intervalMs ?? 2000\n try {\n for (let i = 0; i < attempts; i++) {\n const res = await postComplete(ctx, correlationId, opts.providerRef)\n if (res.step === 'settled') return settlementToResult(res.result)\n if (res.step === 'failed') return { ok: false, error: res.error }\n if (i < attempts - 1) await sleep(intervalMs)\n }\n } catch (e) {\n return { ok: false, error: toCheckoutError(e) }\n }\n return {\n ok: false,\n error: checkoutError(\n 'processing_error',\n opts.timeoutMessage ?? 'The payment has not been confirmed yet — please check back shortly.',\n // `completion_timeout` marks \"the poll ran out while the attempt was still\n // pending\" — drivers key double-charge guards on it (the attempt may still\n // settle server-side; it was never refused).\n { retryable: true, declineCode: 'completion_timeout' },\n ),\n }\n}\n\n/**\n * The `redirect` step hand-off: navigate to the provider-hosted page and return a\n * promise that settles ONLY if the buyer comes back to this very page. The page\n * is leaving — resolving early would fire `purchase.complete`/navigation for a\n * charge that hasn't happened, and rejecting would flash an error during the\n * (successful) hand-off — so while the navigation proceeds the promise stays\n * pending and `useCheckout` stays in `loading`, the truthful UI for a page\n * mid-navigation. The hosted page's SUCCESS return URL re-enters the funnel with\n * `af_checkout={correlationId}` (a fresh load — this promise is gone; the SDK's\n * resume leg settles the attempt).\n *\n * The one way THIS page comes back to life is a bfcache restore (browser Back\n * from the hosted page): `pageshow` with `persisted: true`. That is a buyer who\n * abandoned the hosted checkout — resolve `canceled` so the trigger re-enables\n * and inline redirect placeholders recover instead of spinning forever.\n */\nexport function leaveForRedirect(url: string): Promise<CheckoutResult> {\n return new Promise<CheckoutResult>((resolve) => {\n if (typeof window !== 'undefined') {\n const onPageshow = (event: PageTransitionEvent) => {\n if (!event.persisted) return\n window.removeEventListener('pageshow', onPageshow)\n resolve({\n ok: false,\n error: checkoutError('canceled', 'Returned from the hosted checkout without completing'),\n })\n }\n window.addEventListener('pageshow', onPageshow)\n }\n window.location.assign(url)\n })\n}\n"]}