@appfunnel-dev/sdk 2.0.0 → 2.1.0

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 (40) hide show
  1. package/CHANGELOG.md +69 -3
  2. package/dist/{checkout-BYcOfyg_.d.ts → checkout-BPYU4wXF.d.ts} +50 -16
  3. package/dist/{checkout-BM1T-u6_.d.cts → checkout-BzP__Mxe.d.cts} +50 -16
  4. package/dist/{chunk-PN7CRJAF.js → chunk-AW2YURFE.js} +60 -19
  5. package/dist/chunk-AW2YURFE.js.map +1 -0
  6. package/dist/{chunk-MBTHQP4Z.cjs → chunk-DTSLJ24T.cjs} +11 -3
  7. package/dist/chunk-DTSLJ24T.cjs.map +1 -0
  8. package/dist/{chunk-NA7DRFOA.cjs → chunk-MBSXEIXT.cjs} +60 -19
  9. package/dist/chunk-MBSXEIXT.cjs.map +1 -0
  10. package/dist/{chunk-KVMNVX6J.js → chunk-USAECFWO.js} +11 -3
  11. package/dist/chunk-USAECFWO.js.map +1 -0
  12. package/dist/driver-paddle.cjs +14 -14
  13. package/dist/driver-paddle.d.cts +1 -1
  14. package/dist/driver-paddle.d.ts +1 -1
  15. package/dist/driver-paddle.js +1 -1
  16. package/dist/driver-stripe.cjs +31 -15
  17. package/dist/driver-stripe.cjs.map +1 -1
  18. package/dist/driver-stripe.d.cts +1 -1
  19. package/dist/driver-stripe.d.ts +1 -1
  20. package/dist/driver-stripe.js +18 -2
  21. package/dist/driver-stripe.js.map +1 -1
  22. package/dist/index.cjs +78 -67
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +25 -6
  25. package/dist/index.d.ts +25 -6
  26. package/dist/index.js +13 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/{manifest-BajvtPWs.d.cts → manifest-Dp1oRvqp.d.cts} +23 -0
  29. package/dist/{manifest-BajvtPWs.d.ts → manifest-Dp1oRvqp.d.ts} +23 -0
  30. package/dist/manifest-entry.cjs +9 -1
  31. package/dist/manifest-entry.cjs.map +1 -1
  32. package/dist/manifest-entry.d.cts +15 -2
  33. package/dist/manifest-entry.d.ts +15 -2
  34. package/dist/manifest-entry.js +9 -1
  35. package/dist/manifest-entry.js.map +1 -1
  36. package/package.json +1 -1
  37. package/dist/chunk-KVMNVX6J.js.map +0 -1
  38. package/dist/chunk-MBTHQP4Z.cjs.map +0 -1
  39. package/dist/chunk-NA7DRFOA.cjs.map +0 -1
  40. package/dist/chunk-PN7CRJAF.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### `methods` is now `options`, and it styles the wallet button
6
+
7
+ `<Checkout>` and `<CheckoutButton>` take `options` where they took `methods`. Everything `methods`
8
+ did works the same under the new name, and `methods` keeps working.
9
+
10
+ New in `options`: `express`, the wallet button. It takes Stripe's own Express Checkout Element
11
+ options — which wallets show (`paymentMethods`), how many buttons per row and how many rows
12
+ (`layout`), and the buttons' height, colour and label (`buttonHeight`, `buttonTheme`, `buttonType`).
13
+
14
+ ```tsx
15
+ <Checkout
16
+ offering="plan"
17
+ options={{ express: { paymentMethods: { link: 'auto' }, layout: { maxColumns: 1 }, buttonHeight: 48 } }}
18
+ />
19
+ ```
20
+
21
+ ### The wallet button offers Apple Pay and Google Pay only
22
+
23
+ The wallet button used to offer every wallet the Stripe account had turned on, Link, PayPal, Amazon
24
+ Pay and Klarna included. It now offers Apple Pay and Google Pay, both `'always'`; the others are off
25
+ unless a funnel names them in `options.express.paymentMethods`. Anything you name is merged over
26
+ that default, one wallet at a time.
27
+
28
+ `'always'` shows Apple Pay and Google Pay wherever the browser supports them, even before the
29
+ visitor has added a card. Set `'auto'` to show them only where a card is ready to pay.
30
+
31
+ ## 2.0.1
32
+
33
+ A checkout that names no offering is now caught, in both places it can be caught.
34
+
35
+ ### A missing `offering` fails the build
36
+
37
+ `<Checkout>`, `<CheckoutButton>` and `<UpsellButton>` must name the offering slot they charge.
38
+ A tag that doesn't now fails the build, with the file and line:
39
+
40
+ ```
41
+ checkout with no `offering`:
42
+ pages/paywall.tsx:27 <Checkout>
43
+ ```
44
+
45
+ This closes a gap where such a tag was invisible end to end. It contributes no offering
46
+ reference, so the manifest's unknown/unassigned-slot validation never saw the slot and the
47
+ publish gate passed with nothing to check — while at runtime the charge resolved to nothing and
48
+ the page shipped with a dead paywall. A funnel build is a bundler transform, so types are
49
+ stripped rather than checked; nothing else in the deploy path was looking.
50
+
51
+ The shape that hits this most often is the pre-2.0 spelling `<Checkout product="plan">`, where
52
+ the offering arrives under the old prop name and `offering` is simply absent.
53
+
54
+ Tags whose offering cannot be read statically are unaffected: `offering={expr}` and a tag
55
+ carrying a spread (`<Checkout {...props} />`) both pass — the same relaxation the rest of the
56
+ page scan applies to anything it cannot enumerate.
57
+
58
+ ### A missing `offering` is refused at runtime
59
+
60
+ For the cases no static scan can see — an `offering={expr}` that evaluated to nothing, a spread
61
+ that did not carry one — the components now refuse instead of resolving nothing. The refusal
62
+ reports through the normal failure path (`checkout.failed`, a non-retryable `processing_error`,
63
+ any `onFailed` routes) and renders nothing chargeable. On `<UpsellButton>` the trigger itself
64
+ never renders: an upsell charges off-session on the first tap, so there must be nothing to tap.
65
+
3
66
  ## 2.0.0
