@appfunnel-dev/sdk 2.0.0-canary.2 → 2.0.0-canary.4

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 (37) hide show
  1. package/dist/{checkout-DiQvRT5q.d.ts → checkout-7Dy6IedP.d.ts} +3 -0
  2. package/dist/{checkout-CZmEvWfC.d.cts → checkout-Dz8cGkB_.d.cts} +3 -0
  3. package/dist/{chunk-7UC5VXOR.js → chunk-AKO6XKXP.js} +28 -8
  4. package/dist/chunk-AKO6XKXP.js.map +1 -0
  5. package/dist/{chunk-VQOD2Z6Q.cjs → chunk-CY4VBSMX.cjs} +5 -3
  6. package/dist/chunk-CY4VBSMX.cjs.map +1 -0
  7. package/dist/{chunk-Z3TWO2PW.cjs → chunk-JSRKA375.cjs} +29 -7
  8. package/dist/chunk-JSRKA375.cjs.map +1 -0
  9. package/dist/{chunk-UIR6TGEW.js → chunk-M6U3FNRW.js} +5 -3
  10. package/dist/chunk-M6U3FNRW.js.map +1 -0
  11. package/dist/driver-paddle.cjs +14 -14
  12. package/dist/driver-paddle.d.cts +1 -1
  13. package/dist/driver-paddle.d.ts +1 -1
  14. package/dist/driver-paddle.js +1 -1
  15. package/dist/driver-stripe.cjs +13 -13
  16. package/dist/driver-stripe.d.cts +1 -1
  17. package/dist/driver-stripe.d.ts +1 -1
  18. package/dist/driver-stripe.js +1 -1
  19. package/dist/index.cjs +93 -54
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +29 -6
  22. package/dist/index.d.ts +29 -6
  23. package/dist/index.js +55 -16
  24. package/dist/index.js.map +1 -1
  25. package/dist/{manifest-DQThneiG.d.cts → manifest-Cr2y1op6.d.cts} +40 -3
  26. package/dist/{manifest-DQThneiG.d.ts → manifest-Cr2y1op6.d.ts} +40 -3
  27. package/dist/manifest-entry.cjs +129 -20
  28. package/dist/manifest-entry.cjs.map +1 -1
  29. package/dist/manifest-entry.d.cts +28 -3
  30. package/dist/manifest-entry.d.ts +28 -3
  31. package/dist/manifest-entry.js +105 -5
  32. package/dist/manifest-entry.js.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/chunk-7UC5VXOR.js.map +0 -1
  35. package/dist/chunk-UIR6TGEW.js.map +0 -1
  36. package/dist/chunk-VQOD2Z6Q.cjs.map +0 -1
  37. package/dist/chunk-Z3TWO2PW.cjs.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ComponentType, ButtonHTMLAttributes } from 'react';
