@wix/himalaya-cli 0.1572.0 → 0.1574.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 (103) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/app-module.d.ts +2 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/message-format.d.ts +59 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/checkout/src/index.d.ts +4 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +1 -1
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-contract.d.ts +2 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/index.d.ts +1 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/one-app-page.d.ts +61 -0
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/social-feed/src/index.d.ts +34 -9
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/checkout.mjs +28 -3
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +3 -3
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +2 -1
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/l10n.mjs +8 -3
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +573 -73
  14. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +3 -3
  15. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-social-feed.mjs +24 -1
  16. package/dist/cli.mjs +2708 -1182
  17. package/dist/preview-assets.generated.bin +0 -0
  18. package/guides/himi-json-output.md +41 -2
  19. package/guides/himi-mcp.md +1 -1
  20. package/guides/himi-test.md +3 -2
  21. package/guides/iap-products.md +1 -1
  22. package/guides/importing-oneapp-translations.md +115 -0
  23. package/guides/index.json +6 -0
  24. package/guides/one-app-section-mappings.md +16 -4
  25. package/guides/one-app-to-himi-app.md +221 -10
  26. package/guides/qa-builds.md +21 -1
  27. package/guides/sdk-reference.json +28 -4
  28. package/guides/templates.md +3 -2
  29. package/guides/testing-himi-apps.md +1 -1
  30. package/guides/webview-page-sdk.md +68 -3
  31. package/package.json +3 -3
  32. package/templates/beauty-salon/dev/index.ts +22 -6
  33. package/templates/beauty-salon/tier5-src/_shared/iap-worker.ts +2 -0
  34. package/templates/beauty-salon/tier5-src/_shared/payment-setup.ts +6 -0
  35. package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +5 -2
  36. package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  37. package/templates/beauty-salon/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  38. package/templates/beauty-salon-owner/dev/index.ts +22 -6
  39. package/templates/branded-lite/dev/index.ts +22 -6
  40. package/templates/branded-lite/tier5-src/_shared/iap-worker.ts +2 -0
  41. package/templates/branded-lite/tier5-src/_shared/payment-setup.ts +6 -0
  42. package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +5 -2
  43. package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  44. package/templates/branded-lite/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  45. package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +5 -2
  46. package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +9 -4
  47. package/templates/community-club/dev/index.ts +22 -6
  48. package/templates/community-club/tier5-src/_shared/iap-worker.ts +2 -0
  49. package/templates/community-club/tier5-src/_shared/payment-setup.ts +6 -0
  50. package/templates/community-club/tier5-src/himi-events-detail/index.ts +9 -4
  51. package/templates/community-club-owner/dev/index.ts +22 -6
  52. package/templates/events-meetups/dev/index.ts +22 -6
  53. package/templates/events-meetups/tier5-src/_shared/iap-worker.ts +2 -0
  54. package/templates/events-meetups/tier5-src/_shared/payment-setup.ts +6 -0
  55. package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +9 -4
  56. package/templates/events-meetups-owner/dev/index.ts +22 -6
  57. package/templates/gym-fitness/dev/index.ts +22 -6
  58. package/templates/gym-fitness/tier5-src/_shared/iap-worker.ts +2 -0
  59. package/templates/gym-fitness/tier5-src/_shared/payment-setup.ts +6 -0
  60. package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +5 -2
  61. package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  62. package/templates/gym-fitness/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  63. package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +9 -4
  64. package/templates/gym-fitness-owner/dev/index.ts +22 -6
  65. package/templates/himi-chat/dev/index.ts +22 -6
  66. package/templates/nonprofit-cause/dev/index.ts +22 -6
  67. package/templates/nonprofit-cause/tier5-src/_shared/iap-worker.ts +2 -0
  68. package/templates/nonprofit-cause/tier5-src/_shared/payment-setup.ts +6 -0
  69. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  70. package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +9 -4
  71. package/templates/nonprofit-cause-owner/dev/index.ts +22 -6
  72. package/templates/owner-lite/dev/index.ts +22 -6
  73. package/templates/podcast-creator/dev/index.ts +22 -6
  74. package/templates/podcast-creator/tier5-src/_shared/iap-worker.ts +2 -0
  75. package/templates/podcast-creator/tier5-src/_shared/payment-setup.ts +6 -0
  76. package/templates/podcast-creator/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  77. package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +9 -4
  78. package/templates/podcast-creator-owner/dev/index.ts +22 -6
  79. package/templates/portfolio-creative/dev/index.ts +22 -6
  80. package/templates/portfolio-creative/tier5-src/_shared/iap-worker.ts +2 -0
  81. package/templates/portfolio-creative/tier5-src/_shared/payment-setup.ts +6 -0
  82. package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  83. package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +9 -4
  84. package/templates/portfolio-creative-owner/dev/index.ts +22 -6
  85. package/templates/restaurant/dev/index.ts +22 -6
  86. package/templates/restaurant/tier5-src/_shared/iap-worker.ts +2 -0
  87. package/templates/restaurant/tier5-src/_shared/payment-setup.ts +6 -0
  88. package/templates/restaurant/tier5-src/himi-events-detail/index.ts +9 -4
  89. package/templates/restaurant-owner/dev/index.ts +22 -6
  90. package/templates/retail-store/dev/index.ts +22 -6
  91. package/templates/retail-store/tier5-src/_shared/iap-worker.ts +2 -0
  92. package/templates/retail-store/tier5-src/_shared/payment-setup.ts +6 -0
  93. package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  94. package/templates/retail-store-owner/dev/index.ts +22 -6
  95. package/templates/surface-showcase/dev/index.ts +22 -6
  96. package/templates/yoga-studio/dev/index.ts +22 -6
  97. package/templates/yoga-studio/tier5-src/_shared/iap-worker.ts +2 -0
  98. package/templates/yoga-studio/tier5-src/_shared/payment-setup.ts +6 -0
  99. package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +5 -2
  100. package/templates/yoga-studio/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  101. package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +5 -2
  102. package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +9 -4
  103. package/templates/yoga-studio-owner/dev/index.ts +22 -6
@@ -2771,7 +2771,7 @@
2771
2771
  "name": "OrderFailureCode",
2772
2772
  "kind": "type",
2773
2773
  "jsdoc": "",