4
67
 
5
68
  First stable release of the v2 SDK, and the first v2 release published to the `latest`
@@ -7,11 +70,14 @@ dist-tag. Everything from `2.0.0-canary.1` through `2.0.0-canary.36` is folded i
7
70
  release; the canary line is closed.
8
71
 
9
72
  **`npm install @appfunnel-dev/sdk` now resolves to 2.0.0, not 0.17.0.** v2 is a rewrite,
10
- not an upgrade — a 0.17 funnel will not build against it. Stay on 0.17 by pinning the
11
- exact version:
73
+ not an upgrade — a 0.17 funnel will not build against it.
74
+
75
+ **The 2.0.x line is the only thing on the registry.** Every 0.x release and the whole
76
+ `2.0.0-canary.*` line have been unpublished, so there is no version to stay on: a funnel
77
+ still pinned to one of them fails its next build at install (`ETARGET`). Move the pin:
12
78
 
13
79
  ```json
14
- { "dependencies": { "@appfunnel-dev/sdk": "0.17.0" } }
80
+ { "dependencies": { "@appfunnel-dev/sdk": "2.0.0" } }
15
81
  ```
16
82
 
17
83
  ### The v2 surface
@@ -1,5 +1,5 @@
1
1
  import { ReactNode, ComponentType, ReactElement } from 'react';
2
- import { Appearance } from '@stripe/stripe-js';
2
+ import { Appearance, StripeExpressCheckoutElementOptions } from '@stripe/stripe-js';
3
3
  import { CheckoutSettings } from '@paddle/paddle-js';
4
4
  import { c as CheckoutProvider, b as CheckoutIntent, d as CheckoutSurface, U as UpsellKind, C as CheckoutError, a as CheckoutErrorCategory, h as PaymentMode, H as HostedPaymentMode, e as OpensIn, i as PaymentRendering } from './capabilities-BtnJi2JD.js';
5
5
 