3
- import { V as VariableValue, a as VariableConfig, A as Acquisition, P as Product, D as Direction, F as FunnelContext, R as RuntimeExperiment, b as FunnelSnapshot, c as FunnelDefinition, d as buildContext, e as ProductInput, f as PageMeta, g as PageContext } from './manifest-DQThneiG.cjs';
4
- export { B as BrowserContext, h as BuildContextOptions, C as ClickIdContext, i as CompileInput, j as Condition, k as ConditionOp, l as DeviceContext, E as EdgeCondition, m as ExperimentIssue, n as ExperimentResolution, o as ExperimentValidation, p as ExperimentVariant, q as FlowPage, r as FunnelLocales, s as FunnelManifest, G as Gate, I as IdentityContext, t as Interval, L as LocaleContext, M as ManifestEdge, u as ManifestPage, v as ManifestValidation, w as Money, O as OsContext, x as PageType, y as Predicate, z as ResolvedProduct, H as Route, S as SessionContext, J as SystemContext, U as UtmContext, K as assignVariant, N as bucketingSeed, Q as buildAcquisition, T as buildCatalog, W as compileManifest, X as currencyExponent, Y as defineFunnel, Z as definePage, _ as entryGuard, $ as evaluateCondition, a0 as evaluateGate, a1 as expectedPathLength, a2 as fnv1a, a3 as formatMoney, a4 as formatProduct, a5 as hashToUnit, a6 as isRtl, a7 as isVariantKey, a8 as nextPage, a9 as outgoingKeys, aa as pageMeta, ab as parseSlotKey, ac as pickByWeight, ad as resolveExperiments, ae as resolveLocale, af as resolveProduct, ag as resolveRoute, ah as validateExperiments } from './manifest-DQThneiG.cjs';
5
- import { a as CheckoutDriver, C as CheckoutDriverContext } from './checkout-CZmEvWfC.cjs';
6
- export { b as Checkout, c as CheckoutCallbacks, d as CheckoutHandle, e as CheckoutProps, f as CheckoutRequest, g as CheckoutResult, h as CheckoutResume, i as CheckoutStatus, F as FailureModalProps, j as FailureRoute, M as MockDriverOptions, O as OnFailedMap, U as Upsell, k as UpsellProps, l as UseCheckoutOptions, m as createMockDriver, n as driverWithEmail, u as useCheckout } from './checkout-CZmEvWfC.cjs';
3
+ import { V as VariableValue, a as VariableConfig, A as Acquisition, P as Product, D as Direction, F as FunnelContext, R as RuntimeExperiment, b as FunnelSnapshot, c as FunnelDefinition, d as buildContext, e as ProductInput, f as PageMeta, g as PageContext } from './manifest-Cr2y1op6.cjs';
4
+ export { B as BrowserContext, h as BuildContextOptions, C as ClickIdContext, i as CompileInput, j as Condition, k as ConditionOp, l as DeviceContext, E as EdgeCondition, m as ExperimentIssue, n as ExperimentResolution, o as ExperimentValidation, p as ExperimentVariant, q as FlowPage, r as FunnelLocales, s as FunnelManifest, G as Gate, I as IdentityContext, t as Interval, L as LocaleContext, M as ManifestEdge, u as ManifestPage, v as ManifestValidation, w as Money, O as OsContext, x as PageType, y as Predicate, z as ResolvedProduct, H as Route, S as SessionContext, J as SystemContext, U as UtmContext, K as assignVariant, N as bucketingSeed, Q as buildAcquisition, T as buildCatalog, W as compileManifest, X as currencyExponent, Y as defineFunnel, Z as definePage, _ as entryGuard, $ as evaluateCondition, a0 as evaluateGate, a1 as expectedPathLength, a2 as fnv1a, a3 as formatMoney, a4 as formatProduct, a5 as hashToUnit, a6 as isRtl, a7 as isVariantKey, a8 as nextPage, a9 as outgoingKeys, aa as pageMeta, ab as parseSlotKey, ac as pickByWeight, ad as resolveExperiments, ae as resolveLocale, af as resolveProduct, ag as resolveRoute, ah as validateExperiments } from './manifest-Cr2y1op6.cjs';
5
+ import { a as CheckoutDriver, C as CheckoutDriverContext } from './checkout-Dz8cGkB_.cjs';
6
+ export { b as Checkout, c as CheckoutCallbacks, d as CheckoutHandle, e as CheckoutProps, f as CheckoutRequest, g as CheckoutResult, h as CheckoutResume, i as CheckoutStatus, F as FailureModalProps, j as FailureRoute, M as MockDriverOptions, O as OnFailedMap, U as Upsell, k as UpsellProps, l as UseCheckoutOptions, m as createMockDriver, n as driverWithEmail, u as useCheckout } from './checkout-Dz8cGkB_.cjs';
7
7
  import { ToasterProps } from 'sonner';
8
8
  export { ToasterProps, toast } from 'sonner';
9
9
  export { C as CheckoutError, a as CheckoutErrorCategory, b as CheckoutIntent, c as CheckoutProvider, d as CheckoutSurface, I as INLINE_SURFACES, O as OffSessionReliability, P as PROVIDER_PROFILES, e as ProviderProfile, S as SurfaceCapability, U as UpsellKind, V as ValidationResult, f as checkoutError, i as isInlineSurface, g as isMerchantOfRecord, h as isOrchestrator, s as surfacesFor, v as validateCheckout, j as validateUpsell } from './capabilities-7_hy5f5G.cjs';
@@ -113,8 +113,16 @@ interface FunnelEventDataMap {
113
113
  priceId?: string;
114
114
  productName?: string;
115
115
  surface?: string;
116
+ amount?: number;
117
+ currency?: string;
118
+ eventId?: string;
119
+ };
120
+ 'checkout.payment_added': {
121
+ productId?: string;
122
+ amount?: number;
123
+ currency?: string;
124
+ eventId?: string;
116
125
  };
