@wix/himalaya-cli 0.1551.0 → 0.1552.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 (64) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +3 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments-contract.d.ts +2 -2
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +24 -2
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/order-refs.d.ts +12 -3
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/forms/src/extensions.d.ts +83 -1
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +146 -23
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +8 -5
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +147 -24
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-forms.mjs +19 -0
  10. package/dist/cli.mjs +2455 -2166
  11. package/fixtures/wix-canned.json +14 -7
  12. package/guides/getting-started.md +17 -1
  13. package/guides/headless-loop.md +7 -1
  14. package/guides/himi-skill.md +17 -1
  15. package/guides/himi-test.md +95 -7
  16. package/guides/iap-pricing.md +1 -1
  17. package/guides/iap-products.md +2 -2
  18. package/guides/iap-screen-adoption.md +1 -1
  19. package/guides/index.json +1 -1
  20. package/guides/sdk-reference.json +82 -4
  21. package/guides/skill.md +16 -0
  22. package/package.json +2 -2
  23. package/templates/beauty-salon/config.ts +13 -0
  24. package/templates/beauty-salon/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  25. package/templates/beauty-salon/tier5-src/_shared/iap-worker.ts +1 -1
  26. package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +1 -1
  27. package/templates/branded-lite/config.ts +13 -0
  28. package/templates/branded-lite/dev/index.ts +1338 -2011
  29. package/templates/branded-lite/tier5-src/_shared/branded-form.ts +262 -0
  30. package/templates/branded-lite/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  31. package/templates/branded-lite/tier5-src/_shared/iap-worker.ts +1 -1
  32. package/templates/branded-lite/tier5-src/himi-branded-lite-form/index.ts +399 -0
  33. package/templates/branded-lite/tier5-src/himi-branded-lite-forms/index.ts +22 -7
  34. package/templates/branded-lite/tier5-src/himi-store-product/index.ts +1 -1
  35. package/templates/community-club/config.ts +13 -0
  36. package/templates/community-club/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  37. package/templates/community-club/tier5-src/_shared/iap-worker.ts +1 -1
  38. package/templates/events-meetups/config.ts +13 -0
  39. package/templates/events-meetups/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  40. package/templates/events-meetups/tier5-src/_shared/iap-worker.ts +1 -1
  41. package/templates/gym-fitness/config.ts +13 -0
  42. package/templates/gym-fitness/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  43. package/templates/gym-fitness/tier5-src/_shared/iap-worker.ts +1 -1
  44. package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +1 -1
  45. package/templates/nonprofit-cause/config.ts +13 -0
  46. package/templates/nonprofit-cause/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  47. package/templates/nonprofit-cause/tier5-src/_shared/iap-worker.ts +1 -1
  48. package/templates/podcast-creator/config.ts +13 -0
  49. package/templates/podcast-creator/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  50. package/templates/podcast-creator/tier5-src/_shared/iap-worker.ts +1 -1
  51. package/templates/portfolio-creative/config.ts +13 -0
  52. package/templates/portfolio-creative/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  53. package/templates/portfolio-creative/tier5-src/_shared/iap-worker.ts +1 -1
  54. package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +1 -1
  55. package/templates/restaurant/config.ts +13 -0
  56. package/templates/restaurant/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  57. package/templates/restaurant/tier5-src/_shared/iap-worker.ts +1 -1
  58. package/templates/retail-store/config.ts +13 -0
  59. package/templates/retail-store/tier5-src/_shared/iap-worker.ts +1 -1
  60. package/templates/retail-store/tier5-src/himi-store-product/index.ts +1 -1
  61. package/templates/yoga-studio/config.ts +13 -0
  62. package/templates/yoga-studio/tier5-src/_shared/iap-attendance-purchase.ts +1 -1
  63. package/templates/yoga-studio/tier5-src/_shared/iap-worker.ts +1 -1
  64. package/templates/branded-lite/tier5-src/wix-spaces-form-detail/index.ts +0 -30
@@ -781,6 +781,9 @@ export declare function datePicker(props: {
781
781
  maxUnix?: number;
782
782
  mode?: "date" | "time" | "datetime";
783
783
  onChange?: ActionShape;
784
+ /** "$service:" binding → unix seconds or an ISO-8601 date; the picker prefills from and
785
+ * tracks it (every renderer reads it; it overrides `valueUnix`). */
786
+ valuePath?: string;
784
787
  }, opts?: {
785
788
  id?: string;
786
789
  }): Component;
@@ -37,8 +37,8 @@ export interface AppleSaleRule {
37
37
  appProductId(entityId: string): string;
38
38
  productType: "NON_CONSUMABLE" | "CONSUMABLE";
39
39
  }
40
- export declare const BOOKINGS_APPLE_SALES_ENABLED = false;
41
- export declare const EVENTS_APPLE_SALES_ENABLED = false;
40
+ export declare const BOOKINGS_APPLE_SALES_ENABLED = true;
41
+ export declare const EVENTS_APPLE_SALES_ENABLED = true;
42
42
  /**
43
43
  * Whether Wix Stores items sell through the App Store, through wix-iap StartItemPayment
44
44
  * (catalyst-server #24496), which places the member's eCom order for one catalog item. OFF: that RPC
@@ -97,6 +97,16 @@ export type OrderAttempt = (
97
97
  noOrderCreated?: true;
98
98
  };
99
99
  export type AccessRead = "granted" | "pending" | "none" | "unavailable" | "sessionChanged";
100
+ /** What a repeatable sale stores before an inventory write whose answer may be lost: the ids it
101
+ * targets or created, and when Wix will itself have released anything it created. */
102
+ export interface OpeningGuard {
103
+ verticalRef?: VerticalRef;
104
+ releaseAt?: number;
105
+ }
106
+ /** `released`: nothing the guarded write may have created can be sold twice, so the guard goes.
107
+ * `held`: still unknown, and it blocks this sale. `unrelated`: still unknown, but it targets other
108
+ * inventory than this sale's. */
109
+ export type OpeningResolution = "released" | "held" | "unrelated" | "unavailable" | "sessionChanged";
100
110
  /** An item wix-iap can order itself (StartItemPayment): the item as eCom's catalog reference, and
101
111
  * how the adapter finds the eCom order that call places again. */
