@appfunnel-dev/sdk 2.0.0-canary.1 → 2.0.0-canary.10
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-VV7TFC64.cjs +106 -0
- package/dist/chunk-VV7TFC64.cjs.map +1 -0
- package/dist/chunk-VW2HVPR4.js +446 -0
- package/dist/chunk-VW2HVPR4.js.map +1 -0
- package/dist/chunk-WYUDL4FI.cjs +8 -0
- package/dist/chunk-WYUDL4FI.cjs.map +1 -0
- package/dist/chunk-Y4YNJ2EX.cjs +476 -0
- package/dist/chunk-Y4YNJ2EX.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 +2650 -841
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +343 -1005
- package/dist/index.d.ts +343 -1005
- package/dist/index.js +2381 -701
- package/dist/index.js.map +1 -1
- package/dist/manifest-B3Tdab0M.d.cts +920 -0
- package/dist/manifest-B3Tdab0M.d.ts +920 -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
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @appfunnel-dev/sdk
|
|
2
2
|
|
|
3
|
-
Headless SDK for building
|
|
3
|
+
Headless SDK for building Appfunnel pages with React. Write funnel pages as React components while the SDK handles payments, tracking, navigation, and variable state.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider capability matrix + checkout error taxonomy — the PURE half of the
|
|
3
|
+
* checkout seam, extracted from checkout.tsx so it ships through the **manifest
|
|
4
|
+
* entry** (no React): the API's publish validation, the editor, AI authoring, and
|
|
5
|
+
* local dev all run the SAME checks the runtime primitives use (phase-3 §3.1b —
|
|
6
|
+
* "the SDK ships this as data"). The React half (driver seam, `useCheckout`,
|
|
7
|
+
* `<Checkout>`/`<Upsell>`) stays in ./checkout, which re-exports everything here.
|
|
8
|
+
*/
|
|
9
|
+
type CheckoutSurface = 'express' | 'element' | 'embedded' | 'sheet' | 'popup' | 'redirect';
|
|
10
|
+
type CheckoutProvider = 'stripe' | 'paddle' | 'whop' | 'primer' | 'solidgate';
|
|
11
|
+
type CheckoutIntent = 'purchase' | 'upsell';
|
|
12
|
+
/** Surfaces that render inline (vs. those that open on click). */
|
|
13
|
+
declare const INLINE_SURFACES: ReadonlySet<CheckoutSurface>;
|
|
14
|
+
declare function isInlineSurface(surface: CheckoutSurface): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The kind of off-session upsell (researched against Funnelfox's changelog, which
|
|
17
|
+
* ships these as distinct features). The discriminating real-world constraint is
|
|
18
|
+
* **stacking a 2nd concurrent subscription** — Paddle can't, SolidGate/Stripe can.
|
|
19
|
+
*/
|
|
20
|
+
type UpsellKind = 'subscription' | 'one-time' | 'upgrade';
|
|
21
|
+
/** Reliability of an off-session upsell charge AFTER a purchase on a surface. */
|
|
22
|
+
type OffSessionReliability = 'reliable' | 'conditional' | 'none';
|
|
23
|
+
interface SurfaceCapability {
|
|
24
|
+
purchase: boolean;
|
|
25
|
+
/** Apple/Google Pay available on this surface. */
|
|
26
|
+
wallets: boolean;
|
|
27
|
+
/** Reliability of an off-session upsell after a purchase on this surface. */
|
|
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';
|
|
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';
|
|
49
|
+
interface ProviderProfile {
|
|
50
|
+
/** Merchant-of-Record (handles tax/compliance) vs. a PSP you settle through. */
|
|
51
|
+
isMoR: boolean;
|
|
52
|
+
/** Upsell kinds this provider can charge off-session (one-click, no re-entry). */
|
|
53
|
+
offSessionUpsells: UpsellKind[];
|
|
54
|
+
/** Who charges the saved method off-session: you (PSP) or the provider (MoR/managed). */
|
|
55
|
+
tokenModel: 'merchant' | 'provider';
|
|
56
|
+
/** Routes across multiple processors (Primer; SolidGate also offers this) vs. a single acquirer. */
|
|
57
|
+
orchestrator: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Driver tier (Wave-4 decision, Simon 2026-07-02): `full` = a real driver ships
|
|
60
|
+
* (Stripe, Paddle); `planned` = profile only — authoring/publish validate against
|
|
61
|
+
* it, but no charge path exists yet (Whop, Primer, SolidGate). A new provider
|
|
62
|
+
* becomes buildable by writing a driver; its profile already gates what authors
|
|
63
|
+
* can express, so funnels written today stay valid when the driver lands.
|
|
64
|
+
*/
|
|
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[];
|
|
71
|
+
/** Supported surfaces → per-surface capabilities. **Absent = not supported.** */
|
|
72
|
+
surfaces: Partial<Record<CheckoutSurface, SurfaceCapability>>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Provider capabilities (researched 2026-06-23 against provider docs + Funnelfox;
|
|
76
|
+
* trial mechanics re-verified 2026-07-02, doc 09 §4.4).
|
|
77
|
+
*
|
|
78
|
+
* Three real-world corrections to the naive model:
|
|
79
|
+
* 1. **Off-session upsell support is per-provider by *kind*** (`offSessionUpsells`):
|
|
80
|
+
* Paddle can't stack a 2nd concurrent subscription (upgrade + one-time only);
|
|
81
|
+
* Stripe/SolidGate/Whop/Primer can do all kinds.
|
|
82
|
+
* 2. **Off-session *reliability* varies by the paywall surface** (`SurfaceCapability.
|
|
83
|
+
* offSession`): Stripe **managed** checkout (`embedded`/`redirect`) upsells as a
|
|
84
|
+
* direct charge against Stripe's vault → `conditional` (may decline); Elements
|
|
85
|
+
* surfaces are `reliable`. Primer is `conditional` everywhere (PSP-dependent).
|
|
86
|
+
* 3. **Surface availability is a hard constraint.** MoR/managed providers can't
|
|
87
|
+
* expose a raw card field to your own UI: Paddle has no `express`/`element`/your
|
|
88
|
+
* `sheet`; Whop has no raw `element`/`express`; Primer has no hosted `redirect`;
|
|
89
|
+
* Stripe/SolidGate have no `popup`. A missing entry means "not possible."
|
|
90
|
+
*/
|
|
91
|
+
declare const PROVIDER_PROFILES: Record<CheckoutProvider, ProviderProfile>;
|
|
92
|
+
interface ValidationResult {
|
|
93
|
+
ok: boolean;
|
|
94
|
+
reason?: string;
|
|
95
|
+
/** Non-fatal caveat (e.g. Paddle upgrade-only). */
|
|
96
|
+
note?: string;
|
|
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;
|
|
131
|
+
/** Surfaces a provider supports, for the editor/AI to steer authors. */
|
|
132
|
+
declare function surfacesFor(provider: CheckoutProvider): CheckoutSurface[];
|
|
133
|
+
/** Whether a provider is a Merchant of Record (it owns tax/compliance). */
|
|
134
|
+
declare function isMerchantOfRecord(provider: CheckoutProvider): boolean;
|
|
135
|
+
/** Whether a provider routes across multiple processors (Primer, SolidGate). */
|
|
136
|
+
declare function isOrchestrator(provider: CheckoutProvider): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Validate a (provider, surface) for a **purchase**: the surface must exist for
|
|
139
|
+
* the provider and accept a payment. Surface availability is the real constraint
|
|
140
|
+
* (e.g. Paddle has no `element`).
|
|
141
|
+
*/
|
|
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;
|
|
152
|
+
/**
|
|
153
|
+
* Validate an **off-session upsell** of `kind` after a purchase made on
|
|
154
|
+
* `paywallSurface`. Combines provider support for the kind (Paddle can't stack a
|
|
155
|
+
* 2nd subscription) with the paywall surface's off-session reliability (Stripe
|
|
156
|
+
* managed checkout is `conditional` — returned as a `note`, not a hard block).
|
|
157
|
+
*/
|
|
158
|
+
declare function validateUpsell(provider: CheckoutProvider, paywallSurface: CheckoutSurface, kind?: UpsellKind): ValidationResult;
|
|
159
|
+
/**
|
|
160
|
+
* Normalized failure category — the driver/platform maps each provider's decline
|
|
161
|
+
* codes into one of these so funnels can route/recover by reason. `authentication_
|
|
162
|
+
* required` (3DS/SCA) and `requires_payment_method` are the cases an **off-session**
|
|
163
|
+
* upsell can't satisfy → recover on-session (re-collect a card).
|
|
164
|
+
*/
|
|
165
|
+
type CheckoutErrorCategory = 'card_declined' | 'insufficient_funds' | 'expired_card' | 'prepaid_card' | 'authentication_required' | 'requires_payment_method' | 'processing_error' | 'canceled' | 'unknown';
|
|
166
|
+
interface CheckoutError {
|
|
167
|
+
category: CheckoutErrorCategory;
|
|
168
|
+
message: string;
|
|
169
|
+
/** Raw provider decline code, if any (e.g. Stripe `card_declined`/`insufficient_funds`). */
|
|
170
|
+
declineCode?: string;
|
|
171
|
+
/** Whether re-attempting (e.g. an on-session re-collection) may succeed. */
|
|
172
|
+
retryable: boolean;
|
|
173
|
+
}
|
|
174
|
+
/** Build a {@link CheckoutError} (driver/test helper). */
|
|
175
|
+
declare function checkoutError(category: CheckoutErrorCategory, message: string, extra?: {
|
|
176
|
+
declineCode?: string;
|
|
177
|
+
retryable?: boolean;
|
|
178
|
+
}): CheckoutError;
|
|
179
|
+
|
|
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 };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider capability matrix + checkout error taxonomy — the PURE half of the
|
|
3
|
+
* checkout seam, extracted from checkout.tsx so it ships through the **manifest
|
|
4
|
+
* entry** (no React): the API's publish validation, the editor, AI authoring, and
|
|
5
|
+
* local dev all run the SAME checks the runtime primitives use (phase-3 §3.1b —
|
|
6
|
+
* "the SDK ships this as data"). The React half (driver seam, `useCheckout`,
|
|
7
|
+
* `<Checkout>`/`<Upsell>`) stays in ./checkout, which re-exports everything here.
|
|
8
|
+
*/
|
|
9
|
+
type CheckoutSurface = 'express' | 'element' | 'embedded' | 'sheet' | 'popup' | 'redirect';
|
|
10
|
+
type CheckoutProvider = 'stripe' | 'paddle' | 'whop' | 'primer' | 'solidgate';
|
|
11
|
+
type CheckoutIntent = 'purchase' | 'upsell';
|
|
12
|
+
/** Surfaces that render inline (vs. those that open on click). */
|
|
13
|
+
declare const INLINE_SURFACES: ReadonlySet<CheckoutSurface>;
|
|
14
|
+
declare function isInlineSurface(surface: CheckoutSurface): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The kind of off-session upsell (researched against Funnelfox's changelog, which
|
|
17
|
+
* ships these as distinct features). The discriminating real-world constraint is
|
|
18
|
+
* **stacking a 2nd concurrent subscription** — Paddle can't, SolidGate/Stripe can.
|
|
19
|
+
*/
|
|
20
|
+
type UpsellKind = 'subscription' | 'one-time' | 'upgrade';
|
|
21
|
+
/** Reliability of an off-session upsell charge AFTER a purchase on a surface. */
|
|
22
|
+
type OffSessionReliability = 'reliable' | 'conditional' | 'none';
|
|
23
|
+
interface SurfaceCapability {
|
|
24
|
+
purchase: boolean;
|
|
25
|
+
/** Apple/Google Pay available on this surface. */
|
|
26
|
+
wallets: boolean;
|
|
27
|
+
/** Reliability of an off-session upsell after a purchase on this surface. */
|
|
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';
|
|
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';
|
|
49
|
+
interface ProviderProfile {
|
|
50
|
+
/** Merchant-of-Record (handles tax/compliance) vs. a PSP you settle through. */
|
|
51
|
+
isMoR: boolean;
|
|
52
|
+
/** Upsell kinds this provider can charge off-session (one-click, no re-entry). */
|
|
53
|
+
offSessionUpsells: UpsellKind[];
|
|
54
|
+
/** Who charges the saved method off-session: you (PSP) or the provider (MoR/managed). */
|
|
55
|
+
tokenModel: 'merchant' | 'provider';
|
|
56
|
+
/** Routes across multiple processors (Primer; SolidGate also offers this) vs. a single acquirer. */
|
|
57
|
+
orchestrator: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Driver tier (Wave-4 decision, Simon 2026-07-02): `full` = a real driver ships
|
|
60
|
+
* (Stripe, Paddle); `planned` = profile only — authoring/publish validate against
|
|
61
|
+
* it, but no charge path exists yet (Whop, Primer, SolidGate). A new provider
|
|
62
|
+
* becomes buildable by writing a driver; its profile already gates what authors
|
|
63
|
+
* can express, so funnels written today stay valid when the driver lands.
|
|
64
|
+
*/
|
|
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[];
|
|
71
|
+
/** Supported surfaces → per-surface capabilities. **Absent = not supported.** */
|
|
72
|
+
surfaces: Partial<Record<CheckoutSurface, SurfaceCapability>>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Provider capabilities (researched 2026-06-23 against provider docs + Funnelfox;
|
|
76
|
+
* trial mechanics re-verified 2026-07-02, doc 09 §4.4).
|
|
77
|
+
*
|
|
78
|
+
* Three real-world corrections to the naive model:
|
|
79
|
+
* 1. **Off-session upsell support is per-provider by *kind*** (`offSessionUpsells`):
|
|
80
|
+
* Paddle can't stack a 2nd concurrent subscription (upgrade + one-time only);
|
|
81
|
+
* Stripe/SolidGate/Whop/Primer can do all kinds.
|
|
82
|
+
* 2. **Off-session *reliability* varies by the paywall surface** (`SurfaceCapability.
|
|
83
|
+
* offSession`): Stripe **managed** checkout (`embedded`/`redirect`) upsells as a
|
|
84
|
+
* direct charge against Stripe's vault → `conditional` (may decline); Elements
|
|
85
|
+
* surfaces are `reliable`. Primer is `conditional` everywhere (PSP-dependent).
|
|
86
|
+
* 3. **Surface availability is a hard constraint.** MoR/managed providers can't
|
|
87
|
+
* expose a raw card field to your own UI: Paddle has no `express`/`element`/your
|
|
88
|
+
* `sheet`; Whop has no raw `element`/`express`; Primer has no hosted `redirect`;
|
|
89
|
+
* Stripe/SolidGate have no `popup`. A missing entry means "not possible."
|
|
90
|
+
*/
|
|
91
|
+
declare const PROVIDER_PROFILES: Record<CheckoutProvider, ProviderProfile>;
|
|
92
|
+
interface ValidationResult {
|
|
93
|
+
ok: boolean;
|
|
94
|
+
reason?: string;
|
|
95
|
+
/** Non-fatal caveat (e.g. Paddle upgrade-only). */
|
|
96
|
+
note?: string;
|
|
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;
|
|
131
|
+
/** Surfaces a provider supports, for the editor/AI to steer authors. */
|
|
132
|
+
declare function surfacesFor(provider: CheckoutProvider): CheckoutSurface[];
|
|
133
|
+
/** Whether a provider is a Merchant of Record (it owns tax/compliance). */
|
|
134
|
+
declare function isMerchantOfRecord(provider: CheckoutProvider): boolean;
|
|
135
|
+
/** Whether a provider routes across multiple processors (Primer, SolidGate). */
|
|
136
|
+
declare function isOrchestrator(provider: CheckoutProvider): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Validate a (provider, surface) for a **purchase**: the surface must exist for
|
|
139
|
+
* the provider and accept a payment. Surface availability is the real constraint
|
|
140
|
+
* (e.g. Paddle has no `element`).
|
|
141
|
+
*/
|
|
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;
|
|
152
|
+
/**
|
|
153
|
+
* Validate an **off-session upsell** of `kind` after a purchase made on
|
|
154
|
+
* `paywallSurface`. Combines provider support for the kind (Paddle can't stack a
|
|
155
|
+
* 2nd subscription) with the paywall surface's off-session reliability (Stripe
|
|
156
|
+
* managed checkout is `conditional` — returned as a `note`, not a hard block).
|
|
157
|
+
*/
|
|
158
|
+
declare function validateUpsell(provider: CheckoutProvider, paywallSurface: CheckoutSurface, kind?: UpsellKind): ValidationResult;
|
|
159
|
+
/**
|
|
160
|
+
* Normalized failure category — the driver/platform maps each provider's decline
|
|
161
|
+
* codes into one of these so funnels can route/recover by reason. `authentication_
|
|
162
|
+
* required` (3DS/SCA) and `requires_payment_method` are the cases an **off-session**
|
|
163
|
+
* upsell can't satisfy → recover on-session (re-collect a card).
|
|
164
|
+
*/
|
|
165
|
+
type CheckoutErrorCategory = 'card_declined' | 'insufficient_funds' | 'expired_card' | 'prepaid_card' | 'authentication_required' | 'requires_payment_method' | 'processing_error' | 'canceled' | 'unknown';
|
|
166
|
+
interface CheckoutError {
|
|
167
|
+
category: CheckoutErrorCategory;
|
|
168
|
+
message: string;
|
|
169
|
+
/** Raw provider decline code, if any (e.g. Stripe `card_declined`/`insufficient_funds`). */
|
|
170
|
+
declineCode?: string;
|
|
171
|
+
/** Whether re-attempting (e.g. an on-session re-collection) may succeed. */
|
|
172
|
+
retryable: boolean;
|
|
173
|
+
}
|
|
174
|
+
/** Build a {@link CheckoutError} (driver/test helper). */
|
|
175
|
+
declare function checkoutError(category: CheckoutErrorCategory, message: string, extra?: {
|
|
176
|
+
declineCode?: string;
|
|
177
|
+
retryable?: boolean;
|
|
178
|
+
}): CheckoutError;
|
|
179
|
+
|
|
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 };
|