@wix/himalaya-cli 0.1242.0 → 0.1244.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 (33) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/catalog-adapters/online-programs.d.ts +6 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/catalog-price-fields.d.ts +4 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/catalog-pricing-contract.d.ts +87 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/catalog-pricing-core.d.ts +10 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/catalog-pricing.d.ts +6 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/index.d.ts +4 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/mapping-source.d.ts +5 -0
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/tokens.d.ts +94 -0
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/checkout/src/index.d.ts +3 -0
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/checkout.mjs +3 -0
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +40 -6
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +192 -0
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +3 -0
  14. package/dist/cli.mjs +365 -226
  15. package/dist/preview-assets.generated.bin +0 -0
  16. package/guides/getting-started.md +6 -0
  17. package/guides/himi-skill.md +6 -0
  18. package/guides/iap-pricing.md +162 -211
  19. package/guides/index.json +7 -6
  20. package/guides/sdk-reference.json +96 -0
  21. package/guides/tier-5-add-a-screen.md +9 -0
  22. package/guides/tokens.md +28 -0
  23. package/guides/wix-api-usage.md +11 -0
  24. package/guides/wix-site-to-himi-app.md +1 -1
  25. package/package.json +2 -2
  26. package/templates/branded-lite/dev/index.ts +110 -3
  27. package/templates/branded-lite/tier5-src/_shared/iap-worker.ts +65 -0
  28. package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +179 -52
  29. package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +158 -38
  30. package/templates/yoga-studio/dev/index.ts +110 -3
  31. package/templates/yoga-studio/tier5-src/_shared/iap-worker.ts +65 -0
  32. package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +179 -52
  33. package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +158 -38