102
112
  export interface ItemPayment {
@@ -113,8 +123,18 @@ export interface IapOrderAdapter {
113
123
  eligibility(item: IapOrderItem): IapEligibility;
114
124
  /** Opens, or reuses the vertical's own open, order for the signed-in member. */
115
125
  /** Repeatable adapters call beforeCreate immediately before their first inventory write,
116
- * after read-only checks, and abort without writing if it returns false. */
117
- openOrder(item: IapOrderItem, bridge: SessionBoundBridge, beforeCreate?: () => boolean): Promise<OrderAttempt>;
126
+ * after read-only checks, and abort without writing if it returns false. They call afterCreate
127
+ * with the ids of each inventory write as soon as it answers, so a later failure can be undone. */
128
+ openOrder(item: IapOrderItem, bridge: SessionBoundBridge, beforeCreate?: (guard: OpeningGuard) => boolean, afterCreate?: (guard: OpeningGuard) => void): Promise<OrderAttempt>;
129
+ /** Settles an earlier attempt's guard before a new sale of the same item. `settled`: the attempt
130
+ * that wrote it has had time to finish, so what it created may be undone; before then it may be
131
+ * another screen's sale still in flight. `claim` must answer true immediately before anything
132
+ * is undone: it takes the guard from that sale, which is then never charged. With no item (on
133
+ * entry, not a sale) only `released` matters. Without it, a guard holds its item until its `releaseAt`. */
134
+ resolveOpening?(guard: OpeningGuard, item: IapOrderItem | undefined, bridge: SessionBoundBridge, options: {
135
+ settled: boolean;
136
+ claim(): boolean;
137
+ }): Promise<OpeningResolution>;
118
138
  /** Re-reads an order opened earlier; `open` only while it can still be paid. */
119
139
  reopenOrder(order: {
120
140
  cashierOrderId: string;
@@ -157,6 +177,8 @@ export interface OrderRef {
157
177
  appleTransactionId?: string;
158
178
  /** The StoreKit token StartPayment minted for this payment (D61): recovery finds the order by it. */
159
179
  appAccountToken?: string;
180
+ /** For `openingOrder`: when anything its write created has been released by Wix. */
181
+ releaseAt?: number;
160
182
  updatedAt: number;
161
183
  }
162
184
  export interface OrderRefPartition {
@@ -1,4 +1,4 @@
1
- import type { OrderRefPartition, OrderRefStore } from "./apple-iap-contract.js";
1
+ import type { IapOrderAdapter, IapOrderItem, OpeningResolution, OrderRef, OrderRefPartition, OrderRefStore, SessionBoundBridge } from "./apple-iap-contract.js";
2
2
  export interface KeyValueStorage {
3
3
  getItem(key: string): string | null;
4
4
  setItem(key: string, value: string): void;
@@ -12,9 +12,18 @@ export declare const NEEDS_SUPPORT_HOLD_MS = 604800000;
12
12
  export declare const ITEM_START_HOLD_MS = 3600000;
13
13
  /** Drops every hold older than its stage's hold time, whatever its key (an item, or a `payment:`
14
14
  * with no local order), so holds never accumulate and lock the member out. A held proof still
15
- * unfinished renews its hold on every retry, so only one Apple no longer delivers expires. Returns
16
- * the keys it removed. */
15
+ * unfinished renews its hold on every retry, so only one Apple no longer delivers expires. A
16
+ * creation guard goes at its own `releaseAt`. Returns the keys it removed. */
17
17
  export declare function pruneExpiredHolds(partition: OrderRefPartition, now: number): string[];
18
+ /** How long (5 minutes) a creation guard may belong to a sale still running in another screen's
19
+ * worker, which shares these references but not that sale's coordinator: longer than any one
20
+ * openOrder takes. Until then nothing it may have created is undone. A literal, as above. */
21
+ export declare const OPENING_SETTLE_MS = 300000;
22
+ /** Asks the guard's adapter to settle it. Before undoing anything the adapter claims the guard
23
+ * (restamps it), so the sale that wrote it, should it still be running, is no longer charged. It
24
+ * is dropped only while it is still the stored guard: a slow answer never removes what another
25
+ * worker wrote under the key since. A guard changed meanwhile is judged again as it now is. */
26
+ export declare function settleOpening(partition: OrderRefPartition, adapter: IapOrderAdapter, guard: OrderRef, item: IapOrderItem | undefined, bridge: SessionBoundBridge, now: number): Promise<OpeningResolution | "storageUnavailable">;
18
27
  /** References one member keeps at once. When full, the oldest reference from before Apple's sheet
19
28
  * is evicted; a partition full of handed-over payments refuses new sales instead. */
20
29
  export declare const MAX_ORDER_REFS = 16;
@@ -1 +1,83 @@
1
- export {};
1
+ import { type WixCallOptions, type WixSubmission } from "./index.js";
2
+ export interface WixFormSubmissionInput {
3
+ formId: string;
4
+ /** Keyed by each field's `target`; the value shape follows the field's `inputType`
5
+ * ("About Submission Values"). A key that is no field's target fails the whole submission. */
6
+ submissions: Record<string, unknown>;
7
+ }
8
+ export interface WixCreateSubmissionRequest {
9
+ submission: WixFormSubmissionInput;
10
+ captchaToken?: string;
11
+ }
12
+ export interface WixCreateSubmissionResponse {
13
+ submission?: WixSubmission;
14
+ }
15
+ export interface WixSubmissionValidationError {
16
+ /** The field's `target`. */
17
+ errorPath?: string;
18
+ errorType?: string;
19
+ errorMessage?: string;
20
+ params?: Record<string, unknown>;
21
+ }
22
+ export interface WixValidateFormSubmissionResponse {
23
+ valid?: boolean;
24
+ violationErrors?: Array<{
25
+ name?: string;
26
+ description?: string;
27
+ ruleName?: string;
28
+ submissionValidationErrors?: {
29
+ errors?: WixSubmissionValidationError[];
30
+ };
31
+ }>;
32
+ }
33
+ /** The endpoint answered: `value` is what the call returns without `{ offline: "queue" }`. */
34
+ export interface WixSent<T> {
35
+ status: "sent";
36
+ value: T;
37
+ }
38
+ /** The write waits in the device's offline outbox; `writeId` names it in
39
+ * `wixApi.offline` events. */
40
+ export interface WixQueued {
41
+ status: "queued";
42
+ writeId: string;
43
+ tag: string;
44
+ }
45
+ export type WixOfflineResult<T> = WixSent<T> | WixQueued;
46
+ /** Per-call options of a write the runtime may queue offline. `offline: "queue"` opts this ONE
47
+ * call into the outbox, and the answer becomes a `WixOfflineResult<T>`. It queues only if the
48
+ * app declares a `queueOffline` cache rule for the endpoint; a form send has no server-side
49
+ * dedupe (`unsentOnly`), so on web it is refused offline (`err.code === "offline_write_blocked"`). */
50
+ export interface WixQueueCallOptions extends WixCallOptions {
51
+ offline?: "queue";
52
+ }
53
+ /**
54
+ * Sends a filled form. Creates a submission (and, through the form's contact mapping, a
55
+ * contact) on the site, so call it only on the submitter's explicit action. An anonymous
56
+ * visitor may call it. Keys of `submissions` are field targets; Wix rejects the whole
57
+ * submission for an unknown key or an invalid value, with per-field `submissionValidationErrors`.
58
+ * With `{ offline: "queue" }` the answer is an envelope: `sent`, or `queued` while the device is
59
+ * offline (native only — on web it is refused offline with `code: "offline_write_blocked"`).
60
+ *
61
+ * POST /form-submission-service/v4/submissions
62
+ */
63
+ export declare function createSubmission(req: WixCreateSubmissionRequest, opts: WixQueueCallOptions & {
64
+ offline: "queue";
65
+ }): Promise<WixOfflineResult<WixCreateSubmissionResponse>>;
66
+ /**
67
+ * Sends a filled form. Creates a submission (and, through the form's contact mapping, a
68
+ * contact) on the site, so call it only on the submitter's explicit action. An anonymous
69
+ * visitor may call it. Keys of `submissions` are field targets; Wix rejects the whole
70
+ * submission for an unknown key or an invalid value, with per-field `submissionValidationErrors`.
71
+ * With `{ offline: "queue" }` the answer is an envelope: `sent`, or `queued` while the device is
72
+ * offline (native only — on web it is refused offline with `code: "offline_write_blocked"`).
73
+ *
74
+ * POST /form-submission-service/v4/submissions
75
+ */
76
+ export declare function createSubmission(req: WixCreateSubmissionRequest, opts?: WixCallOptions): Promise<WixCreateSubmissionResponse>;
77
+ /**
78
+ * Checks a filled form against the form's rules without creating anything. Answers
79
+ * `valid: false` with one `submissionValidationErrors.errors[]` entry per failed value.
80
+ *
81
+ * POST /form-submission-service/v4/submissions/validate
82
+ */
83
+ export declare function validateFormSubmission(req: WixCreateSubmissionRequest, opts?: WixCallOptions): Promise<WixValidateFormSubmissionResponse>;
@@ -687,8 +687,8 @@ var sameReference = (value, reference) => isObject2(value) && value.kind === ref
687
687
 
688
688
  // ../../core/sdk/iap/src/wix-payments-contract.ts
689
689
  var HIMALAYA_SERVE_IAP_ROUTE = { prefix: "/himalaya-serve/v1/iap" };
690
- var BOOKINGS_APPLE_SALES_ENABLED = false;
691
- var EVENTS_APPLE_SALES_ENABLED = false;
690
+ var BOOKINGS_APPLE_SALES_ENABLED = true;
691
+ var EVENTS_APPLE_SALES_ENABLED = true;
692
692
  var ATTENDANCE_SALE_RULE = { appProductId: (id) => id, productType: "CONSUMABLE" };
693
693
  var STORES_ITEM_PAYMENT_ENABLED = false;
694
694
  var STORES_APPLE_SALES_ENABLED = true;
@@ -1428,10 +1428,13 @@ function locationType(raw) {
1428
1428
  return type === "CUSTOM" && addresses.every(describesNoPlace) ? ADDRESSLESS_CUSTOM : type;
1429
1429
  }
1430
1430
  function describesNoPlace(address) {
1431
- const parts = ["country", "subdivision", "city", "postalCode", "addressLine", "addressLine2", "streetAddress", "location"];
1431
+ const parts = ["country", "subdivision", "city", "postalCode", "addressLine2", "streetAddress", "location"];
1432
1432
  if (parts.some((key) => address[key] !== void 0 && address[key] !== "")) return false;
1433
- const description = text(address.formattedAddress)?.trim() ?? "";
1434
- return description === "" || ONLINE_LABEL.test(description);
1433
+ return [address.addressLine, address.formattedAddress].every((value) => {
1434
+ if (value !== void 0 && typeof value !== "string") return false;
1435
+ const description = value?.trim() ?? "";
1436
+ return description === "" || ONLINE_LABEL.test(description);
1437
+ });
1435
1438
  }
1436
1439
  var ONLINE_LABEL = /^(online|virtual)(\s+(class|classes|classroom|course|session|sessions|meeting|lesson|training|workshop|event))?$/i;
1437
1440
  function ticketAttendanceOf(event, definition) {
@@ -1701,9 +1704,33 @@ function isFinalRefusal(status) {
1701
1704
  var NEEDS_SUPPORT_HOLD_MS = 6048e5;
1702
1705
  var ITEM_START_HOLD_MS = 36e5;
1703
1706
  function pruneExpiredHolds(partition, now) {
1704
- const expired = partition.refs().filter((ref) => now - ref.updatedAt >= (HOLD_MS[ref.stage] ?? Infinity));
1707
+ const expired = partition.refs().filter((ref) => now - ref.updatedAt >= (HOLD_MS[ref.stage] ?? Infinity) || released(ref, now));
1705
1708
  return expired.filter((ref) => partition.remove(ref.key)).map((ref) => ref.key);
1706
1709
  }
1710
+ var OPENING_SETTLE_MS = 3e5;
1711
+ async function settleOpening(partition, adapter, guard, item, bridge, now) {
1712
+ const resolve = adapter.resolveOpening;
1713
+ if (!resolve) return "held";
1714
+ let current = guard;
1715
+ const claim = () => {
1716
+ const next = { ...current, updatedAt: now };
1717
+ if (!isStoredGuard(partition, current) || !partition.put(next)) return false;
1718
+ current = next;
1719
+ return true;
1720
+ };
1721
+ const resolution = await resolve(guard, item, bridge, { settled: now - guard.updatedAt >= OPENING_SETTLE_MS, claim });
1722
+ if (isStoredGuard(partition, current)) {
1723
+ if (resolution !== "released") return resolution;
1724
+ return partition.remove(guard.key) ? "released" : "storageUnavailable";
1725
+ }
1726
+ const stored = partition.refs().find((ref) => ref.key === guard.key);
1727
+ if (!stored) return "released";
1728
+ return stored.stage === "openingOrder" ? resolve(stored, item, bridge, { settled: false, claim: () => false }) : "held";
1729
+ }
1730
+ function isStoredGuard(partition, guard) {
1731
+ const stored = partition.refs().find((ref) => ref.key === guard.key);
1732
+ return stored?.stage === "openingOrder" && stored.updatedAt === guard.updatedAt;
1733
+ }
1707
1734
  var MAX_ORDER_REFS = 16;
1708
1735
  function createWorkerOrderRefStore(storage) {
1709
1736
  const kv = () => storage ?? hostStorage();
@@ -1780,10 +1807,14 @@ function writeVerified(store, location, value) {
1780
1807
  function isOrderRef(value) {
1781
1808
  if (!isObject6(value) || typeof value.key !== "string" || !STAGES.has(value.stage)) return false;
1782
1809
  if (typeof value.updatedAt !== "number") return false;
1810
+ if (value.releaseAt !== void 0 && typeof value.releaseAt !== "number") return false;
1783
1811
  const optional = ["kind", "cashierOrderId", "wixTransactionId", "paymentId", "storeProductId", "appProductId", "appleTransactionId", "appAccountToken"];
1784
1812
  if (!optional.every((field) => value[field] === void 0 || typeof value[field] === "string")) return false;
1785
1813
  return value.verticalRef === void 0 || isStringMap(value.verticalRef);
1786
1814
  }
1815
+ function released(ref, now) {
1816
+ return ref.stage === "openingOrder" && ref.releaseAt !== void 0 && now >= ref.releaseAt;
1817
+ }
1787
1818
  function isPartitionKey(key) {
1788
1819
  return isUUID3(key.purchasesAppId) && isUUID3(key.siteId) && isUUID3(key.memberId);
1789
1820
  }
@@ -1920,6 +1951,8 @@ function createWixOrderBackend(deps) {
1920
1951
  async function getPurchaseContext(session, catalogRef) {
1921
1952
  const sale = await saleFor(session, catalogRef);
1922
1953
  if (isStop(sale)) return contextResult(catalogRef, sale);
1954
+ const unresolved = await resolveOpenings(sale);
1955
+ if (unresolved) return contextResult(catalogRef, unresolved);
1923
1956
  const blocked = saleBlocked(sale);
1924
1957
  if (blocked) return contextResult(catalogRef, blocked);
1925
1958
  const itemPayment2 = deps.itemPaymentEnabled ? sale.adapter.itemPayment?.(sale.item) : null;
@@ -1938,11 +1971,26 @@ function createWixOrderBackend(deps) {
1938
1971
  if (failure3) return stopWith("rejected", failure3);
1939
1972
  if (sale.adapter.repeatable) {
1940
1973
  const prefix = `${sale.item.reference.kind}:${sale.item.reference.id}#`;
1941
- const unresolved = sale.partition.refs().find((ref) => ref.key.startsWith(prefix) && ["openingOrder", "held", "needsSupport"].includes(ref.stage));
1942
- if (unresolved) return stopWith("rejected", unresolved.stage === "openingOrder" ? "outcomeUnknown" : "needsSupport");
1974
+ const unresolved = sale.partition.refs().find((ref) => ref.key.startsWith(prefix) && !sale.unrelated.has(ref.key) && ["openingOrder", "held", "needsSupport"].includes(ref.stage));
1975
+ if (unresolved) return stopWith("rejected", unresolved.stage === "openingOrder" ? "paymentInProgress" : "needsSupport");
1943
1976
  }
1944
1977
  return deps.productBusy?.(sale.appleProduct.productId) ? stopWith("rejected", "paymentInProgress") : null;
1945
1978
  }
1979
+ async function resolveOpenings(sale) {
1980
+ if (!sale.adapter.repeatable || !sale.adapter.resolveOpening) return null;
1981
+ for (const guard of openingsOf(sale)) {
1982
+ const resolution = await settleOpening(sale.partition, sale.adapter, guard, sale.item, sale.bridge, now());
1983
+ if (!sale.bridge.isCurrent() || resolution === "sessionChanged") return STALE2;
1984
+ if (resolution === "unavailable") return retry("unavailable");
1985
+ if (resolution === "storageUnavailable") return stopWith("rejected", "storageUnavailable");
1986
+ if (resolution === "unrelated") sale.unrelated.add(guard.key);
1987
+ }
1988
+ return null;
1989
+ }
1990
+ function openingsOf(sale) {
1991
+ const prefix = `${sale.item.reference.kind}:${sale.item.reference.id}#`;
1992
+ return sale.partition.refs().filter((ref) => ref.stage === "openingOrder" && ref.key.startsWith(prefix));
1993
+ }
1946
1994
  async function saleFor(session, catalogRef) {
1947
1995
  const sold = deps.itemFor(catalogRef);
1948
1996
  const adapter = sold && adapterFor(sold.item.reference.kind);
@@ -1963,13 +2011,15 @@ function createWixOrderBackend(deps) {
1963
2011
  adapter,
1964
2012
  member,
1965
2013
  partition: member.partition,
1966
- bridge: bindBridge(session, deps.session, deps.bridge)
2014
+ bridge: bindBridge(session, deps.session, deps.bridge),
2015
+ unrelated: /* @__PURE__ */ new Set()
1967
2016
  };
1968
2017
  }
1969
2018
  async function orderFor(sale) {
1970
2019
  const existing = refAt(sale.partition, sale.catalogRef);
1971
2020
  let prevented;
1972
- const beforeCreate = () => {
2021
+ let guarded;
2022
+ const beforeCreate = (guard = {}) => {
1973
2023
  if (!sale.adapter.repeatable || existing) return true;
1974
2024
  if (!sale.bridge.isCurrent()) {
1975
2025
  prevented = STALE2;
@@ -1980,18 +2030,30 @@ function createWixOrderBackend(deps) {
1980
2030
  prevented = blocked;
1981
2031
  return false;
1982
2032
  }
1983
- const opening = persist(sale, { key: sale.catalogRef, kind: sale.adapter.kind, stage: "openingOrder", updatedAt: now() });
2033
+ const opening = persist(sale, { ...guard, key: sale.catalogRef, kind: sale.adapter.kind, stage: "openingOrder", updatedAt: now() });
1984
2034
  if (isStop(opening)) {
1985
2035
  prevented = opening;
1986
2036
  return false;
1987
2037
  }
2038
+ guarded = opening.updatedAt;
1988
2039
  return true;
1989
2040
  };
1990
- const attempt3 = existing?.cashierOrderId && existing.verticalRef ? await reopenOrOpen(sale, { cashierOrderId: existing.cashierOrderId, verticalRef: existing.verticalRef }) : await sale.adapter.openOrder(sale.item, sale.bridge, beforeCreate);
2041
+ const afterCreate = (guard) => {
2042
+ const opening = refAt(sale.partition, sale.catalogRef);
2043
+ if (opening?.stage !== "openingOrder" || opening.updatedAt !== guarded) return;
2044
+ const recorded = persist(sale, { ...opening, ...guard, verticalRef: { ...opening.verticalRef, ...guard.verticalRef }, updatedAt: now() });
2045
+ if (!isStop(recorded)) guarded = recorded.updatedAt;
2046
+ };
2047
+ const attempt3 = existing?.cashierOrderId && existing.verticalRef ? await reopenOrOpen(sale, { cashierOrderId: existing.cashierOrderId, verticalRef: existing.verticalRef }) : await sale.adapter.openOrder(sale.item, sale.bridge, beforeCreate, afterCreate);
1991
2048
  if (prevented) return prevented;
2049
+ if (guarded !== void 0 && attempt3.status === "open" && !stillGuarded(sale, guarded)) return retry("unavailable");
1992
2050
  const ref = recordOrder(sale, existing, attempt3);
1993
2051
  return isStop(ref) ? ref : { ref, placed: isPlaced(attempt3) };
1994
2052
  }
2053
+ function stillGuarded(sale, guarded) {
2054
+ const stored = refAt(sale.partition, sale.catalogRef);
2055
+ return stored?.stage === "openingOrder" && stored.updatedAt === guarded;
2056
+ }
1995
2057
  async function reopenOrOpen(sale, order) {
1996
2058
  const reopened = await sale.adapter.reopenOrder(order, sale.bridge);
1997
2059
  const closed = reopened.status === "refused" && reopened.reason === "orderClosed";
@@ -2234,8 +2296,9 @@ var NOT_CONFIGURED = { stop: true, result: "rejected", failure: "notConfigured"
2234
2296
  var UNAVAILABLE = { stop: true, result: "retryable", failure: "unavailable" };
2235
2297
  var OUTCOME_UNKNOWN = { stop: true, result: "rejected", failure: "outcomeUnknown" };
2236
2298
  var BLOCKING_STAGES = {
2299
+ // A repeatable sale's guard settles on a later tap, so it keeps the action tappable.
2237
2300
  startingItem: "outcomeUnknown",
2238
- openingOrder: "outcomeUnknown",
2301
+ openingOrder: "paymentInProgress",
2239
2302
  submitted: "paymentInProgress",
2240
2303
  held: "paymentHeld",
2241
2304
  needsSupport: "needsSupport"
@@ -2494,6 +2557,7 @@ function enabledCheckout(deps) {
2494
2557
  if (!tracked) return backOffDue();
2495
2558
  partitionRead = null;
2496
2559
  for (const key of pruneExpiredHolds(tracked.partition, deps.clock.now())) payments.delete(key);
2560
+ if (!options.dueOnly) await settleOpenings(session, tracked.partition);
2497
2561
  for (const ref of tracked.partition.refs()) {
2498
2562
  if (!isCurrent(session)) return;
2499
2563
  if (ref.stage === "needsSupport") setPayment(ref.key, "needsSupport");
@@ -2509,6 +2573,13 @@ function enabledCheckout(deps) {
2509
2573
  }
2510
2574
  publish();
2511
2575
  }
2576
+ async function settleOpenings(session, partition) {
2577
+ for (const ref of partition.refs()) {
2578
+ const adapter = deps.adapters.find((candidate) => candidate.kind === ref.kind);
2579
+ if (ref.stage !== "openingOrder" || !adapter?.resolveOpening || !isCurrent(session)) continue;
2580
+ await settleOpening(partition, adapter, ref, void 0, bindBridge(session, deps.session, bridge), deps.clock.now());
2581
+ }
2582
+ }
2512
2583
  async function trackedPartition(session) {
2513
2584
  const binding = await bindingOf(deps.binding);
2514
2585
  if (!binding || !isCurrent(session)) return binding === null ? "none" : null;
@@ -3322,6 +3393,12 @@ var BOOKING = "bookingsService";
3322
3393
  var TICKET = "eventsTicket";
3323
3394
  var NOT_ENABLED = { status: "refused", reason: "notEnabled" };
3324
3395
  var CHANGED = { status: "refused", reason: "productChanged" };
3396
+ var MINUTE_MS = 6e4;
3397
+ var RELEASE_MARGIN_MS = 3e5;
3398
+ var DEFAULT_RESERVATION_MINUTES = 60;
3399
+ var PAYMENT_TAKEN = ["PAID", "PARTIALLY_PAID", "EXEMPT", "REFUNDED"];
3400
+ var HAS_OFFSET = /(?:Z|[+-]\d{2}:?\d{2})$/i;
3401
+ var LATEST_ZONE_MS = 432e5;
3325
3402
  function attendanceOrdersWith(options) {
3326
3403
  const now = options.now ?? Date.now;
3327
3404
  return {
@@ -3329,7 +3406,8 @@ function attendanceOrdersWith(options) {
3329
3406
  kind: BOOKING,
3330
3407
  repeatable: true,
3331
3408
  eligibility: (item) => eligible(item, BOOKING, options.bookingsEnabled),
3332
- openOrder: (item, bridge, beforeCreate) => options.bookingsEnabled ? openBooking(item, bridge, beforeCreate) : Promise.resolve(NOT_ENABLED),
3409
+ openOrder: (item, bridge, beforeCreate, afterCreate) => options.bookingsEnabled ? openBooking(item, bridge, beforeCreate, afterCreate) : Promise.resolve(NOT_ENABLED),
3410
+ resolveOpening: resolveBookingOpening,
3333
3411
  readAccess: bookingAccess,
3334
3412
  reopenOrder: (order, bridge) => reopen(order, bridge, bookingAccess)
3335
3413
  },
@@ -3337,7 +3415,7 @@ function attendanceOrdersWith(options) {
3337
3415
  kind: TICKET,
3338
3416
  repeatable: true,
3339
3417
  eligibility: (item) => eligible(item, TICKET, options.eventsEnabled),
3340
- openOrder: (item, bridge, beforeCreate) => options.eventsEnabled ? openTicket(item, bridge, now, beforeCreate) : Promise.resolve(NOT_ENABLED),
3418
+ openOrder: (item, bridge, beforeCreate, afterCreate) => options.eventsEnabled ? openTicket(item, bridge, now, beforeCreate, afterCreate) : Promise.resolve(NOT_ENABLED),
3341
3419
  readAccess: ticketAccess,
3342
3420
  reopenOrder: (order, bridge) => reopen(order, bridge, ticketAccess, now)
3343
3421
  }
@@ -3350,7 +3428,7 @@ function eligible(item, kind, enabled) {
3350
3428
  const selection = kind === BOOKING ? item.attendance.booking : item.attendance.ticket;
3351
3429
  return selection ? { eligible: true } : UNSUPPORTED;
3352
3430
  }
3353
- async function openBooking(item, bridge, beforeCreate) {
3431
+ async function openBooking(item, bridge, beforeCreate, afterCreate) {
3354
3432
  if (!eligible(item, BOOKING, true).eligible) return notCreated(ORDER_REFUSED);
3355
3433
  const selected = item.attendance.booking;
3356
3434
  if (selected.slot.serviceId !== item.reference.id || !isNonEmpty3(selected.slot.startDate) || !isNonEmpty3(selected.slot.endDate)) return notCreated(ORDER_REFUSED);
@@ -3364,11 +3442,49 @@ async function openBooking(item, bridge, beforeCreate) {
3364
3442
  if (isRefusal(availability)) return notCreated(attemptRefused(availability));
3365
3443
  const entries = isObject7(availability.body) ? availability.body.availabilityEntries : void 0;
3366
3444
  if (!Array.isArray(entries) || !entries.some((entry) => isObject7(entry) && entry.bookable === true && matchesSlot(entry.slot, selected.slot))) return notCreated(CHANGED);
3367
- if (beforeCreate && !beforeCreate()) return notCreated({ status: "refused", reason: "storageUnavailable" });
3445
+ if (beforeCreate && !beforeCreate(slotGuard(selected.slot))) return notCreated({ status: "refused", reason: "storageUnavailable" });
3368
3446
  const created = await bridge.call("POST", "/bookings/v2/bookings", { booking: { bookedEntity: { slot: selected.slot }, contactDetails: selected.contactDetails, totalParticipants: 1, selectedPaymentOption: "ONLINE", ...selected.formInfo ? { formInfo: selected.formInfo } : {} } });
3369
3447
  if (isRefusal(created)) return creationRefused(created);
3370
3448
  const bookingId = bodyObject(created, "booking")?.id;
3371
- return isNonEmpty3(bookingId) ? bookingCheckout(item, bookingId, bridge) : ORDER_REFUSED;
3449
+ if (!isNonEmpty3(bookingId)) return ORDER_REFUSED;
3450
+ afterCreate?.({ verticalRef: { bookingId } });
3451
+ return bookingCheckout(item, bookingId, bridge);
3452
+ }
3453
+ function slotGuard(slot) {
3454
+ const verticalRef = {};
3455
+ for (const key of ["sessionId", "startDate"]) if (isNonEmpty3(slot[key])) verticalRef[key] = slot[key];
3456
+ const ends = latestInstant(String(slot.endDate));
3457
+ return Number.isFinite(ends) ? { verticalRef, releaseAt: ends + RELEASE_MARGIN_MS } : { verticalRef };
3458
+ }
3459
+ function latestInstant(value) {
3460
+ if (HAS_OFFSET.test(value)) return Date.parse(value);
3461
+ return Date.parse(`${value}Z`) + LATEST_ZONE_MS;
3462
+ }
3463
+ async function resolveBookingOpening(guard, item, bridge, options) {
3464
+ const scope = sameSlot(guard.verticalRef, item?.attendance?.booking?.slot) ? "held" : "unrelated";
3465
+ const bookingId = guard.verticalRef?.bookingId;
3466
+ if (!bookingId || !options.settled) return scope;
3467
+ const released2 = await releaseBooking(bookingId, bridge, options.claim);
3468
+ return released2 === "held" || released2 === "unavailable" && scope === "unrelated" ? scope : released2;
3469
+ }
3470
+ async function releaseBooking(bookingId, bridge, claim) {
3471
+ const read2 = await bridge.call("POST", "/bookings/v2/bookings/query", { query: { filter: { id: { $eq: bookingId } } } });
3472
+ if (isRefusal(read2)) return read2.status === "http" && read2.httpStatus === 404 ? "released" : openingRefused(read2);
3473
+ const rows = isObject7(read2.body) ? read2.body.bookings : void 0;
3474
+ const booking = Array.isArray(rows) ? rows.find((b) => isObject7(b) && b.id === bookingId) : void 0;
3475
+ if (!isObject7(booking)) return "held";
3476
+ if (["CANCELED", "CANCELLED", "DECLINED"].includes(String(booking.status)) || PAYMENT_TAKEN.includes(String(booking.paymentStatus))) return "released";
3477
+ if (booking.status !== "CREATED" || !claim()) return "held";
3478
+ const cancelled = await bridge.call("POST", `/bookings/v2/bookings/${encodeURIComponent(bookingId)}/cancel`, { participantNotification: { notifyParticipants: false }, revision: booking.revision });
3479
+ return isRefusal(cancelled) ? openingRefused(cancelled) : "released";
3480
+ }
3481
+ function openingRefused(refusal) {
3482
+ if (refusal.status === "sessionChanged") return "sessionChanged";
3483
+ return refusal.status === "unavailable" ? "unavailable" : "held";
3484
+ }
3485
+ function sameSlot(guarded, slot) {
3486
+ if (!guarded || !slot) return true;
3487
+ return guarded.sessionId === slot.sessionId && Date.parse(guarded.startDate ?? "") === Date.parse(String(slot.startDate));
3372
3488
  }
3373
3489
  async function bookingCheckout(item, bookingId, bridge) {
3374
3490
  const checkout = await bridge.call("POST", "/ecom/v1/checkouts", { lineItems: [{ quantity: 1, catalogReference: { appId: "13d21c63-b5ec-5912-8397-c3a5ddb27a97", catalogItemId: bookingId } }], channelType: "OTHER_PLATFORM" });
@@ -3397,7 +3513,7 @@ async function bookingAccess(ref, bridge) {
3397
3513
  if (booking.status === "CANCELED" || booking.status === "CANCELLED" || booking.status === "DECLINED") return "none";
3398
3514
  return payment === "PAID" && booking.status === "CONFIRMED" && booking.paymentStatus === "PAID" ? "granted" : "pending";
3399
3515
  }
3400
- async function openTicket(item, bridge, now, beforeCreate) {
3516
+ async function openTicket(item, bridge, now, beforeCreate, afterCreate) {
3401
3517
  if (!eligible(item, TICKET, true).eligible) return notCreated(ORDER_REFUSED);
3402
3518
  const selected = item.attendance.ticket;
3403
3519
  if (!isNonEmpty3(selected.eventId)) return notCreated(ORDER_REFUSED);
@@ -3408,16 +3524,23 @@ async function openTicket(item, bridge, now, beforeCreate) {
3408
3524
  const definitions = isObject7(available.body) ? available.body.definitions : void 0;
3409
3525
  const live = Array.isArray(definitions) ? definitions.find((t) => isObject7(t) && t.id === item.reference.id && t.saleStatus === "SALE_STARTED") : void 0;
3410
3526
  if (!isObject7(live) || !samePrice(live.price, item)) return notCreated(CHANGED);
3411
- if (live.eventId !== selected.eventId || classifyForApple(availableTicketAttendanceOf(bodyObject(read2, "event"), live), item.attendance.currency) !== "appleIap") return notCreated(ORDER_REFUSED);
3412
- if (beforeCreate && !beforeCreate()) return notCreated({ status: "refused", reason: "storageUnavailable" });
3413
- return reserveTicket(item, bridge, now);
3527
+ const event = bodyObject(read2, "event");
3528
+ if (live.eventId !== selected.eventId || classifyForApple(availableTicketAttendanceOf(event, live), item.attendance.currency) !== "appleIap") return notCreated(ORDER_REFUSED);
3529
+ if (beforeCreate && !beforeCreate({ releaseAt: now() + reservationMinutes(event) * MINUTE_MS + RELEASE_MARGIN_MS })) return notCreated({ status: "refused", reason: "storageUnavailable" });
3530
+ return reserveTicket(item, bridge, now, afterCreate);
3531
+ }
3532
+ function reservationMinutes(event) {
3533
+ const registration = isObject7(event?.registration) ? event.registration : void 0;
3534
+ const minutes = isObject7(registration?.tickets) ? registration.tickets.reservationDurationInMinutes : void 0;
3535
+ return typeof minutes === "number" && minutes > 0 ? minutes : DEFAULT_RESERVATION_MINUTES;
3414
3536
  }
3415
- async function reserveTicket(item, bridge, now) {
3537
+ async function reserveTicket(item, bridge, now, afterCreate) {
3416
3538
  const selected = item.attendance.ticket;
3417
3539
  const held = await bridge.call("POST", "/events/v1/checkout/reservations", { eventId: selected.eventId, ticketQuantities: [{ ticketDefinitionId: item.reference.id, quantity: 1 }], ignoreLimits: false, allowHiddenTickets: false });
3418
3540
  if (isRefusal(held)) return creationRefused(held);
3419
3541
  const reservation = held.body;
3420
3542
  if (!isObject7(reservation) || !isNonEmpty3(reservation.id) || !future(reservation.expires, now)) return ORDER_CLOSED;
3543
+ afterCreate?.({ verticalRef: { reservationId: reservation.id }, releaseAt: Date.parse(reservation.expires) + RELEASE_MARGIN_MS });
3421
3544
  if (!heldAsSelected(reservation, item)) return CHANGED;
3422
3545
  const placed = await bridge.call("POST", "/events/v1/checkout", { eventId: selected.eventId, reservationId: reservation.id, buyer: selected.buyer, ...selected.guests ? { guests: selected.guests } : {}, ...selected.buyerSubmission ? { buyerSubmission: selected.buyerSubmission } : {}, ...selected.guestSubmissions ? { guestSubmissions: selected.guestSubmissions } : {}, options: { silent: false, payInPerson: false, ignoreFormValidation: false, markAsPaid: false } });
3423
3546
  if (isRefusal(placed)) return attemptRefused(placed);
@@ -664,8 +664,8 @@ var IAP_CATALOG_KINDS = ["onlineProgram", "storesProduct", "pricingPlan", "booki
664
664
 
665
665
  // ../../core/sdk/iap/src/wix-payments-contract.ts
666
666
  var HIMALAYA_SERVE_IAP_ROUTE = { prefix: "/himalaya-serve/v1/iap" };
667
- var BOOKINGS_APPLE_SALES_ENABLED = false;
668
- var EVENTS_APPLE_SALES_ENABLED = false;
667
+ var BOOKINGS_APPLE_SALES_ENABLED = true;
668
+ var EVENTS_APPLE_SALES_ENABLED = true;
669
669
  var ATTENDANCE_SALE_RULE = { appProductId: (id) => id, productType: "CONSUMABLE" };
670
670
  var STORES_ITEM_PAYMENT_ENABLED = false;
671
671
  var STORES_APPLE_SALES_ENABLED = true;
@@ -1444,10 +1444,13 @@ function locationType(raw) {
1444
1444
  return type === "CUSTOM" && addresses.every(describesNoPlace) ? ADDRESSLESS_CUSTOM : type;
1445
1445
  }
1446
1446
  function describesNoPlace(address) {
1447
- const parts = ["country", "subdivision", "city", "postalCode", "addressLine", "addressLine2", "streetAddress", "location"];
1447
+ const parts = ["country", "subdivision", "city", "postalCode", "addressLine2", "streetAddress", "location"];
1448
1448
  if (parts.some((key) => address[key] !== void 0 && address[key] !== "")) return false;
1449
- const description = text(address.formattedAddress)?.trim() ?? "";
1450
- return description === "" || ONLINE_LABEL.test(description);
1449
+ return [address.addressLine, address.formattedAddress].every((value) => {
1450
+ if (value !== void 0 && typeof value !== "string") return false;
1451
+ const description = value?.trim() ?? "";
1452
+ return description === "" || ONLINE_LABEL.test(description);
1453
+ });
1451
1454
  }
1452
1455
  var ONLINE_LABEL = /^(online|virtual)(\s+(class|classes|classroom|course|session|sessions|meeting|lesson|training|workshop|event))?$/i;
1453
1456
  function ticketAttendanceOf(event, definition) {