@wix/himalaya-cli 0.1368.0 → 0.1369.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.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/mapping-source.d.ts +3 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments-contract.d.ts +44 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments-wire.d.ts +14 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments.d.ts +9 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +18 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-core.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/checkout-source.d.ts +8 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-core.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/iap-order-backend.d.ts +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/index.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/order-refs.d.ts +5 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +167 -41
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +75 -21
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +147 -30
- package/dist/cli.mjs +7 -6
- package/guides/iap-screen-adoption.md +3 -2
- package/guides/sdk-reference.json +57 -9
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +49 -1
- package/templates/beauty-salon/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +68 -5
- package/templates/branded-lite/dev/index.ts +49 -1
- package/templates/branded-lite/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +68 -5
- package/templates/community-club/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/events-meetups/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/gym-fitness/dev/index.ts +49 -1
- package/templates/gym-fitness/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +68 -5
- package/templates/nonprofit-cause/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/podcast-creator/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/portfolio-creative/dev/index.ts +49 -1
- package/templates/portfolio-creative/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +68 -5
- package/templates/restaurant/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/retail-store/dev/index.ts +49 -1
- package/templates/retail-store/tier5-src/_shared/iap-presentation.ts +4 -2
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +68 -5
- package/templates/yoga-studio/tier5-src/_shared/iap-presentation.ts +4 -2
|
@@ -26717,7 +26717,55 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
26717
26717
|
"modifiers": {
|
|
26718
26718
|
"cornerRadius": 8,
|
|
26719
26719
|
"pressFeedback": "scale",
|
|
26720
|
-
"haptic": "medium"
|
|
26720
|
+
"haptic": "medium",
|
|
26721
|
+
"hiddenPath": "$service:himiStoreProduct.cartHidden"
|
|
26722
|
+
}
|
|
26723
|
+
},
|
|
26724
|
+
{
|
|
26725
|
+
"id": "himi_product.buy",
|
|
26726
|
+
"body": {
|
|
26727
|
+
"package": "himalaya",
|
|
26728
|
+
"type": "Button",
|
|
26729
|
+
"props": {
|
|
26730
|
+
"title": "$service:himiStoreProduct.buyLabel",
|
|
26731
|
+
"style": "primary",
|
|
26732
|
+
"fullWidth": true,
|
|
26733
|
+
"disabledPath": "$service:himiStoreProduct.buyDisabled",
|
|
26734
|
+
"action": {
|
|
26735
|
+
"actionId": "tier5.himiStoreProduct.buy"
|
|
26736
|
+
}
|
|
26737
|
+
}
|
|
26738
|
+
},
|
|
26739
|
+
"modifiers": {
|
|
26740
|
+
"hiddenPath": "$service:himiStoreProduct.buyHidden"
|
|
26741
|
+
}
|
|
26742
|
+
},
|
|
26743
|
+
{
|
|
26744
|
+
"id": "himi_product.buy_note",
|
|
26745
|
+
"body": {
|
|
26746
|
+
"package": "himalaya",
|
|
26747
|
+
"type": "Text",
|
|
26748
|
+
"props": {
|
|
26749
|
+
"text": "$service:himiStoreProduct.buyNote",
|
|
26750
|
+
"style": "footnote",
|
|
26751
|
+
"color": "textSecondary",
|
|
26752
|
+
"collapseWhenEmpty": true
|
|
26753
|
+
}
|
|
26754
|
+
},
|
|
26755
|
+
"modifiers": {
|
|
26756
|
+
"hiddenPath": "$service:himiStoreProduct.buyHidden"
|
|
26757
|
+
}
|
|
26758
|
+
},
|
|
26759
|
+
{
|
|
26760
|
+
"id": "himi_product.purchased",
|
|
26761
|
+
"body": {
|
|
26762
|
+
"package": "himalaya",
|
|
26763
|
+
"type": "Text",
|
|
26764
|
+
"props": {
|
|
26765
|
+
"text": "$service:himiStoreProduct.purchasedNote",
|
|
26766
|
+
"style": "footnote",
|
|
26767
|
+
"collapseWhenEmpty": true
|
|
26768
|
+
}
|
|
26721
26769
|
}
|
|
26722
26770
|
},
|
|
26723
26771
|
{
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineService, type ServiceDefinition } from "@himalaya/state";
|
|
2
|
-
import { createIapCommerce, type IapCommerce } from "@himalaya/iap-checkout";
|
|
2
|
+
import { createIapCommerce, type IapCommerce, type IapOrderAdapter } from "@himalaya/iap-checkout";
|
|
3
3
|
import { iapPriceFields, type IapCatalogItem } from "@himalaya/iap";
|
|
4
4
|
import { iapPriceCopy, resetUnhydratedKeysOnOlderRuntime } from "./iap-worker.js";
|
|
5
5
|
|
|
6
6
|
// Presentation only. Checkout, mapping, cache, session and refresh policy stay in the shared flow.
|
|
7
7
|
export function withIapPresentation<S extends object>(options: {
|
|
8
8
|
register?: typeof defineService;
|
|
9
|
+
/** The verticals this screen sells; none for a screen that only shows prices. */
|
|
10
|
+
adapters?: readonly IapOrderAdapter[];
|
|
9
11
|
onCreate?(commerce: IapCommerce): void;
|
|
10
12
|
onContent?(state: S): void;
|
|
11
13
|
items(state: S): IapCatalogItem[];
|
|
@@ -15,7 +17,7 @@ export function withIapPresentation<S extends object>(options: {
|
|
|
15
17
|
prefix: string;
|
|
16
18
|
}): typeof defineService {
|
|
17
19
|
return ((name: string, definition: ServiceDefinition<S>) => {
|
|
18
|
-
const commerce = createIapCommerce({ onChange: () => { void service.iapPublish(); } });
|
|
20
|
+
const commerce = createIapCommerce({ adapters: options.adapters, onChange: () => { void service.iapPublish(); } });
|
|
19
21
|
options.onCreate?.(commerce);
|
|
20
22
|
let items: IapCatalogItem[] = [];
|
|
21
23
|
let contentVersion = 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Tier-5 bundle: himi-store — product detail.
|
|
2
2
|
import { withIapPresentation } from "../_shared/iap-presentation.js";
|
|
3
|
-
import { type IapCommerce } from "@himalaya/iap-checkout";
|
|
4
|
-
import { iapPriceCopy } from "../_shared/iap-worker.js";
|
|
3
|
+
import { itemPaymentEnabled, storesProductOrders, type IapCommerce, type IapPurchaseFields } from "@himalaya/iap-checkout";
|
|
4
|
+
import { iapPriceCopy, iapPurchaseCopy } from "../_shared/iap-worker.js";
|
|
5
5
|
import { share as shareSheet } from "@himalaya/share";
|
|
6
6
|
import { addToCurrentCart, getCurrentCart } from "@himalaya/wix-ecom";
|
|
7
7
|
import { get, upsert, enqueue, type DbEntity } from "@himalaya/local-db";
|
|
@@ -202,6 +202,14 @@ interface S {
|
|
|
202
202
|
opt1Label: string; opt1Chips: OptionChip[]; opt1Selected: string[]; noOpt1: boolean;
|
|
203
203
|
opt2Label: string; opt2Chips: OptionChip[]; opt2Selected: string[]; noOpt2: boolean;
|
|
204
204
|
opt3Label: string; opt3Chips: OptionChip[]; opt3Selected: string[]; noOpt3: boolean;
|
|
205
|
+
/** The App Store action (`commerce.purchaseFields`), in place of Add to cart while it shows. */
|
|
206
|
+
cartHidden: boolean;
|
|
207
|
+
buyHidden: boolean;
|
|
208
|
+
buyLabel: string;
|
|
209
|
+
buyDisabled: boolean;
|
|
210
|
+
buyNote: string;
|
|
211
|
+
/** Shown once the eCom order reads paid, never on the payment's approval alone. */
|
|
212
|
+
purchasedNote: string;
|
|
205
213
|
}
|
|
206
214
|
|
|
207
215
|
function inputId(): string {
|
|
@@ -309,14 +317,53 @@ function derive(s: S): S {
|
|
|
309
317
|
// read answering late — must not replace the badge the add set (#3468 review).
|
|
310
318
|
let cartBadgeEpoch = 0;
|
|
311
319
|
|
|
320
|
+
// A digital product with no options sells through the App Store once the platform pays for Wix
|
|
321
|
+
// Stores items with wix-iap StartItemPayment (`itemPaymentEnabled`). Until then, and for any other
|
|
322
|
+
// product, an App Store route only blocks the regular checkout, as before.
|
|
323
|
+
const SELLS_THROUGH_APPLE = itemPaymentEnabled();
|
|
324
|
+
const WIX_STORES_APP = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
|
|
325
|
+
const NOT_APPLE: IapPurchaseFields = { appleRoute: false, accessReady: false, buyLabel: "", buyDisabled: true, buyBusy: false, buyNote: "" };
|
|
326
|
+
|
|
312
327
|
let productPricing: IapCommerce;
|
|
328
|
+
let checkoutEntered = false;
|
|
313
329
|
const defineService = withIapPresentation<S>({
|
|
314
|
-
|
|
330
|
+
adapters: SELLS_THROUGH_APPLE ? [storesProductOrders] : [],
|
|
331
|
+
onCreate: commerce => { productPricing = commerce; }, prefix: "product",
|
|
332
|
+
transientKeys: ["price", "optionsIncomplete", "cartHint", "cartHidden", "buyHidden", "buyLabel", "buyDisabled", "buyNote", "purchasedNote"], contentActions: ["load", "paintCached"],
|
|
315
333
|
items: state => inputId() ? [{ reference: { kind: "storesProduct", id: inputId() }, commerceDisplayPrice: state.price }] : [],
|
|
316
|
-
present: (state, price, allowed) =>
|
|
317
|
-
|
|
334
|
+
present: (state, price, allowed) => {
|
|
335
|
+
const purchase = applePurchase(state);
|
|
336
|
+
const unavailable = iapPriceCopy("product").purchaseUnavailable;
|
|
337
|
+
const blocked = lastProduct?.productType?.toLowerCase() === "digital" || (!state.optionsUnknown && !allowed(inputId()));
|
|
338
|
+
return {
|
|
339
|
+
...state, price: price(inputId()), optionsIncomplete: derive(state).optionsIncomplete || !allowed(inputId()) || lastProduct?.productType?.toLowerCase() === "digital",
|
|
340
|
+
cartHint: !purchase.appleRoute && blocked ? unavailable : state.cartHint === unavailable ? "" : state.cartHint,
|
|
341
|
+
cartHidden: purchase.appleRoute,
|
|
342
|
+
buyHidden: !purchase.appleRoute || purchase.accessReady, buyLabel: purchase.buyLabel, buyDisabled: purchase.buyDisabled, buyNote: purchase.buyNote,
|
|
343
|
+
purchasedNote: purchase.accessReady ? copyParam("product.purchasedText", "Purchased") : "",
|
|
344
|
+
};
|
|
345
|
+
},
|
|
318
346
|
});
|
|
319
347
|
|
|
348
|
+
/** The App Store action, only for the one shape it sells: digital, with no options to choose, read
|
|
349
|
+
* from the product loaded for the id on screen (as Add to cart is). A cached product whose
|
|
350
|
+
* options were never read is not known to have none. */
|
|
351
|
+
function applePurchase(state: S): IapPurchaseFields {
|
|
352
|
+
const id = inputId();
|
|
353
|
+
const loaded = !!id && lastLoadedId === id;
|
|
354
|
+
const sellable = SELLS_THROUGH_APPLE && loaded && lastProduct?.productType?.toLowerCase() === "digital" && !state.optionsUnknown && state.optionGroups.length === 0;
|
|
355
|
+
const copy = { ...iapPurchaseCopy("product"), buy: copyParam("product.buyCta", "Buy") };
|
|
356
|
+
const fields = sellable ? productPricing.purchaseFields({ kind: "storesProduct", id }, copy) : NOT_APPLE;
|
|
357
|
+
return fields.appleRoute ? fields : NOT_APPLE;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** On every entry: the first starts the checkout (recovering held purchases), later ones reconcile. */
|
|
361
|
+
function enterCheckout(): void {
|
|
362
|
+
if (!SELLS_THROUGH_APPLE) return;
|
|
363
|
+
void (checkoutEntered ? productPricing.reconcile() : productPricing.start());
|
|
364
|
+
checkoutEntered = true;
|
|
365
|
+
}
|
|
366
|
+
|
|
320
367
|
const svc = defineService<S, {
|
|
321
368
|
load: (s: S, arg?: unknown) => Promise<S>;
|
|
322
369
|
setQuantity: (s: S, arg?: unknown) => S;
|
|
@@ -329,6 +376,8 @@ const svc = defineService<S, {
|
|
|
329
376
|
toggleSave: (s: S) => S;
|
|
330
377
|
/** [rico] #3372 — RichContent's on_link: `{ url }` of the tapped link. */
|
|
331
378
|
openLink: (s: S, arg?: unknown) => Promise<S>;
|
|
379
|
+
/** The App Store Buy: through the shared checkout, for the product on screen. */
|
|
380
|
+
buy: (s: S) => Promise<S>;
|
|
332
381
|
}>("himiStoreProduct", {
|
|
333
382
|
state: derive({
|
|
334
383
|
status: "loading",
|
|
@@ -375,10 +424,17 @@ const svc = defineService<S, {
|
|
|
375
424
|
opt1Label: "", opt1Chips: [], opt1Selected: [], noOpt1: true,
|
|
376
425
|
opt2Label: "", opt2Chips: [], opt2Selected: [], noOpt2: true,
|
|
377
426
|
opt3Label: "", opt3Chips: [], opt3Selected: [], noOpt3: true,
|
|
427
|
+
cartHidden: false,
|
|
428
|
+
buyHidden: true,
|
|
429
|
+
buyLabel: "",
|
|
430
|
+
buyDisabled: true,
|
|
431
|
+
buyNote: "",
|
|
432
|
+
purchasedNote: "",
|
|
378
433
|
}),
|
|
379
434
|
actions: {
|
|
380
435
|
load: async (s, arg): Promise<S> => {
|
|
381
436
|
stashCopy(arg);
|
|
437
|
+
enterCheckout();
|
|
382
438
|
const sequence = ++productLoadSequence;
|
|
383
439
|
lastProduct = undefined;
|
|
384
440
|
const id = inputId();
|
|
@@ -657,6 +713,13 @@ const svc = defineService<S, {
|
|
|
657
713
|
openLink: async (s, arg) => ((await openRichLink(arg))
|
|
658
714
|
? s
|
|
659
715
|
: derive({ ...s, added: false, cartHint: copyParam("product.linkOpenFailed", "This link could not be opened.") })),
|
|
716
|
+
buy: async (s): Promise<S> => {
|
|
717
|
+
const id = inputId();
|
|
718
|
+
const purchase = applePurchase(s);
|
|
719
|
+
if (!purchase.appleRoute || purchase.buyDisabled) return s;
|
|
720
|
+
await productPricing.buy({ reference: { kind: "storesProduct", id }, catalogReference: { appId: WIX_STORES_APP, catalogItemId: id }, quantity: 1 });
|
|
721
|
+
return svc.getState() as S;
|
|
722
|
+
},
|
|
660
723
|
},
|
|
661
724
|
});
|
|
662
725
|
|
|
@@ -82885,7 +82885,55 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
82885
82885
|
"modifiers": {
|
|
82886
82886
|
"cornerRadius": 8,
|
|
82887
82887
|
"pressFeedback": "scale",
|
|
82888
|
-
"haptic": "medium"
|
|
82888
|
+
"haptic": "medium",
|
|
82889
|
+
"hiddenPath": "$service:himiStoreProduct.cartHidden"
|
|
82890
|
+
}
|
|
82891
|
+
},
|
|
82892
|
+
{
|
|
82893
|
+
"id": "himi_product.buy",
|
|
82894
|
+
"body": {
|
|
82895
|
+
"package": "himalaya",
|
|
82896
|
+
"type": "Button",
|
|
82897
|
+
"props": {
|
|
82898
|
+
"title": "$service:himiStoreProduct.buyLabel",
|
|
82899
|
+
"style": "primary",
|
|
82900
|
+
"fullWidth": true,
|
|
82901
|
+
"disabledPath": "$service:himiStoreProduct.buyDisabled",
|
|
82902
|
+
"action": {
|
|
82903
|
+
"actionId": "tier5.himiStoreProduct.buy"
|
|
82904
|
+
}
|
|
82905
|
+
}
|
|
82906
|
+
},
|
|
82907
|
+
"modifiers": {
|
|
82908
|
+
"hiddenPath": "$service:himiStoreProduct.buyHidden"
|
|
82909
|
+
}
|
|
82910
|
+
},
|
|
82911
|
+
{
|
|
82912
|
+
"id": "himi_product.buy_note",
|
|
82913
|
+
"body": {
|
|
82914
|
+
"package": "himalaya",
|
|
82915
|
+
"type": "Text",
|
|
82916
|
+
"props": {
|
|
82917
|
+
"text": "$service:himiStoreProduct.buyNote",
|
|
82918
|
+
"style": "footnote",
|
|
82919
|
+
"color": "textSecondary",
|
|
82920
|
+
"collapseWhenEmpty": true
|
|
82921
|
+
}
|
|
82922
|
+
},
|
|
82923
|
+
"modifiers": {
|
|
82924
|
+
"hiddenPath": "$service:himiStoreProduct.buyHidden"
|
|
82925
|
+
}
|
|
82926
|
+
},
|
|
82927
|
+
{
|
|
82928
|
+
"id": "himi_product.purchased",
|
|
82929
|
+
"body": {
|
|
82930
|
+
"package": "himalaya",
|
|
82931
|
+
"type": "Text",
|
|
82932
|
+
"props": {
|
|
82933
|
+
"text": "$service:himiStoreProduct.purchasedNote",
|
|
82934
|
+
"style": "footnote",
|
|
82935
|
+
"collapseWhenEmpty": true
|
|
82936
|
+
}
|
|
82889
82937
|
}
|
|
82890
82938
|
},
|
|
82891
82939
|
{
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineService, type ServiceDefinition } from "@himalaya/state";
|
|
2
|
-
import { createIapCommerce, type IapCommerce } from "@himalaya/iap-checkout";
|
|
2
|
+
import { createIapCommerce, type IapCommerce, type IapOrderAdapter } from "@himalaya/iap-checkout";
|
|
3
3
|
import { iapPriceFields, type IapCatalogItem } from "@himalaya/iap";
|
|
4
4
|
import { iapPriceCopy, resetUnhydratedKeysOnOlderRuntime } from "./iap-worker.js";
|
|
5
5
|
|
|
6
6
|
// Presentation only. Checkout, mapping, cache, session and refresh policy stay in the shared flow.
|
|
7
7
|
export function withIapPresentation<S extends object>(options: {
|
|
8
8
|
register?: typeof defineService;
|
|
9
|
+
/** The verticals this screen sells; none for a screen that only shows prices. */
|
|
10
|
+
adapters?: readonly IapOrderAdapter[];
|
|
9
11
|
onCreate?(commerce: IapCommerce): void;
|
|
10
12
|
onContent?(state: S): void;
|
|
11
13
|
items(state: S): IapCatalogItem[];
|
|
@@ -15,7 +17,7 @@ export function withIapPresentation<S extends object>(options: {
|
|
|
15
17
|
prefix: string;
|
|
16
18
|
}): typeof defineService {
|
|
17
19
|
return ((name: string, definition: ServiceDefinition<S>) => {
|
|
18
|
-
const commerce = createIapCommerce({ onChange: () => { void service.iapPublish(); } });
|
|
20
|
+
const commerce = createIapCommerce({ adapters: options.adapters, onChange: () => { void service.iapPublish(); } });
|
|
19
21
|
options.onCreate?.(commerce);
|
|
20
22
|
let items: IapCatalogItem[] = [];
|
|
21
23
|
let contentVersion = 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Tier-5 bundle: himi-store — product detail.
|
|
2
2
|
import { withIapPresentation } from "../_shared/iap-presentation.js";
|
|
3
|
-
import { type IapCommerce } from "@himalaya/iap-checkout";
|
|
4
|
-
import { iapPriceCopy } from "../_shared/iap-worker.js";
|
|
3
|
+
import { itemPaymentEnabled, storesProductOrders, type IapCommerce, type IapPurchaseFields } from "@himalaya/iap-checkout";
|
|
4
|
+
import { iapPriceCopy, iapPurchaseCopy } from "../_shared/iap-worker.js";
|
|
5
5
|
import { share as shareSheet } from "@himalaya/share";
|
|
6
6
|
import { addToCurrentCart, getCurrentCart } from "@himalaya/wix-ecom";
|
|
7
7
|
import { get, upsert, enqueue, type DbEntity } from "@himalaya/local-db";
|
|
@@ -202,6 +202,14 @@ interface S {
|
|
|
202
202
|
opt1Label: string; opt1Chips: OptionChip[]; opt1Selected: string[]; noOpt1: boolean;
|
|
203
203
|
opt2Label: string; opt2Chips: OptionChip[]; opt2Selected: string[]; noOpt2: boolean;
|
|
204
204
|
opt3Label: string; opt3Chips: OptionChip[]; opt3Selected: string[]; noOpt3: boolean;
|
|
205
|
+
/** The App Store action (`commerce.purchaseFields`), in place of Add to cart while it shows. */
|
|
206
|
+
cartHidden: boolean;
|
|
207
|
+
buyHidden: boolean;
|
|
208
|
+
buyLabel: string;
|
|
209
|
+
buyDisabled: boolean;
|
|
210
|
+
buyNote: string;
|
|
211
|
+
/** Shown once the eCom order reads paid, never on the payment's approval alone. */
|
|
212
|
+
purchasedNote: string;
|
|
205
213
|
}
|
|
206
214
|
|
|
207
215
|
function inputId(): string {
|
|
@@ -309,14 +317,53 @@ function derive(s: S): S {
|
|
|
309
317
|
// read answering late — must not replace the badge the add set (#3468 review).
|
|
310
318
|
let cartBadgeEpoch = 0;
|
|
311
319
|
|
|
320
|
+
// A digital product with no options sells through the App Store once the platform pays for Wix
|
|
321
|
+
// Stores items with wix-iap StartItemPayment (`itemPaymentEnabled`). Until then, and for any other
|
|
322
|
+
// product, an App Store route only blocks the regular checkout, as before.
|
|
323
|
+
const SELLS_THROUGH_APPLE = itemPaymentEnabled();
|
|
324
|
+
const WIX_STORES_APP = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
|
|
325
|
+
const NOT_APPLE: IapPurchaseFields = { appleRoute: false, accessReady: false, buyLabel: "", buyDisabled: true, buyBusy: false, buyNote: "" };
|
|
326
|
+
|
|
312
327
|
let productPricing: IapCommerce;
|
|
328
|
+
let checkoutEntered = false;
|
|
313
329
|
const defineService = withIapPresentation<S>({
|
|
314
|
-
|
|
330
|
+
adapters: SELLS_THROUGH_APPLE ? [storesProductOrders] : [],
|
|
331
|
+
onCreate: commerce => { productPricing = commerce; }, prefix: "product",
|
|
332
|
+
transientKeys: ["price", "optionsIncomplete", "cartHint", "cartHidden", "buyHidden", "buyLabel", "buyDisabled", "buyNote", "purchasedNote"], contentActions: ["load", "paintCached"],
|
|
315
333
|
items: state => inputId() ? [{ reference: { kind: "storesProduct", id: inputId() }, commerceDisplayPrice: state.price }] : [],
|
|
316
|
-
present: (state, price, allowed) =>
|
|
317
|
-
|
|
334
|
+
present: (state, price, allowed) => {
|
|
335
|
+
const purchase = applePurchase(state);
|
|
336
|
+
const unavailable = iapPriceCopy("product").purchaseUnavailable;
|
|
337
|
+
const blocked = lastProduct?.productType?.toLowerCase() === "digital" || (!state.optionsUnknown && !allowed(inputId()));
|
|
338
|
+
return {
|
|
339
|
+
...state, price: price(inputId()), optionsIncomplete: derive(state).optionsIncomplete || !allowed(inputId()) || lastProduct?.productType?.toLowerCase() === "digital",
|
|
340
|
+
cartHint: !purchase.appleRoute && blocked ? unavailable : state.cartHint === unavailable ? "" : state.cartHint,
|
|
341
|
+
cartHidden: purchase.appleRoute,
|
|
342
|
+
buyHidden: !purchase.appleRoute || purchase.accessReady, buyLabel: purchase.buyLabel, buyDisabled: purchase.buyDisabled, buyNote: purchase.buyNote,
|
|
343
|
+
purchasedNote: purchase.accessReady ? copyParam("product.purchasedText", "Purchased") : "",
|
|
344
|
+
};
|
|
345
|
+
},
|
|
318
346
|
});
|
|
319
347
|
|
|
348
|
+
/** The App Store action, only for the one shape it sells: digital, with no options to choose, read
|
|
349
|
+
* from the product loaded for the id on screen (as Add to cart is). A cached product whose
|
|
350
|
+
* options were never read is not known to have none. */
|
|
351
|
+
function applePurchase(state: S): IapPurchaseFields {
|
|
352
|
+
const id = inputId();
|
|
353
|
+
const loaded = !!id && lastLoadedId === id;
|
|
354
|
+
const sellable = SELLS_THROUGH_APPLE && loaded && lastProduct?.productType?.toLowerCase() === "digital" && !state.optionsUnknown && state.optionGroups.length === 0;
|
|
355
|
+
const copy = { ...iapPurchaseCopy("product"), buy: copyParam("product.buyCta", "Buy") };
|
|
356
|
+
const fields = sellable ? productPricing.purchaseFields({ kind: "storesProduct", id }, copy) : NOT_APPLE;
|
|
357
|
+
return fields.appleRoute ? fields : NOT_APPLE;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** On every entry: the first starts the checkout (recovering held purchases), later ones reconcile. */
|
|
361
|
+
function enterCheckout(): void {
|
|
362
|
+
if (!SELLS_THROUGH_APPLE) return;
|
|
363
|
+
void (checkoutEntered ? productPricing.reconcile() : productPricing.start());
|
|
364
|
+
checkoutEntered = true;
|
|
365
|
+
}
|
|
366
|
+
|
|
320
367
|
const svc = defineService<S, {
|
|
321
368
|
load: (s: S, arg?: unknown) => Promise<S>;
|
|
322
369
|
setQuantity: (s: S, arg?: unknown) => S;
|
|
@@ -329,6 +376,8 @@ const svc = defineService<S, {
|
|
|
329
376
|
toggleSave: (s: S) => S;
|
|
330
377
|
/** [rico] #3372 — RichContent's on_link: `{ url }` of the tapped link. */
|
|
331
378
|
openLink: (s: S, arg?: unknown) => Promise<S>;
|
|
379
|
+
/** The App Store Buy: through the shared checkout, for the product on screen. */
|
|
380
|
+
buy: (s: S) => Promise<S>;
|
|
332
381
|
}>("himiStoreProduct", {
|
|
333
382
|
state: derive({
|
|
334
383
|
status: "loading",
|
|
@@ -375,10 +424,17 @@ const svc = defineService<S, {
|
|
|
375
424
|
opt1Label: "", opt1Chips: [], opt1Selected: [], noOpt1: true,
|
|
376
425
|
opt2Label: "", opt2Chips: [], opt2Selected: [], noOpt2: true,
|
|
377
426
|
opt3Label: "", opt3Chips: [], opt3Selected: [], noOpt3: true,
|
|
427
|
+
cartHidden: false,
|
|
428
|
+
buyHidden: true,
|
|
429
|
+
buyLabel: "",
|
|
430
|
+
buyDisabled: true,
|
|
431
|
+
buyNote: "",
|
|
432
|
+
purchasedNote: "",
|
|
378
433
|
}),
|
|
379
434
|
actions: {
|
|
380
435
|
load: async (s, arg): Promise<S> => {
|
|
381
436
|
stashCopy(arg);
|
|
437
|
+
enterCheckout();
|
|
382
438
|
const sequence = ++productLoadSequence;
|
|
383
439
|
lastProduct = undefined;
|
|
384
440
|
const id = inputId();
|
|
@@ -657,6 +713,13 @@ const svc = defineService<S, {
|
|
|
657
713
|
openLink: async (s, arg) => ((await openRichLink(arg))
|
|
658
714
|
? s
|
|
659
715
|
: derive({ ...s, added: false, cartHint: copyParam("product.linkOpenFailed", "This link could not be opened.") })),
|
|
716
|
+
buy: async (s): Promise<S> => {
|
|
717
|
+
const id = inputId();
|
|
718
|
+
const purchase = applePurchase(s);
|
|
719
|
+
if (!purchase.appleRoute || purchase.buyDisabled) return s;
|
|
720
|
+
await productPricing.buy({ reference: { kind: "storesProduct", id }, catalogReference: { appId: WIX_STORES_APP, catalogItemId: id }, quantity: 1 });
|
|
721
|
+
return svc.getState() as S;
|
|
722
|
+
},
|
|
660
723
|
},
|
|
661
724
|
});
|
|
662
725
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineService, type ServiceDefinition } from "@himalaya/state";
|
|
2
|
-
import { createIapCommerce, type IapCommerce } from "@himalaya/iap-checkout";
|
|
2
|
+
import { createIapCommerce, type IapCommerce, type IapOrderAdapter } from "@himalaya/iap-checkout";
|
|
3
3
|
import { iapPriceFields, type IapCatalogItem } from "@himalaya/iap";
|
|
4
4
|
import { iapPriceCopy, resetUnhydratedKeysOnOlderRuntime } from "./iap-worker.js";
|
|
5
5
|
|
|
6
6
|
// Presentation only. Checkout, mapping, cache, session and refresh policy stay in the shared flow.
|
|
7
7
|
export function withIapPresentation<S extends object>(options: {
|
|
8
8
|
register?: typeof defineService;
|
|
9
|
+
/** The verticals this screen sells; none for a screen that only shows prices. */
|
|
10
|
+
adapters?: readonly IapOrderAdapter[];
|
|
9
11
|
onCreate?(commerce: IapCommerce): void;
|
|
10
12
|
onContent?(state: S): void;
|
|
11
13
|
items(state: S): IapCatalogItem[];
|
|
@@ -15,7 +17,7 @@ export function withIapPresentation<S extends object>(options: {
|
|
|
15
17
|
prefix: string;
|
|
16
18
|
}): typeof defineService {
|
|
17
19
|
return ((name: string, definition: ServiceDefinition<S>) => {
|
|
18
|
-
const commerce = createIapCommerce({ onChange: () => { void service.iapPublish(); } });
|
|
20
|
+
const commerce = createIapCommerce({ adapters: options.adapters, onChange: () => { void service.iapPublish(); } });
|
|
19
21
|
options.onCreate?.(commerce);
|
|
20
22
|
let items: IapCatalogItem[] = [];
|
|
21
23
|
let contentVersion = 0;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineService, type ServiceDefinition } from "@himalaya/state";
|
|
2
|
-
import { createIapCommerce, type IapCommerce } from "@himalaya/iap-checkout";
|
|
2
|
+
import { createIapCommerce, type IapCommerce, type IapOrderAdapter } from "@himalaya/iap-checkout";
|
|
3
3
|
import { iapPriceFields, type IapCatalogItem } from "@himalaya/iap";
|
|
4
4
|
import { iapPriceCopy, resetUnhydratedKeysOnOlderRuntime } from "./iap-worker.js";
|
|
5
5
|
|
|
6
6
|
// Presentation only. Checkout, mapping, cache, session and refresh policy stay in the shared flow.
|
|
7
7
|
export function withIapPresentation<S extends object>(options: {
|
|
8
8
|
register?: typeof defineService;
|
|
9
|
+
/** The verticals this screen sells; none for a screen that only shows prices. */
|
|
10
|
+
adapters?: readonly IapOrderAdapter[];
|
|
9
11
|
onCreate?(commerce: IapCommerce): void;
|
|
10
12
|
onContent?(state: S): void;
|
|
11
13
|
items(state: S): IapCatalogItem[];
|
|
@@ -15,7 +17,7 @@ export function withIapPresentation<S extends object>(options: {
|
|
|
15
17
|
prefix: string;
|
|
16
18
|
}): typeof defineService {
|
|
17
19
|
return ((name: string, definition: ServiceDefinition<S>) => {
|
|
18
|
-
const commerce = createIapCommerce({ onChange: () => { void service.iapPublish(); } });
|
|
20
|
+
const commerce = createIapCommerce({ adapters: options.adapters, onChange: () => { void service.iapPublish(); } });
|
|
19
21
|
options.onCreate?.(commerce);
|
|
20
22
|
let items: IapCatalogItem[] = [];
|
|
21
23
|
let contentVersion = 0;
|
|
@@ -31318,7 +31318,55 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
31318
31318
|
"modifiers": {
|
|
31319
31319
|
"cornerRadius": 8,
|
|
31320
31320
|
"pressFeedback": "scale",
|
|
31321
|
-
"haptic": "medium"
|
|
31321
|
+
"haptic": "medium",
|
|
31322
|
+
"hiddenPath": "$service:himiStoreProduct.cartHidden"
|
|
31323
|
+
}
|
|
31324
|
+
},
|
|
31325
|
+
{
|
|
31326
|
+
"id": "himi_product.buy",
|
|
31327
|
+
"body": {
|
|
31328
|
+
"package": "himalaya",
|
|
31329
|
+
"type": "Button",
|
|
31330
|
+
"props": {
|
|
31331
|
+
"title": "$service:himiStoreProduct.buyLabel",
|
|
31332
|
+
"style": "primary",
|
|
31333
|
+
"fullWidth": true,
|
|
31334
|
+
"disabledPath": "$service:himiStoreProduct.buyDisabled",
|
|
31335
|
+
"action": {
|
|
31336
|
+
"actionId": "tier5.himiStoreProduct.buy"
|
|
31337
|
+
}
|
|
31338
|
+
}
|
|
31339
|
+
},
|
|
31340
|
+
"modifiers": {
|
|
31341
|
+
"hiddenPath": "$service:himiStoreProduct.buyHidden"
|
|
31342
|
+
}
|
|
31343
|
+
},
|
|
31344
|
+
{
|
|
31345
|
+
"id": "himi_product.buy_note",
|
|
31346
|
+
"body": {
|
|
31347
|
+
"package": "himalaya",
|
|
31348
|
+
"type": "Text",
|
|
31349
|
+
"props": {
|
|
31350
|
+
"text": "$service:himiStoreProduct.buyNote",
|
|
31351
|
+
"style": "footnote",
|
|
31352
|
+
"color": "textSecondary",
|
|
31353
|
+
"collapseWhenEmpty": true
|
|
31354
|
+
}
|
|
31355
|
+
},
|
|
31356
|
+
"modifiers": {
|
|
31357
|
+
"hiddenPath": "$service:himiStoreProduct.buyHidden"
|
|
31358
|
+
}
|
|
31359
|
+
},
|
|
31360
|
+
{
|
|
31361
|
+
"id": "himi_product.purchased",
|
|
31362
|
+
"body": {
|
|
31363
|
+
"package": "himalaya",
|
|
31364
|
+
"type": "Text",
|
|
31365
|
+
"props": {
|
|
31366
|
+
"text": "$service:himiStoreProduct.purchasedNote",
|
|
31367
|
+
"style": "footnote",
|
|
31368
|
+
"collapseWhenEmpty": true
|
|
31369
|
+
}
|
|
31322
31370
|
}
|
|
31323
31371
|
},
|
|
31324
31372
|
{
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineService, type ServiceDefinition } from "@himalaya/state";
|
|
2
|
-
import { createIapCommerce, type IapCommerce } from "@himalaya/iap-checkout";
|
|
2
|
+
import { createIapCommerce, type IapCommerce, type IapOrderAdapter } from "@himalaya/iap-checkout";
|
|
3
3
|
import { iapPriceFields, type IapCatalogItem } from "@himalaya/iap";
|
|
4
4
|
import { iapPriceCopy, resetUnhydratedKeysOnOlderRuntime } from "./iap-worker.js";
|
|
5
5
|
|
|
6
6
|
// Presentation only. Checkout, mapping, cache, session and refresh policy stay in the shared flow.
|
|
7
7
|
export function withIapPresentation<S extends object>(options: {
|
|
8
8
|
register?: typeof defineService;
|
|
9
|
+
/** The verticals this screen sells; none for a screen that only shows prices. */
|
|
10
|
+
adapters?: readonly IapOrderAdapter[];
|
|
9
11
|
onCreate?(commerce: IapCommerce): void;
|
|
10
12
|
onContent?(state: S): void;
|
|
11
13
|
items(state: S): IapCatalogItem[];
|
|
@@ -15,7 +17,7 @@ export function withIapPresentation<S extends object>(options: {
|
|
|
15
17
|
prefix: string;
|
|
16
18
|
}): typeof defineService {
|
|
17
19
|
return ((name: string, definition: ServiceDefinition<S>) => {
|
|
18
|
-
const commerce = createIapCommerce({ onChange: () => { void service.iapPublish(); } });
|
|
20
|
+
const commerce = createIapCommerce({ adapters: options.adapters, onChange: () => { void service.iapPublish(); } });
|
|
19
21
|
options.onCreate?.(commerce);
|
|
20
22
|
let items: IapCatalogItem[] = [];
|
|
21
23
|
let contentVersion = 0;
|