@wix/himalaya-cli 0.1320.0 → 0.1321.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/dev-server/src/app-config.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/app-module.d.ts +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/biometrics/src/index.d.ts +11 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/catalog-pricing-contract.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/host-session.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/index.d.ts +13 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/mapping-source.d.ts +8 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments.d.ts +5 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/sdk-types/src/capabilities.generated.d.ts +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/tokens.d.ts +9 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +5 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-core.d.ts +3 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/checkout-source.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-contract.d.ts +83 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-core.d.ts +36 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce.d.ts +8 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/index.d.ts +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/purchase-fields.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/audio-record.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calendar.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calls.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/contacts.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +1205 -71
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +125 -13
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/location.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/motion.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/photos.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdk-types.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/tasks.mjs +1 -1
- package/dist/cli.mjs +62 -23
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/getting-started.md +4 -2
- package/guides/himi-skill.md +4 -2
- package/guides/iap-pricing.md +33 -26
- package/guides/index.json +2 -1
- package/guides/sdk-reference.json +17 -5
- package/guides/tokens.md +22 -1
- package/package.json +2 -2
- package/templates/branded-lite/dev/index.ts +41 -0
- package/templates/branded-lite/tier5-src/_shared/iap-worker.ts +21 -44
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +77 -47
- package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +24 -38
- package/templates/yoga-studio/dev/index.ts +41 -0
- package/templates/yoga-studio/tier5-src/_shared/iap-worker.ts +21 -44
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +77 -47
- package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +24 -38
package/guides/iap-pricing.md
CHANGED
|
@@ -6,9 +6,11 @@ displays or sells: a program, a product, a plan, or anything else you registered
|
|
|
6
6
|
which items are sold through the App Store, fetches their prices from Apple, and returns display
|
|
7
7
|
state plus a payment route. Your screen binds the result to its own UI.
|
|
8
8
|
|
|
9
|
-
> **
|
|
10
|
-
>
|
|
11
|
-
>
|
|
9
|
+
> **On for bound apps only.** The mapping is read from the app's purchases catalog, which
|
|
10
|
+
> `himi store iap connect` sets up. An app with no purchases-app binding reads every item as
|
|
11
|
+
> `disabled` and keeps its existing price and checkout. This page shows prices and blocks the wrong
|
|
12
|
+
> checkout; selling an App Store item is the shared checkout's job (`createIapCommerce` +
|
|
13
|
+
> `commerce.purchaseFields`, [Wix Apple-IAP checkout](iap-wix-checkout.md#using-it-from-a-screen)).
|
|
12
14
|
> Only one-time purchases (non-consumables) are supported. Subscriptions and consumables are not.
|
|
13
15
|
|
|
14
16
|
## What you get per item
|
|
@@ -20,7 +22,7 @@ what to do:
|
|
|
20
22
|
|---|---|---|
|
|
21
23
|
| `disabled` | Your screen's own price, exactly as before | Allowed |
|
|
22
24
|
| `resolving` | A "Loading price…" label, never your own price | Blocked |
|
|
23
|
-
| `appleIap` | `displayPrice` exactly as Apple formats it, plus a "One-time purchase" note | Blocked;
|
|
25
|
+
| `appleIap` | `displayPrice` exactly as Apple formats it, plus a "One-time purchase" note | Blocked; the shared checkout sells it |
|
|
24
26
|
| `regular` | `displayPrice` (the commerce price you passed), or nothing | Allowed |
|
|
25
27
|
| `unavailable` | "Price unavailable", plus Retry when `retryable` | Blocked |
|
|
26
28
|
|
|
@@ -34,10 +36,9 @@ does not mean the item is free. Only an explicit `regular` route keeps your exis
|
|
|
34
36
|
returns `null` for any other route. It is for a checkout to compare with the product it is asked to
|
|
35
37
|
sell. Do not put it in screen state.
|
|
36
38
|
|
|
37
|
-
> **
|
|
38
|
-
>
|
|
39
|
-
>
|
|
40
|
-
> flow for them.
|
|
39
|
+
> **Buy through the shared checkout only.** `@wix/himalaya/iap-checkout` (`createIapCommerce`) is
|
|
40
|
+
> the App Store checkout built on this layer, and it sells only in a bound app. Keep App Store items
|
|
41
|
+
> blocked in your own checkout as described above. Never build your own purchase flow for them.
|
|
41
42
|
|
|
42
43
|
## Catalog references
|
|
43
44
|
|
|
@@ -52,13 +53,16 @@ when it registers the item with Apple:
|
|
|
52
53
|
|
|
53
54
|
## Add it to a worker
|
|
54
55
|
|
|
55
|
-
This is a complete worker for one screen
|
|
56
|
+
This is a complete worker for one screen that shows an App Store price and guards its existing
|
|
57
|
+
checkout. Copy the pattern, not the course names. A screen that also SELLS the item passes its
|
|
58
|
+
vertical adapter to `createIapCommerce` and binds Get access with `commerce.purchaseFields` instead of
|
|
59
|
+
blocking ([checkout recipe](iap-wix-checkout.md#using-it-from-a-screen)).
|
|
56
60
|
|
|
57
61
|
```ts
|
|
58
62
|
// catalog-pricing-worker.ts
|
|
59
63
|
import { defineService } from "@himalaya/state";
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
64
|
+
import { iapPriceFields, onlineProgramItem } from "@himalaya/iap";
|
|
65
|
+
import { createIapCommerce } from "@himalaya/iap-checkout";
|
|
62
66
|
|
|
63
67
|
const COPY = {
|
|
64
68
|
loading: "Loading price…",
|
|
@@ -74,25 +78,25 @@ type CourseState = Record<string, unknown>;
|
|
|
74
78
|
|
|
75
79
|
/** `startCheckout` is the screen's existing commerce flow. */
|
|
76
80
|
export function registerCourseScreen(course: Course, startCheckout: () => Promise<string>) {
|
|
77
|
-
|
|
81
|
+
// No adapters: this screen shows the price and keeps its own checkout. `commerce.pricing` is the
|
|
82
|
+
// shared `createIapCatalogPricing` layer; the host scopes its lookup (app identity, platform, and
|
|
83
|
+
// the app's purchases-app site when it has one).
|
|
84
|
+
const commerce = createIapCommerce({ onChange: () => { void service.publish(); } });
|
|
85
|
+
const pricing = commerce.pricing;
|
|
78
86
|
const item = onlineProgramItem({ id: course.id }, course.commercePrice)!;
|
|
79
87
|
let message = "";
|
|
80
88
|
let scopeIncomplete = false;
|
|
81
89
|
|
|
82
|
-
async function hostScope() {
|
|
83
|
-
const info = await appInfo().catch(() => undefined);
|
|
84
|
-
scopeIncomplete = !info?.appId || !info?.bundleId || !info?.platform;
|
|
85
|
-
return { scope: { appId: info?.appId ?? "", bundleId: info?.bundleId ?? "" }, platform: info?.platform ?? "" };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
90
|
async function loadPrices() {
|
|
89
|
-
if (pricing.enabled)
|
|
91
|
+
if (pricing.enabled) scopeIncomplete = !(await commerce.loadPrices([item])).scopeComplete;
|
|
90
92
|
void service.publish();
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
function present(state: CourseState): CourseState {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
const view = pricing.view(item.reference);
|
|
97
|
+
// `disabled`: no source, or an app that sells nothing through the App Store — the screen's own price.
|
|
98
|
+
if (view.route === "disabled") return { ...state, priceText: course.commercePrice, buyDisabled: false, message };
|
|
99
|
+
const price = iapPriceFields(view, COPY);
|
|
96
100
|
return { ...state, priceText: price.priceText, priceNote: price.priceNote, hidePriceRetry: price.hidePriceRetry && !scopeIncomplete,
|
|
97
101
|
buyDisabled: price.actionDisabled, buyNote: price.actionNote, message };
|
|
98
102
|
}
|
|
@@ -125,14 +129,14 @@ export function registerCourseScreen(course: Course, startCheckout: () => Promis
|
|
|
125
129
|
}
|
|
126
130
|
```
|
|
127
131
|
|
|
128
|
-
Bind the fields in the screen descriptor and declare
|
|
132
|
+
Bind the fields in the screen descriptor and declare the capabilities the worker calls. To show
|
|
129
133
|
real App Store prices, the app must also grant `iap` (and `app_info`) in its `config.ts`
|
|
130
134
|
`capabilities`, so its native shell registers the store bridge; until it does, the screen fails
|
|
131
135
|
closed to "Price unavailable" rather than showing a price it could not confirm.
|
|
132
136
|
|
|
133
137
|
```json
|
|
134
138
|
{
|
|
135
|
-
"logicBundle": { "declaredCapabilities": ["iap", "app_info"] },
|
|
139
|
+
"logicBundle": { "declaredCapabilities": ["iap", "app_info", "wix_api"] },
|
|
136
140
|
"price": { "type": "Text", "props": { "text": "$service:course.priceText", "collapseWhenEmpty": true } },
|
|
137
141
|
"note": { "type": "Text", "props": { "text": "$service:course.priceNote", "collapseWhenEmpty": true } },
|
|
138
142
|
"retry": { "type": "Button", "props": { "title": "Try again", "action": { "actionId": "tier5.course.refreshPrice" } },
|
|
@@ -163,8 +167,9 @@ closed to "Price unavailable" rather than showing a price it could not confirm.
|
|
|
163
167
|
- **Keep prices out of hydration** with `notHydrated`, so a cold start never shows an old amount.
|
|
164
168
|
- **Content first.** A pricing failure is per item. Never replace the screen with an error because a
|
|
165
169
|
price could not load.
|
|
166
|
-
- **iOS only.**
|
|
167
|
-
|
|
170
|
+
- **iOS only.** Android, web and macOS binaries carry no purchases-app binding, so the shipped
|
|
171
|
+
source reads every item there as `disabled` and the screen's own price and checkout apply. A
|
|
172
|
+
mapping that does name an App Store product on those hosts comes back `unavailable`.
|
|
168
173
|
|
|
169
174
|
## Adopting it
|
|
170
175
|
|
|
@@ -173,7 +178,9 @@ closed to "Price unavailable" rather than showing a price it could not confirm.
|
|
|
173
178
|
- **Existing screens, including apps created from a template before this existed:** an SDK update
|
|
174
179
|
never changes a screen that does not call this layer. Add the worker calls, the bindings and the
|
|
175
180
|
two capabilities above. After that, screens pick up layer fixes on their next build.
|
|
176
|
-
- **Custom screens:** follow the worker above for any item you
|
|
181
|
+
- **Custom screens:** follow the worker above for any item you price, and the
|
|
182
|
+
[checkout recipe](iap-wix-checkout.md#using-it-from-a-screen) for any item you sell, whatever its
|
|
183
|
+
vertical.
|
|
177
184
|
- **New item types:** use the kind `himi store iap` registers for it, build `{ kind, id }`, and call
|
|
178
185
|
the same layer. Never write your own mapping lookup, App Store price request or payment guard.
|
|
179
186
|
|
package/guides/index.json
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
],
|
|
21
21
|
"guardrails": [
|
|
22
22
|
"Prefer Tier-5 (SDUI descriptor + JS worker) over native screens — reach for native only when SDUI genuinely can't express it.",
|
|
23
|
-
"A screen that shows or sells an item that may be an App Store in-app purchase (a program, product or plan — template or custom screen alike) uses the shared layer `createIapCatalogPricing` from `@himalaya/iap` (`himi docs iap-pricing`): it batches the mapping lookup, fetches Apple prices and decides the payment route. Never hand-wire a mapping, an Apple price request or a payment guard in a worker. Call `checkoutGuard` immediately before a commerce flow starts, and `continueCheckoutGuard` before each later step of a flow already under way (a payment after a join), so a routine price refresh cannot strand it halfway. It is off until Wix connects the mapping service
|
|
23
|
+
"A screen that shows or sells an item that may be an App Store in-app purchase (a program, product or plan — template or custom screen alike) uses the shared layer `createIapCatalogPricing` from `@himalaya/iap` (`himi docs iap-pricing`): it batches the mapping lookup, fetches Apple prices and decides the payment route. Never hand-wire a mapping, an Apple price request or a payment guard in a worker. Call `checkoutGuard` immediately before a commerce flow starts, and `continueCheckoutGuard` before each later step of a flow already under way (a payment after a join), so a routine price refresh cannot strand it halfway. It is off until Wix connects the mapping service.",
|
|
24
|
+
"To SELL an App Store item, a screen composes `createIapCommerce` from `@himalaya/iap-checkout` once per worker (its catalog pricing plus the shared checkout, with the vertical's order adapter) and binds its Get access action with `commerce.purchaseFields`: the order, Apple's sheet, proof delivery, finishing and the access read all happen inside it. Never call StoreKit (`purchaseForVerification`), `finishTransaction` or the payment client from a worker, never fall back to Wix checkout for an App Store item, and show access only when it reports `accessReady`. Purchasing stays unavailable until Wix provides its routes and mapping.",
|
|
24
25
|
"Keep structure descriptor-authored: use FragmentSlot for enumerable variants and bound list/grid templates for repetition. Use WorkerSubtree only for runtime-unknowable structure, with an explicit screen allowlist, policy budgets, and shipped fallback.",
|
|
25
26
|
"Bindings ($service:<svc>.<path>, $item.<field>, $flag:<key>) are plain strings in ANY prop — INCLUDING Modifiers token refs (background, color, tint), so a worker can drive per-item theming, not just text. They CANNOT target strict-enum fields (style, size, alignment, severity, presentation, …) — those fail at render, not validation.",
|
|
26
27
|
"Workers are sandboxed: no DOM, no timers, no raw fetch. Produce state synchronously in actions; do not rely on an ambient refresh tick.",
|
|
@@ -432,8 +432,8 @@
|
|
|
432
432
|
{
|
|
433
433
|
"name": "BiometricKind",
|
|
434
434
|
"kind": "type",
|
|
435
|
-
"jsdoc": "",
|
|
436
|
-
"decl": "export type BiometricKind = \"face\" | \"fingerprint\" | \"iris\" | \"none\";"
|
|
435
|
+
"jsdoc": "/**\n * The enrolled biometric, as far as the platform can tell:\n * - `\"face\"` / `\"fingerprint\"` / `\"iris\"` — that modality.\n * - `\"biometric\"` — a biometric is enrolled but the platform can't say which\n * (Android phones with both face and fingerprint hardware; the web). Label\n * the unlock neutrally (\"Use biometrics\") rather than guessing a modality.\n * - `\"none\"` — nothing enrolled, or no hardware.\n *\n * Additive: treat any value you don't recognise like `\"biometric\"`.\n */",
|
|
436
|
+
"decl": "export type BiometricKind = \"face\" | \"fingerprint\" | \"iris\" | \"biometric\" | \"none\";"
|
|
437
437
|
},
|
|
438
438
|
{
|
|
439
439
|
"name": "available",
|
|
@@ -1878,7 +1878,7 @@
|
|
|
1878
1878
|
"name": "IapMappingSource",
|
|
1879
1879
|
"kind": "interface",
|
|
1880
1880
|
"jsdoc": "/** A deployed Wix→Apple mapping backend. Never supplied by a screen. */",
|
|
1881
|
-
"decl": "export interface IapMappingSource {\n mapping: PricingMappingProvider;\n /** The site this backend serves, used when the caller passes no site. */\n siteId?: string;\n}"
|
|
1881
|
+
"decl": "export interface IapMappingSource {\n mapping: PricingMappingProvider;\n /** Whether this app sells through the App Store at all — the binary carries a purchases-app binding.\n * When it answers false, every item reads `disabled` with no lookup, exactly as with no source, so the\n * screen's own commerce applies; asked again on every load. Absent means always active. */\n active?(): Promise<boolean>;\n /** The site this backend serves, used when the caller passes no site. */\n siteId?: string;\n}"
|
|
1882
1882
|
},
|
|
1883
1883
|
{
|
|
1884
1884
|
"name": "IapPollDirective",
|
|
@@ -2065,7 +2065,7 @@
|
|
|
2065
2065
|
{
|
|
2066
2066
|
"name": "createWixIapClient",
|
|
2067
2067
|
"kind": "function",
|
|
2068
|
-
"jsdoc": "/**\n * Creates a client for Wix's in-app-purchase payments service (`mobile-apps-wix-iap`), which pays\n * a Wix order with a store purchase the member makes in the app.\n *\n *
|
|
2068
|
+
"jsdoc": "/**\n * Creates a client for Wix's in-app-purchase payments service (`mobile-apps-wix-iap`), which pays\n * a Wix order with a store purchase the member makes in the app.\n *\n * The shared checkout (`createIapCommerce`) builds it with Serve's IAP route for an app whose binary\n * carries a purchases-app binding; `route` stays a parameter with no default. It calls\n * StartPayment (`POST …/payments/start`), CompletePayment (`POST …/payments/complete`)\n * and GetPayment (`GET …/payments/{id}`) through the\n * `wix_api` bridge. A matched or approved Payment is not durable access and never finishes the\n * store transaction.\n *\n * A sale needs the binding baked into the binary and an active mapping for the item; without them\n * nothing is offered, and before a production Serve release carries the IAP routes the mapping read\n * fails, so nothing is offered either. When this client uses the host\n * bridge, it also requires the host's identity clock and passes that captured expectation\n * through the bridge. An injected transport keeps the previous call shape unless the session\n * itself exposes an identity.\n */",
|
|
2069
2069
|
"decl": "export function createWixIapClient(deps: WixIapClientDependencies): WixIapClient;",
|
|
2070
2070
|
"verified": false
|
|
2071
2071
|
},
|
|
@@ -2079,7 +2079,7 @@
|
|
|
2079
2079
|
"name": "HostIdentitySource",
|
|
2080
2080
|
"kind": "interface",
|
|
2081
2081
|
"jsdoc": "/** A session provider that can also name the principal captured for one generation. */",
|
|
2082
|
-
"decl": "export interface HostIdentitySource {\n readonly identityBound: boolean;\n identity(session: SessionSnapshot): HostCallExpectation | null;\n}"
|
|
2082
|
+
"decl": "export interface HostIdentitySource {\n readonly identityBound: boolean;\n identity(session: SessionSnapshot): HostCallExpectation | null;\n /** The site the native bridge fences protected calls to, from the latest ready snapshot. Null\n * when the host installed no session source: iOS publishes the bridge (`identityBound`) to\n * every worker that has `wix_api`, with or without one. */\n site(): string | null;\n}"
|
|
2083
2083
|
},
|
|
2084
2084
|
{
|
|
2085
2085
|
"name": "HostSessionProvider",
|
|
@@ -2248,6 +2248,18 @@
|
|
|
2248
2248
|
"kind": "const",
|
|
2249
2249
|
"jsdoc": "/** The managed native verbs as the delivery coordinator's store. */",
|
|
2250
2250
|
"decl": "export const appleManagedStore: TransactionStore = { purchaseForVerification, getUnfinishedTransactions, finishTransaction };"
|
|
2251
|
+
},
|
|
2252
|
+
{
|
|
2253
|
+
"name": "hostAppBindingSource",
|
|
2254
|
+
"kind": "function",
|
|
2255
|
+
"jsdoc": "/** The purchases-service binding the host baked into this binary at build time (the app's build\n * contract → Info.plist), checked natively against the running bundle, app and site. Never read\n * from a screen, OTA config or navigation param: a worker cannot choose which purchases app it\n * sells through. Null means not provisioned: an unbound build, an older binary without the verb,\n * or a platform with no managed App Store purchasing. A host that has the verb and fails to answer\n * proves neither, so that read rejects and is not kept: the caller must not fall back to the\n * unbound app's commerce. The binding cannot change while the process runs, so a source asks the\n * host once. */",
|
|
2256
|
+
"decl": "export function hostAppBindingSource(): AppBindingSource;"
|
|
2257
|
+
},
|
|
2258
|
+
{
|
|
2259
|
+
"name": "sharedHostAppBindingSource",
|
|
2260
|
+
"kind": "function",
|
|
2261
|
+
"jsdoc": "/** One binding source per worker, shared by pricing and checkout, so one screen never acts on two\n * different answers to the same question. */",
|
|
2262
|
+
"decl": "export function sharedHostAppBindingSource(): AppBindingSource;"
|
|
2251
2263
|
}
|
|
2252
2264
|
]
|
|
2253
2265
|
},
|
package/guides/tokens.md
CHANGED
|
@@ -18,7 +18,7 @@ one registry, mirrored byte-for-byte by the iOS + Android decoders.
|
|
|
18
18
|
| `motions` | name → { duration, easing?, spring? } | optional |
|
|
19
19
|
| `materials` | name → { style, tint?, opacity?, blur? } | optional |
|
|
20
20
|
| `tabBar` / `drawer` / `sidebar` | nav-chrome style blocks | optional |
|
|
21
|
-
| `kit` | { material?, variant?, density?, motion? } | optional — the active design kit |
|
|
21
|
+
| `kit` | { material?, variant?, density?, motion?, reduceMotion?, colorScheme? } | optional — the active design kit |
|
|
22
22
|
| `contentMaxWidth` | { base, medium?, expanded? } | optional |
|
|
23
23
|
| `elevations` | name → { level?, tint?, shadow?, border? } | optional — a Card wears `card` |
|
|
24
24
|
| `controls` | { button?: { sm?, md?, lg?, variants? } } | optional — every Button's size and paint |
|
|
@@ -39,6 +39,27 @@ platform's own UI face — SF Pro on iOS/macOS, Roboto on Android, the system UI
|
|
|
39
39
|
A color is a hex string, OR a per-scheme object `{ light, dark?, highContrast? }`. In the
|
|
40
40
|
object form **`light` is required**; `dark`/`highContrast` fall back to it.
|
|
41
41
|
|
|
42
|
+
## Light or dark — `kit.colorScheme`
|
|
43
|
+
|
|
44
|
+
By default an app follows the device appearance. To pin it, set `kit.colorScheme` — it ships
|
|
45
|
+
OTA with the tokens, so a published release re-skins installed apps without a new binary:
|
|
46
|
+
|
|
47
|
+
| value | effect |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `"system"` (or absent) | follow the device light/dark setting |
|
|
50
|
+
| `"light"` | always light |
|
|
51
|
+
| `"dark"` | always dark |
|
|
52
|
+
|
|
53
|
+
Only those three lowercase values are recognised: another string counts as `"system"` and
|
|
54
|
+
`himi validate` warns; a non-string (a number, a boolean) is an error, because it fails the
|
|
55
|
+
whole tokens.json on device. The pin replaces only the device light/dark signal: an explicit
|
|
56
|
+
in-app theme choice (`theme.set`, a host's own Appearance setting), a preview's scheme pin,
|
|
57
|
+
and the OS high-contrast setting still win over it. Native chrome — the status bar and the
|
|
58
|
+
navigation bar — follows the pin too. Binaries older than the core release that added it
|
|
59
|
+
ignore the key and keep following the device. Set it in the top-level `kit` block (an idiom
|
|
60
|
+
overlay cannot change it), merged into the existing block rather than replacing it; adopting a
|
|
61
|
+
design kit keeps it.
|
|
62
|
+
|
|
42
63
|
## Motion
|
|
43
64
|
|
|
44
65
|
`easing` is `linear`, `easeIn`, `easeOut`, `easeInOut`, `spring` or `emphasized` (Material 3's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1321.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "3318113ec10ae018bbbe09e99bb3bef1207460516f9ca12d20e128ff"
|
|
75
75
|
}
|
|
@@ -85162,6 +85162,9 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
85162
85162
|
"actionId": "tier5.himiBrandedProgram.enroll"
|
|
85163
85163
|
}
|
|
85164
85164
|
}
|
|
85165
|
+
},
|
|
85166
|
+
"modifiers": {
|
|
85167
|
+
"hiddenPath": "$service:himiBrandedProgram.enrollHidden"
|
|
85165
85168
|
}
|
|
85166
85169
|
},
|
|
85167
85170
|
{
|
|
@@ -85175,6 +85178,44 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
85175
85178
|
"color": "textSecondary",
|
|
85176
85179
|
"collapseWhenEmpty": true
|
|
85177
85180
|
}
|
|
85181
|
+
},
|
|
85182
|
+
"modifiers": {
|
|
85183
|
+
"hiddenPath": "$service:himiBrandedProgram.enrollHidden"
|
|
85184
|
+
}
|
|
85185
|
+
},
|
|
85186
|
+
{
|
|
85187
|
+
"id": "himi_program.buy",
|
|
85188
|
+
"body": {
|
|
85189
|
+
"package": "himalaya",
|
|
85190
|
+
"type": "Button",
|
|
85191
|
+
"props": {
|
|
85192
|
+
"title": "$service:himiBrandedProgram.buyLabel",
|
|
85193
|
+
"style": "primary",
|
|
85194
|
+
"fullWidth": true,
|
|
85195
|
+
"disabledPath": "$service:himiBrandedProgram.buyDisabled",
|
|
85196
|
+
"action": {
|
|
85197
|
+
"actionId": "tier5.himiBrandedProgram.buy"
|
|
85198
|
+
}
|
|
85199
|
+
}
|
|
85200
|
+
},
|
|
85201
|
+
"modifiers": {
|
|
85202
|
+
"hiddenPath": "$service:himiBrandedProgram.buyHidden"
|
|
85203
|
+
}
|
|
85204
|
+
},
|
|
85205
|
+
{
|
|
85206
|
+
"id": "himi_program.buy_note",
|
|
85207
|
+
"body": {
|
|
85208
|
+
"package": "himalaya",
|
|
85209
|
+
"type": "Text",
|
|
85210
|
+
"props": {
|
|
85211
|
+
"text": "$service:himiBrandedProgram.buyNote",
|
|
85212
|
+
"style": "footnote",
|
|
85213
|
+
"color": "textSecondary",
|
|
85214
|
+
"collapseWhenEmpty": true
|
|
85215
|
+
}
|
|
85216
|
+
},
|
|
85217
|
+
"modifiers": {
|
|
85218
|
+
"hiddenPath": "$service:himiBrandedProgram.buyHidden"
|
|
85178
85219
|
}
|
|
85179
85220
|
},
|
|
85180
85221
|
{
|
|
@@ -1,49 +1,11 @@
|
|
|
1
|
-
// Worker-side glue for the shared IAP
|
|
2
|
-
// shared by every screen that shows or sells an
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import type {
|
|
1
|
+
// Worker-side glue for the shared IAP layer (`@himalaya/iap` catalog pricing and
|
|
2
|
+
// `@himalaya/iap-checkout` → createIapCommerce), shared by every screen that shows or sells an
|
|
3
|
+
// IAP-backed item. Mapping, Apple prices, payment guards, the host scope and the sale live in the
|
|
4
|
+
// SDK; this only supplies the copy and the hydration reset.
|
|
5
|
+
import type { IapPriceCopy } from "@himalaya/iap";
|
|
6
|
+
import type { IapPurchaseCopy } from "@himalaya/iap-checkout";
|
|
6
7
|
import { copyParam } from "./himi-copy.js";
|
|
7
8
|
|
|
8
|
-
export interface HostIapScope {
|
|
9
|
-
scope: IapCatalogScope;
|
|
10
|
-
platform: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let hostScope: Promise<HostIapScope> | undefined;
|
|
14
|
-
|
|
15
|
-
/** Read once per worker, and only by an enabled screen: a disabled one makes no app-info call.
|
|
16
|
-
* App identity and platform come from the host, never a screen param. The site is the host-owned
|
|
17
|
-
* native preference business (the web partition uses an OAuth client ID); without one, the mapping source's
|
|
18
|
-
* bound site applies. A failed read leaves identity empty, which fails every item closed — and is
|
|
19
|
-
* not kept, so the next lookup asks the host again instead of staying invalid for good. */
|
|
20
|
-
export function hostIapScope(): Promise<HostIapScope> {
|
|
21
|
-
return hostScope ??= readHostScope().then((read) => {
|
|
22
|
-
if (!hostScopeComplete(read)) hostScope = undefined;
|
|
23
|
-
return read;
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** An incomplete scope prices every item `invalidInput`, which the shared layer never offers to
|
|
28
|
-
* retry — so a screen offers Retry itself while its own host read is what failed. */
|
|
29
|
-
export function hostScopeComplete(read: HostIapScope): boolean {
|
|
30
|
-
return !!read.scope.appId && !!read.scope.bundleId && !!read.platform;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function readHostScope(): Promise<HostIapScope> {
|
|
34
|
-
const identity = await appInfo().catch(() => undefined);
|
|
35
|
-
const siteId = identity?.platform === "web" ? "" : hostSiteId();
|
|
36
|
-
return {
|
|
37
|
-
scope: { appId: identity?.appId ?? "", bundleId: identity?.bundleId ?? "", ...(siteId ? { siteId } : {}) },
|
|
38
|
-
platform: identity?.platform ?? "",
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function hostSiteId(): string {
|
|
43
|
-
const value = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input?.__himiPreferenceBusiness;
|
|
44
|
-
return typeof value === "string" ? value.trim() : "";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
9
|
/** Price labels from the screen's copy pack (`<prefix>.priceLoadingText`, …). */
|
|
48
10
|
export function iapPriceCopy(prefix: string): IapPriceCopy {
|
|
49
11
|
return {
|
|
@@ -54,6 +16,21 @@ export function iapPriceCopy(prefix: string): IapPriceCopy {
|
|
|
54
16
|
};
|
|
55
17
|
}
|
|
56
18
|
|
|
19
|
+
/** App Store purchase labels from the screen's copy pack (`<prefix>.buyCta`, …), for
|
|
20
|
+
* `iapPurchaseFields`. Customer copy only: no backend names, ids or diagnostics. */
|
|
21
|
+
export function iapPurchaseCopy(prefix: string): IapPurchaseCopy {
|
|
22
|
+
return {
|
|
23
|
+
buy: copyParam(`${prefix}.buyCta`, "Get access"),
|
|
24
|
+
processing: copyParam(`${prefix}.purchaseProcessingText`, "Processing your purchase…"),
|
|
25
|
+
awaitingApproval: copyParam(`${prefix}.purchaseAwaitingApprovalText`, "Waiting for approval"),
|
|
26
|
+
unavailable: copyParam(`${prefix}.purchaseTemporarilyUnavailableText`, "Purchasing is temporarily unavailable."),
|
|
27
|
+
signInRequired: copyParam(`${prefix}.purchaseSignInText`, "Sign in to buy."),
|
|
28
|
+
retry: copyParam(`${prefix}.purchaseRetryText`, "Something went wrong. Try again."),
|
|
29
|
+
priceChanged: copyParam(`${prefix}.purchasePriceChangedText`, "The price changed. Check it and try again."),
|
|
30
|
+
support: copyParam(`${prefix}.purchaseSupportText`, "We couldn't finish this purchase. Contact support."),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
57
34
|
/** What `notHydrated` does, for a runtime that ignores the option (a binary older than it, still
|
|
58
35
|
* served new bundles): put the listed keys back to their declared values before any action runs,
|
|
59
36
|
* so a persisted price or an enabled purchase action never paints as this run's. */
|
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
// back APPROVAL_PENDING (the owner approves; there is nothing to charge). Any failure
|
|
15
15
|
// degrades to the site-directing message rather than a dead button.
|
|
16
16
|
//
|
|
17
|
-
// App Store prices
|
|
18
|
-
//
|
|
17
|
+
// App Store prices, the payment route and the App Store sale come from the shared IAP layer
|
|
18
|
+
// (`createIapCommerce` in @himalaya/iap-checkout). An app that sells nothing through the App Store
|
|
19
|
+
// reads every program as `disabled` and keeps Enrol on its own commerce. An Apple-routed program
|
|
20
|
+
// offers Get access in place of Enrol and sells only through the shared checkout, never through the
|
|
21
|
+
// Wix payment rail.
|
|
19
22
|
import { defineService } from "@himalaya/state";
|
|
20
23
|
import {
|
|
21
24
|
getProgram,
|
|
@@ -31,8 +34,9 @@ import { cleanTitle, initials } from "../_shared/himi-text.js";
|
|
|
31
34
|
import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
|
|
32
35
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
33
36
|
import { formatCurrency } from "../_shared/engine-intl.js";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
37
|
+
import { iapPriceFields, onlineProgramItem, type IapCatalogItem, type IapPollDirective, type IapPriceFields } from "@himalaya/iap";
|
|
38
|
+
import { createIapCommerce, onlineProgramOrders, type IapPurchaseFields } from "@himalaya/iap-checkout";
|
|
39
|
+
import { iapPriceCopy, iapPurchaseCopy, resetUnhydratedKeysOnOlderRuntime } from "../_shared/iap-worker.js";
|
|
36
40
|
|
|
37
41
|
/** Wix Online Programs appDefId — the vertical a participant's cashier order is
|
|
38
42
|
* paid under (mirrors AppDefId.CHALLENGES in Wix's RN apps). */
|
|
@@ -66,6 +70,13 @@ interface State {
|
|
|
66
70
|
/** Bound to the Enrol CTA's `disabledPath`; `enroll` re-checks the route itself regardless. */
|
|
67
71
|
enrollDisabled: boolean;
|
|
68
72
|
enrollNote: string;
|
|
73
|
+
/** An App Store program sells through Get access instead. */
|
|
74
|
+
enrollHidden: boolean;
|
|
75
|
+
/** The shared checkout's action (`commerce.purchaseFields`): shown only on the App Store route. */
|
|
76
|
+
buyHidden: boolean;
|
|
77
|
+
buyLabel: string;
|
|
78
|
+
buyDisabled: boolean;
|
|
79
|
+
buyNote: string;
|
|
69
80
|
_poll?: IapPollDirective;
|
|
70
81
|
}
|
|
71
82
|
|
|
@@ -174,15 +185,6 @@ async function fetchProgram(id: string): Promise<WixProgram | undefined> {
|
|
|
174
185
|
}
|
|
175
186
|
}
|
|
176
187
|
|
|
177
|
-
async function load(state: State, arg?: unknown): Promise<State> {
|
|
178
|
-
stashCopy(arg);
|
|
179
|
-
try {
|
|
180
|
-
return ready(state, await fetchProgram(inputId()));
|
|
181
|
-
} catch {
|
|
182
|
-
return derive({ ...state, status: "error" });
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
188
|
/** Whether the existing commerce flow may start (before the join) and continue (before the Wix
|
|
187
189
|
* payment): a price refresh can move the program onto a route this flow must not charge. */
|
|
188
190
|
interface CommerceGuards {
|
|
@@ -190,8 +192,6 @@ interface CommerceGuards {
|
|
|
190
192
|
mayContinue(): boolean;
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
const ALWAYS_ALLOWED: CommerceGuards = { mayStart: () => true, mayContinue: () => true };
|
|
194
|
-
|
|
195
195
|
async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<string> {
|
|
196
196
|
const blocked = () => iapPriceCopy("program").purchaseUnavailable;
|
|
197
197
|
if (!guards.mayStart()) return blocked();
|
|
@@ -251,27 +251,27 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
|
|
|
251
251
|
return copyTemplate("program.joinOnSiteMessage", "Enrolment happens on the website — visit the site to join {name}.", { name });
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
|
|
255
|
-
if (!inputId()) return state;
|
|
256
|
-
return derive({ ...state, actionMessage: await enrollmentMessage(state.name, ALWAYS_ALLOWED) });
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// ── App Store pricing (enabled only when a mapping backend is connected) ─────
|
|
254
|
+
// ── Content, App Store pricing and sale ─────
|
|
260
255
|
//
|
|
261
|
-
// One writer: `
|
|
262
|
-
// through the synchronous `pricingPublish`, which re-reads the latest content
|
|
263
|
-
// action returning its own state could land after a refresh and
|
|
256
|
+
// One writer: `load`, `enroll` and `buy` keep their results here and every state
|
|
257
|
+
// change goes through the synchronous `pricingPublish`, which re-reads the latest content, route
|
|
258
|
+
// and checkout state. An async action returning its own state could land after a refresh and
|
|
259
|
+
// revive a stale price, route or purchase.
|
|
264
260
|
|
|
265
|
-
const
|
|
266
|
-
const
|
|
261
|
+
const commerce = createIapCommerce({ adapters: [onlineProgramOrders], onChange: () => { void svc.pricingPublish(); } });
|
|
262
|
+
const pricing = commerce.pricing;
|
|
263
|
+
const PRICING_NOT_HYDRATED = [
|
|
264
|
+
"priceLine", "priceNote", "hidePriceRetry", "enrollDisabled", "enrollNote", "enrollHidden", "buyHidden", "buyLabel", "buyDisabled", "buyNote",
|
|
265
|
+
] as const;
|
|
267
266
|
const CONTENT_KEYS = ["status", "name", "description", "meta", "image", "initial", "emptyText"] as const;
|
|
268
267
|
type Content = Pick<State, (typeof CONTENT_KEYS)[number]>;
|
|
269
268
|
const NO_PRICE: IapPriceFields = { priceText: "", priceNote: "", priceLoading: false, hidePriceRetry: true, actionDisabled: true, actionNote: "" };
|
|
269
|
+
const NOT_APPLE: IapPurchaseFields = { appleRoute: false, accessReady: false, buyLabel: "", buyDisabled: true, buyBusy: false, buyNote: "" };
|
|
270
270
|
let content: Content;
|
|
271
271
|
let pricedItem: IapCatalogItem | undefined;
|
|
272
|
-
let scopeIncomplete = false;
|
|
273
272
|
let actionMessage = "";
|
|
274
273
|
let loadSeq = 0;
|
|
274
|
+
let commerceStarted = false;
|
|
275
275
|
|
|
276
276
|
function contentOf(state: State): Content {
|
|
277
277
|
return Object.fromEntries(CONTENT_KEYS.map((key) => [key, state[key]])) as Content;
|
|
@@ -279,7 +279,7 @@ function contentOf(state: State): Content {
|
|
|
279
279
|
|
|
280
280
|
const programReference = () => onlineProgramItem({ id: inputId() })?.reference;
|
|
281
281
|
|
|
282
|
-
async function
|
|
282
|
+
async function load(state: State, arg?: unknown): Promise<undefined> {
|
|
283
283
|
stashCopy(arg);
|
|
284
284
|
const seq = ++loadSeq;
|
|
285
285
|
const found = await fetchProgram(inputId()).then((program) => ({ program }), () => undefined);
|
|
@@ -288,20 +288,27 @@ async function loadPriced(state: State, arg?: unknown): Promise<undefined> {
|
|
|
288
288
|
pricedItem = found?.program ? onlineProgramItem({ id: inputId() }, priceOf(found.program)) : pricedItem;
|
|
289
289
|
void svc.pricingPublish();
|
|
290
290
|
if (found?.program) void priceProgram();
|
|
291
|
+
void resumeCommerce();
|
|
291
292
|
return undefined;
|
|
292
293
|
}
|
|
293
294
|
|
|
295
|
+
/** First entry recovers held purchases; every later one (a return to the screen) reconciles. */
|
|
296
|
+
async function resumeCommerce(): Promise<void> {
|
|
297
|
+
const first = !commerceStarted;
|
|
298
|
+
commerceStarted = true;
|
|
299
|
+
await (first ? commerce.start() : commerce.reconcile());
|
|
300
|
+
void svc.pricingPublish();
|
|
301
|
+
}
|
|
302
|
+
|
|
294
303
|
/** Content never waits on this, and the host scope is re-read each time, so a failed app-info
|
|
295
304
|
* read recovers on the next refresh. */
|
|
296
305
|
async function priceProgram(): Promise<void> {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
scopeIncomplete = !hostScopeComplete(host);
|
|
301
|
-
if (pricedItem) await pricing.load({ ...host, items: [pricedItem] });
|
|
306
|
+
if (!pricedItem) return;
|
|
307
|
+
await commerce.loadPrices([pricedItem]);
|
|
308
|
+
void svc.pricingPublish();
|
|
302
309
|
}
|
|
303
310
|
|
|
304
|
-
async function
|
|
311
|
+
async function enroll(): Promise<undefined> {
|
|
305
312
|
const reference = programReference();
|
|
306
313
|
if (!reference) return undefined;
|
|
307
314
|
// A tap that lands as a refresh starts is refused without a verdict: the route is unknown, not
|
|
@@ -314,13 +321,29 @@ async function enrollPriced(): Promise<undefined> {
|
|
|
314
321
|
return undefined;
|
|
315
322
|
}
|
|
316
323
|
|
|
324
|
+
/** The App Store sale: only through the shared checkout, and only for the route the price shows. */
|
|
325
|
+
async function buy(): Promise<undefined> {
|
|
326
|
+
const reference = programReference();
|
|
327
|
+
if (!reference) return undefined;
|
|
328
|
+
await commerce.buy({ reference });
|
|
329
|
+
void svc.pricingPublish();
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
|
|
317
333
|
function pricedState(state: State): State {
|
|
318
334
|
const reference = programReference();
|
|
319
|
-
const
|
|
335
|
+
const view = reference && pricing.view(reference);
|
|
336
|
+
const price = view ? iapPriceFields(view, iapPriceCopy("program")) : NO_PRICE;
|
|
337
|
+
const purchase = reference ? commerce.purchaseFields(reference, iapPurchaseCopy("program")) : NOT_APPLE;
|
|
338
|
+
const poll = commerce.pollDirective("himiBrandedProgram.iapPoll");
|
|
320
339
|
return derive({
|
|
321
|
-
...state, ...content,
|
|
322
|
-
|
|
323
|
-
|
|
340
|
+
...state, ...content,
|
|
341
|
+
actionMessage: purchase.accessReady ? copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name: content.name }) : actionMessage,
|
|
342
|
+
// `disabled`: this app sells nothing through the App Store, so the program shows its own price.
|
|
343
|
+
priceLine: view?.route === "disabled" ? pricedItem?.commerceDisplayPrice ?? "" : price.priceText, priceNote: price.priceNote, hidePriceRetry: view?.route === "disabled" || (price.hidePriceRetry && commerce.scopeComplete()),
|
|
344
|
+
enrollDisabled: price.actionDisabled, enrollNote: price.actionNote, enrollHidden: purchase.appleRoute,
|
|
345
|
+
buyHidden: !purchase.appleRoute || purchase.accessReady, buyLabel: purchase.buyLabel, buyDisabled: purchase.buyDisabled, buyNote: purchase.buyNote,
|
|
346
|
+
...(poll ? { _poll: poll } : {}),
|
|
324
347
|
});
|
|
325
348
|
}
|
|
326
349
|
|
|
@@ -344,34 +367,41 @@ const INITIAL_STATE: State = derive({
|
|
|
344
367
|
hideContent: true,
|
|
345
368
|
priceNote: "",
|
|
346
369
|
hidePriceRetry: true,
|
|
347
|
-
enrollDisabled:
|
|
370
|
+
enrollDisabled: true,
|
|
348
371
|
enrollNote: "",
|
|
372
|
+
enrollHidden: false,
|
|
373
|
+
buyHidden: true,
|
|
374
|
+
buyLabel: "",
|
|
375
|
+
buyDisabled: true,
|
|
376
|
+
buyNote: "",
|
|
349
377
|
});
|
|
350
378
|
|
|
351
379
|
type Actions = {
|
|
352
380
|
load: (state: State, arg?: unknown) => Promise<State | undefined>;
|
|
353
381
|
enroll: (state: State) => Promise<State | undefined>;
|
|
382
|
+
buy: (state: State) => Promise<State | undefined>;
|
|
354
383
|
pricingPublish: (state: State) => State;
|
|
355
384
|
pricingStart: (state: State) => State;
|
|
356
385
|
pricingRefresh: (state: State) => Promise<undefined>;
|
|
386
|
+
iapPoll: (state: State) => Promise<undefined>;
|
|
357
387
|
};
|
|
358
388
|
|
|
359
389
|
const svc = defineService<State, Actions>("himiBrandedProgram", {
|
|
360
390
|
state: INITIAL_STATE,
|
|
361
|
-
|
|
391
|
+
notHydrated: [...PRICING_NOT_HYDRATED],
|
|
362
392
|
actions: {
|
|
363
|
-
load
|
|
364
|
-
enroll
|
|
393
|
+
load,
|
|
394
|
+
enroll,
|
|
395
|
+
buy,
|
|
365
396
|
pricingPublish: pricedState,
|
|
366
|
-
pricingStart:
|
|
397
|
+
pricingStart: pricedState,
|
|
367
398
|
pricingRefresh: async () => { await priceProgram(); return undefined; },
|
|
399
|
+
iapPoll: async () => { await commerce.poll(); void svc.pricingPublish(); return undefined; },
|
|
368
400
|
},
|
|
369
401
|
});
|
|
370
402
|
|
|
371
403
|
content = contentOf(svc.getState());
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
void svc.pricingStart();
|
|
375
|
-
}
|
|
404
|
+
resetUnhydratedKeysOnOlderRuntime(svc, INITIAL_STATE, PRICING_NOT_HYDRATED);
|
|
405
|
+
void svc.pricingStart();
|
|
376
406
|
|
|
377
407
|
export { svc };
|