@@ -0,0 +1,6 @@
1
+ import type { IapCatalogItem } from "../catalog-pricing-contract.js";
2
+ /** An Online Programs program as a shared IAP catalog item. A program without a Wix ID cannot be
3
+ * mapped (a title fallback is not an identity), so it has no item and its screen shows no price. */
4
+ export declare function onlineProgramItem(program: {
5
+ id?: string | null;
6
+ }, commerceDisplayPrice?: string): IapCatalogItem | undefined;
@@ -0,0 +1,4 @@
1
+ import type { IapItemView, IapPriceCopy, IapPriceFields } from "./catalog-pricing-contract.js";
2
+ /** Bindable display state for one item. A screen spreads these into its own state and binds
3
+ * them; the labels stay in the screen's copy pack. */
4
+ export declare function iapPriceFields(view: IapItemView, copy: IapPriceCopy): IapPriceFields;
@@ -0,0 +1,87 @@
1
+ import type { CatalogReference, PricingMappingProvider, PricingUnavailableReason } from "./pricing-contract.js";
2
+ /** The Wix entity types `himi store iap` writes into its Wix→Apple mapping, so a mapping
3
+ * backend can serve that export without translating kinds. */
4
+ export declare const IAP_CATALOG_KINDS: readonly ["onlineProgram", "storesProduct", "pricingPlan"];
5
+ export type IapCatalogKind = typeof IAP_CATALOG_KINDS[number];
6
+ export interface IapCatalogItem {
7
+ reference: CatalogReference;
8
+ /** The screen's own formatted commerce price; shown only on an explicit regular route. */
9
+ commerceDisplayPrice?: string;
10
+ }
11
+ export interface IapCatalogScope {
12
+ appId: string;
13
+ bundleId: string;
14
+ /** Wix metaSiteId. Omit it to use the connected mapping source's bound site. */
15
+ siteId?: string;
16
+ contextRevision?: string;
17
+ }
18
+ export interface IapCatalogInput {
19
+ scope: IapCatalogScope;
20
+ /** The host's own platform (from `@himalaya/app-info`); only "ios" resolves Apple prices. */
21
+ platform: string;
22
+ items: IapCatalogItem[];
23
+ }
24
+ /** `disabled` means no backend is connected: the screen's existing commerce behavior applies. */
25
+ export type IapPaymentRoute = "disabled" | "regular" | "appleIap" | "resolving" | "unavailable";
26
+ /** Serializable per-item state a screen binds. Carries no credential, transaction or entitlement. */
27
+ export interface IapItemView {
28
+ reference: CatalogReference;
29
+ route: IapPaymentRoute;
30
+ /** Apple: StoreKit's string verbatim. Regular: the caller's commerce price. Otherwise "". */
31
+ displayPrice: string;
32
+ reason?: PricingUnavailableReason;
33
+ retryable: boolean;
34
+ /** True only when the existing commerce checkout may run: disabled or an explicit regular route. */
35
+ canStartRegularCheckout: boolean;
36
+ /** Apple purchasing is not available until a backend verifies transactions. */
37
+ purchasable: false;
38
+ }
39
+ export interface IapCheckoutDecision {
40
+ allowed: boolean;
41
+ route: IapPaymentRoute;
42
+ }
43
+ /** A deployed Wix→Apple mapping backend. Never supplied by a screen. */
44
+ export interface IapMappingSource {
45
+ mapping: PricingMappingProvider;
46
+ /** The site this backend serves, used when the caller passes no site. */
47
+ siteId?: string;
48
+ }
49
+ export interface IapPollDirective {
50
+ id: string;
51
+ action: string;
52
+ intervalMs: number;
53
+ immediate: boolean;
54
+ }
55
+ export interface IapCatalogPricing {
56
+ readonly enabled: boolean;
57
+ /** Batched mapping + Apple lookup for the items a screen shows. */
58
+ load(input: IapCatalogInput): Promise<void>;
59
+ refresh(): Promise<void>;
60
+ view(reference: CatalogReference): IapItemView;
61
+ /** May a commerce flow START now? Re-read immediately before the first mutation: the route as it
62
+ * is now, so a refresh in flight (`resolving`) blocks. */
63
+ checkoutGuard(reference: CatalogReference): IapCheckoutDecision;
64
+ /** May a flow that already STARTED take its next commerce step (e.g. pay after a join)? While no
65
+ * new answer exists (a refresh in flight, or a mapping backend that did not answer) it uses the
66
+ * route the last settled lookup gave for the current scope, so a routine refresh cannot strand a
67
+ * regular checkout halfway; any new answer other than regular stops it. */
68
+ continueCheckoutGuard(reference: CatalogReference): IapCheckoutDecision;
69
+ /** The host-scheduled visible refresh; undefined while disabled. */
70
+ pollDirective(action: string): IapPollDirective | undefined;
71
+ dispose(): void;
72
+ }
73
+ /** Labels come from the screen's copy pack; the shared layer only chooses between them. */
74
+ export interface IapPriceCopy {
75
+ loading: string;
76
+ unavailable: string;
77
+ oneTimePurchase: string;
78
+ purchaseUnavailable: string;
79
+ }
80
+ export interface IapPriceFields {
81
+ priceText: string;
82
+ priceNote: string;
83
+ priceLoading: boolean;
84
+ hidePriceRetry: boolean;
85
+ actionDisabled: boolean;
86
+ actionNote: string;
87
+ }
@@ -0,0 +1,10 @@
1
+ import type { Product } from "./index.js";
2
+ import type { IapCatalogPricing, IapMappingSource } from "./catalog-pricing-contract.js";
3
+ /** The catalog layer with its dependencies explicit. Internal: the public factory supplies the
4
+ * connected mapping source, so a screen cannot pass its own. Tests use it directly. */
5
+ export interface IapCatalogPricingDependencies {
6
+ source: IapMappingSource | undefined;
7
+ getProducts(productIds: string[]): Promise<Product[]>;
8
+ onChange?(): void;
9
+ }
10
+ export declare function createIapCatalogPricingWith(deps: IapCatalogPricingDependencies): IapCatalogPricing;
@@ -0,0 +1,6 @@
1
+ import type { IapCatalogPricing } from "./catalog-pricing-contract.js";
2
+ /** One screen worker's catalog pricing. Activation comes only from the connected mapping
3
+ * source, so a screen cannot turn Apple prices on by supplying its own mapping. */
4
+ export declare function createIapCatalogPricing(options?: {
5
+ onChange?(): void;
6
+ }): IapCatalogPricing;
@@ -3,6 +3,10 @@ export * from "./delivery-contract.js";
3
3
  export * from "./delivery.js";
4
4
  export * from "./pricing-contract.js";
5
5
  export * from "./pricing.js";
6
+ export * from "./catalog-pricing-contract.js";
7
+ export * from "./catalog-pricing.js";
8
+ export * from "./catalog-price-fields.js";
9
+ export * from "./catalog-adapters/online-programs.js";
6
10
  /** The three store product categories. */
