@wix/himalaya-cli 0.1496.0 → 0.1498.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 (138) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/attendance-classifier.d.ts +37 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/catalog-pricing-contract.d.ts +1 -1
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/index.d.ts +2 -1
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments-contract.d.ts +11 -3
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +27 -3
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-contract.d.ts +1 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/iap-verticals/attendance.d.ts +10 -0
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/iap-verticals/index.d.ts +1 -0
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/index.d.ts +1 -1
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/events/src/index.d.ts +33 -2
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +346 -26
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +132 -3
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +366 -48
  14. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +5 -0
  15. package/dist/cli.mjs +817 -666
  16. package/guides/iap-products.md +7 -1
  17. package/package.json +2 -2
  18. package/templates/beauty-salon/dev/index.ts +394 -0
  19. package/templates/beauty-salon/tier5-src/_shared/iap-attendance-form.ts +106 -0
  20. package/templates/beauty-salon/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  21. package/templates/beauty-salon/tier5-src/_shared/iap-attendance.ts +38 -0
  22. package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +52 -7
  23. package/templates/beauty-salon-owner/dev/index.ts +571 -0
  24. package/templates/beauty-salon-owner/himalaya.content.json +6 -0
  25. package/templates/beauty-salon-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  26. package/templates/beauty-salon-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  27. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  28. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  29. package/templates/branded-lite/dev/index.ts +1429 -278
  30. package/templates/branded-lite/tier5-src/_shared/business-chat-presenter.ts +46 -8
  31. package/templates/branded-lite/tier5-src/_shared/iap-attendance-form.ts +106 -0
  32. package/templates/branded-lite/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  33. package/templates/branded-lite/tier5-src/_shared/iap-attendance.ts +38 -0
  34. package/templates/branded-lite/tier5-src/business-chat/index.ts +3 -2
  35. package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +52 -7
  36. package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +112 -21
  37. package/templates/community-club/dev/index.ts +397 -2
  38. package/templates/community-club/tier5-src/_shared/iap-attendance-form.ts +106 -0
  39. package/templates/community-club/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  40. package/templates/community-club/tier5-src/_shared/iap-attendance.ts +38 -0
  41. package/templates/community-club/tier5-src/himi-events-detail/index.ts +112 -21
  42. package/templates/community-club-owner/dev/index.ts +571 -0
  43. package/templates/community-club-owner/himalaya.content.json +6 -0
  44. package/templates/community-club-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  45. package/templates/community-club-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  46. package/templates/community-club-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  47. package/templates/community-club-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  48. package/templates/events-meetups/dev/index.ts +397 -2
  49. package/templates/events-meetups/tier5-src/_shared/iap-attendance-form.ts +106 -0
  50. package/templates/events-meetups/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  51. package/templates/events-meetups/tier5-src/_shared/iap-attendance.ts +38 -0
  52. package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +112 -21
  53. package/templates/events-meetups-owner/dev/index.ts +571 -0
  54. package/templates/events-meetups-owner/himalaya.content.json +6 -0
  55. package/templates/events-meetups-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  56. package/templates/events-meetups-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  57. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  58. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  59. package/templates/gym-fitness/dev/index.ts +828 -39
  60. package/templates/gym-fitness/tier5-src/_shared/iap-attendance-form.ts +106 -0
  61. package/templates/gym-fitness/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  62. package/templates/gym-fitness/tier5-src/_shared/iap-attendance.ts +38 -0
  63. package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +52 -7
  64. package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +112 -21
  65. package/templates/gym-fitness-owner/dev/index.ts +571 -0
  66. package/templates/gym-fitness-owner/himalaya.content.json +6 -0
  67. package/templates/gym-fitness-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  68. package/templates/gym-fitness-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  69. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  70. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  71. package/templates/nonprofit-cause/dev/index.ts +397 -2
  72. package/templates/nonprofit-cause/tier5-src/_shared/iap-attendance-form.ts +106 -0
  73. package/templates/nonprofit-cause/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  74. package/templates/nonprofit-cause/tier5-src/_shared/iap-attendance.ts +38 -0
  75. package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +112 -21
  76. package/templates/nonprofit-cause-owner/dev/index.ts +571 -0
  77. package/templates/nonprofit-cause-owner/himalaya.content.json +6 -0
  78. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  79. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  80. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  81. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  82. package/templates/owner-lite/dev/index.ts +571 -0
  83. package/templates/owner-lite/himalaya.content.json +6 -0
  84. package/templates/owner-lite/tier5-src/_shared/owner-today-rollup.ts +307 -0
  85. package/templates/owner-lite/tier5-src/_shared/owner-widget-reads.ts +23 -0
  86. package/templates/owner-lite/tier5-src/himi-owner-lite-today/index.ts +8 -20
  87. package/templates/owner-lite/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  88. package/templates/podcast-creator/dev/index.ts +397 -2
  89. package/templates/podcast-creator/tier5-src/_shared/iap-attendance-form.ts +106 -0
  90. package/templates/podcast-creator/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  91. package/templates/podcast-creator/tier5-src/_shared/iap-attendance.ts +38 -0
  92. package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +112 -21
  93. package/templates/podcast-creator-owner/dev/index.ts +571 -0
  94. package/templates/podcast-creator-owner/himalaya.content.json +6 -0
  95. package/templates/podcast-creator-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  96. package/templates/podcast-creator-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  97. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  98. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  99. package/templates/portfolio-creative/dev/index.ts +397 -2
  100. package/templates/portfolio-creative/tier5-src/_shared/iap-attendance-form.ts +106 -0
  101. package/templates/portfolio-creative/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  102. package/templates/portfolio-creative/tier5-src/_shared/iap-attendance.ts +38 -0
  103. package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +112 -21
  104. package/templates/portfolio-creative-owner/dev/index.ts +571 -0
  105. package/templates/portfolio-creative-owner/himalaya.content.json +6 -0
  106. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  107. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  108. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  109. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  110. package/templates/restaurant/dev/index.ts +397 -2
  111. package/templates/restaurant/tier5-src/_shared/iap-attendance-form.ts +106 -0
  112. package/templates/restaurant/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  113. package/templates/restaurant/tier5-src/_shared/iap-attendance.ts +38 -0
  114. package/templates/restaurant/tier5-src/himi-events-detail/index.ts +112 -21
  115. package/templates/restaurant-owner/dev/index.ts +571 -0
  116. package/templates/restaurant-owner/himalaya.content.json +6 -0
  117. package/templates/restaurant-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  118. package/templates/restaurant-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  119. package/templates/restaurant-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  120. package/templates/restaurant-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  121. package/templates/retail-store-owner/dev/index.ts +571 -0
  122. package/templates/retail-store-owner/himalaya.content.json +6 -0
  123. package/templates/retail-store-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  124. package/templates/retail-store-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  125. package/templates/retail-store-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  126. package/templates/retail-store-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
  127. package/templates/yoga-studio/dev/index.ts +828 -39
  128. package/templates/yoga-studio/tier5-src/_shared/iap-attendance-form.ts +106 -0
  129. package/templates/yoga-studio/tier5-src/_shared/iap-attendance-purchase.ts +211 -0
  130. package/templates/yoga-studio/tier5-src/_shared/iap-attendance.ts +38 -0
  131. package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +52 -7
  132. package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +112 -21
  133. package/templates/yoga-studio-owner/dev/index.ts +571 -0
  134. package/templates/yoga-studio-owner/himalaya.content.json +6 -0
  135. package/templates/yoga-studio-owner/tier5-src/_shared/owner-today-rollup.ts +307 -0
  136. package/templates/yoga-studio-owner/tier5-src/_shared/owner-widget-reads.ts +23 -0
  137. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-today/index.ts +8 -20
  138. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-today-rollup/index.ts +121 -0
