@wix/himalaya-cli 0.1388.0 → 0.1389.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 (97) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-contract.d.ts +24 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-core.d.ts +3 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/index.d.ts +1 -1
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +57 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +57 -0
  6. package/dist/cli.mjs +7 -5
  7. package/guides/iap-screen-adoption.md +53 -1
  8. package/guides/sdk-reference.json +8 -2
  9. package/package.json +2 -2
  10. package/templates/beauty-salon/dev/index.ts +193 -1
  11. package/templates/beauty-salon/tier5-src/_shared/iap-worker.ts +84 -3
  12. package/templates/beauty-salon/tier5-src/himi-store-account/index.ts +36 -1
  13. package/templates/beauty-salon-owner/config.ts +11 -0
  14. package/templates/beauty-salon-owner/dev/index.ts +540 -0
  15. package/templates/beauty-salon-owner/himalaya.content.json +6 -0
  16. package/templates/beauty-salon-owner/platforms.json +2 -1
  17. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  18. package/templates/branded-lite/dev/index.ts +194 -2
  19. package/templates/branded-lite/tier5-src/_shared/iap-worker.ts +84 -3
  20. package/templates/branded-lite/tier5-src/himi-store-account/index.ts +36 -1
  21. package/templates/community-club/dev/index.ts +193 -1
  22. package/templates/community-club/tier5-src/_shared/iap-worker.ts +84 -3
  23. package/templates/community-club/tier5-src/himi-store-account/index.ts +36 -1
  24. package/templates/community-club-owner/config.ts +11 -0
  25. package/templates/community-club-owner/dev/index.ts +540 -0
  26. package/templates/community-club-owner/himalaya.content.json +6 -0
  27. package/templates/community-club-owner/platforms.json +2 -1
  28. package/templates/community-club-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  29. package/templates/events-meetups/dev/index.ts +193 -1
  30. package/templates/events-meetups/tier5-src/_shared/iap-worker.ts +84 -3
  31. package/templates/events-meetups/tier5-src/himi-store-account/index.ts +36 -1
  32. package/templates/events-meetups-owner/config.ts +11 -0
  33. package/templates/events-meetups-owner/dev/index.ts +540 -0
  34. package/templates/events-meetups-owner/himalaya.content.json +6 -0
  35. package/templates/events-meetups-owner/platforms.json +2 -1
  36. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  37. package/templates/gym-fitness/dev/index.ts +193 -1
  38. package/templates/gym-fitness/tier5-src/_shared/iap-worker.ts +84 -3
  39. package/templates/gym-fitness/tier5-src/himi-store-account/index.ts +36 -1
  40. package/templates/gym-fitness-owner/config.ts +11 -0
  41. package/templates/gym-fitness-owner/dev/index.ts +540 -0
  42. package/templates/gym-fitness-owner/himalaya.content.json +6 -0
  43. package/templates/gym-fitness-owner/platforms.json +2 -1
  44. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  45. package/templates/nonprofit-cause/dev/index.ts +193 -1
  46. package/templates/nonprofit-cause/tier5-src/_shared/iap-worker.ts +84 -3
  47. package/templates/nonprofit-cause/tier5-src/himi-store-account/index.ts +36 -1
  48. package/templates/nonprofit-cause-owner/config.ts +11 -0
  49. package/templates/nonprofit-cause-owner/dev/index.ts +540 -0
  50. package/templates/nonprofit-cause-owner/himalaya.content.json +6 -0
  51. package/templates/nonprofit-cause-owner/platforms.json +2 -1
  52. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  53. package/templates/owner-lite/config.ts +11 -0
  54. package/templates/owner-lite/dev/index.ts +540 -0
  55. package/templates/owner-lite/himalaya.content.json +6 -0
  56. package/templates/owner-lite/platforms.json +2 -1
  57. package/templates/owner-lite/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  58. package/templates/podcast-creator/dev/index.ts +193 -1
  59. package/templates/podcast-creator/tier5-src/_shared/iap-worker.ts +84 -3
  60. package/templates/podcast-creator/tier5-src/himi-store-account/index.ts +36 -1
  61. package/templates/podcast-creator-owner/config.ts +11 -0
  62. package/templates/podcast-creator-owner/dev/index.ts +540 -0
  63. package/templates/podcast-creator-owner/himalaya.content.json +6 -0
  64. package/templates/podcast-creator-owner/platforms.json +2 -1
  65. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  66. package/templates/portfolio-creative/dev/index.ts +193 -1
  67. package/templates/portfolio-creative/tier5-src/_shared/iap-worker.ts +84 -3
  68. package/templates/portfolio-creative/tier5-src/himi-store-account/index.ts +36 -1
  69. package/templates/portfolio-creative-owner/config.ts +11 -0
  70. package/templates/portfolio-creative-owner/dev/index.ts +540 -0
  71. package/templates/portfolio-creative-owner/himalaya.content.json +6 -0
  72. package/templates/portfolio-creative-owner/platforms.json +2 -1
  73. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  74. package/templates/restaurant/dev/index.ts +193 -1
  75. package/templates/restaurant/tier5-src/_shared/iap-worker.ts +84 -3
  76. package/templates/restaurant/tier5-src/himi-store-account/index.ts +36 -1
  77. package/templates/restaurant-owner/config.ts +11 -0
  78. package/templates/restaurant-owner/dev/index.ts +540 -0
  79. package/templates/restaurant-owner/himalaya.content.json +6 -0
  80. package/templates/restaurant-owner/platforms.json +2 -1
  81. package/templates/restaurant-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  82. package/templates/retail-store/dev/index.ts +193 -1
  83. package/templates/retail-store/tier5-src/_shared/iap-worker.ts +84 -3
  84. package/templates/retail-store/tier5-src/himi-store-account/index.ts +36 -1
  85. package/templates/retail-store-owner/config.ts +11 -0
  86. package/templates/retail-store-owner/dev/index.ts +540 -0
  87. package/templates/retail-store-owner/himalaya.content.json +6 -0
  88. package/templates/retail-store-owner/platforms.json +2 -1
  89. package/templates/retail-store-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
  90. package/templates/yoga-studio/dev/index.ts +193 -1
  91. package/templates/yoga-studio/tier5-src/_shared/iap-worker.ts +84 -3
  92. package/templates/yoga-studio/tier5-src/himi-store-account/index.ts +36 -1
  93. package/templates/yoga-studio-owner/config.ts +11 -0
  94. package/templates/yoga-studio-owner/dev/index.ts +540 -0
  95. package/templates/yoga-studio-owner/himalaya.content.json +6 -0
  96. package/templates/yoga-studio-owner/platforms.json +2 -1
  97. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-wrist/index.ts +350 -0