117
- 'checkout.payment_added': Record<string, never>;
118
126
  'checkout.failed': {
119
127
  category: string;
120
128
  declineCode?: string;
@@ -126,6 +134,13 @@ interface FunnelEventDataMap {
126
134
  currency?: string;
127
135
  email?: string;
128
136
  eventId?: string;
137
+ productId?: string;
138
+ };
139
+ 'subscription.created': {
140
+ amount?: number;
141
+ currency?: string;
142
+ productId?: string;
143
+ eventId?: string;
129
144
  };
130
145
  'experiment.exposure': {
131
146
  experimentId: string;
@@ -518,6 +533,8 @@ interface MountOpts {
518
533
  initialKey?: string;
519
534
  /** Asset map: logical name → content-addressed CDN URL. */
520
535
  assets?: Record<string, string>;
536
+ /** Per-locale message catalogs (`messages/<locale>.json`) — overrides the mount's baked-in ones. */
537
+ messages?: MessageCatalog;
521
538
  /** The SIGNED anonymous visitor id (bare id, verified server-side; phase-1 §1.2) — the
522
539
  * renderer mints/reads the `af_vid` cookie and injects the value here. Feeds
523
540
  * `context.identity.visitorId` (experiment bucketing fallback seed, session continuity). */
@@ -581,6 +598,12 @@ interface CreateFunnelTreeInput {
581
598
  * resolves the provider's TEST store from it (that's the quarantine, not the client).
582
599
  */
583
600
  checkoutDriver?: (ctx: CheckoutDriverContext) => CheckoutDriver;
601
+ /**
602
+ * The funnel's message catalogs (`messages/<locale>.json`), keyed by locale. The generated
603
+ * `mount.tsx` imports the catalogs and passes them here so `t()` resolves at render (SSR + client);
604
+ * `MountOpts.messages` can still override per-request. Omit for funnels with no translations.
605
+ */
606
+ messages?: MessageCatalog;
584
607
  }
585
608
  /**
586
609
  * Build a funnel's `tree(opts)` — the standard provider stack over your pages.
@@ -590,7 +613,7 @@ interface CreateFunnelTreeInput {
590
613
  * export const tree = createFunnelTree({ config, pages, layout: Layout })
591
614
  * ```
592
615
  */
593
- declare function createFunnelTree({ config, pages, layout, checkoutDriver, }: CreateFunnelTreeInput): (opts: MountOpts) => ReactNode;
616
+ declare function createFunnelTree({ config, pages, layout, checkoutDriver, messages, }: CreateFunnelTreeInput): (opts: MountOpts) => ReactNode;
594
617
 
595
618
  /**
596
619
  * The two-tier component model (doc 07 §"magic ≠ black box"): the magic lives in
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ComponentType, ButtonHTMLAttributes } from 'react';
3
- import { V as VariableValue, a as VariableConfig, A as Acquisition, P as Product, D as Direction, F as FunnelContext, R as RuntimeExperiment, b as FunnelSnapshot, c as FunnelDefinition, d as buildContext, e as ProductInput, f as PageMeta, g as PageContext } from './manifest-DQThneiG.js';
4
- export { B as BrowserContext, h as BuildContextOptions, C as ClickIdContext, i as CompileInput, j as Condition, k as ConditionOp, l as DeviceContext, E as EdgeCondition, m as ExperimentIssue, n as ExperimentResolution, o as ExperimentValidation, p as ExperimentVariant, q as FlowPage, r as FunnelLocales, s as FunnelManifest, G as Gate, I as IdentityContext, t as Interval, L as LocaleContext, M as ManifestEdge, u as ManifestPage, v as ManifestValidation, w as Money, O as OsContext, x as PageType, y as Predicate, z as ResolvedProduct, H as Route, S as SessionContext, J as SystemContext, U as UtmContext, K as assignVariant, N as bucketingSeed, Q as buildAcquisition, T as buildCatalog, W as compileManifest, X as currencyExponent, Y as defineFunnel, Z as definePage, _ as entryGuard, $ as evaluateCondition, a0 as evaluateGate, a1 as expectedPathLength, a2 as fnv1a, a3 as formatMoney, a4 as formatProduct, a5 as hashToUnit, a6 as isRtl, a7 as isVariantKey, a8 as nextPage, a9 as outgoingKeys, aa as pageMeta, ab as parseSlotKey, ac as pickByWeight, ad as resolveExperiments, ae as resolveLocale, af as resolveProduct, ag as resolveRoute, ah as validateExperiments } from './manifest-DQThneiG.js';
5
- import { a as CheckoutDriver, C as CheckoutDriverContext } from './checkout-DiQvRT5q.js';
6
- export { b as Checkout, c as CheckoutCallbacks, d as CheckoutHandle, e as CheckoutProps, f as CheckoutRequest, g as CheckoutResult, h as CheckoutResume, i as CheckoutStatus, F as FailureModalProps, j as FailureRoute, M as MockDriverOptions, O as OnFailedMap, U as Upsell, k as UpsellProps, l as UseCheckoutOptions, m as createMockDriver, n as driverWithEmail, u as useCheckout } from './checkout-DiQvRT5q.js';
3
+ import { V as VariableValue, a as VariableConfig, A as Acquisition, P as Product, D as Direction, F as FunnelContext, R as RuntimeExperiment, b as FunnelSnapshot, c as FunnelDefinition, d as buildContext, e as ProductInput, f as PageMeta, g as PageContext } from './manifest-Cr2y1op6.js';
4
+ export { B as BrowserContext, h as BuildContextOptions, C as ClickIdContext, i as CompileInput, j as Condition, k as ConditionOp, l as DeviceContext, E as EdgeCondition, m as ExperimentIssue, n as ExperimentResolution, o as ExperimentValidation, p as ExperimentVariant, q as FlowPage, r as FunnelLocales, s as FunnelManifest, G as Gate, I as IdentityContext, t as Interval, L as LocaleContext, M as ManifestEdge, u as ManifestPage, v as ManifestValidation, w as Money, O as OsContext, x as PageType, y as Predicate, z as ResolvedProduct, H as Route, S as SessionContext, J as SystemContext, U as UtmContext, K as assignVariant, N as bucketingSeed, Q as buildAcquisition, T as buildCatalog, W as compileManifest, X as currencyExponent, Y as defineFunnel, Z as definePage, _ as entryGuard, $ as evaluateCondition, a0 as evaluateGate, a1 as expectedPathLength, a2 as fnv1a, a3 as formatMoney, a4 as formatProduct, a5 as hashToUnit, a6 as isRtl, a7 as isVariantKey, a8 as nextPage, a9 as outgoingKeys, aa as pageMeta, ab as parseSlotKey, ac as pickByWeight, ad as resolveExperiments, ae as resolveLocale, af as resolveProduct, ag as resolveRoute, ah as validateExperiments } from './manifest-Cr2y1op6.js';
5
+ import { a as CheckoutDriver, C as CheckoutDriverContext } from './checkout-7Dy6IedP.js';
6
+ export { b as Checkout, c as CheckoutCallbacks, d as CheckoutHandle, e as CheckoutProps, f as CheckoutRequest, g as CheckoutResult, h as CheckoutResume, i as CheckoutStatus, F as FailureModalProps, j as FailureRoute, M as MockDriverOptions, O as OnFailedMap, U as Upsell, k as UpsellProps, l as UseCheckoutOptions, m as createMockDriver, n as driverWithEmail, u as useCheckout } from './checkout-7Dy6IedP.js';
7
7
  import { ToasterProps } from 'sonner';
8
8
  export { ToasterProps, toast } from 'sonner';
9
9
  export { C as CheckoutError, a as CheckoutErrorCategory, b as CheckoutIntent, c as CheckoutProvider, d as CheckoutSurface, I as INLINE_SURFACES, O as OffSessionReliability, P as PROVIDER_PROFILES, e as ProviderProfile, S as SurfaceCapability, U as UpsellKind, V as ValidationResult, f as checkoutError, i as isInlineSurface, g as isMerchantOfRecord, h as isOrchestrator, s as surfacesFor, v as validateCheckout, j as validateUpsell } from './capabilities-7_hy5f5G.js';
@@ -113,8 +113,16 @@ interface FunnelEventDataMap {
113
113
  priceId?: string;
114
114
  productName?: string;
115
115
  surface?: string;
116
+ amount?: number;
117
+ currency?: string;
118
+ eventId?: string;
119
+ };
120
+ 'checkout.payment_added': {
121
+ productId?: string;
122
+ amount?: number;
123
+ currency?: string;
124
+ eventId?: string;
116
125
  };
117
- 'checkout.payment_added': Record<string, never>;
118
126
  'checkout.failed': {
119
127
  category: string;
120
128
  declineCode?: string;
@@ -126,6 +134,13 @@ interface FunnelEventDataMap {
126
134
  currency?: string;
127
135
  email?: string;
128
136
  eventId?: string;
137
+ productId?: string;
138
+ };
139
+ 'subscription.created': {
140
+ amount?: number;
141
+ currency?: string;
142
+ productId?: string;
143
+ eventId?: string;
129
144
  };
130
145
  'experiment.exposure': {
131
146
  experimentId: string;
@@ -518,6 +533,8 @@ interface MountOpts {
518
533
  initialKey?: string;
519
534
  /** Asset map: logical name → content-addressed CDN URL. */
520
535
  assets?: Record<string, string>;
536
+ /** Per-locale message catalogs (`messages/<locale>.json`) — overrides the mount's baked-in ones. */
537
+ messages?: MessageCatalog;
521
538
  /** The SIGNED anonymous visitor id (bare id, verified server-side; phase-1 §1.2) — the
522
539
  * renderer mints/reads the `af_vid` cookie and injects the value here. Feeds
523
540
  * `context.identity.visitorId` (experiment bucketing fallback seed, session continuity). */
@@ -581,6 +598,12 @@ interface CreateFunnelTreeInput {
581
598
  * resolves the provider's TEST store from it (that's the quarantine, not the client).
582
599
  */
583
600
  checkoutDriver?: (ctx: CheckoutDriverContext) => CheckoutDriver;
601
+ /**
602
+ * The funnel's message catalogs (`messages/<locale>.json`), keyed by locale. The generated
603
+ * `mount.tsx` imports the catalogs and passes them here so `t()` resolves at render (SSR + client);
604
+ * `MountOpts.messages` can still override per-request. Omit for funnels with no translations.
605
+ */
606
+ messages?: MessageCatalog;
584
607
  }
585
608
  /**
586
609
  * Build a funnel's `tree(opts)` — the standard provider stack over your pages.
@@ -590,7 +613,7 @@ interface CreateFunnelTreeInput {
590
613
  * export const tree = createFunnelTree({ config, pages, layout: Layout })
591
614
  * ```
592
615
  */
593
- declare function createFunnelTree({ config, pages, layout, checkoutDriver, }: CreateFunnelTreeInput): (opts: MountOpts) => ReactNode;
616
+ declare function createFunnelTree({ config, pages, layout, checkoutDriver, messages, }: CreateFunnelTreeInput): (opts: MountOpts) => ReactNode;
594
617
 
595
618
  /**
596
619
  * The two-tier component model (doc 07 §"magic ≠ black box"): the magic lives in
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { isRtl, formatProduct, bucketingSeed, resolveExperiments, entryGuard, evaluateGate, expectedPathLength, nextPage, outgoingKeys, buildCatalog, resolveLocale } from './chunk-7UC5VXOR.js';
2
- export { assignVariant, bucketingSeed, buildCatalog, compileManifest, currencyExponent, defineFunnel, definePage, entryGuard, evaluateCondition, evaluateGate, expectedPathLength, fnv1a, formatMoney, formatProduct, hashToUnit, isRtl, isVariantKey, nextPage, outgoingKeys, pageMeta, parseSlotKey, pickByWeight, resolveExperiments, resolveLocale, resolveProduct, resolveRoute, validateExperiments } from './chunk-7UC5VXOR.js';
1
+ import { isRtl, formatProduct, bucketingSeed, resolveExperiments, entryGuard, evaluateGate, expectedPathLength, nextPage, outgoingKeys, buildCatalog, resolveLocale } from './chunk-AKO6XKXP.js';
2
+ export { assignVariant, bucketingSeed, buildCatalog, compileManifest, currencyExponent, defineFunnel, definePage, entryGuard, evaluateCondition, evaluateGate, expectedPathLength, fnv1a, formatMoney, formatProduct, hashToUnit, isRtl, isVariantKey, nextPage, outgoingKeys, pageMeta, parseSlotKey, pickByWeight, resolveExperiments, resolveLocale, resolveProduct, resolveRoute, validateExperiments } from './chunk-AKO6XKXP.js';
3
3
  import { checkoutError, isInlineSurface, PROVIDER_PROFILES } from './chunk-YY375F2B.js';
4
4
  export { INLINE_SURFACES, PROVIDER_PROFILES, checkoutError, isInlineSurface, isMerchantOfRecord, isOrchestrator, surfacesFor, validateCheckout, validateUpsell } from './chunk-YY375F2B.js';
5
5
  import React, { createContext, createElement, useCallback, useContext, useMemo, lazy, useRef, useState, useEffect, useTransition, Suspense, Fragment, isValidElement, cloneElement, useReducer, useSyncExternalStore } from 'react';
@@ -1098,6 +1098,14 @@ function driverWithEmail(driver, getEmail) {
1098
1098
  renderInline: driver.renderInline ? (req, cb) => driver.renderInline(enrich(req), cb) : void 0
1099
1099
  };
1100
1100
  }
1101
+ function driverWithCatalog(driver, catalog) {
1102
+ const enrich = (req) => req.catalogKey !== void 0 ? req : { ...req, catalogKey: catalog.get(req.product)?.catalogKey };
1103
+ return {
1104
+ ...driver,
1105
+ start: (req) => driver.start(enrich(req)),
1106
+ renderInline: driver.renderInline ? (req, cb) => driver.renderInline(enrich(req), cb) : void 0
1107
+ };
1108
+ }
1101
1109
  var DriverContext = createContext(null);
1102
1110
  function CheckoutDriverProvider({
1103
1111
  driver,
@@ -1112,12 +1120,25 @@ var FALLBACK_DRIVER = createMockDriver();
1112
1120
  function useCheckout(opts = {}) {
1113
1121
  const driver = useDriver();
1114
1122
  const tracker = useTrackerRef();
1123
+ const catalog = useCatalog();
1115
1124
  const nav = useNavigation();
1116
1125
  const [status, setStatus] = useState("idle");
1117
1126
  const [error, setError] = useState(null);
1118
1127
  const intent = opts.intent ?? "purchase";
1119
1128
  const kind = opts.kind;
1120
1129
  const advance = opts.advanceOnSuccess ?? true;
1130
+ const productFacts = useCallback(
1131
+ (product) => {
1132
+ const resolved = product ? catalog.get(product) : void 0;
1133
+ return {
1134
+ productId: resolved?.catalogKey ?? product,
1135
+ amount: resolved ? resolved.priceMinor / 100 : void 0,
1136
+ currency: resolved?.currency,
1137
+ isSubscription: resolved ? resolved.period !== "one-time" : kind === "subscription" || kind === "upgrade"
1138
+ };
1139
+ },
1140
+ [catalog, kind]
1141
+ );
1121
1142
  const recovering = useRef(false);
1122
1143
  const failRef = useRef(() => {
1123
1144
  });
@@ -1125,18 +1146,24 @@ function useCheckout(opts = {}) {
1125
1146
  async () => ({ ok: false })
1126
1147
  );
1127
1148
  const succeed = useCallback(
1128
- (result) => {
1149
+ (result, product) => {
1129
1150
  setStatus("success");
1151
+ const { productId, isSubscription } = productFacts(product);
1152
+ const amount = result.amountMinor != null ? result.amountMinor / 100 : void 0;
1130
1153
  tracker.track("purchase.complete", {
1131
- amount: result.amountMinor != null ? result.amountMinor / 100 : void 0,
1154
+ amount,
1132
1155
  currency: result.currency,
1156
+ productId,
1133
1157
  eventId: result.eventId
1134
1158
  // server-minted (browser↔CAPI dedup); undefined if none
1135
1159
  });
1160
+ if (isSubscription) {
1161
+ tracker.track("subscription.created", { amount, currency: result.currency, productId, eventId: result.eventId });
1162
+ }
1136
1163
  opts.onSuccess?.(result);
1137
1164
  if (advance) nav.next();
1138
1165
  },
1139
- [tracker, nav, advance, opts]
1166
+ [tracker, nav, advance, opts, productFacts]
1140
1167
  );
1141
1168
  const succeedRef = useRef(succeed);
1142
1169
  succeedRef.current = succeed;
@@ -1144,11 +1171,18 @@ function useCheckout(opts = {}) {
1144
1171
  (product, surface) => {
1145
1172
  setStatus("loading");
1146
1173
  setError(null);
1147
- tracker.track("checkout.start", { productId: product, surface });
1174
+ const { productId, amount, currency } = productFacts(product);
1175
+ tracker.track("checkout.start", { productId, amount, currency, surface, eventId: newEventId() });
1148
1176
  },
1149
- [tracker]
1177
+ [tracker, productFacts]
1178
+ );
1179
+ const paymentAdded = useCallback(
1180
+ (product) => {
1181
+ const { productId, amount, currency } = productFacts(product);
1182
+ tracker.track("checkout.payment_added", { productId, amount, currency, eventId: newEventId() });
1183
+ },
1184
+ [tracker, productFacts]
1150
1185
  );
1151
- const paymentAdded = useCallback(() => tracker.track("checkout.payment_added", {}), [tracker]);
1152
1186
  const hostInSheet = (surface) => surface === "sheet" || isInlineSurface(surface) && !!PROVIDER_PROFILES[driver.provider]?.surfaces.sheet;
1153
1187
  const startRecovery = (product, surface) => {
1154
1188
  recovering.current = true;
@@ -1159,9 +1193,9 @@ function useCheckout(opts = {}) {
1159
1193
  surface: "sheet",
1160
1194
  intent,
1161
1195
  kind,
1162
- onSuccess: (r) => succeedRef.current(r),
1196
+ onSuccess: (r) => succeedRef.current(r, product),
1163
1197
  onError: (e) => failRef.current(e, product),
1164
- onPaymentAdded: () => paymentAdded()
1198
+ onPaymentAdded: () => paymentAdded(product)
1165
1199
  });
1166
1200
  } else {
1167
1201
  void attemptRef.current(product, surface);
@@ -1215,7 +1249,7 @@ function useCheckout(opts = {}) {
1215
1249
  begin(product, surface);
1216
1250
  try {
1217
1251
  const result = await driver.start({ product, intent, surface, kind });
1218
- if (result.ok) succeedRef.current(result);
1252
+ if (result.ok) succeedRef.current(result, product);
1219
1253
  else failRef.current(result.error ?? checkoutError("unknown", "Checkout failed"), product);
1220
1254
  return result;
1221
1255
  } catch (e) {
@@ -1234,9 +1268,9 @@ function useCheckout(opts = {}) {
1234
1268
  recovering.current = false;
1235
1269
  begin(product, surface);
1236
1270
  },
1237
- onSuccess: (result) => succeedRef.current(result),
1271
+ onSuccess: (result) => succeedRef.current(result, product),
1238
1272
  onError: (err) => failRef.current(err, product),
1239
- onPaymentAdded: () => paymentAdded()
1273
+ onPaymentAdded: () => paymentAdded(product)
1240
1274
  }),
1241
1275
  [begin, paymentAdded]
1242
1276
  );
@@ -2567,12 +2601,15 @@ function FunnelProvider({
2567
2601
  const value2 = s.get("user.email");
2568
2602
  return typeof value2 === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value2) ? value2 : void 0;
2569
2603
  };
2604
+ const catalog = buildCatalog(products ?? []);
2570
2605
  return {
2571
2606
  funnel: createFunnel(s, ctx, busTracker, experiments ?? []),
2572
2607
  store: s,
2573
2608
  context: ctx,
2574
- catalog: buildCatalog(products ?? []),
2575
- driver: driverWithEmail(checkout ?? createMockDriver(), emailForCheckout),
2609
+ catalog,
2610
+ // Slot catalog-key resolution wraps the email-enriching driver, so the wire charges the real
2611
+ // catalog key while pages + analytics keep the durable SLOT identity (req.product).
2612
+ driver: driverWithCatalog(driverWithEmail(checkout ?? createMockDriver(), emailForCheckout), catalog),
2576
2613
  tracker: busTracker,
2577
2614
  bus
2578
2615
  };
@@ -2844,7 +2881,8 @@ function createFunnelTree({
2844
2881
  config,
2845
2882
  pages,
2846
2883
  layout,
2847
- checkoutDriver
2884
+ checkoutDriver,
2885
+ messages
2848
2886
  }) {
2849
2887
  return function tree(opts) {
2850
2888
  const mode = opts.mode ?? "live";
@@ -2877,6 +2915,7 @@ function createFunnelTree({
2877
2915
  children: createElement(FunnelProvider, {
2878
2916
  config,
2879
2917
  products: opts.products ?? [],
2918
+ messages: opts.messages ?? messages,
2880
2919
  locale: opts.locale,
2881
2920
  tracker,
2882
2921
  checkout,