2774
- "decl": "export type OrderFailureCode =\n | \"notEnabled\" | \"notConfigured\" | \"signInRequired\" | \"unsupportedItem\" | \"notAppleRoute\"\n | \"storageUnavailable\" | \"approvalRequired\" | \"orderClosed\" | \"orderRefused\" | \"chargeFailed\"\n | \"paymentRefused\" | \"paymentHeld\" | \"alreadyOwned\" | \"paymentInProgress\" | \"productChanged\"\n | \"invalidAssociation\" | \"needsSupport\" | \"unavailable\" | \"sessionChanged\" | \"outcomeUnknown\";"
2774
+ "decl": "export type OrderFailureCode =\n | \"notEnabled\" | \"notConfigured\" | \"signInRequired\" | \"unsupportedItem\" | \"notAppleRoute\"\n | \"storageUnavailable\" | \"approvalRequired\" | \"orderClosed\" | \"orderRefused\" | \"chargeFailed\"\n | \"paymentRefused\" | \"paymentHeld\" | \"alreadyOwned\" | \"paymentInProgress\" | \"productChanged\"\n | \"sitePaymentUnavailable\" | \"invalidAssociation\" | \"needsSupport\" | \"unavailable\" | \"sessionChanged\" | \"outcomeUnknown\";"
2775
2775
  },
2776
2776
  {
2777
2777
  "name": "PaymentAccess",
@@ -2849,7 +2849,7 @@
2849
2849
  "name": "IapPurchaseCopy",
2850
2850
  "kind": "interface",
2851
2851
  "jsdoc": "/** Labels from the screen's copy pack; the shared layer only chooses between them. */",
2852
- "decl": "export interface IapPurchaseCopy {\n buy: string;\n buyAnother?: string;\n processing: string;\n awaitingApproval: string;\n unavailable: string;\n signInRequired: string;\n retry: string;\n priceChanged: string;\n support: string;\n}"
2852
+ "decl": "export interface IapPurchaseCopy {\n buy: string;\n buyAnother?: string;\n processing: string;\n awaitingApproval: string;\n unavailable: string;\n signInRequired: string;\n retry: string;\n /** Optional for existing SDK consumers; the app supplies a localized recovery message. */\n sitePaymentUnavailable?: string;\n priceChanged: string;\n support: string;\n}"
2853
2853
  },
2854
2854
  {
2855
2855
  "name": "IapPurchaseFields",
@@ -25560,13 +25560,37 @@
25560
25560
  "jsdoc": "",
25561
25561
  "decl": "export interface WixListFeedItemsResponse {\n feedItems: WixFeedItem[];\n profiles?: WixFeedProfile[];\n nextPageCursor?: string;\n}"
25562
25562
  },
25563
+ {
25564
+ "name": "WixFeedPageOptions",
25565
+ "kind": "interface",
25566
+ "jsdoc": "/** Read controls shared by the source's single-group and central feeds. */",
25567
+ "decl": "export interface WixFeedPageOptions extends WixCallOptions {\n /** Opaque server cursor; pass it back unchanged when loading the next page. */\n cursor?: string;\n invalidatePermissionsCache?: boolean;\n}"
25568
+ },
25569
+ {
25570
+ "name": "WixGroupFeedOptions",
25571
+ "kind": "interface",
25572
+ "jsdoc": "",
25573
+ "decl": "export interface WixGroupFeedOptions extends WixFeedPageOptions {\n topicId?: string;\n}"
25574
+ },
25575
+ {
25576
+ "name": "WixCentralFeedOptions",
25577
+ "kind": "interface",
25578
+ "jsdoc": "",
25579
+ "decl": "export interface WixCentralFeedOptions extends WixFeedPageOptions {\n withSuggestedPosts?: boolean;\n /** Omit to use the central-feed service's default representation. */\n contentType?: \"DRAFTJS\";\n}"
25580
+ },
25563
25581
  {
25564
25582
  "name": "listFeedItems",
25565
25583
  "kind": "function",
25566
- "jsdoc": "/**\n * List a group's activity feed — GET\n * https://social.wix.com/serverless/mobile/feed/v4/feedPosts (the ENRICHED read the\n * real mobile app uses). Each item carries content (Ricos blocks), commentsCount, and\n * reactionsList (count = length); the response's top-level `profiles[]` resolves each\n * item's author.id → userName + profileImage. Verified live (200). Replaces the lean\n * GET /api/v1/feed/items read, which omitted counts + author.\n *\n * @scope member — verified live with a signed-in member session; visitor/anonymous\n * readability of a public group's feed is unconfirmed.\n */",
25567
- "decl": "export function listFeedItems(groupId: string, limit = 20, options?: WixCallOptions): Promise<WixListFeedItemsResponse>;",
25584
+ "jsdoc": "/**\n * List one group's activity feed. Mirrors wix-one-app-feed getFeedPosts:\n * containerId + Groups appDefId + feedType=all + contentType=DRAFTJS, with optional\n * topic and opaque paging cursor. Existing site/cache options remain per-call.\n * Reactions lists must not be treated as authoritative totals.\n *\n * @scope member — verified live with a signed-in member session; visitor readability\n * depends on group permissions and is not established by the central-feed probe.\n */",
25585
+ "decl": "export function listFeedItems(groupId: string, limit = 20, options?: WixGroupFeedOptions): Promise<WixListFeedItemsResponse>;",
25568
25586
  "scope": "member"
25569
25587
  },