@@ -50,6 +50,10 @@ export interface IapCommerce {
50
50
  buy(item: IapOrderItem): Promise<AppleIapCheckoutState>;
51
51
  /** On resume: re-reads Apple's unfinished queue and the payments being tracked. */
52
52
  reconcile(): Promise<AppleIapCheckoutState>;
53
+ /** Restore purchases (App Review 3.1.1): syncs Apple's purchase history to the device, then makes a
54
+ * new attempt at every unfinished purchase (a scheduled retry is not waited out) and re-reads the
55
+ * member's access. Needs a signed-in member; taps while one runs share it. */
56
+ restore(): Promise<IapRestoreResult>;
53
57
  tick(): Promise<AppleIapCheckoutState>;
54
58
  /** The screen's one `_poll` for prices and purchase, or undefined when the cadence it already
55
59
  * armed still holds (or there is nothing to poll). Emit it from every publish. */
@@ -59,6 +63,26 @@ export interface IapCommerce {
59
63
  poll(): Promise<void>;
60
64
  dispose(): void;
61
65
  }
66
+ /** What a restore found. Counts only: never a product, transaction or member id.
67
+ * - `needsSignIn`: no member is signed in; Apple was not asked.
68
+ * - `restored`: `recovered` purchases were taken by Wix during this restore; `refused` stay held,
69
+ * unfinished, because Wix refused them (another member's purchase, say), plus any Wix refused for good
70
+ * during this restore (finished; the member needs support); `pending` are charged but
71
+ * not yet taken (Wix could not be reached, or asked to retry), and stay held for the next attempt.
72
+ * - `failed`: `storeUnavailable` when the App Store sync or Apple's queue could not be read,
73
+ * `unavailable` when this host does not sell through the App Store, `sessionChanged` when the
74
+ * member changed. */
75
+ export type IapRestoreResult = {
76
+ status: "needsSignIn";
77
+ } | {
78
+ status: "restored";
79
+ recovered: number;
80
+ refused: number;
81
+ pending: number;
82
+ } | {
83
+ status: "failed";
84
+ code: "storeUnavailable" | "unavailable" | "sessionChanged";
85
+ };
62
86
  /** Labels from the screen's copy pack; the shared layer only chooses between them. */
63
87
  export interface IapPurchaseCopy {
64
88
  buy: string;
@@ -24,6 +24,9 @@ export interface IapCommerceDependencies {
24
24
  identity: MemberIdentitySource;
25
25
  host: CommerceHost;
26
26
  store: TransactionStore;
27
+ /** StoreKit's `AppStore.sync()`: brings Apple's purchase history to the device and may ask the
28
+ * person to sign in to their Apple Account. Rejects when the App Store cannot be reached. */
29
+ syncAppStore(): Promise<void>;
27
30
  refs: OrderRefStore;
28
31
  clock: Clock;
29
32
  adapters: readonly IapOrderAdapter[];
@@ -5,4 +5,4 @@ export { onlineProgramOrders, pricingPlanOrders, storesProductOrders, classifyCa
5
5
  export { createWorkerOrderRefStore } from "./order-refs.js";
6
6
  export { itemPaymentEnabled, storesAppleSalesEnabled } from "./checkout-source.js";
7
7
  export type { AccessRead, AppleIapCheckout, AppleIapCheckoutDependencies, AppleIapCheckoutPhase, AppleIapCheckoutState, BridgeOutcome, BridgeRefusal, CashierRoute, IapEligibility, IapOrderAdapter, IapOrderItem, OrderAttempt, OrderFailureCode, OrderPartitionKey, OrderRef, OrderRefPartition, OrderRefStage, OrderRefStore, PaymentAccess, PaymentProgress, SessionBoundBridge, VerticalRef, WixBridge, } from "./apple-iap-contract.js";
8
- export type { AppleIapCheckoutRoutes, IapCommerce, IapCommerceOptions, IapPurchaseCopy, IapPurchaseFields, IapPurchaseGap, IapPurchaseReadiness, } from "./commerce-contract.js";
8
+ export type { AppleIapCheckoutRoutes, IapCommerce, IapCommerceOptions, IapPurchaseCopy, IapPurchaseFields, IapPurchaseGap, IapPurchaseReadiness, IapRestoreResult, } from "./commerce-contract.js";
@@ -1345,6 +1345,10 @@ async function getProducts(productIds) {
1345
1345
  });
1346
1346
  return Array.isArray(r?.products) ? r.products : [];
1347
1347
  }
1348
+ async function restore() {
1349
+ const r = await dispatch("capability.iap.restore");
1350
+ return Array.isArray(r?.entitlements) ? r.entitlements : [];
1351
+ }
1348
1352
  async function purchaseForVerification(input) {
1349
1353
  if (!nonempty2(input?.productId) || !isUUID2(input?.appAccountToken)) return failure2("invalidArgument");
1350
1354
  const result = await managedDispatch(() => dispatch("capability.iap.purchaseForVerification", input));
@@ -2526,6 +2530,7 @@ function createIapCommerceWith(deps) {
2526
2530
  let stopWatchingAccess;
2527
2531
  let checkoutStarted = false;
2528
2532
  let recheckAfterOpening = false;
2533
+ let restoring;
2529
2534
  async function loadPrices(items) {
2530
2535
  if (!deps.pricing.enabled) return { scopeComplete: true };
2531
2536
  lastPricedAt = deps.clock.now();
@@ -2557,6 +2562,38 @@ function createIapCommerceWith(deps) {
2557
2562
  function start() {
2558
2563
  return recover((opened) => opened.start());
2559
2564
  }
2565
+ function restore2() {
2566
+ return restoring ?? (restoring = restoreOnce().finally(() => {
2567
+ restoring = void 0;
2568
+ }));
2569
+ }
2570
+ async function restoreOnce() {
2571
+ const session = deps.session.current();
2572
+ if (session.kind !== "member") return { status: "needsSignIn" };
2573
+ const before = state().purchase.items;
2574
+ const opened = await startedCheckout();
2575
+ if (deps.session.current().generation !== session.generation) return { status: "failed", code: "sessionChanged" };
2576
+ if (!opened) return { status: "failed", code: "unavailable" };
2577
+ if (!await syncedAppStore()) return { status: "failed", code: "storeUnavailable" };
2578
+ if (deps.session.current().generation !== session.generation) return { status: "failed", code: "sessionChanged" };
2579
+ const purchase = (await recover((opened2) => opened2.retry())).purchase;
2580
+ if (deps.session.current().generation !== session.generation) return { status: "failed", code: "sessionChanged" };
2581
+ const after = purchase.items;
2582
+ const newly = (matches) => Math.max(0, countOf(after, matches) - countOf(before, matches));
2583
+ const result = {
2584
+ status: "restored",
2585
+ recovered: newly(isRecovered),
2586
+ // Held refusals stay in the queue; a final refusal is finished, so only this restore's count.
2587
+ refused: countOf(after, isHeldRefusal) + newly(isFinalRefusal2),
2588
+ pending: countOf(after, isStillPending)
2589
+ };
2590
+ const settled = result.recovered + newly(isFinalRefusal2) > 0;
2591
+ if (purchase.unfinishedCount === null && !settled) return { status: "failed", code: "storeUnavailable" };
2592
+ return result;
2593
+ }
2594
+ function syncedAppStore() {
2595
+ return deps.syncAppStore().then(() => true, () => false);
2596
+ }
2560
2597
  async function buy(item) {
2561
2598
  if (unanswered2(deps.pricing.view(item.reference))) return state();
2562
2599
  const started = await startedCheckout();
@@ -2687,6 +2724,7 @@ function createIapCommerceWith(deps) {
2687
2724
  start,
2688
2725
  buy,
2689
2726
  reconcile: reconcile2,
2727
+ restore: restore2,
2690
2728
  tick: () => checkout ? checkout.tick() : Promise.resolve(state()),
2691
2729
  pollDirective,
2692
2730
  poll,
@@ -2707,6 +2745,22 @@ var SUSPENDED_AFTER_MS = 2 * PRICE_REFRESH_MS;
2707
2745
  function unanswered2(view) {
2708
2746
  return view.route === "resolving" || view.reason === "mappingUnavailable";
2709
2747
  }
2748
+ function isRecovered(item) {
2749
+ return item.completion === "finished" && item.access !== "rejected";
2750
+ }
2751
+ function isFinalRefusal2(item) {
2752
+ return item.completion === "finished" && item.access === "rejected";
2753
+ }
2754
+ function isHeldRefusal(item) {
2755
+ const unfinished = item.completion === "pending" || item.completion === "retryRequired";
2756
+ return unfinished && (item.access === "ownershipConflict" || item.access === "rejected");
2757
+ }
2758
+ function isStillPending(item) {
2759
+ return item.completion !== "finished" && item.completion !== "notFound" && !isHeldRefusal(item);
2760
+ }
2761
+ function countOf(items, matches) {
2762
+ return items.filter(matches).length;
2763
+ }
2710
2764
  function hostFor(identity, binding) {
2711
2765
  if (binding.himalayaAppId !== identity.appId || binding.bundleId !== identity.bundleId) return null;
2712
2766
  return { appId: identity.appId, bundleId: identity.bundleId, siteId: binding.siteId };
@@ -2725,6 +2779,9 @@ function createIapCommerce(options = {}) {
2725
2779
  session,
2726
2780
  host: hostOfWorker(),
2727
2781
  store: appleManagedStore,
2782
+ syncAppStore: async () => {
2783
+ await restore();
2784
+ },
2728
2785
  refs: createWorkerOrderRefStore(),
2729
2786
  clock: { now: () => Date.now() },
2730
2787
  adapters: options.adapters ?? [],
@@ -1622,6 +1622,10 @@ async function getProducts(productIds) {
1622
1622
  });
1623
1623
  return Array.isArray(r?.products) ? r.products : [];
1624
1624
  }
1625
+ async function restore() {
1626
+ const r = await dispatch2("capability.iap.restore");
1627
+ return Array.isArray(r?.entitlements) ? r.entitlements : [];
1628
+ }
1625
1629
  async function purchaseForVerification(input) {
1626
1630
  if (!nonempty2(input?.productId) || !isUUID2(input?.appAccountToken)) return failure2("invalidArgument");
1627
1631
  const result = await managedDispatch(() => dispatch2("capability.iap.purchaseForVerification", input));
@@ -2797,6 +2801,7 @@ function createIapCommerceWith(deps) {
2797
2801
  let stopWatchingAccess;
2798
2802
  let checkoutStarted = false;
2799
2803
  let recheckAfterOpening = false;
2804
+ let restoring;
2800
2805
  async function loadPrices(items) {
2801
2806
  if (!deps.pricing.enabled) return { scopeComplete: true };
2802
2807
  lastPricedAt = deps.clock.now();
@@ -2828,6 +2833,38 @@ function createIapCommerceWith(deps) {
2828
2833
  function start() {
2829
2834
  return recover((opened) => opened.start());
2830
2835
  }
2836
+ function restore2() {
2837
+ return restoring ?? (restoring = restoreOnce().finally(() => {
2838
+ restoring = void 0;
2839
+ }));
2840
+ }
2841
+ async function restoreOnce() {
2842
+ const session = deps.session.current();
2843
+ if (session.kind !== "member") return { status: "needsSignIn" };
2844
+ const before = state().purchase.items;
2845
+ const opened = await startedCheckout();
2846
+ if (deps.session.current().generation !== session.generation) return { status: "failed", code: "sessionChanged" };
2847
+ if (!opened) return { status: "failed", code: "unavailable" };
2848
+ if (!await syncedAppStore()) return { status: "failed", code: "storeUnavailable" };
2849
+ if (deps.session.current().generation !== session.generation) return { status: "failed", code: "sessionChanged" };
2850
+ const purchase = (await recover((opened2) => opened2.retry())).purchase;
2851
+ if (deps.session.current().generation !== session.generation) return { status: "failed", code: "sessionChanged" };
2852
+ const after = purchase.items;
2853
+ const newly = (matches) => Math.max(0, countOf(after, matches) - countOf(before, matches));
2854
+ const result = {
2855
+ status: "restored",
2856
+ recovered: newly(isRecovered),
2857
+ // Held refusals stay in the queue; a final refusal is finished, so only this restore's count.
2858
+ refused: countOf(after, isHeldRefusal) + newly(isFinalRefusal2),
2859
+ pending: countOf(after, isStillPending)
2860
+ };
2861
+ const settled = result.recovered + newly(isFinalRefusal2) > 0;
2862
+ if (purchase.unfinishedCount === null && !settled) return { status: "failed", code: "storeUnavailable" };
2863
+ return result;
2864
+ }
2865
+ function syncedAppStore() {
2866
+ return deps.syncAppStore().then(() => true, () => false);
2867
+ }
2831
2868
  async function buy(item) {
2832
2869
  if (unanswered2(deps.pricing.view(item.reference))) return state();
2833
2870
  const started = await startedCheckout();
@@ -2958,6 +2995,7 @@ function createIapCommerceWith(deps) {
2958
2995
  start,
2959
2996
  buy,
2960
2997
  reconcile: reconcile2,
2998
+ restore: restore2,
2961
2999
  tick: () => checkout ? checkout.tick() : Promise.resolve(state()),
2962
3000
  pollDirective,
2963
3001
  poll,
@@ -2978,6 +3016,22 @@ var SUSPENDED_AFTER_MS = 2 * PRICE_REFRESH_MS;
2978
3016
  function unanswered2(view) {
2979
3017
  return view.route === "resolving" || view.reason === "mappingUnavailable";
2980
3018
  }
3019
+ function isRecovered(item) {
3020
+ return item.completion === "finished" && item.access !== "rejected";
3021
+ }
3022
+ function isFinalRefusal2(item) {
3023
+ return item.completion === "finished" && item.access === "rejected";
3024
+ }
3025
+ function isHeldRefusal(item) {
3026
+ const unfinished = item.completion === "pending" || item.completion === "retryRequired";
3027
+ return unfinished && (item.access === "ownershipConflict" || item.access === "rejected");
3028
+ }
3029
+ function isStillPending(item) {
3030
+ return item.completion !== "finished" && item.completion !== "notFound" && !isHeldRefusal(item);
3031
+ }
3032
+ function countOf(items, matches) {
3033
+ return items.filter(matches).length;
3034
+ }
2981
3035
  function hostFor(identity, binding) {
2982
3036
  if (binding.himalayaAppId !== identity.appId || binding.bundleId !== identity.bundleId) return null;
2983
3037
  return { appId: identity.appId, bundleId: identity.bundleId, siteId: binding.siteId };
@@ -2996,6 +3050,9 @@ function createIapCommerce(options = {}) {
2996
3050
  session,
2997
3051
  host: hostOfWorker(),
2998
3052
  store: appleManagedStore,
3053
+ syncAppStore: async () => {
3054
+ await restore();
3055
+ },
2999
3056
  refs: createWorkerOrderRefStore(),
3000
3057
  clock: { now: () => Date.now() },
3001
3058
  adapters: options.adapters ?? [],