@@ -99,10 +99,11 @@ interface CheckoutRequest {
99
99
  */
100
100
  appearance?: CheckoutAppearance;
101
101
  /**
102
- * How the provider LISTS the payment methods see {@link CheckoutMethods}. Display-only, same
103
- * as `appearance`: it never leaves the browser.
102
+ * How the provider LISTS the payment methods and draws the wallet button — the author's
103
+ * `options` (see {@link CheckoutOptions}). Display-only, same as `appearance`: it never leaves
104
+ * the browser.
104
105
  */
105
- methods?: CheckoutMethods;
106
+ methods?: CheckoutOptions;
106
107
  /**
107
108
  * Classes for the buttons the SDK renders itself — see {@link CheckoutClassNames}. Display-only.
108
109
  */
@@ -162,22 +163,22 @@ interface CheckoutAppearance {
162
163
  paddle?: Pick<CheckoutSettings, 'theme' | 'frameStyle' | 'frameInitialHeight'>;
163
164
  }
164
165
  /**
165
- * How the list of payment methods is PRESENTED, in the surfaces the SDK mounts itself.
166
+ * How the payment methods are PRESENTED the method list and the wallet button — in the surfaces
167
+ * the SDK mounts itself.
166
168
  *
167
169
  * Normalized, unlike {@link CheckoutAppearance} — and the difference is real rather than an
168
170
  * inconsistency. Appearance is a token bag whose true cross-provider intersection is roughly empty,
169
171
  * so a shared shape there would be a lie. "Stack the methods and start them collapsed" is one idea
170
172
  * with one meaning, so it gets one spelling.
171
173
  *
172
- * The DEFAULT is `{ layout: 'accordion', collapsed: true }`: the buyer sees the methods they can pay
173
- * with wallets included and picks one before any field appears, instead of landing on an open
174
- * card form with the wallet they'd have preferred hidden below it.
174
+ * The DEFAULT is `{ layout: 'accordion', collapsed: false }` the first method open and a wallet
175
+ * button offering Apple Pay and Google Pay (see {@link CheckoutOptions.express}).
175
176
  *
176
177
  * ```tsx
177
178
  * <Checkout offering="plan" /> // accordion, collapsed
178
- * <Checkout offering="plan" methods={{ collapsed: false }} /> // first method open
179
- * <Checkout offering="plan" methods={{ layout: 'tabs' }} /> // methods across the top
180
- * <Checkout offering="plan" methods={{ order: ['apple_pay', 'card'] }} /> // lead with Apple Pay
179
+ * <Checkout offering="plan" options={{ collapsed: false }} /> // first method open
180
+ * <Checkout offering="plan" options={{ layout: 'tabs' }} /> // methods across the top
181
+ * <Checkout offering="plan" options={{ order: ['apple_pay', 'card'] }} /> // lead with Apple Pay
181
182
  * ```
182
183
  *
183
184
  * **Only applies where the SDK mounts the method list** — `payment="card"`, and the `payment="auto"`
@@ -186,7 +187,7 @@ interface CheckoutAppearance {
186
187
  * setting. Wallets also have to be able to appear at all before ordering them means anything — that
187
188
  * needs the domain registered with Stripe as a payment method domain.
188
189
  */
189
- interface CheckoutMethods {
190
+ interface CheckoutOptions {
190
191
  /** `'accordion'` (default) stacks the methods as rows; `'tabs'` runs them across the top. */
191
192
  layout?: 'accordion' | 'tabs';
192
193
  /** Start with every method closed, so the buyer chooses before any fields render. Default `true`. */
@@ -208,7 +209,32 @@ interface CheckoutMethods {
208
209
  * nowhere else to go on this screen. Leave it on unless the funnel is deliberately wallet-only.
209
210
  */
210
211
  payAnotherWay?: boolean;
212
+ /**
213
+ * The wallet BUTTON — Stripe's Express Checkout Element options, passed through as-is: which
214
+ * wallets it offers (`paymentMethods`), how many buttons per row and how many rows (`layout`),
215
+ * and the buttons' height, colour and label (`buttonHeight`, `buttonTheme`, `buttonType`).
216
+ *
217
+ * ```tsx
218
+ * <Checkout offering="plan" options={{ express: { paymentMethods: { link: 'auto' } } }} />
219
+ * <Checkout offering="plan" options={{ express: { layout: { maxColumns: 1 }, buttonHeight: 48 } }} />
220
+ * ```
221
+ *
222
+ * `paymentMethods` defaults to Apple Pay and Google Pay (`'always'`) with Link, PayPal, Amazon Pay
223
+ * and Klarna off (`'never'`), merged per wallet over that default — `{ link: 'auto' }` adds Link
224
+ * and leaves the rest as they were. `'always'` shows Apple Pay / Google Pay wherever the browser
225
+ * supports them, even before the buyer has added a card; `'auto'` only where one is ready to pay.
226
+ * Everything else unset is Stripe's default. Stripe only.
227
+ */
228
+ express?: CheckoutExpressOptions;
211
229
  }
230
+ /**
231
+ * The part of Stripe's `StripeExpressCheckoutElementOptions` that decides what the wallet button
232
+ * offers and how it LOOKS. The rest (line items, shipping, email and phone collection) belongs to the
233
+ * charge, which the SDK sets, so it isn't offered here.
234
+ */
235
+ type CheckoutExpressOptions = Pick<StripeExpressCheckoutElementOptions, 'paymentMethods' | 'paymentMethodOrder' | 'layout' | 'buttonHeight' | 'buttonTheme' | 'buttonType'>;
236
+ /** @deprecated Renamed to {@link CheckoutOptions}. */
237
+ type CheckoutMethods = CheckoutOptions;
212
238
  interface CheckoutResult {
213
239
  ok: boolean;
214
240
  error?: CheckoutError;
@@ -222,6 +248,12 @@ interface CheckoutResult {
222
248
  * source of truth for payment events; the SDK doesn't mint these.)
223
249
  */
224
250
  eventId?: string;
251
+ /**
252
+ * Server-decided acquisition shape. A trial start reports `subscription.trial_started`
253
+ * (→ Meta/TikTok StartTrial), never `purchase.complete`/`subscription.created` — see
254
+ * {@link CheckoutSettlement.acquisition}.
255
+ */
256
+ acquisition?: 'direct' | 'free_trial' | 'paid_trial';
225
257
  }
226
258
  interface CheckoutCallbacks {
227
259
  onSuccess: (result: CheckoutResult) => void;
@@ -502,9 +534,11 @@ interface CheckoutCommonProps extends Omit<UseCheckoutOptions, 'managed'> {
502
534
  * fields render in a cross-origin iframe the funnel's CSS can't reach.
503
535
  */
504
536
  appearance?: CheckoutAppearance;
505
- /** How the provider lists the payment methods ({@link CheckoutMethods}) — accordion + collapsed
506
- * by default. Ignored on surfaces where the provider owns the whole UI. */
507
- methods?: CheckoutMethods;
537
+ /** How the payment methods are listed and how the wallet button looks ({@link CheckoutOptions}).
538
+ * Ignored on surfaces where the provider owns the whole UI. */
539
+ options?: CheckoutOptions;
540
+ /** @deprecated Renamed to `options`. */
541
+ methods?: CheckoutOptions;
508
542
  }
509
543
  /**
510
544
  * The `managed` × `payment` constraint, as a type rather than a runtime rejection.
@@ -741,4 +775,4 @@ declare function UpsellButton(props: UpsellButtonProps): ReactNode;
741
775
  */
742
776
  declare function CheckoutResume(options?: UseCheckoutOptions): ReactNode;
743
777
 
744
- export { useCheckout as A, type CheckoutDriverContext as C, type FailureModalProps as F, type MockDriverOptions as M, type OnFailedMap as O, type PaymentAxis as P, type TriggerAxis as T, UpsellButton as U, type CheckoutDriver as a, Checkout as b, type CheckoutAppearance as c, CheckoutButton as d, type CheckoutButtonProps as e, type CheckoutCallbacks as f, type CheckoutClassNames as g, type CheckoutCommonProps as h, type CheckoutHandle as i, type CheckoutInlineProps as j, type CheckoutMethods as k, type CheckoutProps as l, type CheckoutRequest as m, type CheckoutResult as n, CheckoutResume as o, CheckoutSpinner as p, type CheckoutStatus as q, type FailureRoute as r, type OpensAxis as s, type TriggerState as t, type UpsellButtonProps as u, type UpsellCommonProps as v, type UpsellRecovery as w, type UseCheckoutOptions as x, createMockDriver as y, driverWithEmail as z };
778
+ export { createMockDriver as A, driverWithEmail as B, type CheckoutDriverContext as C, useCheckout as D, type FailureModalProps as F, type MockDriverOptions as M, type OnFailedMap as O, type PaymentAxis as P, type TriggerAxis as T, UpsellButton as U, type CheckoutDriver as a, Checkout as b, type CheckoutAppearance as c, CheckoutButton as d, type CheckoutButtonProps as e, type CheckoutCallbacks as f, type CheckoutClassNames as g, type CheckoutCommonProps as h, type CheckoutExpressOptions as i, type CheckoutHandle as j, type CheckoutInlineProps as k, type CheckoutMethods as l, type CheckoutOptions as m, type CheckoutProps as n, type CheckoutRequest as o, type CheckoutResult as p, CheckoutResume as q, CheckoutSpinner as r, type CheckoutStatus as s, type FailureRoute as t, type OpensAxis as u, type TriggerState as v, type UpsellButtonProps as w, type UpsellCommonProps as x, type UpsellRecovery as y, type UseCheckoutOptions as z };
@@ -1,5 +1,5 @@
1
1
  import { ReactNode, ComponentType, ReactElement } from 'react';
2
- import { Appearance } from '@stripe/stripe-js';
2
+ import { Appearance, StripeExpressCheckoutElementOptions } from '@stripe/stripe-js';
3
3
  import { CheckoutSettings } from '@paddle/paddle-js';
4
4
  import { c as CheckoutProvider, b as CheckoutIntent, d as CheckoutSurface, U as UpsellKind, C as CheckoutError, a as CheckoutErrorCategory, h as PaymentMode, H as HostedPaymentMode, e as OpensIn, i as PaymentRendering } from './capabilities-BtnJi2JD.cjs';
5
5
 
@@ -99,10 +99,11 @@ interface CheckoutRequest {
99
99
  */
100
100
  appearance?: CheckoutAppearance;
101
101
  /**
102
- * How the provider LISTS the payment methods see {@link CheckoutMethods}. Display-only, same
103
- * as `appearance`: it never leaves the browser.
102
+ * How the provider LISTS the payment methods and draws the wallet button — the author's
103
+ * `options` (see {@link CheckoutOptions}). Display-only, same as `appearance`: it never leaves
104
+ * the browser.
104
105
  */
105
- methods?: CheckoutMethods;
106
+ methods?: CheckoutOptions;
106
107
  /**
107
108
  * Classes for the buttons the SDK renders itself — see {@link CheckoutClassNames}. Display-only.
108
109
  */
@@ -162,22 +163,22 @@ interface CheckoutAppearance {
162
163
  paddle?: Pick<CheckoutSettings, 'theme' | 'frameStyle' | 'frameInitialHeight'>;
163
164
  }
164
165
  /**
165
- * How the list of payment methods is PRESENTED, in the surfaces the SDK mounts itself.
166
+ * How the payment methods are PRESENTED the method list and the wallet button — in the surfaces
167
+ * the SDK mounts itself.
166
168
  *
167
169
  * Normalized, unlike {@link CheckoutAppearance} — and the difference is real rather than an
168
170
  * inconsistency. Appearance is a token bag whose true cross-provider intersection is roughly empty,
169
171
  * so a shared shape there would be a lie. "Stack the methods and start them collapsed" is one idea
170
172
  * with one meaning, so it gets one spelling.
171
173
  *
172
- * The DEFAULT is `{ layout: 'accordion', collapsed: true }`: the buyer sees the methods they can pay
173
- * with wallets included and picks one before any field appears, instead of landing on an open
174
- * card form with the wallet they'd have preferred hidden below it.
174
+ * The DEFAULT is `{ layout: 'accordion', collapsed: false }` the first method open and a wallet
175
+ * button offering Apple Pay and Google Pay (see {@link CheckoutOptions.express}).
175
176
  *
176
177
  * ```tsx
177
178
  * <Checkout offering="plan" /> // accordion, collapsed
178
- * <Checkout offering="plan" methods={{ collapsed: false }} /> // first method open
179
- * <Checkout offering="plan" methods={{ layout: 'tabs' }} /> // methods across the top
180
- * <Checkout offering="plan" methods={{ order: ['apple_pay', 'card'] }} /> // lead with Apple Pay
179
+ * <Checkout offering="plan" options={{ collapsed: false }} /> // first method open
180
+ * <Checkout offering="plan" options={{ layout: 'tabs' }} /> // methods across the top
181
+ * <Checkout offering="plan" options={{ order: ['apple_pay', 'card'] }} /> // lead with Apple Pay
181
182
  * ```
182
183
  *
183
184
  * **Only applies where the SDK mounts the method list** — `payment="card"`, and the `payment="auto"`
@@ -186,7 +187,7 @@ interface CheckoutAppearance {
186
187
  * setting. Wallets also have to be able to appear at all before ordering them means anything — that
187
188
  * needs the domain registered with Stripe as a payment method domain.
188
189
  */
189
- interface CheckoutMethods {
190
+ interface CheckoutOptions {
190
191
  /** `'accordion'` (default) stacks the methods as rows; `'tabs'` runs them across the top. */
191
192
  layout?: 'accordion' | 'tabs';
192
193
  /** Start with every method closed, so the buyer chooses before any fields render. Default `true`. */
@@ -208,7 +209,32 @@ interface CheckoutMethods {
208
209
  * nowhere else to go on this screen. Leave it on unless the funnel is deliberately wallet-only.
209
210
  */
210
211
  payAnotherWay?: boolean;
212
+ /**
213
+ * The wallet BUTTON — Stripe's Express Checkout Element options, passed through as-is: which
214
+ * wallets it offers (`paymentMethods`), how many buttons per row and how many rows (`layout`),
215
+ * and the buttons' height, colour and label (`buttonHeight`, `buttonTheme`, `buttonType`).
216
+ *
217
+ * ```tsx
218
+ * <Checkout offering="plan" options={{ express: { paymentMethods: { link: 'auto' } } }} />
219
+ * <Checkout offering="plan" options={{ express: { layout: { maxColumns: 1 }, buttonHeight: 48 } }} />
220
+ * ```
221
+ *
222
+ * `paymentMethods` defaults to Apple Pay and Google Pay (`'always'`) with Link, PayPal, Amazon Pay
223
+ * and Klarna off (`'never'`), merged per wallet over that default — `{ link: 'auto' }` adds Link
224
+ * and leaves the rest as they were. `'always'` shows Apple Pay / Google Pay wherever the browser
225
+ * supports them, even before the buyer has added a card; `'auto'` only where one is ready to pay.
226
+ * Everything else unset is Stripe's default. Stripe only.
227
+ */
228
+ express?: CheckoutExpressOptions;
211
229
  }
230
+ /**
231
+ * The part of Stripe's `StripeExpressCheckoutElementOptions` that decides what the wallet button
232
+ * offers and how it LOOKS. The rest (line items, shipping, email and phone collection) belongs to the
233
+ * charge, which the SDK sets, so it isn't offered here.
234
+ */
235
+ type CheckoutExpressOptions = Pick<StripeExpressCheckoutElementOptions, 'paymentMethods' | 'paymentMethodOrder' | 'layout' | 'buttonHeight' | 'buttonTheme' | 'buttonType'>;
236
+ /** @deprecated Renamed to {@link CheckoutOptions}. */
237
+ type CheckoutMethods = CheckoutOptions;
212
238
  interface CheckoutResult {
213
239
  ok: boolean;
214
240
  error?: CheckoutError;
@@ -222,6 +248,12 @@ interface CheckoutResult {
222
248
  * source of truth for payment events; the SDK doesn't mint these.)
223
249
  */
224
250
  eventId?: string;
251
+ /**
252
+ * Server-decided acquisition shape. A trial start reports `subscription.trial_started`
253
+ * (→ Meta/TikTok StartTrial), never `purchase.complete`/`subscription.created` — see
254
+ * {@link CheckoutSettlement.acquisition}.
255
+ */
256
+ acquisition?: 'direct' | 'free_trial' | 'paid_trial';
225
257
  }
226
258
  interface CheckoutCallbacks {
227
259
  onSuccess: (result: CheckoutResult) => void;
@@ -502,9 +534,11 @@ interface CheckoutCommonProps extends Omit<UseCheckoutOptions, 'managed'> {
502
534
  * fields render in a cross-origin iframe the funnel's CSS can't reach.
503
535
  */
504
536
  appearance?: CheckoutAppearance;
505
- /** How the provider lists the payment methods ({@link CheckoutMethods}) — accordion + collapsed
506
- * by default. Ignored on surfaces where the provider owns the whole UI. */
507
- methods?: CheckoutMethods;
537
+ /** How the payment methods are listed and how the wallet button looks ({@link CheckoutOptions}).
538
+ * Ignored on surfaces where the provider owns the whole UI. */
539
+ options?: CheckoutOptions;
540
+ /** @deprecated Renamed to `options`. */
541
+ methods?: CheckoutOptions;
508
542
  }
509
543
  /**
510
544
  * The `managed` × `payment` constraint, as a type rather than a runtime rejection.
@@ -741,4 +775,4 @@ declare function UpsellButton(props: UpsellButtonProps): ReactNode;
741
775
  */
742
776
  declare function CheckoutResume(options?: UseCheckoutOptions): ReactNode;
743
777
 
744
- export { useCheckout as A, type CheckoutDriverContext as C, type FailureModalProps as F, type MockDriverOptions as M, type OnFailedMap as O, type PaymentAxis as P, type TriggerAxis as T, UpsellButton as U, type CheckoutDriver as a, Checkout as b, type CheckoutAppearance as c, CheckoutButton as d, type CheckoutButtonProps as e, type CheckoutCallbacks as f, type CheckoutClassNames as g, type CheckoutCommonProps as h, type CheckoutHandle as i, type CheckoutInlineProps as j, type CheckoutMethods as k, type CheckoutProps as l, type CheckoutRequest as m, type CheckoutResult as n, CheckoutResume as o, CheckoutSpinner as p, type CheckoutStatus as q, type FailureRoute as r, type OpensAxis as s, type TriggerState as t, type UpsellButtonProps as u, type UpsellCommonProps as v, type UpsellRecovery as w, type UseCheckoutOptions as x, createMockDriver as y, driverWithEmail as z };
778
+ export { createMockDriver as A, driverWithEmail as B, type CheckoutDriverContext as C, useCheckout as D, type FailureModalProps as F, type MockDriverOptions as M, type OnFailedMap as O, type PaymentAxis as P, type TriggerAxis as T, UpsellButton as U, type CheckoutDriver as a, Checkout as b, type CheckoutAppearance as c, CheckoutButton as d, type CheckoutButtonProps as e, type CheckoutCallbacks as f, type CheckoutClassNames as g, type CheckoutCommonProps as h, type CheckoutExpressOptions as i, type CheckoutHandle as j, type CheckoutInlineProps as k, type CheckoutMethods as l, type CheckoutOptions as m, type CheckoutProps as n, type CheckoutRequest as o, type CheckoutResult as p, CheckoutResume as q, CheckoutSpinner as r, type CheckoutStatus as s, type FailureRoute as t, type OpensAxis as u, type TriggerState as v, type UpsellButtonProps as w, type UpsellCommonProps as x, type UpsellRecovery as y, type UseCheckoutOptions as z };
@@ -491,10 +491,23 @@ function interpolate(str, params) {
491
491
  if (!params) return str;
492
492
  return str.replace(/\{\{(\w+)\}\}/g, (_, k) => k in params ? String(params[k]) : `{{${k}}}`);
493
493
  }
494
- function lookup(catalog, chain, key) {
494
+ var baseLang = (locale) => locale.split("-")[0];
495
+ function lookupMessage(catalog, chain, key) {
495
496
  for (const loc of chain) {
496
- const v = catalog[loc]?.[key];
497
- if (v !== void 0) return v;
497
+ const lang = baseLang(loc);
498
+ const exact = catalog[loc]?.[key];
499
+ if (exact !== void 0) return exact;
500
+ if (lang !== loc) {
501
+ const wide = catalog[lang]?.[key];
502
+ if (wide !== void 0) return wide;
503
+ }
504
+ const siblings = Object.keys(catalog).filter(
505
+ (k) => k !== loc && k !== lang && baseLang(k) === lang
506
+ );
507
+ if (siblings.length === 1) {
508
+ const only = catalog[siblings[0]]?.[key];
509
+ if (only !== void 0) return only;
510
+ }
498
511
  }
499
512
  return void 0;
500
513
  }
@@ -538,9 +551,11 @@ function useTranslation() {
538
551
  }, [locale, config]);
539
552
  const t = useMemo(() => {
540
553
  const fn = ((key, params) => {
541
- const raw = lookup(catalog, chain, key);
554
+ const raw = lookupMessage(catalog, chain, key);
542
555
  if (raw === void 0) {
543
- devWarn(`[appfunnel] missing translation: "${key}" (${locale})`);
556
+ devWarn(
557
+ `[appfunnel] missing translation: "${key}" (${locale}; tried ${chain.join(" \u2192 ")})`
558
+ );
544
559
  return key;
545
560
  }
546
561
  return interpolate(raw, params);
@@ -6978,6 +6993,10 @@ function Modal(props) {
6978
6993
  }
6979
6994
 
6980
6995
  // src/commerce/checkout.tsx
6996
+ function refuseOffering(offering, as) {
6997
+ if (typeof offering === "string" && offering.trim() !== "") return void 0;
6998
+ return `<${as}> has no \`offering\` \u2014 there is nothing to charge. Name the offering slot (offering="plan"). If you are migrating from an older SDK, the prop used to be \`product\`.`;
6999
+ }
6981
7000
  function refuseSurface(provider, surface, managed) {
6982
7001
  if (!managed) return void 0;
6983
7002
  const check = validateCheckout(provider, surface, { managed });
@@ -7158,20 +7177,31 @@ function useCheckout(opts = {}) {
7158
7177
  funnel.data.set({ purchased: true });
7159
7178
  const { productId, isSubscription } = productFacts(product);
7160
7179
  const amount = result.amountMinor != null ? result.amountMinor / 100 : void 0;
7161
- tracker.track("purchase.complete", {
7162
- amount,
7163
- currency: result.currency,
7164
- productId,
7165
- eventId: result.eventId
7166
- // server-minted (browser↔CAPI dedup); undefined if none
7167
- });
7168
- if (isSubscription) {
7169
- tracker.track("subscription.created", {
7180
+ const isTrialStart = result.acquisition === "free_trial" || result.acquisition === "paid_trial";
7181
+ if (isTrialStart) {
7182
+ tracker.track("subscription.trial_started", {
7170
7183
  amount,
7171
7184
  currency: result.currency,
7172
7185
  productId,
7173
7186
  eventId: result.eventId
7187
+ // server-minted (browser↔CAPI dedup)
7174
7188
  });
7189
+ } else {
7190
+ tracker.track("purchase.complete", {
7191
+ amount,
7192
+ currency: result.currency,
7193
+ productId,
7194
+ eventId: result.eventId
7195
+ // server-minted (browser↔CAPI dedup); undefined if none
7196
+ });
7197
+ if (isSubscription) {
7198
+ tracker.track("subscription.created", {
7199
+ amount,
7200
+ currency: result.currency,
7201
+ productId,
7202
+ eventId: result.eventId
7203
+ });
7204
+ }
7175
7205
  }
7176
7206
  opts.onSuccess?.(result);
7177
7207
  if (advance) nav.next();
@@ -7542,7 +7572,7 @@ function CheckoutImpl({
7542
7572
  notePreviewCheckout(as, product);
7543
7573
  checkout.callbacksFor(product, surface).onSuccess({ ok: true });
7544
7574
  };
7545
- const rejection = refuseSurface(driver.provider, surface, managed);
7575
+ const rejection = refuseOffering(offering, as) ?? refuseSurface(driver.provider, surface, managed);
7546
7576
  const callbacksRef = useRef(checkout.callbacksFor);
7547
7577
  callbacksRef.current = checkout.callbacksFor;
7548
7578
  useEffect(() => {
@@ -7610,6 +7640,7 @@ function Checkout(props) {
7610
7640
  payment,
7611
7641
  className,
7612
7642
  appearance,
7643
+ options: displayOptions,
7613
7644
  methods,
7614
7645
  classNames,
7615
7646
  ...options
@@ -7620,7 +7651,8 @@ function Checkout(props) {
7620
7651
  offering,
7621
7652
  className,
7622
7653
  appearance,
7623
- methods,
7654
+ // `options` is the prop; `methods` is its old name, still accepted.
7655
+ methods: displayOptions ?? methods,
7624
7656
  classNames,
7625
7657
  as: "Checkout",
7626
7658
  // The axes resolve, and the result is always an INLINE rendering: `<Checkout>` is the payment
@@ -7638,6 +7670,7 @@ function CheckoutButton(props) {
7638
7670
  children,
7639
7671
  className,
7640
7672
  appearance,
7673
+ options: displayOptions,
7641
7674
  methods,
7642
7675
  classNames,
7643
7676
  ...options
@@ -7648,7 +7681,7 @@ function CheckoutButton(props) {
7648
7681
  offering,
7649
7682
  className,
7650
7683
  appearance,
7651
- methods,
7684
+ methods: displayOptions ?? methods,
7652
7685
  classNames,
7653
7686
  asChild,
7654
7687
  children,
@@ -7697,6 +7730,14 @@ function UpsellButton(props) {
7697
7730
  );
7698
7731
  const checkout = useCheckout({ ...options, intent: "upsell", recoverySurface });
7699
7732
  const isSample = useOffering(offering)?.sample === true;
7733
+ const rejection = refuseOffering(offering, "UpsellButton");
7734
+ const callbacksRef = useRef(checkout.callbacksFor);
7735
+ callbacksRef.current = checkout.callbacksFor;
7736
+ useEffect(() => {
7737
+ if (!rejection) return;
7738
+ callbacksRef.current(product).onError(checkoutError("processing_error", rejection, { retryable: false }));
7739
+ }, []);
7740
+ if (rejection) return null;
7700
7741
  return trigger(
7701
7742
  () => {
7702
7743
  if (isSample) {
@@ -9494,5 +9535,5 @@ function useGroupProgress() {
9494
9535
  }
9495
9536
 
9496
9537
  export { Checkout, CheckoutButton, CheckoutResume, CheckoutSpinner, EmbedBridge, FunnelProvider, FunnelToaster, FunnelView, Modal, Sheet, UpsellButton, VariableStore, attachBus, buildAcquisition, buildContext, createBus, createConsoleTracker, createFunnelStore, createMockDriver, defineModal, dismissAllModals, driverWithEmail, ensureCheckoutButtonStyles, exposurePayload, hideModal, isValidEmail, newEventId, registerModal, removeModal, showModal, toast, unregisterModal, useActiveLocale, useCheckout, useClickIds, useContextValue, useData, useDevice, useExperiment, useField, useFunnel, useGroupProgress, useLocale, useMarketingConsent, useModal, useNavigation, useOffering, useOfferings, usePage, useResponse, useSystem, useTracker, useTranslation, useUserAttribute, useUtm, withBus };
9497
- //# sourceMappingURL=chunk-PN7CRJAF.js.map
9498
- //# sourceMappingURL=chunk-PN7CRJAF.js.map
9538
+ //# sourceMappingURL=chunk-AW2YURFE.js.map
9539
+ //# sourceMappingURL=chunk-AW2YURFE.js.map