25588
+ {
25589
+ "name": "listCentralFeedItems",
25590
+ "kind": "function",
25591
+ "jsdoc": "/**\n * List the site's combined feed, rather than substituting a selected group's feed.\n * Mirrors wix-one-app-feed getCentralFeedPosts: there is no containerId or appDefId.\n * Authentication and business selection belong to the bridge (or per-call `site`).\n *\n * Source: wix-one-app-feed@6ef53170e94e27349d2a6c1cf08038b078d86228,\n * src/services/feed-api.ts. A real Visitor read returned 200 with posts and a cursor.\n * That establishes the server contract only: native consumers need a core release with the exact read-route guard, and\n * production web still refuses social.wix.com. This SDK method does\n * not change those policies or establish membership/write permission. Consumers must\n * surface a transport refusal; they must not substitute a group directory or empty feed.\n */",
25592
+ "decl": "export function listCentralFeedItems(limit = 20, options?: WixCentralFeedOptions): Promise<WixListFeedItemsResponse>;"
25593
+ },
25570
25594
  {
25571
25595
  "name": "createFeedItem",
25572
25596
  "kind": "function",
@@ -106,8 +106,9 @@ details beside the app:
106
106
  | `.himi/himi-test.stub.json` | a typed-journey skeleton per form (every field, then its submit) with `<FILL>` slots |
107
107
 
108
108
  `bootsOffline` is the consent preflight: it is `false` when any screen's `onAppear` dispatches into
109
- a worker that can reach Wix or the network, and `bootRequests` names each one. It is static and errs
110
- on the safe side ("may request"). The commands behind it:
109
+ a worker that can reach Wix or the network, or when a screen boots such a worker that calls an
110
+ action while its module evaluates (`svc.load();` at the top level). `bootRequests` names each one,
111
+ with `trigger: "onAppear"` or `"module-load"`. It is static and errs on the safe side ("may request"). The commands behind it:
111
112
 
112
113
  ```sh
113
114
  himi template recommend --text "a salon where clients book and buy products" # rank the catalog (or --verticals / --book)
@@ -267,7 +267,7 @@ render, local browser, zero network. `--no-shots` opts out (sweeps should).
267
267
 
268
268
  Absence is said, never silent: without the browser the report carries
269
269
  `shots.unavailable` and the ledger names the one-time remedy —
270
- `npx playwright install chromium`. Web fidelity, honestly named: these are the web
270
+ the install command `himi render --png` prints. Web fidelity, honestly named: these are the web
271
271
  renderer's pixels; native truth still comes from a device (`himi push` a preview).
272
272
 
273
273
  Hygiene: a **full** run owns `.himi/shots/` and clears it first, so a deleted screen
@@ -94,7 +94,28 @@ every message none of them claims, including a handler registered directly on
94
94
  `himalaya.webview.onPageMessage` after `@himalaya/webview` loaded).
95
95
 
96
96
  ```ts
97
- import { emitToPage, onPageLifecycle, onPageRequest, openPageStream } from "@himalaya/webview";
97
+ import { defineService } from "@himalaya/state";
98
+ import { emitToPage, onPageLifecycle, onPageRequest, openPageStream, type PageStream } from "@himalaya/webview";
99
+
100
+ // The worker has NO setTimeout / setInterval on any renderer. A stream that produces over time
101
+ // is paced by the host clock: an action returns a `_poll`, and each tick sends the next chunk.
102
+ const watching = new Set<PageStream>();
103
+ let armed = false; // re-arming a live id with `immediate` fires an extra tick at once — arm once
104
+ const steps = defineService("steps", {
105
+ state: {},
106
+ actions: {
107
+ arm: (s) => ({ ...s, _poll: { id: "steps", intervalMs: 1000, action: "steps.tick", immediate: true } }),
108
+ tick: (s) => {
109
+ for (const stream of watching) {
110
+ if (stream.closed) watching.delete(stream); // ended, or the page broke out of its loop
111
+ else stream.next({ steps: Math.random() });
112
+ }
113
+ if (watching.size) return s;
114
+ armed = false;
115
+ return { ...s, _poll: { id: "steps", stop: true } };
116
+ },
117
+ },
118
+ });
98
119
 
99
120
  onPageRequest("nano", {
100
121
  // (args, ctx) → value | promise. ctx = { instanceId, requestId, active }.
@@ -102,8 +123,8 @@ onPageRequest("nano", {
102
123
  // Return a stream handle to answer with many chunks.
103
124
  "steps.watch": (_args, ctx) => {
104
125
  const stream = openPageStream(ctx.instanceId, ctx.requestId);
105
- const timer = setInterval(() => stream.next({ steps: Math.random() }), 1000);
106
- stream.onCancel(() => clearInterval(timer)); // the page broke out of its loop
126
+ watching.add(stream);
127
+ if (!armed) { armed = true; void steps.arm(); }
107
128
  return stream;
108
129
  },
109
130
  });
@@ -112,6 +133,9 @@ emitToPage("*", "nano.theme.changed", { dark: true }); // or one instance id
112
133
  const off = onPageLifecycle(({ instanceId, state }) => console.log(instanceId, state)); // mounted … unmounted
113
134
  ```
114
135
 
136
+ A timer in a worker is not a style choice: `setInterval` here throws `Can't find variable` on the
137
+ device, and the page sees the request fail (`_poll`: [Tier-5 guide](tier-5-add-a-screen.md#refresh-on-a-cadence-with-_poll-the-worker-has-no-clock)).
138
+
115
139
  - `ctx.instanceId` is the WebView the **host** says the request came from; a page-sent `iid` is
116
140
  ignored. `ctx.active` is live: `false` between `inactive` and `active` lifecycle events.
117
141
  - A namespace — and an exposed capability id — follows the capability-id pattern
@@ -255,3 +279,44 @@ The page is served at `/web/webview-sdk.html`. Its companion worker is
255
279
  probes. Safe probes run on load.
256
280
  Verbs that prompt, write, or open system UI stay allowlisted and are not called. Streaming
257
281
  helpers that return a live object stay on the worker.
282
+
283
+ ## Embedding worker-bridged WebViews
284
+
285
+ A Tier-5 SDUI screen embeds any number of `WebView` nodes that talk to the **owning screen's
286
+ worker** over the same channel: set `bridge: "worker"` on the node, declare `"webview"`
287
+ in the screen's `logic_bundle`, and give each widget an explicit `instance_id` (inside a
288
+ `LazyList`/`Carousel` item template it must be item-derived, e.g. `"row-$item.id"`). Untrusted or
289
+ LLM-generated inline HTML additionally wants `origin_isolation: "per_instance"` (its own origin +
290
+ storage per widget), `frame_policy: "content"` (top frame pinned, bridge main-frame-only),
291
+ `bridge_security: { profile: "app_content" }` (multi-line-friendly caps) and `console_forwarding`.
292
+ The host injects the page client at document start, so the page imports nothing; an app alias
293
+ (`inject_js_document_start: "window.demoSdk = himalaya.demo;"`) runs after it, and the platform
294
+ knows no app names. On web (phase 1), only an app-controlled **cross-origin `url`** page listed in
295
+ `allowed_origins` is bridged; an inline (`srcdoc`) widget stays unbridged and the worker hears
296
+ `mounted` with `detail: { bridged: false, reason: "web_srcdoc_unbridged" }` — show a fallback.
297
+
298
+ A complete shape to copy — the demo app's **Embedded widgets** screen (`demo_app_embedded_widgets`)
299
+ is built exactly this way:
300
+
301
+ - three isolated inline widgets in a `Carousel` with `live_window: 1`, so only the current widget
302
+ and its neighbours are mounted (swiping to widget 3 unmounts widget 1), plus one bridged
303
+ cross-origin `url` page — all served by **one** worker;
304
+ - the worker answers `onPageRequest("demo", { echo, bump, stream })`: a request, a per-instance
305
+ counter that answers with `emitToPage(ctx.instanceId, "demo.tick", n)`, and a 5-chunk stream the
306
+ page can stop (`break` out of its loop), paced by a host `_poll` as above;
307
+ - `muted_path`, `active_path` and one widget's `reload_key_path` bound to worker state, and an
308
+ `onPageLifecycle` handler that shows each widget's last state;
309
+ - each widget keeps its own `localStorage` counter, which survives a relaunch and is never visible
310
+ to its neighbours.
311
+
312
+ Test such a screen by the document, not only by lifecycle envelopes: put state in a widget's page
313
+ (an echo reply), swipe away and back, and check the state survived where the widget should have
314
+ stayed mounted. **Crash recovery:** when a widget's content process dies, the host sends `crashed`
315
+ and then remounts it (`reloaded`). On iOS and macOS each widget has its own content process, so the
316
+ page exhausting memory (the demo's "Crash widget 3" does) kills only that widget; killing the
317
+ `com.apple.WebKit.WebContent` process serving it by hand on a simulator triggers the same two
318
+ envelopes. **On Android, don't use a memory hog:** one renderer process serves every WebView of the
319
+ app — the screen's own worker included — so the hog takes them all down, and repeated renderer
320
+ crashes exhaust the worker's recovery budget. Trigger it by hand instead: kill the app's sandboxed
321
+ WebView process (`adb shell ps -A | grep sandboxed_process`, then `adb shell kill <pid>`) and watch
322
+ for `crashed` then `reloaded`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1572.0",
3
+ "version": "0.1574.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@modelcontextprotocol/server": "^2.0.0",
38
- "@rive-app/canvas": "^2.21.0",
38
+ "@rive-app/canvas": "2.38.3",
39
39
  "ajv": "^8.17.1",
40
40
  "ajv-formats": "^3.0.1",
41
41
  "cheerio": "^1.0.0",
@@ -71,5 +71,5 @@
71
71
  "artifactId": "himalaya-cli"
72
72
  }
73
73
  },
74
- "falconPackageHash": "20229381643935ec0f9390addc404f459d7040e7dfd5b634ab41385c"
74
+ "falconPackageHash": "3931950af337466648e08fa667462e52b854969f6441f512f82b9241"
75
75
  }
@@ -41003,15 +41003,31 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
41003
41003
 
41004
41004
  // Live seed-id enumeration: a published release serves only EXACT screens, so
41005
41005
  // `himi push` bakes each templated detail route's CURRENT catalog ids as exact
41006
- // screens via `seedIds`. Anonymous visitor rung against this app's own clientId;
41007
- // best-effort (offline build → no baked details, preview still live-resolves).
41006
+ // screens via `seedIds`. Anonymous visitor rung against this app's own clientId — or,
41007
+ // for an app bound through the One App visitor identity (`visitorSource: "one-app"`,
41008
+ // no client), that site's One App visitor instance. Best-effort (offline build → no
41009
+ // baked details, preview still live-resolves).
41010
+ async function liveSeedToken(WIX: string): Promise<string | undefined> {
41011
+ const auth = (config as { auth?: { config?: { clientId?: string; visitorSource?: string; metaSiteId?: string } } }).auth?.config;
41012
+ // A One App binding boots as the site's One App visitor, so the seed reads as that visitor too —
41013
+ // even beside a member-login clientId, and never repointed by a stray local WIX_CLIENT_ID.
41014
+ const oneApp = auth?.visitorSource === "one-app";
41015
+ const clientId = oneApp ? "" : process.env.WIX_CLIENT_ID ?? auth?.clientId ?? "";
41016
+ if (clientId && !clientId.startsWith("YOUR_")) {
41017
+ const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
41018
+ return ((await mint.json()) as { access_token?: string }).access_token;
41019
+ }
41020
+ const msid = oneApp ? auth?.metaSiteId ?? "" : "";
41021
+ if (!/^[0-9a-fA-F-]{10,64}$/.test(msid)) return undefined;
41022
+ const mint = await fetch(`https://apps.wix.com/_api/one-app-session-web/v3/businesses/${msid}/signed-instance?signed_instance_type=Visitor`, { headers: { Accept: "application/json" }, redirect: "error" });
41023
+ const visitor = (await mint.json()) as { signedInstance?: string; signedInstanceType?: string };
41024
+ return visitor.signedInstanceType === "Visitor" ? visitor.signedInstance : undefined;
41025
+ }
41026
+
41008
41027
  async function liveSeedIds(spec: LiveSeed): Promise<string[]> {
41009
41028
  try {
41010
41029
  const WIX = "https://www.wixapis.com";
41011
- const clientId = process.env.WIX_CLIENT_ID ?? (config as { auth?: { config?: { clientId?: string } } }).auth?.config?.clientId ?? "";
41012
- if (!clientId || clientId.startsWith("YOUR_")) return [];
41013
- const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
41014
- const token = ((await mint.json()) as { access_token?: string }).access_token;
41030
+ const token = await liveSeedToken(WIX);
41015
41031
  if (!token) return [];
41016
41032
  const res = await fetch(WIX + spec.path, { method: "POST", headers: { Authorization: token, "Content-Type": "application/json" }, body: JSON.stringify(spec.body) });
41017
41033
  const json = (await res.json()) as Record<string, unknown>;
@@ -4,6 +4,7 @@
4
4
  // SDK; this only supplies the copy and the hydration reset.
5
5
  import { hostSessionFromGlobal, type IapPriceCopy } from "@himalaya/iap";
6
6
  import type { IapCommerce, IapPurchaseCopy, IapRestoreResult } from "@himalaya/iap-checkout";
7
+ import { paymentSetupMessage } from "./payment-setup.js";
7
8
  import { copyParam, copyTemplate } from "./himi-copy.js";
8
9
 
9
10
  /** Price labels from the screen's copy pack (`<prefix>.priceLoadingText`, …). */
@@ -25,6 +26,7 @@ export function iapPurchaseCopy(prefix: string): IapPurchaseCopy {
25
26
  awaitingApproval: copyParam(`${prefix}.purchaseAwaitingApprovalText`, "Waiting for approval"),
26
27
  unavailable: copyParam(`${prefix}.purchaseTemporarilyUnavailableText`, "Purchasing is temporarily unavailable."),
27
28
  signInRequired: copyParam(`${prefix}.purchaseSignInText`, "Sign in to buy."),
29
+ sitePaymentUnavailable: paymentSetupMessage(prefix),
28
30
  retry: copyParam(`${prefix}.purchaseRetryText`, "Something went wrong. Try again."),
29
31
  priceChanged: copyParam(`${prefix}.purchasePriceChangedText`, "The price changed. Check it and try again."),
30
32
  support: copyParam(`${prefix}.purchaseSupportText`, "We couldn't finish this purchase. Contact support."),
@@ -0,0 +1,6 @@
1
+ import { copyParam } from "./himi-copy.js";
2
+
3
+ /** No provider or country guess: Wix only established that online payment is unavailable. */
4
+ export function paymentSetupMessage(prefix: string): string {
5
+ return copyParam(`${prefix}.paymentSetupUnavailable`, "This site is not accepting online payments. Go back or contact the business for other payment options.");
6
+ }
@@ -1,3 +1,4 @@
1
+ import { paymentSetupMessage } from "../_shared/payment-setup.js";
1
2
  import { withOnlineWrites, offlineWritesEnabled } from "../_shared/online-actions.js";
2
3
  // Tier-5 bundle: himi-booking — availability and member booking.
3
4
  import { defineService as registerService, navigate } from "@himalaya/state";
@@ -9,7 +10,7 @@ import { dispatch } from "@himalaya/dispatch";
9
10
  import { bookSchedule, bookSlot, getService, queryServiceAvailability, registerToWaitlist, type WixBookingSlotFull, type WixSelectedPaymentOption, type WixService } from "@himalaya/wix-bookings";
10
11
  import { createCheckout, createOrder } from "@himalaya/wix-ecom";
11
12
  import { createRedirectSession, type WixSlot } from "@himalaya/wix-redirects";
12
- import { payCashierOrder } from "@himalaya/checkout";
13
+ import { isPaymentSetupFailure, payCashierOrder } from "@himalaya/checkout";
13
14
  import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
14
15
  import { session as wixPlatformSession } from "@himalaya/wix-platform";
15
16
  import { displayName, getMyData, type WixMyData } from "@himalaya/wix-users";
@@ -1587,10 +1588,12 @@ async function bookSelectedIn(s: S): Promise<S> {
1587
1588
  ? copyParam("time.coursePaymentCancelled", "Payment cancelled — your course booking wasn't confirmed. Try again.")
1588
1589
  : copyParam("time.paymentCancelled", "Payment cancelled — your booking wasn't confirmed. Pick a time to try again.") });
1589
1590
  }
1591
+ if (isPaymentSetupFailure(pay)) return derive({ ...s, bookingInFlight: false, message: paymentSetupMessage("time") });
1590
1592
  return derive({ ...s, bookingInFlight: false, message: s.isCourse
1591
1593
  ? copyParam("time.coursePaymentFailed", "Payment couldn't be completed. Try again.")
1592
1594
  : copyParam("time.paymentFailed", "Payment couldn't be completed. Pick another time or try again.") });
1593
- } catch {
1595
+ } catch (error) {
1596
+ if (isPaymentSetupFailure(error)) return derive({ ...s, bookingInFlight: false, message: paymentSetupMessage("time") });
1594
1597
  return derive({ ...s, bookingInFlight: false, message: s.isCourse
1595
1598
  ? copyParam("time.courseCheckoutFailed", "Checkout could not start. Try again.")
1596
1599
  : copyParam("time.checkoutFailed", "Checkout could not start. Pick another time or try again.") });
@@ -1,3 +1,4 @@
1
+ import { paymentSetupMessage } from "../_shared/payment-setup.js";
1
2
  import { withOnlineWrites } from "../_shared/online-actions.js";
2
3
  import { defineService as registerState } from "@himalaya/state";
3
4
  import { getStoreProduct } from "../_shared/himi-store.js";
@@ -7,7 +8,7 @@ import { withIapPresentation } from "../_shared/iap-presentation.js";
7
8
  import { type IapCommerce, classifyCart } from "@himalaya/iap-checkout";
8
9
  import { addCouponToCurrentCart, calculateCurrentCart, createCheckoutFromCurrentCart, getCurrentCart, removeCouponFromCurrentCart, removeLineItemsFromCurrentCart, updateLineItemsQuantity, type WixCartDetail, type WixCartLineItemDetail } from "@himalaya/wix-ecom";
9
10
  import { createRedirectSession } from "@himalaya/wix-redirects";
10
- import { payCashierOrder } from "@himalaya/checkout";
11
+ import { isPaymentSetupFailure, payCashierOrder } from "@himalaya/checkout";
11
12
  import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
12
13
  import { formatCurrency } from "../_shared/engine-intl.js";
13
14
 
@@ -574,8 +575,10 @@ const svc = defineService<State, {
574
575
  // only confirmed once it lands, so the cart must survive a bail-out.
575
576
  return derive({ ...state, checkoutHint: copyParam("cart.checkoutOpenedMessage", "Checkout opened.") });
576
577
  }
578
+ if (isPaymentSetupFailure(pay)) return derive({ ...state, checkoutHint: paymentSetupMessage("cart") });
577
579
  return derive({ ...state, checkoutHint: copyParam("cart.checkoutFailedMessage", "Checkout could not start.") });
578
- } catch {
580
+ } catch (error) {
581
+ if (isPaymentSetupFailure(error)) return derive({ ...state, checkoutHint: paymentSetupMessage("cart") });
579
582
  return derive({ ...state, checkoutHint: copyParam("cart.checkoutFailedMessage", "Checkout could not start.") });
580
583
  }
581
584
  },
@@ -1,3 +1,4 @@
1
+ import { paymentSetupMessage } from "../_shared/payment-setup.js";
1
2
  import { offlineWriteMessage, offlineWritesEnabled, withOnlineWrites } from "../_shared/online-actions.js";
2
3
  import { connectivity } from "@himalaya/state";
3
4
  import { defineService as registerService } from "../_shared/collection-service.js";
@@ -6,7 +7,7 @@ import { createRedirectSession } from "@himalaya/wix-redirects";
6
7
  import { open as openUrl } from "@himalaya/open-url";
7
8
  import { session as wixPlatformSession } from "@himalaya/wix-platform";
8
9
  import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
9
- import { activePlanIds, entitlement, payCashierOrder } from "@himalaya/checkout";
10
+ import { activePlanIds, entitlement, isPaymentSetupFailure, payCashierOrder } from "@himalaya/checkout";
10
11
  import { hostSessionFromGlobal, iapPriceFields, type IapCatalogItem } from "@himalaya/iap";
11
12
  import { createIapCommerce, pricingPlanOrders } from "@himalaya/iap-checkout";
12
13
  import { iapPriceCopy, iapPurchaseCopy, resetUnhydratedKeysOnOlderRuntime } from "../_shared/iap-worker.js";
@@ -290,7 +291,9 @@ async function regularJoinMessage(row: PlanRow, reference: { kind: string; id: s
290
291
  if (pay.kind === "cancelled") {
291
292
  return copyParam("plans.joinCancelledMessage", "Payment cancelled — you haven't joined.");
292
293
  }
293
- } catch {
294
+ if (isPaymentSetupFailure(pay)) return paymentSetupMessage("plans");
295
+ } catch (error) {
296
+ if (isPaymentSetupFailure(error)) return paymentSetupMessage("plans");
294
297
  }
295
298
  return copyTemplate("plans.joinOnSiteMessage", "Memberships are purchased on the website — visit the site to join {name}.", { name: row.name });
296
299
  }
@@ -86710,15 +86710,31 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
86710
86710
 
86711
86711
  // Live seed-id enumeration: a published release serves only EXACT screens, so
86712
86712
  // `himi push` bakes each templated detail route's CURRENT catalog ids as exact
86713
- // screens via `seedIds`. Anonymous visitor rung against this app's own clientId;
86714
- // best-effort (offline build → no baked details, preview still live-resolves).
86713
+ // screens via `seedIds`. Anonymous visitor rung against this app's own clientId — or,
86714
+ // for an app bound through the One App visitor identity (`visitorSource: "one-app"`,
86715
+ // no client), that site's One App visitor instance. Best-effort (offline build → no
86716
+ // baked details, preview still live-resolves).
86717
+ async function liveSeedToken(WIX: string): Promise<string | undefined> {
86718
+ const auth = (config as { auth?: { config?: { clientId?: string; visitorSource?: string; metaSiteId?: string } } }).auth?.config;
86719
+ // A One App binding boots as the site's One App visitor, so the seed reads as that visitor too —
86720
+ // even beside a member-login clientId, and never repointed by a stray local WIX_CLIENT_ID.
86721
+ const oneApp = auth?.visitorSource === "one-app";
86722
+ const clientId = oneApp ? "" : process.env.WIX_CLIENT_ID ?? auth?.clientId ?? "";
86723
+ if (clientId && !clientId.startsWith("YOUR_")) {
86724
+ const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
86725
+ return ((await mint.json()) as { access_token?: string }).access_token;
86726
+ }
86727
+ const msid = oneApp ? auth?.metaSiteId ?? "" : "";
86728
+ if (!/^[0-9a-fA-F-]{10,64}$/.test(msid)) return undefined;
86729
+ const mint = await fetch(`https://apps.wix.com/_api/one-app-session-web/v3/businesses/${msid}/signed-instance?signed_instance_type=Visitor`, { headers: { Accept: "application/json" }, redirect: "error" });
86730
+ const visitor = (await mint.json()) as { signedInstance?: string; signedInstanceType?: string };
86731
+ return visitor.signedInstanceType === "Visitor" ? visitor.signedInstance : undefined;
86732
+ }
86733
+
86715
86734
  async function liveSeedIds(spec: LiveSeed): Promise<string[]> {
86716
86735
  try {
86717
86736
  const WIX = "https://www.wixapis.com";
86718
- const clientId = process.env.WIX_CLIENT_ID ?? (config as { auth?: { config?: { clientId?: string } } }).auth?.config?.clientId ?? "";
86719
- if (!clientId || clientId.startsWith("YOUR_")) return [];
86720
- const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
86721
- const token = ((await mint.json()) as { access_token?: string }).access_token;
86737
+ const token = await liveSeedToken(WIX);
86722
86738
  if (!token) return [];
86723
86739
  const res = await fetch(WIX + spec.path, { method: "POST", headers: { Authorization: token, "Content-Type": "application/json" }, body: JSON.stringify(spec.body) });
86724
86740
  const json = (await res.json()) as Record<string, unknown>;
@@ -104655,15 +104655,31 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
104655
104655
 
104656
104656
  // Live seed-id enumeration: a published release serves only EXACT screens, so
104657
104657
  // `himi push` bakes each templated detail route's CURRENT catalog ids as exact
104658
- // screens via `seedIds`. Anonymous visitor rung against this app's own clientId;
104659
- // best-effort (offline build → no baked details, preview still live-resolves).
104658
+ // screens via `seedIds`. Anonymous visitor rung against this app's own clientId — or,
104659
+ // for an app bound through the One App visitor identity (`visitorSource: "one-app"`,
104660
+ // no client), that site's One App visitor instance. Best-effort (offline build → no
104661
+ // baked details, preview still live-resolves).
104662
+ async function liveSeedToken(WIX: string): Promise<string | undefined> {
104663
+ const auth = (config as { auth?: { config?: { clientId?: string; visitorSource?: string; metaSiteId?: string } } }).auth?.config;
104664
+ // A One App binding boots as the site's One App visitor, so the seed reads as that visitor too —
104665
+ // even beside a member-login clientId, and never repointed by a stray local WIX_CLIENT_ID.
104666
+ const oneApp = auth?.visitorSource === "one-app";
104667
+ const clientId = oneApp ? "" : process.env.WIX_CLIENT_ID ?? auth?.clientId ?? "";
104668
+ if (clientId && !clientId.startsWith("YOUR_")) {
104669
+ const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
104670
+ return ((await mint.json()) as { access_token?: string }).access_token;
104671
+ }
104672
+ const msid = oneApp ? auth?.metaSiteId ?? "" : "";
104673
+ if (!/^[0-9a-fA-F-]{10,64}$/.test(msid)) return undefined;
104674
+ const mint = await fetch(`https://apps.wix.com/_api/one-app-session-web/v3/businesses/${msid}/signed-instance?signed_instance_type=Visitor`, { headers: { Accept: "application/json" }, redirect: "error" });
104675
+ const visitor = (await mint.json()) as { signedInstance?: string; signedInstanceType?: string };
104676
+ return visitor.signedInstanceType === "Visitor" ? visitor.signedInstance : undefined;
104677
+ }
104678
+
104660
104679
  async function liveSeedIds(spec: LiveSeed): Promise<string[]> {
104661
104680
  try {
104662
104681
  const WIX = "https://www.wixapis.com";
104663
- const clientId = process.env.WIX_CLIENT_ID ?? (config as { auth?: { config?: { clientId?: string } } }).auth?.config?.clientId ?? "";
104664
- if (!clientId || clientId.startsWith("YOUR_")) return [];
104665
- const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
104666
- const token = ((await mint.json()) as { access_token?: string }).access_token;
104682
+ const token = await liveSeedToken(WIX);
104667
104683
  if (!token) return [];
104668
104684
  const res = await fetch(WIX + spec.path, { method: "POST", headers: { Authorization: token, "Content-Type": "application/json" }, body: JSON.stringify(spec.body) });
104669
104685
  const json = (await res.json()) as Record<string, unknown>;
@@ -4,6 +4,7 @@
4
4
  // SDK; this only supplies the copy and the hydration reset.
5
5
  import { hostSessionFromGlobal, type IapPriceCopy } from "@himalaya/iap";
6
6
  import type { IapCommerce, IapPurchaseCopy, IapRestoreResult } from "@himalaya/iap-checkout";
7
+ import { paymentSetupMessage } from "./payment-setup.js";
7
8
  import { copyParam, copyTemplate } from "./himi-copy.js";
8
9
 
9
10
  /** Price labels from the screen's copy pack (`<prefix>.priceLoadingText`, …). */
@@ -25,6 +26,7 @@ export function iapPurchaseCopy(prefix: string): IapPurchaseCopy {
25
26
  awaitingApproval: copyParam(`${prefix}.purchaseAwaitingApprovalText`, "Waiting for approval"),
26
27
  unavailable: copyParam(`${prefix}.purchaseTemporarilyUnavailableText`, "Purchasing is temporarily unavailable."),
27
28
  signInRequired: copyParam(`${prefix}.purchaseSignInText`, "Sign in to buy."),
29
+ sitePaymentUnavailable: paymentSetupMessage(prefix),
28
30
  retry: copyParam(`${prefix}.purchaseRetryText`, "Something went wrong. Try again."),
29
31
  priceChanged: copyParam(`${prefix}.purchasePriceChangedText`, "The price changed. Check it and try again."),
30
32
  support: copyParam(`${prefix}.purchaseSupportText`, "We couldn't finish this purchase. Contact support."),
@@ -0,0 +1,6 @@
1
+ import { copyParam } from "./himi-copy.js";
2
+
3
+ /** No provider or country guess: Wix only established that online payment is unavailable. */
4
+ export function paymentSetupMessage(prefix: string): string {
5
+ return copyParam(`${prefix}.paymentSetupUnavailable`, "This site is not accepting online payments. Go back or contact the business for other payment options.");
6
+ }
@@ -1,3 +1,4 @@
1
+ import { paymentSetupMessage } from "../_shared/payment-setup.js";
1
2
  import { withOnlineWrites, offlineWritesEnabled } from "../_shared/online-actions.js";
2
3
  // Tier-5 bundle: himi-booking — availability and member booking.
3
4
  import { defineService as registerService, navigate } from "@himalaya/state";
@@ -9,7 +10,7 @@ import { dispatch } from "@himalaya/dispatch";
9
10
  import { bookSchedule, bookSlot, getService, queryServiceAvailability, registerToWaitlist, type WixBookingSlotFull, type WixSelectedPaymentOption, type WixService } from "@himalaya/wix-bookings";
10
11
  import { createCheckout, createOrder } from "@himalaya/wix-ecom";
11
12
  import { createRedirectSession, type WixSlot } from "@himalaya/wix-redirects";
12
- import { payCashierOrder } from "@himalaya/checkout";
13
+ import { isPaymentSetupFailure, payCashierOrder } from "@himalaya/checkout";
13
14
  import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
14
15
  import { session as wixPlatformSession } from "@himalaya/wix-platform";
15
16
  import { displayName, getMyData, type WixMyData } from "@himalaya/wix-users";
@@ -1587,10 +1588,12 @@ async function bookSelectedIn(s: S): Promise<S> {
1587
1588
  ? copyParam("time.coursePaymentCancelled", "Payment cancelled — your course booking wasn't confirmed. Try again.")
1588
1589
  : copyParam("time.paymentCancelled", "Payment cancelled — your booking wasn't confirmed. Pick a time to try again.") });
1589
1590
  }
1591
+ if (isPaymentSetupFailure(pay)) return derive({ ...s, bookingInFlight: false, message: paymentSetupMessage("time") });
1590
1592
  return derive({ ...s, bookingInFlight: false, message: s.isCourse
1591
1593
  ? copyParam("time.coursePaymentFailed", "Payment couldn't be completed. Try again.")
1592
1594
  : copyParam("time.paymentFailed", "Payment couldn't be completed. Pick another time or try again.") });
1593
- } catch {
1595
+ } catch (error) {
1596
+ if (isPaymentSetupFailure(error)) return derive({ ...s, bookingInFlight: false, message: paymentSetupMessage("time") });
1594
1597
  return derive({ ...s, bookingInFlight: false, message: s.isCourse
1595
1598
  ? copyParam("time.courseCheckoutFailed", "Checkout could not start. Try again.")
1596
1599
  : copyParam("time.checkoutFailed", "Checkout could not start. Pick another time or try again.") });
@@ -1,3 +1,4 @@
1
+ import { paymentSetupMessage } from "../_shared/payment-setup.js";
1
2
  import { withOnlineWrites } from "../_shared/online-actions.js";
2
3
  import { defineService as registerState } from "@himalaya/state";
3
4
  import { getStoreProduct } from "../_shared/himi-store.js";
@@ -7,7 +8,7 @@ import { withIapPresentation } from "../_shared/iap-presentation.js";
7
8
  import { type IapCommerce, classifyCart } from "@himalaya/iap-checkout";
8
9
  import { addCouponToCurrentCart, calculateCurrentCart, createCheckoutFromCurrentCart, getCurrentCart, removeCouponFromCurrentCart, removeLineItemsFromCurrentCart, updateLineItemsQuantity, type WixCartDetail, type WixCartLineItemDetail } from "@himalaya/wix-ecom";
9
10
  import { createRedirectSession } from "@himalaya/wix-redirects";
10
- import { payCashierOrder } from "@himalaya/checkout";
11
+ import { isPaymentSetupFailure, payCashierOrder } from "@himalaya/checkout";
11
12
  import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
12
13
  import { formatCurrency } from "../_shared/engine-intl.js";
13
14
 
@@ -574,8 +575,10 @@ const svc = defineService<State, {
574
575
  // only confirmed once it lands, so the cart must survive a bail-out.
575
576
  return derive({ ...state, checkoutHint: copyParam("cart.checkoutOpenedMessage", "Checkout opened.") });
576
577
  }
578
+ if (isPaymentSetupFailure(pay)) return derive({ ...state, checkoutHint: paymentSetupMessage("cart") });
577
579
  return derive({ ...state, checkoutHint: copyParam("cart.checkoutFailedMessage", "Checkout could not start.") });
578
- } catch {
580
+ } catch (error) {
581
+ if (isPaymentSetupFailure(error)) return derive({ ...state, checkoutHint: paymentSetupMessage("cart") });
579
582
  return derive({ ...state, checkoutHint: copyParam("cart.checkoutFailedMessage", "Checkout could not start.") });
580
583
  }
581
584
  },
@@ -1,3 +1,4 @@
1
+ import { paymentSetupMessage } from "../_shared/payment-setup.js";
1
2
  import { offlineWriteMessage, offlineWritesEnabled, withOnlineWrites } from "../_shared/online-actions.js";
2
3
  import { connectivity } from "@himalaya/state";
3
4
  import { defineService as registerService } from "../_shared/collection-service.js";
@@ -6,7 +7,7 @@ import { createRedirectSession } from "@himalaya/wix-redirects";
6
7
  import { open as openUrl } from "@himalaya/open-url";
7
8
  import { session as wixPlatformSession } from "@himalaya/wix-platform";
8
9
  import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
9
- import { activePlanIds, entitlement, payCashierOrder } from "@himalaya/checkout";
10
+ import { activePlanIds, entitlement, isPaymentSetupFailure, payCashierOrder } from "@himalaya/checkout";
10
11
  import { hostSessionFromGlobal, iapPriceFields, type IapCatalogItem } from "@himalaya/iap";
11
12
  import { createIapCommerce, pricingPlanOrders } from "@himalaya/iap-checkout";
12
13
  import { iapPriceCopy, iapPurchaseCopy, resetUnhydratedKeysOnOlderRuntime } from "../_shared/iap-worker.js";
@@ -290,7 +291,9 @@ async function regularJoinMessage(row: PlanRow, reference: { kind: string; id: s
290
291
  if (pay.kind === "cancelled") {
291
292
  return copyParam("plans.joinCancelledMessage", "Payment cancelled — you haven't joined.");
292
293
  }
293
- } catch {
294
+ if (isPaymentSetupFailure(pay)) return paymentSetupMessage("plans");
295
+ } catch (error) {
296
+ if (isPaymentSetupFailure(error)) return paymentSetupMessage("plans");
294
297
  }
295
298
  return copyTemplate("plans.joinOnSiteMessage", "Memberships are purchased on the website — visit the site to join {name}.", { name: row.name });
296
299
  }
@@ -1,3 +1,4 @@
1
+ import { paymentSetupMessage } from "../_shared/payment-setup.js";
1
2
  import { dispatch } from "@himalaya/dispatch";
2
3
  import { memberAccess, assertMemberAccess } from "../_shared/member-access.js";
3
4
  import { offlineWriteMessage, withOnlineWrites } from "../_shared/online-actions.js";
@@ -42,7 +43,7 @@ import { getMyMember } from "@himalaya/wix-members";
42
43
  import { session as wixPlatformSession } from "@himalaya/wix-platform";
43
44
  import { cleanTitle, initials } from "../_shared/himi-text.js";
44
45
  import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
45
- import { payCashierOrder } from "@himalaya/checkout";
46
+ import { isPaymentSetupFailure, payCashierOrder } from "@himalaya/checkout";
46
47
  import { formatCurrency } from "../_shared/engine-intl.js";
47
48
  import { iapPriceFields, onlineProgramItem, type IapCatalogItem, type IapPollDirective, type IapPriceFields } from "@himalaya/iap";
48
49
  import { createIapCommerce, onlineProgramOrders, type IapPurchaseFields } from "@himalaya/iap-checkout";
@@ -251,9 +252,11 @@ async function enrollmentMessage(id: string, name: string, guards: CommerceGuard
251
252
  : copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
252
253
  if (pay.kind === "handoff") return "";
253
254
  if (pay.kind === "cancelled") return copyParam("program.joinCancelledMessage", "Payment cancelled — you haven't enrolled.");
254
- } catch {
255
+ if (isPaymentSetupFailure(pay)) return paymentSetupMessage("program");
256
+ } catch (error) {
255
257
  if (!connectivity().online) return offlineWriteMessage;
256
258
  if (preparationWithdrawn) return blocked();
259
+ if (isPaymentSetupFailure(error)) return paymentSetupMessage("program");
257
260
  // fall through to the site-directing message
258
261
  }
259
262
  return copyTemplate("program.joinOnSiteMessage", "Enrolment happens on the website — visit the site to join {name}.", { name });