@@ -36,6 +36,23 @@ import {
36
36
  } from "../_shared/himi-events";
37
37
  import { initials } from "../_shared/himi-text.js";
38
38
  import { hostedSitePageOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
39
+ import { mayUseRegularTicketCheckout } from "../_shared/iap-attendance.js";
40
+ import { ATTENDANCE_INITIAL, ATTENDANCE_NOT_HYDRATED, createAttendancePurchase, type AttendancePurchaseFields } from "../_shared/iap-attendance-purchase.js";
41
+ import { resetUnhydratedKeysOnOlderRuntime } from "../_shared/iap-worker.js";
42
+
43
+ let ticketCheckoutAllowed = false;
44
+ let paymentEvent: unknown;
45
+ let paymentTickets = new Map<string, WixAvailableTicket>();
46
+ let ticketSelectionVersion = 0;
47
+ let lastReady: S | undefined;
48
+ const applePurchase = createAttendancePurchase({ kind: "eventsTicket", prefix: "event", pollAction: "himiEventDetail.iapPoll", onChange: () => { void svc.iapPublish(); } });
49
+
50
+ async function priceTicket(event: unknown, id: string, version: number): Promise<boolean> {
51
+ const available = paymentTickets.get(id);
52
+ if (version !== ticketSelectionVersion) return false;
53
+ await applePurchase.ticket(id, event, available);
54
+ return version === ticketSelectionVersion;
55
+ }
39
56
 
40
57
  /** A hosted signed-in event-page open is in flight (see openRegistration). */
41
58
  let hostedOpenInFlight = false;
@@ -88,7 +105,7 @@ async function writeCachedRow(row: EventRow): Promise<void> {
88
105
  try { await upsert(ENTITY, row as unknown as DbEntity); } catch { /* capability not granted → online-only */ }
89
106
  }
90
107
 
91
- interface S {
108
+ interface S extends AttendancePurchaseFields {
92
109
  status: "loading" | "ready";
93
110
  id: string;
94
111
  title: string;
@@ -139,6 +156,8 @@ interface S {
139
156
  /** The tier the CTA will reserve. Defaults to the cheapest on-sale tier so the
140
157
  * button is never dead, and follows the buyer's tap after that. */
141
158
  selectedTierId: string;
159
+ ticketPurchaseBlocked: boolean;
160
+ regularTicketsHidden: boolean;
142
161
  /** "General · $25" — echoed next to the CTA so the choice is visible. */
143
162
  selectedTierLabel: string;
144
163
  /** External/ticketed registration URL when the payload carries one. */
@@ -179,7 +198,7 @@ function tierPrice(t: WixAvailableTicket): string {
179
198
  }
180
199
 
181
200
  function tierAvailable(t: WixAvailableTicket): boolean {
182
- return !!t.id && !/SALE_ENDED|CANCELED|SOLD_OUT/i.test(t.saleStatus ?? "");
201
+ return !!t.id && !/SALE_SCHEDULED|SALE_ENDED|CANCELED|SOLD_OUT/i.test(t.saleStatus ?? "");
183
202
  }
184
203
 
185
204
  /** Map the buyer-visible tiers onto rows, cheapest first (so the default pick is
@@ -205,26 +224,36 @@ function tierRows(defs: WixAvailableTicket[], selectedId: string): TicketTierRow
205
224
  }
206
225
 
207
226
  /** Best-effort tier read — never sinks the screen if the tier endpoint fails. */
208
- async function loadTiers(eventId: string): Promise<TicketTierRow[]> {
227
+ async function loadTiers(eventId: string): Promise<{ rows: TicketTierRow[]; tickets: Map<string, WixAvailableTicket> }> {
209
228
  try {
210
229
  const response = await queryAvailableTickets(eventId);
211
230
  const defs = response.definitions ?? [];
231
+ const tickets = new Map(defs.filter((t) => t.id).map((t) => [t.id!, t]));
212
232
  const cheapestAvailable = defs
213
233
  .filter(tierAvailable)
214
234
  .slice()
215
235
  .sort((a, b) => Number(a.price?.amount ?? 0) - Number(b.price?.amount ?? 0))[0];
216
- return tierRows(defs, cheapestAvailable?.id ?? "");
236
+ return { rows: tierRows(defs, cheapestAvailable?.id ?? ""), tickets };
217
237
  } catch {
218
- return [];
238
+ return { rows: [], tickets: new Map() };
219
239
  }
220
240
  }
221
241
 
222
242
  function derive(s: S): S {
243
+ const regular = ticketCheckoutAllowed || applePurchase.usesRegularPlatform();
223
244
  const hasContent = !!s.title;
224
245
  const hasMapMarkers = s.mapMarkers.length > 0;
225
246
  const hasAttendees = s.attendeeAvatars.length > 0;
226
- return {
247
+ const purchase = applePurchase.fields(!!s.selectedTierId);
248
+ const tiers = s.ticketTiers.map((t) => ({ ...t, priceLine: regular ? tierPrice(paymentTickets.get(t.id) ?? {}) : t.id === s.selectedTierId ? purchase.iapPrice : "" }));
249
+ const next = {
227
250
  ...s,
251
+ _poll: undefined,
252
+ ...purchase,
253
+ actionMessage: regular && !ticketCheckoutAllowed && s.actionMessage === copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") ? "" : s.actionMessage,
254
+ ticketTiers: tiers,
255
+ regularTicketsHidden: s.registrationKind !== "tickets" || !purchase.iapBuyHidden,
256
+ ticketPurchaseBlocked: s.registrationKind === "tickets" && !regular,
228
257
  hasDescriptionJson: s.descriptionJson !== "",
229
258
  noMap: !s.mapUrl,
230
259
  hasMapMarkers,
@@ -239,8 +268,8 @@ function derive(s: S): S {
239
268
  showTierPicker: s.ticketTiers.length > 1,
240
269
  hideTierPicker: s.ticketTiers.length <= 1,
241
270
  selectedTierLabel: (() => {
242
- const picked = s.ticketTiers.find((t) => t.id === s.selectedTierId);
243
- return picked ? `${picked.name} · ${picked.priceLine}` : "";
271
+ const picked = tiers.find((t) => t.id === s.selectedTierId);
272
+ return picked ? [picked.name, picked.priceLine].filter(Boolean).join(" · ") : "";
244
273
  })(),
245
274
  showTickets: s.registrationKind === "tickets",
246
275
  hideTickets: s.registrationKind !== "tickets",
@@ -256,6 +285,8 @@ function derive(s: S): S {
256
285
  ? copyParam("event.errorText", "Event could not be loaded.")
257
286
  : copyParam("event.notFoundText", "Event not found.")),
258
287
  };
288
+ if (next.status === "ready") lastReady = next;
289
+ return next;
259
290
  }
260
291
 
261
292
  function calendarStamp(ms: number): string {
@@ -288,7 +319,7 @@ function calendarFallbackUrl(s: S, end: number): string {
288
319
 
289
320
  /** The Events v3 fieldsets the detail reads (see `WixEventFieldset`). DETAILS carries mainImage and
290
321
  * shortDescription: REGISTRATION fills eventType, which skips the query enrichment that used to supply them. */
291
- export const EVENT_DETAIL_FIELDSETS: WixEventFieldset[] = ["DETAILS", "TEXTS", "REGISTRATION", "URLS"];
322
+ export const EVENT_DETAIL_FIELDSETS: WixEventFieldset[] = ["DETAILS", "TEXTS", "REGISTRATION", "URLS", "FORM"];
292
323
 
293
324
  /** Map the decoded registration/eventType onto the CTA variant. The decoded
294
325
  * `eventType` already folds registration.type / registration.initialType /
@@ -401,16 +432,23 @@ async function enrichRegistration(id: string, e: WixEvent): Promise<WixEvent> {
401
432
  }
402
433
 
403
434
  const svc = defineService<S, {
404
- load: (s: S, arg?: unknown) => Promise<S>;
435
+ load: (s: S, arg?: unknown) => Promise<S | undefined>;
405
436
  openMap: (s: S) => Promise<S>;
406
- selectTier: (s: S, arg?: unknown) => Promise<S>;
437
+ selectTier: (s: S, arg?: unknown) => Promise<S | undefined>;
407
438
  openRegistration: (s: S) => Promise<S>;
408
439
  share: (s: S) => Promise<S>;
409
440
  addToCalendar: (s: S) => Promise<S>;
410
441
  /** [rico] #3372 — RichContent's on_link: `{ url }` of the tapped link. */
411
442
  openLink: (s: S, arg?: unknown) => Promise<S>;
443
+ buyApple: (s: S) => Promise<S>;
444
+ setIapField: (s: S, arg?: unknown) => S;
445
+ iapPublish: (s: S) => S;
446
+ iapRefresh: (s: S) => Promise<S>;
447
+ iapPoll: (s: S) => Promise<S>;
412
448
  }>("himiEventDetail", {
449
+ notHydrated: [...ATTENDANCE_NOT_HYDRATED, "ticketPurchaseBlocked"],
413
450
  state: derive({
451
+ ...ATTENDANCE_INITIAL,
414
452
  status: "loading",
415
453
  id: inputId(),
416
454
  title: "",
@@ -433,6 +471,8 @@ const svc = defineService<S, {
433
471
  hasAttendees: false,
434
472
  noAttendees: true,
435
473
  registrationKind: "rsvp",
474
+ ticketPurchaseBlocked: true,
475
+ regularTicketsHidden: true,
436
476
  showRsvp: true,
437
477
  hideRsvp: false,
438
478
  showTickets: false,
@@ -462,7 +502,19 @@ const svc = defineService<S, {
462
502
  noEmpty: true,
463
503
  }),
464
504
  actions: {
465
- load: async (s, arg) => {
505
+ iapPublish: (s) => derive(lastReady ?? s),
506
+ iapRefresh: async (): Promise<S> => { await applePurchase.refresh(); return derive(svc.getState()); },
507
+ iapPoll: async (): Promise<S> => { await applePurchase.poll(); return derive(svc.getState()); },
508
+ setIapField: (s, arg) => { applePurchase.change(arg); return derive(s); },
509
+ buyApple: async (s: S): Promise<S> => {
510
+ if (!s.selectedTierId || !s.ticketTiers.some((t) => t.id === s.selectedTierId && t.available)) return derive(s);
511
+ await applePurchase.buy({ key: s.selectedTierId, eventId: s.id });
512
+ return derive(svc.getState());
513
+ },
514
+ load: async (s: S, arg?: unknown): Promise<S | undefined> => {
515
+ const selectionVersion = ++ticketSelectionVersion;
516
+ ticketCheckoutAllowed = false;
517
+ applePurchase.invalidate();
466
518
  stashCopy(arg);
467
519
  const id = s.id || inputId();
468
520
  if (!id) return derive({ ...s, status: "ready", errored: true });
@@ -479,7 +531,15 @@ const svc = defineService<S, {
479
531
  // screen BEFORE the CTA is tapped. Best-effort: a tier read that fails
480
532
  // leaves the picker collapsed and the CTA falls back to its own read.
481
533
  const kind = registrationKindOf(e);
482
- const tiers = kind === "tickets" ? await loadTiers(id) : [];
534
+ const loaded = kind === "tickets" ? await loadTiers(id) : { rows: [], tickets: new Map<string, WixAvailableTicket>() };
535
+ const tiers = loaded.rows;
536
+ const selectedTierId = tiers.find((t) => t.isSelected)?.id ?? tiers.find((t) => t.available)?.id ?? "";
537
+ const regular = kind !== "tickets" || await mayUseRegularTicketCheckout(e, loaded.tickets.get(selectedTierId));
538
+ if (selectionVersion !== ticketSelectionVersion) return undefined;
539
+ paymentEvent = e;
540
+ paymentTickets = loaded.tickets;
541
+ ticketCheckoutAllowed = regular;
542
+ if (kind === "tickets" && !await priceTicket(e, selectedTierId, selectionVersion)) return undefined;
483
543
  // Write-through: cache the fetched row (same EventRow shape as the list) so a
484
544
  // later offline open of this detail can fall back to it.
485
545
  await writeCachedRow(normalizeEvent(e));
@@ -493,7 +553,7 @@ const svc = defineService<S, {
493
553
  ticketTiers: tiers,
494
554
  // Default the pick to the cheapest AVAILABLE tier so the CTA is never
495
555
  // dead; the buyer's tap overrides it from there.
496
- selectedTierId: tiers.find((t) => t.available)?.id ?? "",
556
+ selectedTierId,
497
557
  title,
498
558
  initial: (title.trim()[0] || "E").toUpperCase(),
499
559
  when: eventWhen(e),
@@ -514,11 +574,12 @@ const svc = defineService<S, {
514
574
  shareUrl: eventPageUrlOf(e),
515
575
  calendarStart: eventStartMillis(e) ?? 0,
516
576
  calendarEnd: eventEndMillis(e) ?? 0,
517
- actionMessage: "",
577
+ actionMessage: ticketCheckoutAllowed || !applePurchase.fields(true, false).iapBuyHidden ? "" : copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable."),
518
578
  statusLabel: titleCase(e.eventStatus) || titleCase((e as unknown as { status?: string }).status),
519
579
  });
520
580
  } catch {
521
581
  // Offline / fetch failure: fall back to the last-synced cached row before erroring.
582
+ if (selectionVersion !== ticketSelectionVersion) return undefined;
522
583
  const cached = await readCachedRow(id);
523
584
  if (cached) {
524
585
  const title = cached.title || "Event";
@@ -552,22 +613,46 @@ const svc = defineService<S, {
552
613
  return derive({ ...s, actionMessage: copyParam("event.mapOpenFailed", "Could not open maps on this device.") });
553
614
  }
554
615
  },
555
- selectTier: async (s, arg) => {
616
+ selectTier: async (s: S, arg?: unknown): Promise<S | undefined> => {
556
617
  const id = typeof arg === "string" ? arg : (arg as { id?: unknown } | undefined)?.id;
557
618
  const wanted = typeof id === "string" ? id : "";
558
619
  const tier = s.ticketTiers.find((t) => t.id === wanted);
559
620
  // Ignore a tap on an off-sale row rather than letting the CTA promise a
560
621
  // ticket it cannot reserve.
561
622
  if (!tier || !tier.available) return s;
623
+ const selectionVersion = ++ticketSelectionVersion;
624
+ ticketCheckoutAllowed = false;
625
+ applePurchase.invalidate();
626
+ const regular = await mayUseRegularTicketCheckout(paymentEvent, paymentTickets.get(wanted));
627
+ if (selectionVersion !== ticketSelectionVersion) return undefined;
628
+ ticketCheckoutAllowed = regular;
629
+ if (!await priceTicket(paymentEvent, wanted, selectionVersion)) return undefined;
562
630
  return derive({
563
631
  ...s,
564
632
  selectedTierId: wanted,
565
633
  ticketTiers: s.ticketTiers.map((t) => ({ ...t, isSelected: t.id === wanted, notSelected: t.id !== wanted })),
566
634
  // A new pick invalidates whatever the last attempt said.
567
- actionMessage: "",
635
+ actionMessage: ticketCheckoutAllowed || !applePurchase.fields(true, false).iapBuyHidden ? "" : copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable."),
568
636
  });
569
637
  },
570
- openRegistration: async (s) => {
638
+ openRegistration: async (s): Promise<S> => {
639
+ let eventPageAllowed = true;
640
+ let checkoutEvent: WixEvent | undefined;
641
+ if (s.registrationKind === "tickets") {
642
+ try {
643
+ const current = await getEvent(s.id, { fields: EVENT_DETAIL_FIELDSETS });
644
+ const event = ((current as unknown as { event?: WixEvent }).event ?? current) as WixEvent;
645
+ checkoutEvent = event;
646
+ eventPageAllowed = await mayUseRegularTicketCheckout(event, undefined);
647
+ ticketCheckoutAllowed = eventPageAllowed;
648
+ if (!ticketCheckoutAllowed) {
649
+ const tickets = await queryAvailableTickets(s.id);
650
+ const selected = (tickets.definitions ?? []).find((ticket) => ticket.id === s.selectedTierId);
651
+ ticketCheckoutAllowed = await mayUseRegularTicketCheckout(event, selected);
652
+ }
653
+ if (!ticketCheckoutAllowed) return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") });
654
+ } catch { return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") }); }
655
+ }
571
656
  // Phase 2c — NATIVE ticket purchase. Reserve the cheapest available tier,
572
657
  // turn the hold into an order, then settle it on the shared in-app pay rail
573
658
  // (@wix/himalaya/checkout): wallet where the app has one, in-app Wix cashier
@@ -598,9 +683,12 @@ const svc = defineService<S, {
598
683
  // picker is collapsed.
599
684
  const tickets = await queryAvailableTickets(s.id);
600
685
  const live = (tickets.definitions ?? []).filter(tierAvailable);
601
- const tier = live.find((t) => t.id === s.selectedTierId)
602
- // The pick went off-sale (or we never had one) ⇒ cheapest available.
603
- ?? live.slice().sort((a, b) => Number(a.price?.amount ?? 0) - Number(b.price?.amount ?? 0))[0];
686
+ // A recovered first read must show the offer before it can be sold.
687
+ if (live.length > 0 && !s.selectedTierId && s.ticketTiers.length === 0) return svc.load();
688
+ const tier = live.find((t) => t.id === s.selectedTierId);
689
+ if (!tier) return derive({ ...s, actionMessage: copyParam("event.ticketChanged", "That ticket is no longer available. Choose a ticket again.") });
690
+ ticketCheckoutAllowed = await mayUseRegularTicketCheckout(checkoutEvent, tier);
691
+ if (!ticketCheckoutAllowed) return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") });
604
692
  if (tier?.id) {
605
693
  const held = await createTicketReservation(s.id, [{ ticketDefinitionId: tier.id, quantity: 1 }]);
606
694
  reservationId = held.id ?? "";
@@ -643,6 +731,7 @@ const svc = defineService<S, {
643
731
  // fall through to the hosted rail below
644
732
  }
645
733
  // Hosted eventsCheckout — pinned to the live reservation when we have one.
734
+ if (!eventPageAllowed && !reservationId) return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") });
646
735
  try {
647
736
  const params = reservationId ? { reservationId } : s.eventSlug ? { eventSlug: s.eventSlug } : undefined;
648
737
  if (params) {
@@ -667,6 +756,7 @@ const svc = defineService<S, {
667
756
  // page → nothing) on its own. Nothing here can change between its prepare and its open (the event
668
757
  // is fixed and the page is where the member chooses and pays), so the guard has nothing to re-read.
669
758
  // A host that refused or failed leaves the visitor page below.
759
+ if (!eventPageAllowed) return derive({ ...s, actionMessage: copyParam("event.purchaseUnavailable", "Purchasing is temporarily unavailable.") });
670
760
  if (isHostedWorker() && s.id && (s.registrationKind === "tickets" || s.registrationKind === "rsvp")) {
671
761
  // One signed-in open at a time: the host keeps a single one-time ticket, so a second tap would
672
762
  // replace the first tap's ticket and drop that tap into the signed-OUT visitor page below.
@@ -747,5 +837,6 @@ const svc = defineService<S, {
747
837
  }),
748
838
  },
749
839
  });
840
+ resetUnhydratedKeysOnOlderRuntime(svc, { ...svc.getState(), ...ATTENDANCE_INITIAL, ticketPurchaseBlocked: true }, [...ATTENDANCE_NOT_HYDRATED, "ticketPurchaseBlocked"]);
750
841
  svc.load();
751
842
  export { svc };