7
11
  export type ProductType = "subscription" | "nonConsumable" | "consumable";
8
12
  export interface Product {
@@ -0,0 +1,5 @@
1
+ import type { IapMappingSource } from "./catalog-pricing-contract.js";
2
+ /** The single activation switch for catalog pricing. No Wix→Apple mapping backend is deployed,
3
+ * so every screen stays on its existing commerce flow. Only automated tests substitute this module
4
+ * (at bundle-build time); the backend adapter replaces it when it exists. */
5
+ export declare function iapMappingSource(): IapMappingSource | undefined;
@@ -20,6 +20,9 @@ export interface DesignTokens {
20
20
  materials?: Record<string, MaterialToken>;
21
21
  /** RFC 0059 / #2931 — named surface elevations, paint only. A `Card` wears `card`. */
22
22
  elevations?: Record<string, ElevationToken>;
23
+ /** #3491 — per-control styling the renderers read instead of hard-coded values. Today: the
24
+ * `button` group (per size and per variant). Absent ⇒ every renderer draws exactly as before. */
25
+ controls?: ControlTokens;
23
26
  /** RFC 0021 (bottom-nav extension) — kit-driven tab bar styling. */
24
27
  tabBar?: TabBarStyle;
25
28
  /** RFC 0010/0021 — kit-driven slide-in drawer styling. */
@@ -300,6 +303,97 @@ export declare const TONAL_ELEVATION_ALPHA: readonly number[];
300
303
  /** The tint alpha an `ElevationToken.level` asks for: rounded and clamped to 0–5; anything
301
304
  * non-numeric is 0 (no tint). */
302
305
  export declare function tonalElevationAlpha(level: number | undefined): number;
306
+ /**
307
+ * #3491 — per-control design tokens. One group per control; `button` is the first.
308
+ *
309
+ * "controls": {
310
+ * "button": {
311
+ * "sm": { "height": 36, "paddingH": 13, "text": "bodyEmphasis", "icon": 14 },
312
+ * "variants": { "secondary": { "radius": 4, "borderWidth": 1 } }
313
+ * }
314
+ * }
315
+ *
316
+ * Every field is optional, at every depth. A field that is absent keeps that renderer's
317
+ * built-in value, so a token set with no `controls` block draws every Button exactly as before.
318
+ */
319
+ export interface ControlTokens {
320
+ button?: ButtonTokens;
321
+ }
322
+ /** The three Button sizes, in the order `ButtonProps.size` names them. An unknown or absent size
323
+ * is `md` on every renderer. */
324
+ export declare const BUTTON_SIZES: readonly ["sm", "md", "lg"];
325
+ export type ButtonSize = (typeof BUTTON_SIZES)[number];
326
+ /** #3491 — Button tokens: one entry per size, plus `variants` keyed by `ButtonProps.style`
327
+ * (`primary`, `secondary`, `tertiary`, `link`, `plain`, `destructive`, or any app style). */
328
+ export interface ButtonTokens {
329
+ sm?: ButtonSizeToken;
330
+ md?: ButtonSizeToken;
331
+ lg?: ButtonSizeToken;
332
+ variants?: Record<string, ButtonVariantToken>;
333
+ }
334
+ /** The geometry of one Button size. Lengths are points (iOS/macOS), dp (Android) or CSS px (web). */
335
+ export interface ButtonSizeToken {
336
+ /** The VISIBLE minimum height of the button. A value under {@link BUTTON_MIN_HIT_TARGET} keeps
337
+ * the tap target at that floor through a transparent hit area, never through the visible box. */
338
+ height?: number;
339
+ /** Horizontal padding between the edge and the label. */
340
+ paddingH?: number;
341
+ /** Vertical padding between the edge and the label. */
342
+ paddingV?: number;
343
+ /** A `typography` token name for the label. */
344
+ text?: string;
345
+ /** The leading/trailing glyph (and spinner) size. */
346
+ icon?: number;
347
+ /** Corner radius. A variant's `radius` wins over this one; `ButtonProps.cornerRadius` wins over both. */
348
+ radius?: number;
349
+ }
350
+ /** The paint of one Button variant (`ButtonProps.style`). Colours are colour-token names or `#hex`. */
351
+ export interface ButtonVariantToken {
352
+ /** The background fill. Absent ⇒ the variant's built-in fill (`accentPrimary`/`danger` when
353
+ * filled, none otherwise). */
354
+ fill?: string;
355
+ /** The label, glyph and spinner colour. */
356
+ label?: string;
357
+ /** The outline colour. Absent ⇒ the label colour. */
358
+ border?: string;
359
+ /** The outline width; 0 = no outline. Absent ⇒ 1 for `secondary`, 0 otherwise. */
360
+ borderWidth?: number;
361
+ /** Corner radius; wins over the size's `radius`. */
362
+ radius?: number;
363
+ }
364
+ /** The smallest tap target a Button may have, on every renderer (WCAG 2.5.8 / Apple HIG 44pt).
365
+ * A visible height below it is padded out by a transparent hit area. */
366
+ export declare const BUTTON_MIN_HIT_TARGET = 44;
367
+ /** The Button tokens for one size + variant, merged. Each field is present only when a token sets
368
+ * it to a usable value, so a renderer falls back to its own built-in value field by field. */
369
+ export interface ResolvedButtonTokens {
370
+ height?: number;
371
+ paddingH?: number;
372
+ paddingV?: number;
373
+ text?: string;
374
+ icon?: number;
375
+ radius?: number;
376
+ fill?: string;
377
+ label?: string;
378
+ border?: string;
379
+ borderWidth?: number;
380
+ }
381
+ /**
382
+ * #3491 — resolve `controls.button` for a Button of `size` and `style`. The shared rule every
383
+ * renderer mirrors (iOS `SDUIButtonTokens`, Android `SDUIButtonTokens`, web, React Native):
384
+ *
385
+ * - `size` is `sm` / `lg`, else `md`; `style` absent is `primary`;
386
+ * - a negative or non-finite length, and an empty name, is "not set";
387
+ * - `radius`: the variant's, else the size's.
388
+ *
389
+ * Read defensively: the tokens document is published data, so any level can be malformed.
390
+ */
391
+ export declare function resolveButtonTokens(tokens: {
392
+ controls?: unknown;
393
+ } | null | undefined, size: string | null | undefined, style: string | null | undefined): ResolvedButtonTokens;
394
+ /** The transparent hit area to add above AND below a Button whose visible minimum height is
395
+ * `visibleHeight`, so its tap target reaches {@link BUTTON_MIN_HIT_TARGET}. 0 when it already does. */
396
+ export declare function buttonHitSlop(visibleHeight: number | undefined): number;
303
397
  /** RFC 0020 — a linear gradient. `colors` are color-token NAMES (theme-aware);
304
398
  * `angle` is degrees clockwise from top (0 = top→bottom, 90 = left→right).
305
399
  * `locations` (0..1) optionally positions each stop. */
@@ -66,6 +66,9 @@ export interface PayRequest {
66
66
  currency?: string;
67
67
  /** Mints the in-app cashier URL. Called ONLY when the wallet rail cannot settle. */
68
68
  cashierUrl?: () => Promise<string | undefined>;
69
+ /** Re-read immediately before money moves (the wallet charge, the cashier opening). The caller's
70
+ * own route can change while a rail is being chosen; false stops the flow with nothing charged. */
71
+ mayCharge?: () => boolean;
69
72
  }
70
73
  /**
71
74
  * Collect payment through the two sanctioned rails, in order.
@@ -122,12 +122,14 @@ async function entitlement() {
122
122
  async function isEntitled() {
123
123
  return (await entitlement()).entitled;
124
124
  }
125
+ var WITHDRAWN = { kind: "failed", reason: "the caller withdrew the charge" };
125
126
  async function payOrder(req) {
126
127
  if (!req.snapshotId && !req.cashierUrl) {
127
128
  return { kind: "failed", reason: "no order to settle" };
128
129
  }
129
130
  const availability = await isAvailable().catch(() => "unavailable");
130
131
  if (availability === "full" && req.snapshotId) {
132
+ if (req.mayCharge?.() === false) return WITHDRAWN;
131
133
  const r = await proceedToPayment({
132
134
  appId: req.appId,
133
135
  snapshotId: req.snapshotId,
@@ -148,6 +150,7 @@ async function payOrder(req) {
148
150
  if (req.cashierUrl) {
149
151
  const url = await req.cashierUrl().catch(() => void 0);
150
152
  if (url) {
153
+ if (req.mayCharge?.() === false) return WITHDRAWN;
151
154
  const opened = await open(url, "in_app_browser").catch(() => ({ opened: false }));
152
155
  if (opened.opened) return { kind: "handoff" };
153
156
  }
@@ -7857,6 +7857,36 @@ function tonalElevationAlpha(level) {
7857
7857
  if (typeof level !== "number" || !Number.isFinite(level)) return 0;
7858
7858
  return TONAL_ELEVATION_ALPHA[Math.min(5, Math.max(0, Math.round(level)))];
7859
7859
  }
7860
+ var BUTTON_SIZES = ["sm", "md", "lg"];
7861
+ var BUTTON_MIN_HIT_TARGET = 44;
7862
+ var nonNegative = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : void 0;
7863
+ var nonEmpty = (v) => typeof v === "string" && v !== "" ? v : void 0;
7864
+ var own = (o, k) => o && typeof o === "object" && Object.prototype.hasOwnProperty.call(o, k) ? o[k] : void 0;
7865
+ function resolveButtonTokens(tokens, size, style) {
7866
+ const button = own(own(tokens, "controls"), "button");
7867
+ const sizeKey = size === "sm" || size === "lg" ? size : "md";
7868
+ const s = own(button, sizeKey);
7869
+ const v = own(own(button, "variants"), style || "primary");
7870
+ const out = {};
7871
+ const set = (k, val) => {
7872
+ if (val !== void 0) out[k] = val;
7873
+ };
7874
+ set("height", nonNegative(own(s, "height")));
7875
+ set("paddingH", nonNegative(own(s, "paddingH")));
7876
+ set("paddingV", nonNegative(own(s, "paddingV")));
7877
+ set("text", nonEmpty(own(s, "text")));
7878
+ set("icon", nonNegative(own(s, "icon")));
7879
+ set("radius", nonNegative(own(v, "radius")) ?? nonNegative(own(s, "radius")));
7880
+ set("fill", nonEmpty(own(v, "fill")));
7881
+ set("label", nonEmpty(own(v, "label")));
7882
+ set("border", nonEmpty(own(v, "border")));
7883
+ set("borderWidth", nonNegative(own(v, "borderWidth")));
7884
+ return out;
7885
+ }
7886
+ function buttonHitSlop(visibleHeight) {
7887
+ if (typeof visibleHeight !== "number" || !Number.isFinite(visibleHeight)) return 0;
7888
+ return Math.max(0, (BUTTON_MIN_HIT_TARGET - visibleHeight) / 2);
7889
+ }
7860
7890
  function buildTokenRegistry(tokens) {
7861
7891
  return {
7862
7892
  colors: new Set(Object.keys(tokens.colors ?? {})),
@@ -9269,16 +9299,16 @@ function shouldRevealError(args) {
9269
9299
  return args.touched && !args.focused;
9270
9300
  }
9271
9301
  }
9272
- var nonEmpty = (type) => (v) => typeof v === "string" && v.length > 0 && isValid({ type }, v);
9302
+ var nonEmpty2 = (type) => (v) => typeof v === "string" && v.length > 0 && isValid({ type }, v);
9273
9303
  var validators = {
9274
9304
  // validates: these are top-level CALLS, which esbuild otherwise has to assume
9275
9305
  // might have side effects — and keeping them retained `validate` and every
9276
9306
  // predicate behind it, DOUBLING a Tier-5 bundle that does no validation at all.
9277
- email: /* @__PURE__ */ nonEmpty("email"),
9278
- phone: /* @__PURE__ */ nonEmpty("phone"),
9279
- url: /* @__PURE__ */ nonEmpty("url"),
9280
- number: /* @__PURE__ */ nonEmpty("number"),
9281
- integer: /* @__PURE__ */ nonEmpty("integer")
9307
+ email: /* @__PURE__ */ nonEmpty2("email"),
9308
+ phone: /* @__PURE__ */ nonEmpty2("phone"),
9309
+ url: /* @__PURE__ */ nonEmpty2("url"),
9310
+ number: /* @__PURE__ */ nonEmpty2("number"),
9311
+ integer: /* @__PURE__ */ nonEmpty2("integer")
9282
9312
  };
9283
9313
  function validateAll(rules, values, catalog) {
9284
9314
  const errors = {};
@@ -13404,6 +13434,8 @@ export {
13404
13434
  APP_ID_PATTERN,
13405
13435
  AppRegistryClient,
13406
13436
  BASE44_ORIGINS,
13437
+ BUTTON_MIN_HIT_TARGET,
13438
+ BUTTON_SIZES,
13407
13439
  CHASSIS_GEOMETRY,
13408
13440
  CORE_COMPONENTS,
13409
13441
  DEFAULT_DISTRIBUTION,
@@ -13463,6 +13495,7 @@ export {
13463
13495
  buildPackageShareUrl,
13464
13496
  buildPlayerPreviewUrl,
13465
13497
  buildTokenRegistry,
13498
+ buttonHitSlop,
13466
13499
  chassisOuterSize,
13467
13500
  checkTierCaps,
13468
13501
  chooseCandidate,
@@ -13506,6 +13539,7 @@ export {
13506
13539
  primarySubtag,
13507
13540
  resolveAppIdentity,
13508
13541
  resolveAppLocale,
13542
+ resolveButtonTokens,
13509
13543
  resolveColor,
13510
13544
  resolveIcon,
13511
13545
  resolveIdiom,
@@ -648,6 +648,194 @@ var isObject2 = (value) => value !== null && typeof value === "object" && !Array
648
648
  var nonempty = (value) => typeof value === "string" && value.trim().length > 0;
649
649
  var sameReference = (value, reference) => isObject2(value) && value.kind === reference.kind && value.id === reference.id;
650
650
 
651
+ // ../../core/sdk/iap/src/catalog-pricing-contract.ts
652
+ var IAP_CATALOG_KINDS = ["onlineProgram", "storesProduct", "pricingPlan"];
653
+
654
+ // ../../core/sdk/iap/src/mapping-source.ts
655
+ function iapMappingSource() {
656
+ return void 0;
657
+ }
658
+
659
+ // ../../core/sdk/iap/src/catalog-pricing-core.ts
660
+ var POLL_ID = "iapCatalogPricing";
661
+ var POLL_INTERVAL_MS = 6e4;
662
+ function createIapCatalogPricingWith(deps) {
663
+ return deps.source ? enabledPricing(deps.source, deps) : disabledPricing();
664
+ }
665
+ function disabledPricing() {
666
+ return {
667
+ enabled: false,
668
+ load: () => Promise.resolve(),
669
+ refresh: () => Promise.resolve(),
670
+ view: disabledView,
671
+ checkoutGuard: (reference) => decisionOf(disabledView(reference)),
672
+ continueCheckoutGuard: (reference) => decisionOf(disabledView(reference)),
673
+ pollDirective: () => void 0,
674
+ dispose: () => {
675
+ }
676
+ };
677
+ }
678
+ function enabledPricing(source, deps) {
679
+ let controller;
680
+ let disposed = false;
681
+ const settled = /* @__PURE__ */ new Map();
682
+ let settledScope = "";
683
+ const rememberSettled = (state) => {
684
+ const scopeKey = JSON.stringify(state.scope);
685
+ if (scopeKey !== settledScope) {
686
+ settled.clear();
687
+ settledScope = scopeKey;
688
+ }
689
+ for (const item of state.items) {
690
+ const answered = viewOf(item.reference, item);
691
+ if (!unanswered(answered)) settled.set(referenceKey(item.reference), answered);
692
+ }
693
+ };
694
+ let lookupRevision = 0;
695
+ let answeredRoutes;
696
+ const mapping = {
697
+ resolve: async (request) => {
698
+ const asked = lookupRevision;
699
+ const answer = await source.mapping.resolve(request);
700
+ if (asked === lookupRevision) answeredRoutes = routesAnswered(answer);
701
+ return answer;
702
+ }
703
+ };
704
+ const trackLookup = (state) => {
705
+ if (state.revision !== lookupRevision) {
706
+ lookupRevision = state.revision;
707
+ answeredRoutes = void 0;
708
+ }
709
+ };
710
+ const ensureController = () => controller ?? (controller = createPricingController({
711
+ mapping,
712
+ getProducts: deps.getProducts,
713
+ onState: (state) => {
714
+ trackLookup(state);
715
+ rememberSettled(state);
716
+ deps.onChange?.();
717
+ }
718
+ }));
719
+ const view = (reference) => disposed ? blockedView(reference, "unavailable") : viewOf(reference, controller && itemFor(controller.getState().items, reference));
720
+ const continueCheckoutGuard = (reference) => {
721
+ const current = view(reference);
722
+ if (!unanswered(current)) return decisionOf(current);
723
+ if (answeredRoutes) return routeDecision(answeredRoutes.get(referenceKey(reference)) ?? "unavailable");
724
+ return decisionOf(settled.get(referenceKey(reference)) ?? current);
725
+ };
726
+ return {
727
+ enabled: true,
728
+ load: (input) => disposed ? Promise.resolve() : ensureController().load(pricingInput(input, source)),
729
+ refresh: () => controller?.refresh() ?? Promise.resolve(),
730
+ view,
731
+ checkoutGuard: (reference) => decisionOf(view(reference)),
732
+ continueCheckoutGuard,
733
+ pollDirective: (action) => ({ id: POLL_ID, action, intervalMs: POLL_INTERVAL_MS, immediate: true }),
734
+ dispose: () => {
735
+ disposed = true;
736
+ settled.clear();
737
+ controller?.dispose();
738
+ }
739
+ };
740
+ }
741
+ function pricingInput(input, source) {
742
+ return { scope: pricingScope(input.scope, source.siteId), platform: input.platform, items: input.items };
743
+ }
744
+ function pricingScope(scope, boundSiteId) {
745
+ return {
746
+ appId: scope.appId,
747
+ bundleId: scope.bundleId,
748
+ siteId: siteIdFor(scope.siteId, boundSiteId),
749
+ ...scope.contextRevision === void 0 ? {} : { contextRevision: scope.contextRevision }
750
+ };
751
+ }
752
+ function siteIdFor(callerSiteId, boundSiteId) {
753
+ if (callerSiteId && boundSiteId && callerSiteId !== boundSiteId) return "";
754
+ return callerSiteId || boundSiteId || "";
755
+ }
756
+ var referenceKey = (reference) => JSON.stringify([reference.kind, reference.id]);
757
+ function routesAnswered(answer) {
758
+ const routes = /* @__PURE__ */ new Map();
759
+ for (const entry of Array.isArray(answer) ? answer : []) {
760
+ const reference = entry?.reference;
761
+ if (typeof reference?.kind !== "string" || typeof reference.id !== "string") continue;
762
+ const key = referenceKey(reference);
763
+ const route = entry.route;
764
+ routes.set(key, routes.has(key) ? "unavailable" : route === "regular" || route === "appleIap" ? route : "unavailable");
765
+ }
766
+ return routes;
767
+ }
768
+ function routeDecision(route) {
769
+ return { allowed: route === "regular", route };
770
+ }
771
+ function unanswered(view) {
772
+ return view.route === "resolving" || view.reason === "mappingUnavailable";
773
+ }
774
+ function itemFor(items, reference) {
775
+ return items.find((item) => item.reference.kind === reference.kind && item.reference.id === reference.id);
776
+ }
777
+ function viewOf(reference, item) {
778
+ if (!item || item.status === "loading") return blockedView(reference, "resolving");
779
+ if (item.status === "ready") return readyView(reference, item);
780
+ return unavailableView(reference, item);
781
+ }
782
+ function readyView(reference, item) {
783
+ return item.source === "appleIap" ? { ...blockedView(reference, "appleIap"), displayPrice: item.displayPrice } : { ...regularView(reference), displayPrice: item.displayPrice };
784
+ }
785
+ function unavailableView(reference, item) {
786
+ if (item.reason === "commercePriceUnavailable") return regularView(reference);
787
+ return { ...blockedView(reference, "unavailable"), reason: item.reason, retryable: item.retryable };
788
+ }
789
+ function blockedView(reference, route) {
790
+ return { reference: { kind: reference.kind, id: reference.id }, route, displayPrice: "", retryable: false, canStartRegularCheckout: false, purchasable: false };
791
+ }
792
+ function regularView(reference) {
793
+ return { ...blockedView(reference, "regular"), canStartRegularCheckout: true };
794
+ }
795
+ function disabledView(reference) {
796
+ return { ...blockedView(reference, "disabled"), canStartRegularCheckout: true };
797
+ }
798
+ function decisionOf(view) {
799
+ return { allowed: view.canStartRegularCheckout, route: view.route };
800
+ }
801
+
802
+ // ../../core/sdk/iap/src/catalog-pricing.ts
803
+ function createIapCatalogPricing(options = {}) {
804
+ return createIapCatalogPricingWith({ source: iapMappingSource(), getProducts, onChange: options.onChange });
805
+ }
806
+
807
+ // ../../core/sdk/iap/src/catalog-price-fields.ts
808
+ function iapPriceFields(view, copy) {
809
+ const fields = {
810
+ priceText: "",
811
+ priceNote: "",
812
+ priceLoading: false,
813
+ hidePriceRetry: true,
814
+ actionDisabled: !view.canStartRegularCheckout,
815
+ actionNote: ""
816
+ };
817
+ switch (view.route) {
818
+ case "resolving":
819
+ return { ...fields, priceText: copy.loading, priceLoading: true };
820
+ case "appleIap":
821
+ return { ...fields, priceText: view.displayPrice, priceNote: copy.oneTimePurchase, actionNote: copy.purchaseUnavailable };
822
+ case "unavailable":
823
+ return { ...fields, priceText: copy.unavailable, hidePriceRetry: !view.retryable };
824
+ case "regular":
825
+ return { ...fields, priceText: view.displayPrice };
826
+ case "disabled":
827
+ return fields;
828
+ }
829
+ }
830
+
831
+ // ../../core/sdk/iap/src/catalog-adapters/online-programs.ts
832
+ var ONLINE_PROGRAM = "onlineProgram";
833
+ function onlineProgramItem(program, commerceDisplayPrice) {
834
+ const id = program.id;
835
+ if (typeof id !== "string" || !id.trim()) return void 0;
836
+ return { reference: { kind: ONLINE_PROGRAM, id }, ...commerceDisplayPrice ? { commerceDisplayPrice } : {} };
837
+ }
838
+
651
839
  // ../../core/sdk/iap/src/index.ts
652
840
  async function getPaywallConfig() {
653
841
  const r = await dispatch("capability.iap.getPaywallConfig");
@@ -742,10 +930,12 @@ function decodeTransaction(value) {
742
930
  return { store, signedTransaction, transactionId, originalTransactionId, productId, appAccountToken, environment };
743
931
  }
744
932
  export {
933
+ IAP_CATALOG_KINDS,
745
934
  MAX_AUTOMATIC_ATTEMPTS,
746
935
  MAX_RETRY_DELAY_MS,
747
936
  appleManagedStore,
748
937
  consume,
938
+ createIapCatalogPricing,
749
939
  createPricingController,
750
940
  createPurchaseCoordinator,
751
941
  entitlements,
@@ -753,6 +943,8 @@ export {
753
943
  getPaywallConfig,
754
944
  getProducts,
755
945
  getUnfinishedTransactions,
946
+ iapPriceFields,
947
+ onlineProgramItem,
756
948
  purchase,
757
949
  purchaseForVerification,
758
950
  restore,
@@ -220,12 +220,14 @@ async function entitlement() {
220
220
  return { entitled: false, planName: "", planId: "" };
221
221
  }
222
222
  }
223
+ var WITHDRAWN = { kind: "failed", reason: "the caller withdrew the charge" };
223
224
  async function payOrder(req) {
224
225
  if (!req.snapshotId && !req.cashierUrl) {
225
226
  return { kind: "failed", reason: "no order to settle" };
226
227
  }
227
228
  const availability = await isAvailable().catch(() => "unavailable");
228
229
  if (availability === "full" && req.snapshotId) {
230
+ if (req.mayCharge?.() === false) return WITHDRAWN;
229
231
  const r = await proceedToPayment({
230
232
  appId: req.appId,
231
233
  snapshotId: req.snapshotId,
@@ -246,6 +248,7 @@ async function payOrder(req) {
246
248
  if (req.cashierUrl) {
247
249
  const url = await req.cashierUrl().catch(() => void 0);
248
250
  if (url) {
251
+ if (req.mayCharge?.() === false) return WITHDRAWN;
249
252
  const opened = await open(url, "in_app_browser").catch(() => ({ opened: false }));
250
253
  if (opened.opened) return { kind: "handoff" };
251
254
  }