@wix/himalaya-cli 0.1346.0 → 0.1348.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/stdlib/flows/checkout/src/index.d.ts +7 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/checkout.mjs +7 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +7 -2
- package/dist/cli.mjs +32 -3
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/_shared/hosted-checkout.ts +42 -0
- package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +9 -3
- package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +11 -3
- package/templates/branded-lite/tier5-src/_shared/hosted-checkout.ts +42 -0
- package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +9 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +11 -3
- package/templates/gym-fitness/tier5-src/_shared/hosted-checkout.ts +42 -0
- package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +9 -3
- package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +11 -3
- package/templates/portfolio-creative/tier5-src/_shared/hosted-checkout.ts +42 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +11 -3
- package/templates/retail-store/tier5-src/_shared/hosted-checkout.ts +42 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +11 -3
- package/templates/yoga-studio/tier5-src/_shared/hosted-checkout.ts +42 -0
- package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +9 -3
|
@@ -62,6 +62,13 @@ export interface PayRequest {
|
|
|
62
62
|
currency?: string;
|
|
63
63
|
/** Mints the in-app cashier URL. Called ONLY when the wallet rail cannot settle. */
|
|
64
64
|
cashierUrl?: () => Promise<string | undefined>;
|
|
65
|
+
/** Opens the cashier ITSELF, in place of `cashierUrl` + an in-app browser: a host that must sign
|
|
66
|
+
* the member in on the site first (a hosted app's checkout, RFC 0071 §4 Step 4b) owns the URL,
|
|
67
|
+
* which carries a session token no worker may see. Resolves whether the cashier opened, or
|
|
68
|
+
* "withdrawn" when [stillMayCharge] — the caller's `mayCharge`, re-read — said no once the host had
|
|
69
|
+
* prepared it: preparing takes network round trips, during which the caller's route can change, so
|
|
70
|
+
* the host asks again right before it opens. */
|
|
71
|
+
openCashier?: (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
|
|
65
72
|
/** Re-read immediately before money moves (the wallet charge, the cashier opening). The caller's
|
|
66
73
|
* own route can change while a rail is being chosen; false stops the flow with nothing charged. */
|
|
67
74
|
mayCharge?: () => boolean | Promise<boolean>;
|
|
@@ -140,7 +140,7 @@ async function isEntitled() {
|
|
|
140
140
|
}
|
|
141
141
|
var WITHDRAWN = { kind: "failed", reason: "the caller withdrew the charge" };
|
|
142
142
|
async function payOrder(req) {
|
|
143
|
-
if (!req.snapshotId && !req.cashierUrl) {
|
|
143
|
+
if (!req.snapshotId && !req.cashierUrl && !req.openCashier) {
|
|
144
144
|
return { kind: "failed", reason: "no order to settle" };
|
|
145
145
|
}
|
|
146
146
|
const availability = await isAvailable().catch(() => "unavailable");
|
|
@@ -163,7 +163,12 @@ async function payOrder(req) {
|
|
|
163
163
|
}
|
|
164
164
|
if (r.status === "cancelled") return { kind: "cancelled" };
|
|
165
165
|
}
|
|
166
|
-
if (req.
|
|
166
|
+
if (req.openCashier) {
|
|
167
|
+
if (await req.mayCharge?.() === false) return WITHDRAWN;
|
|
168
|
+
const opened = await req.openCashier(async () => await req.mayCharge?.() !== false).catch(() => false);
|
|
169
|
+
if (opened === "withdrawn") return WITHDRAWN;
|
|
170
|
+
if (opened === true) return { kind: "handoff" };
|
|
171
|
+
} else if (req.cashierUrl) {
|
|
167
172
|
const url = await req.cashierUrl().catch(() => void 0);
|
|
168
173
|
if (url) {
|
|
169
174
|
if (await req.mayCharge?.() === false) return WITHDRAWN;
|
|
@@ -224,7 +224,7 @@ async function entitlement(planId) {
|
|
|
224
224
|
}
|
|
225
225
|
var WITHDRAWN = { kind: "failed", reason: "the caller withdrew the charge" };
|
|
226
226
|
async function payOrder(req) {
|
|
227
|
-
if (!req.snapshotId && !req.cashierUrl) {
|
|
227
|
+
if (!req.snapshotId && !req.cashierUrl && !req.openCashier) {
|
|
228
228
|
return { kind: "failed", reason: "no order to settle" };
|
|
229
229
|
}
|
|
230
230
|
const availability = await isAvailable().catch(() => "unavailable");
|
|
@@ -247,7 +247,12 @@ async function payOrder(req) {
|
|
|
247
247
|
}
|
|
248
248
|
if (r.status === "cancelled") return { kind: "cancelled" };
|
|
249
249
|
}
|
|
250
|
-
if (req.
|
|
250
|
+
if (req.openCashier) {
|
|
251
|
+
if (await req.mayCharge?.() === false) return WITHDRAWN;
|
|
252
|
+
const opened = await req.openCashier(async () => await req.mayCharge?.() !== false).catch(() => false);
|
|
253
|
+
if (opened === "withdrawn") return WITHDRAWN;
|
|
254
|
+
if (opened === true) return { kind: "handoff" };
|
|
255
|
+
} else if (req.cashierUrl) {
|
|
251
256
|
const url = await req.cashierUrl().catch(() => void 0);
|
|
252
257
|
if (url) {
|
|
253
258
|
if (await req.mayCharge?.() === false) return WITHDRAWN;
|
package/dist/cli.mjs
CHANGED
|
@@ -22951,6 +22951,7 @@ __export(gen_native_config_exports, {
|
|
|
22951
22951
|
LABEL_MODULES_ANDROID_INCLUDE: () => LABEL_MODULES_ANDROID_INCLUDE,
|
|
22952
22952
|
LABEL_MODULES_ANDROID_INSTALLERS: () => LABEL_MODULES_ANDROID_INSTALLERS,
|
|
22953
22953
|
LABEL_MODULES_ANDROID_LINK: () => LABEL_MODULES_ANDROID_LINK,
|
|
22954
|
+
LABEL_MODULES_IOS_FLAGS: () => LABEL_MODULES_IOS_FLAGS,
|
|
22954
22955
|
LABEL_MODULES_IOS_LINK: () => LABEL_MODULES_IOS_LINK,
|
|
22955
22956
|
LABEL_MODULES_IOS_PACKAGE: () => LABEL_MODULES_IOS_PACKAGE,
|
|
22956
22957
|
LABEL_MODULES_SWIFT_IMPORT: () => LABEL_MODULES_SWIFT_IMPORT,
|
|
@@ -22963,7 +22964,9 @@ __export(gen_native_config_exports, {
|
|
|
22963
22964
|
androidPlanFor: () => androidPlanFor,
|
|
22964
22965
|
androidSettingsGradle: () => androidSettingsGradle,
|
|
22965
22966
|
androidTvLeanbackPlan: () => androidTvLeanbackPlan,
|
|
22967
|
+
applyModuleFlags: () => applyModuleFlags,
|
|
22966
22968
|
applyToFile: () => applyToFile,
|
|
22969
|
+
assertModuleFlagsMarkers: () => assertModuleFlagsMarkers,
|
|
22967
22970
|
assertSingleMarkerPair: () => assertSingleMarkerPair,
|
|
22968
22971
|
assertSingleModuleInstall: () => assertSingleModuleInstall,
|
|
22969
22972
|
automotiveAppDescPath: () => automotiveAppDescPath,
|
|
@@ -22983,6 +22986,7 @@ __export(gen_native_config_exports, {
|
|
|
22983
22986
|
iosPlanFor: () => iosPlanFor,
|
|
22984
22987
|
kotlinBootSourcesUnder: () => kotlinBootSourcesUnder,
|
|
22985
22988
|
kotlinFilesWithModuleInstall: () => kotlinFilesWithModuleInstall,
|
|
22989
|
+
linkedFlag: () => linkedFlag,
|
|
22986
22990
|
loadOverrides: () => loadOverrides,
|
|
22987
22991
|
macosPlanFor: () => macosPlanFor,
|
|
22988
22992
|
moduleLinkRenderers: () => moduleLinkRenderers,
|
|
@@ -23001,6 +23005,7 @@ __export(gen_native_config_exports, {
|
|
|
23001
23005
|
renderModulesAndroidInstallers: () => renderModulesAndroidInstallers,
|
|
23002
23006
|
renderModulesAndroidLink: () => renderModulesAndroidLink,
|
|
23003
23007
|
renderModulesAndroidLinkBinary: () => renderModulesAndroidLinkBinary,
|
|
23008
|
+
renderModulesIosFlags: () => renderModulesIosFlags,
|
|
23004
23009
|
renderModulesIosLink: () => renderModulesIosLink,
|
|
23005
23010
|
renderModulesIosLinkBinary: () => renderModulesIosLinkBinary,
|
|
23006
23011
|
renderModulesIosPackage: () => renderModulesIosPackage,
|
|
@@ -23226,13 +23231,16 @@ function renderModulesAndroidLinkBinary(projects, coreVersion, indent) {
|
|
|
23226
23231
|
function moduleLinkRenderers(modules, binary) {
|
|
23227
23232
|
if (!binary) {
|
|
23228
23233
|
return {
|
|
23234
|
+
iosLinked: () => modules.spm,
|
|
23229
23235
|
iosPackage: (i2) => renderModulesIosPackage(modules.spm, i2),
|
|
23230
23236
|
iosLink: (i2) => renderModulesIosLink(modules.spm, i2),
|
|
23231
23237
|
androidInclude: (i2) => renderModulesAndroidInclude(modules.gradle, i2),
|
|
23232
23238
|
androidLink: (i2) => renderModulesAndroidLink(modules.gradle, i2)
|
|
23233
23239
|
};
|
|
23234
23240
|
}
|
|
23241
|
+
const iosLinked = () => modules.spm.length && coreVersionHasModules(binary.coreVersion()) ? modules.spm : [];
|
|
23235
23242
|
return {
|
|
23243
|
+
iosLinked,
|
|
23236
23244
|
iosPackage: (i2) => modules.spm.length && coreVersionHasModules(binary.coreVersion()) ? renderModulesIosPackageBinary(modules.spm, binary.corePackagePath?.(), i2) : [],
|
|
23237
23245
|
// Same version rule as Android: a pre-split core carries the capabilities and has no products.
|
|
23238
23246
|
iosLink: (i2) => modules.spm.length && coreVersionHasModules(binary.coreVersion()) ? renderModulesIosLinkBinary(modules.spm, i2) : [],
|
|
@@ -23246,14 +23254,17 @@ function moduleLinkRenderers(modules, binary) {
|
|
|
23246
23254
|
}
|
|
23247
23255
|
};
|
|
23248
23256
|
}
|
|
23257
|
+
function renderModulesIosFlags(products, indent) {
|
|
23258
|
+
return [`${indent}OTHER_SWIFT_FLAGS: "${["$(inherited)", ...products.map((p2) => `-D${linkedFlag(p2)}`)].join(" ")}"`];
|
|
23259
|
+
}
|
|
23249
23260
|
function renderModulesSwiftImport(indent) {
|
|
23250
23261
|
return NATIVE_MODULES.flatMap(
|
|
23251
|
-
(m2) => m2.ios ? [`${indent}#if canImport(${m2.ios.product})`, `${indent}import ${m2.ios.product}`, `${indent}#endif`] : []
|
|
23262
|
+
(m2) => m2.ios ? [`${indent}#if canImport(${m2.ios.product}) && ${linkedFlag(m2.ios.product)}`, `${indent}import ${m2.ios.product}`, `${indent}#endif`] : []
|
|
23252
23263
|
);
|
|
23253
23264
|
}
|
|
23254
23265
|
function renderModulesSwiftInstall(indent) {
|
|
23255
23266
|
return NATIVE_MODULES.flatMap(
|
|
23256
|
-
(m2) => m2.ios ? [`${indent}#if canImport(${m2.ios.product})`, `${indent}${m2.ios.product}.install()`, `${indent}#endif`] : []
|
|
23267
|
+
(m2) => m2.ios ? [`${indent}#if canImport(${m2.ios.product}) && ${linkedFlag(m2.ios.product)}`, `${indent}${m2.ios.product}.install()`, `${indent}#endif`] : []
|
|
23257
23268
|
);
|
|
23258
23269
|
}
|
|
23259
23270
|
function renderModulesAndroidInstallers(indent) {
|
|
@@ -23433,6 +23444,19 @@ function applyToFile(path2, kind, label2, render, check) {
|
|
|
23433
23444
|
writeFileSync13(path2, after);
|
|
23434
23445
|
return { path: path2, outcome: "written" };
|
|
23435
23446
|
}
|
|
23447
|
+
function assertModuleFlagsMarkers(paths) {
|
|
23448
|
+
for (const path2 of paths) {
|
|
23449
|
+
if (!existsSync34(path2)) continue;
|
|
23450
|
+
const text9 = readFileSync33(path2, "utf8");
|
|
23451
|
+
if (hasMarkers(text9, "yaml", LABEL_MODULES_IOS_LINK) && !hasMarkers(text9, "yaml", LABEL_MODULES_IOS_FLAGS)) {
|
|
23452
|
+
throw new Error(`${path2} links modules (${LABEL_MODULES_IOS_LINK}) but has no ${LABEL_MODULES_IOS_FLAGS} block under its project-level settings:, so no module would install. Add the markers (see apps/_template).`);
|
|
23453
|
+
}
|
|
23454
|
+
}
|
|
23455
|
+
}
|
|
23456
|
+
function applyModuleFlags(path2, products, check) {
|
|
23457
|
+
assertModuleFlagsMarkers([path2]);
|
|
23458
|
+
return applyToFile(path2, "yaml", LABEL_MODULES_IOS_FLAGS, (i2) => renderModulesIosFlags(products, i2), check);
|
|
23459
|
+
}
|
|
23436
23460
|
function applyWholeFile(path2, content, check) {
|
|
23437
23461
|
const current = existsSync34(path2) ? readFileSync33(path2, "utf8") : null;
|
|
23438
23462
|
if (current === content) return { path: path2, outcome: "unchanged" };
|
|
@@ -23485,6 +23509,7 @@ function genNativeConfig(app, opts = {}) {
|
|
|
23485
23509
|
const check = opts.check ?? false;
|
|
23486
23510
|
assertSingleModuleInstall(app, swiftFilesWithModuleMarkers(app));
|
|
23487
23511
|
assertSingleModuleInstall(app, kotlinFilesWithModuleInstall(app));
|
|
23512
|
+
assertModuleFlagsMarkers([iosProjectYml(app), macosProjectYml(app)]);
|
|
23488
23513
|
const configPath = appConfigTs(app);
|
|
23489
23514
|
const configTs = existsSync34(configPath) ? readFileSync33(configPath, "utf8") : "";
|
|
23490
23515
|
const capabilities = parseStringArray(configTs, "capabilities");
|
|
@@ -23589,9 +23614,11 @@ function genNativeConfig(app, opts = {}) {
|
|
|
23589
23614
|
// published AARs (#4055): no source package, no settings include.
|
|
23590
23615
|
applyToFile(iosProjectYml(app), "yaml", LABEL_MODULES_IOS_PACKAGE, moduleLinks.iosPackage, check),
|
|
23591
23616
|
applyToFile(iosProjectYml(app), "yaml", LABEL_MODULES_IOS_LINK, moduleLinks.iosLink, check),
|
|
23617
|
+
applyModuleFlags(iosProjectYml(app), moduleLinks.iosLinked(), check),
|
|
23592
23618
|
// macOS (RFC 0046) reuses the iOS boot, so it links the same macOS-capable modules.
|
|
23593
23619
|
applyToFile(macosProjectYml(app), "yaml", LABEL_MODULES_IOS_PACKAGE, (i2) => renderModulesIosPackage(modules.macos, i2), check),
|
|
23594
23620
|
applyToFile(macosProjectYml(app), "yaml", LABEL_MODULES_IOS_LINK, (i2) => renderModulesIosLink(modules.macos, i2), check),
|
|
23621
|
+
applyModuleFlags(macosProjectYml(app), modules.macos, check),
|
|
23595
23622
|
applyToFile(androidSettingsGradle(app), "slash", LABEL_MODULES_ANDROID_INCLUDE, moduleLinks.androidInclude, check),
|
|
23596
23623
|
applyToFile(androidAppGradle(app), "slash", LABEL_MODULES_ANDROID_LINK, moduleLinks.androidLink, check),
|
|
23597
23624
|
// watchOS (RFC 0047): the watch runtime mode/serve, declared once in config.ts. A watch
|
|
@@ -23685,7 +23712,7 @@ function genNativeConfig(app, opts = {}) {
|
|
|
23685
23712
|
);
|
|
23686
23713
|
return { app, buildSet: ids, files, drift: files.some((f2) => f2.outcome === "drift") };
|
|
23687
23714
|
}
|
|
23688
|
-
var LABEL_IOS_INFO, LABEL_IOS_ENTITLEMENTS, LABEL_ANDROID, LABEL_ANDROID_SERVICES, LABEL_MODULES_IOS_PACKAGE, LABEL_MODULES_IOS_LINK, LABEL_MODULES_ANDROID_INCLUDE, LABEL_MODULES_ANDROID_LINK, LABEL_MODULES_SWIFT_IMPORT, LABEL_MODULES_SWIFT_INSTALL, LABEL_MODULES_ANDROID_INSTALLERS, LABEL_WATCH, LABEL_TV, LABEL_ANDROID_TV, LABEL_ORIENTATION, LABEL_CAR, IOS_ORIENTATION, ALL_ORIENTATIONS, HEALTH_PERMISSION_COPY_META, FROM_APP_PLATFORM_DIR, CORE_ANDROID_CAPABILITY_MODULES, ANDROID_MAX_DEPTH, BOOT_SOURCE_MAX_BYTES, IOS_BUILD_CAPABILITIES_KEY, IOS_DIST_CAPABILITIES_KEY, ANDROID_BUILD_CAPABILITIES_META, ANDROID_DIST_CAPABILITIES_META;
|
|
23715
|
+
var LABEL_IOS_INFO, LABEL_IOS_ENTITLEMENTS, LABEL_ANDROID, LABEL_ANDROID_SERVICES, LABEL_MODULES_IOS_PACKAGE, LABEL_MODULES_IOS_LINK, LABEL_MODULES_ANDROID_INCLUDE, LABEL_MODULES_ANDROID_LINK, LABEL_MODULES_IOS_FLAGS, LABEL_MODULES_SWIFT_IMPORT, LABEL_MODULES_SWIFT_INSTALL, LABEL_MODULES_ANDROID_INSTALLERS, LABEL_WATCH, LABEL_TV, LABEL_ANDROID_TV, LABEL_ORIENTATION, LABEL_CAR, IOS_ORIENTATION, ALL_ORIENTATIONS, HEALTH_PERMISSION_COPY_META, FROM_APP_PLATFORM_DIR, linkedFlag, CORE_ANDROID_CAPABILITY_MODULES, ANDROID_MAX_DEPTH, BOOT_SOURCE_MAX_BYTES, IOS_BUILD_CAPABILITIES_KEY, IOS_DIST_CAPABILITIES_KEY, ANDROID_BUILD_CAPABILITIES_META, ANDROID_DIST_CAPABILITIES_META;
|
|
23689
23716
|
var init_gen_native_config = __esm({
|
|
23690
23717
|
"../native-config/gen-native-config.ts"() {
|
|
23691
23718
|
init_src2();
|
|
@@ -23708,6 +23735,7 @@ var init_gen_native_config = __esm({
|
|
|
23708
23735
|
LABEL_MODULES_IOS_LINK = "himalaya:modules:link";
|
|
23709
23736
|
LABEL_MODULES_ANDROID_INCLUDE = "himalaya:modules:include";
|
|
23710
23737
|
LABEL_MODULES_ANDROID_LINK = "himalaya:modules:dep";
|
|
23738
|
+
LABEL_MODULES_IOS_FLAGS = "himalaya:modules:flags";
|
|
23711
23739
|
LABEL_MODULES_SWIFT_IMPORT = "himalaya:modules:import";
|
|
23712
23740
|
LABEL_MODULES_SWIFT_INSTALL = "himalaya:modules:install";
|
|
23713
23741
|
LABEL_MODULES_ANDROID_INSTALLERS = "himalaya:modules:installers";
|
|
@@ -23725,6 +23753,7 @@ var init_gen_native_config = __esm({
|
|
|
23725
23753
|
ALL_ORIENTATIONS = ["portrait", "portraitUpsideDown", "landscapeLeft", "landscapeRight"];
|
|
23726
23754
|
HEALTH_PERMISSION_COPY_META = "io.himalaya.health.permissionCopy";
|
|
23727
23755
|
FROM_APP_PLATFORM_DIR = "../../../";
|
|
23756
|
+
linkedFlag = (product) => `HIMALAYA_LINKED_${product}`;
|
|
23728
23757
|
CORE_ANDROID_CAPABILITY_MODULES = join34(
|
|
23729
23758
|
dirname21(fileURLToPath14(import.meta.url)),
|
|
23730
23759
|
"../../core/runtime/android/io/himalaya/core/src/main/kotlin/io/himalaya/core/CapabilityModules.kt"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1348.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": "ff7251a5688611526e72aaae9882c46d2f4f3667bbd4f0fa94b5343f"
|
|
75
75
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
|
|
2
|
+
// pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
|
|
3
|
+
// instance, so the HOST opens the site's checkout signed in as that member. The worker names only
|
|
4
|
+
// its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
|
|
5
|
+
// the site's `createSessionCookie` → checkout. The session token never reaches this worker.
|
|
6
|
+
|
|
7
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
8
|
+
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
9
|
+
|
|
10
|
+
/** The host verbs (registered by the host shell, never by the app itself). The first prepares the
|
|
11
|
+
* checkout and answers a one-time ticket; the second opens it. Two steps so the caller's charge guard
|
|
12
|
+
* is re-read after the host's network round trips and right before the checkout opens — the prepared
|
|
13
|
+
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
|
+
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
|
+
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
16
|
+
|
|
17
|
+
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
|
+
export function isHostedWorker(): boolean {
|
|
19
|
+
return hostSiteFromInput((globalThis as { himalaya?: { input?: unknown } }).himalaya?.input) !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
23
|
+
|
|
24
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
25
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
26
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
27
|
+
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
28
|
+
return async (stillMayCharge) => {
|
|
29
|
+
try {
|
|
30
|
+
const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
|
|
31
|
+
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
|
+
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
|
+
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
34
|
+
if (!(await stillMayCharge())) return "withdrawn";
|
|
35
|
+
const result = await dispatch(HOSTED_CHECKOUT_OPEN_ACTION, { ticket }) as HostAnswer;
|
|
36
|
+
const opened = result?.opened ?? result?.payload?.opened;
|
|
37
|
+
return opened === true || opened === "true";
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -9,6 +9,7 @@ import { bookSlot, getService, queryServiceAvailability, type WixBookingSlotFull
|
|
|
9
9
|
import { createCheckout, createOrder } from "@himalaya/wix-ecom";
|
|
10
10
|
import { createRedirectSession, type WixSlot } from "@himalaya/wix-redirects";
|
|
11
11
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
12
|
+
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
12
13
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
13
14
|
import { displayName, getMyData, type WixMyData } from "@himalaya/wix-users";
|
|
14
15
|
import { formatServicePrice, serviceIsChargeable } from "../_shared/wix-service-price.js";
|
|
@@ -662,9 +663,12 @@ const svc = defineService<S, {
|
|
|
662
663
|
// 3. Create the Wix-managed order — mints the cashier snapshot when a
|
|
663
664
|
// balance is due. No paymentGatewayOrderId ⇒ nothing to charge
|
|
664
665
|
// (covered / zero total) ⇒ the booking is already confirmed.
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
666
|
+
// Hosted (Spaces): the site's own checkout page creates the order (as
|
|
667
|
+
// RN does) — creating it here first fails on a site with no payment method set up.
|
|
668
|
+
const hosted = isHostedWorker();
|
|
669
|
+
const orderRes = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
670
|
+
const snapshotId = orderRes.paymentGatewayOrderId ?? "";
|
|
671
|
+
if (!snapshotId && !hosted) {
|
|
668
672
|
return derive({ ...pending, bookingInFlight: false, successMessage: copyParam("time.successMessage", "Booked. Your confirmation is ready in My Bookings."), message: "" });
|
|
669
673
|
}
|
|
670
674
|
// 4. Collect payment IN-APP — wallet when this app has one wired, otherwise
|
|
@@ -676,6 +680,8 @@ const svc = defineService<S, {
|
|
|
676
680
|
appId: WIX_BOOKINGS_APP_ID,
|
|
677
681
|
snapshotId,
|
|
678
682
|
msid: await resolveMsid(),
|
|
683
|
+
// Hosted (Spaces): the host opens this booking's checkout as the lent member.
|
|
684
|
+
...(hosted ? { openCashier: hostedCheckoutOpener(checkoutId) } : {}),
|
|
679
685
|
cashierUrl: async () => {
|
|
680
686
|
const redirect = await createRedirectSession({
|
|
681
687
|
bookingsCheckout: { slotAvailability: { slot: slot as unknown as WixSlot } },
|
|
@@ -6,6 +6,7 @@ import { createCheckoutFromCurrentCart, createOrder, getCurrentCart, removeLineI
|
|
|
6
6
|
import { createRedirectSession } from "@himalaya/wix-redirects";
|
|
7
7
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
8
8
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
9
|
+
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
9
10
|
|
|
10
11
|
/** Wix Stores appDefId — the vertical that owns a cart/ecom order, and therefore the
|
|
11
12
|
* `appId` its cashier order is paid under. (Same constant Wix's own tooling uses.) */
|
|
@@ -317,9 +318,13 @@ const svc = defineService<State, {
|
|
|
317
318
|
if (!checkout.checkoutId) return derive({ ...state, checkoutHint: copyParam("cart.checkoutIdMissingMessage", "Wix did not return a checkout id.") });
|
|
318
319
|
const checkoutId = checkout.checkoutId;
|
|
319
320
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
// Hosted (Spaces): the site's own checkout page creates the order (RN's
|
|
322
|
+
// route) — creating it here first fails on a site with no payment method set up, before the
|
|
323
|
+
// member ever reaches the checkout. The host opens the checkout as the lent member.
|
|
324
|
+
const hosted = isHostedWorker();
|
|
325
|
+
const order = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
326
|
+
const snapshotId = order.paymentGatewayOrderId ?? "";
|
|
327
|
+
if (!snapshotId && !hosted) {
|
|
323
328
|
return derive({ ...state, ...cartConsumedByOrder() });
|
|
324
329
|
}
|
|
325
330
|
|
|
@@ -335,6 +340,9 @@ const svc = defineService<State, {
|
|
|
335
340
|
return !!reference && cartPricing.pricing.continueCheckoutGuard({ kind: "storesProduct", id: reference.catalogItemId }).allowed;
|
|
336
341
|
});
|
|
337
342
|
},
|
|
343
|
+
// Hosted (Spaces): the host opens the checkout as the lent member — Headless redirect
|
|
344
|
+
// sessions do not take a Member instance.
|
|
345
|
+
...(hosted ? { openCashier: hostedCheckoutOpener(checkoutId) } : {}),
|
|
338
346
|
cashierUrl: async () => {
|
|
339
347
|
const redirect = await createRedirectSession({
|
|
340
348
|
ecomCheckout: { checkoutId },
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
|
|
2
|
+
// pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
|
|
3
|
+
// instance, so the HOST opens the site's checkout signed in as that member. The worker names only
|
|
4
|
+
// its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
|
|
5
|
+
// the site's `createSessionCookie` → checkout. The session token never reaches this worker.
|
|
6
|
+
|
|
7
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
8
|
+
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
9
|
+
|
|
10
|
+
/** The host verbs (registered by the host shell, never by the app itself). The first prepares the
|
|
11
|
+
* checkout and answers a one-time ticket; the second opens it. Two steps so the caller's charge guard
|
|
12
|
+
* is re-read after the host's network round trips and right before the checkout opens — the prepared
|
|
13
|
+
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
|
+
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
|
+
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
16
|
+
|
|
17
|
+
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
|
+
export function isHostedWorker(): boolean {
|
|
19
|
+
return hostSiteFromInput((globalThis as { himalaya?: { input?: unknown } }).himalaya?.input) !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
23
|
+
|
|
24
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
25
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
26
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
27
|
+
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
28
|
+
return async (stillMayCharge) => {
|
|
29
|
+
try {
|
|
30
|
+
const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
|
|
31
|
+
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
|
+
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
|
+
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
34
|
+
if (!(await stillMayCharge())) return "withdrawn";
|
|
35
|
+
const result = await dispatch(HOSTED_CHECKOUT_OPEN_ACTION, { ticket }) as HostAnswer;
|
|
36
|
+
const opened = result?.opened ?? result?.payload?.opened;
|
|
37
|
+
return opened === true || opened === "true";
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -9,6 +9,7 @@ import { bookSlot, getService, queryServiceAvailability, type WixBookingSlotFull
|
|
|
9
9
|
import { createCheckout, createOrder } from "@himalaya/wix-ecom";
|
|
10
10
|
import { createRedirectSession, type WixSlot } from "@himalaya/wix-redirects";
|
|
11
11
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
12
|
+
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
12
13
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
13
14
|
import { displayName, getMyData, type WixMyData } from "@himalaya/wix-users";
|
|
14
15
|
import { formatServicePrice, serviceIsChargeable } from "../_shared/wix-service-price.js";
|
|
@@ -662,9 +663,12 @@ const svc = defineService<S, {
|
|
|
662
663
|
// 3. Create the Wix-managed order — mints the cashier snapshot when a
|
|
663
664
|
// balance is due. No paymentGatewayOrderId ⇒ nothing to charge
|
|
664
665
|
// (covered / zero total) ⇒ the booking is already confirmed.
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
666
|
+
// Hosted (Spaces): the site's own checkout page creates the order (as
|
|
667
|
+
// RN does) — creating it here first fails on a site with no payment method set up.
|
|
668
|
+
const hosted = isHostedWorker();
|
|
669
|
+
const orderRes = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
670
|
+
const snapshotId = orderRes.paymentGatewayOrderId ?? "";
|
|
671
|
+
if (!snapshotId && !hosted) {
|
|
668
672
|
return derive({ ...pending, bookingInFlight: false, successMessage: copyParam("time.successMessage", "Booked. Your confirmation is ready in My Bookings."), message: "" });
|
|
669
673
|
}
|
|
670
674
|
// 4. Collect payment IN-APP — wallet when this app has one wired, otherwise
|
|
@@ -676,6 +680,8 @@ const svc = defineService<S, {
|
|
|
676
680
|
appId: WIX_BOOKINGS_APP_ID,
|
|
677
681
|
snapshotId,
|
|
678
682
|
msid: await resolveMsid(),
|
|
683
|
+
// Hosted (Spaces): the host opens this booking's checkout as the lent member.
|
|
684
|
+
...(hosted ? { openCashier: hostedCheckoutOpener(checkoutId) } : {}),
|
|
679
685
|
cashierUrl: async () => {
|
|
680
686
|
const redirect = await createRedirectSession({
|
|
681
687
|
bookingsCheckout: { slotAvailability: { slot: slot as unknown as WixSlot } },
|
|
@@ -6,6 +6,7 @@ import { createCheckoutFromCurrentCart, createOrder, getCurrentCart, removeLineI
|
|
|
6
6
|
import { createRedirectSession } from "@himalaya/wix-redirects";
|
|
7
7
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
8
8
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
9
|
+
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
9
10
|
|
|
10
11
|
/** Wix Stores appDefId — the vertical that owns a cart/ecom order, and therefore the
|
|
11
12
|
* `appId` its cashier order is paid under. (Same constant Wix's own tooling uses.) */
|
|
@@ -317,9 +318,13 @@ const svc = defineService<State, {
|
|
|
317
318
|
if (!checkout.checkoutId) return derive({ ...state, checkoutHint: copyParam("cart.checkoutIdMissingMessage", "Wix did not return a checkout id.") });
|
|
318
319
|
const checkoutId = checkout.checkoutId;
|
|
319
320
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
// Hosted (Spaces): the site's own checkout page creates the order (RN's
|
|
322
|
+
// route) — creating it here first fails on a site with no payment method set up, before the
|
|
323
|
+
// member ever reaches the checkout. The host opens the checkout as the lent member.
|
|
324
|
+
const hosted = isHostedWorker();
|
|
325
|
+
const order = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
326
|
+
const snapshotId = order.paymentGatewayOrderId ?? "";
|
|
327
|
+
if (!snapshotId && !hosted) {
|
|
323
328
|
return derive({ ...state, ...cartConsumedByOrder() });
|
|
324
329
|
}
|
|
325
330
|
|
|
@@ -335,6 +340,9 @@ const svc = defineService<State, {
|
|
|
335
340
|
return !!reference && cartPricing.pricing.continueCheckoutGuard({ kind: "storesProduct", id: reference.catalogItemId }).allowed;
|
|
336
341
|
});
|
|
337
342
|
},
|
|
343
|
+
// Hosted (Spaces): the host opens the checkout as the lent member — Headless redirect
|
|
344
|
+
// sessions do not take a Member instance.
|
|
345
|
+
...(hosted ? { openCashier: hostedCheckoutOpener(checkoutId) } : {}),
|
|
338
346
|
cashierUrl: async () => {
|
|
339
347
|
const redirect = await createRedirectSession({
|
|
340
348
|
ecomCheckout: { checkoutId },
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
|
|
2
|
+
// pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
|
|
3
|
+
// instance, so the HOST opens the site's checkout signed in as that member. The worker names only
|
|
4
|
+
// its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
|
|
5
|
+
// the site's `createSessionCookie` → checkout. The session token never reaches this worker.
|
|
6
|
+
|
|
7
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
8
|
+
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
9
|
+
|
|
10
|
+
/** The host verbs (registered by the host shell, never by the app itself). The first prepares the
|
|
11
|
+
* checkout and answers a one-time ticket; the second opens it. Two steps so the caller's charge guard
|
|
12
|
+
* is re-read after the host's network round trips and right before the checkout opens — the prepared
|
|
13
|
+
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
|
+
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
|
+
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
16
|
+
|
|
17
|
+
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
|
+
export function isHostedWorker(): boolean {
|
|
19
|
+
return hostSiteFromInput((globalThis as { himalaya?: { input?: unknown } }).himalaya?.input) !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
23
|
+
|
|
24
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
25
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
26
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
27
|
+
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
28
|
+
return async (stillMayCharge) => {
|
|
29
|
+
try {
|
|
30
|
+
const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
|
|
31
|
+
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
|
+
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
|
+
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
34
|
+
if (!(await stillMayCharge())) return "withdrawn";
|
|
35
|
+
const result = await dispatch(HOSTED_CHECKOUT_OPEN_ACTION, { ticket }) as HostAnswer;
|
|
36
|
+
const opened = result?.opened ?? result?.payload?.opened;
|
|
37
|
+
return opened === true || opened === "true";
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -9,6 +9,7 @@ import { bookSlot, getService, queryServiceAvailability, type WixBookingSlotFull
|
|
|
9
9
|
import { createCheckout, createOrder } from "@himalaya/wix-ecom";
|
|
10
10
|
import { createRedirectSession, type WixSlot } from "@himalaya/wix-redirects";
|
|
11
11
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
12
|
+
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
12
13
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
13
14
|
import { displayName, getMyData, type WixMyData } from "@himalaya/wix-users";
|
|
14
15
|
import { formatServicePrice, serviceIsChargeable } from "../_shared/wix-service-price.js";
|
|
@@ -662,9 +663,12 @@ const svc = defineService<S, {
|
|
|
662
663
|
// 3. Create the Wix-managed order — mints the cashier snapshot when a
|
|
663
664
|
// balance is due. No paymentGatewayOrderId ⇒ nothing to charge
|
|
664
665
|
// (covered / zero total) ⇒ the booking is already confirmed.
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
666
|
+
// Hosted (Spaces): the site's own checkout page creates the order (as
|
|
667
|
+
// RN does) — creating it here first fails on a site with no payment method set up.
|
|
668
|
+
const hosted = isHostedWorker();
|
|
669
|
+
const orderRes = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
670
|
+
const snapshotId = orderRes.paymentGatewayOrderId ?? "";
|
|
671
|
+
if (!snapshotId && !hosted) {
|
|
668
672
|
return derive({ ...pending, bookingInFlight: false, successMessage: copyParam("time.successMessage", "Booked. Your confirmation is ready in My Bookings."), message: "" });
|
|
669
673
|
}
|
|
670
674
|
// 4. Collect payment IN-APP — wallet when this app has one wired, otherwise
|
|
@@ -676,6 +680,8 @@ const svc = defineService<S, {
|
|
|
676
680
|
appId: WIX_BOOKINGS_APP_ID,
|
|
677
681
|
snapshotId,
|
|
678
682
|
msid: await resolveMsid(),
|
|
683
|
+
// Hosted (Spaces): the host opens this booking's checkout as the lent member.
|
|
684
|
+
...(hosted ? { openCashier: hostedCheckoutOpener(checkoutId) } : {}),
|
|
679
685
|
cashierUrl: async () => {
|
|
680
686
|
const redirect = await createRedirectSession({
|
|
681
687
|
bookingsCheckout: { slotAvailability: { slot: slot as unknown as WixSlot } },
|
|
@@ -6,6 +6,7 @@ import { createCheckoutFromCurrentCart, createOrder, getCurrentCart, removeLineI
|
|
|
6
6
|
import { createRedirectSession } from "@himalaya/wix-redirects";
|
|
7
7
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
8
8
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
9
|
+
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
9
10
|
|
|
10
11
|
/** Wix Stores appDefId — the vertical that owns a cart/ecom order, and therefore the
|
|
11
12
|
* `appId` its cashier order is paid under. (Same constant Wix's own tooling uses.) */
|
|
@@ -317,9 +318,13 @@ const svc = defineService<State, {
|
|
|
317
318
|
if (!checkout.checkoutId) return derive({ ...state, checkoutHint: copyParam("cart.checkoutIdMissingMessage", "Wix did not return a checkout id.") });
|
|
318
319
|
const checkoutId = checkout.checkoutId;
|
|
319
320
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
// Hosted (Spaces): the site's own checkout page creates the order (RN's
|
|
322
|
+
// route) — creating it here first fails on a site with no payment method set up, before the
|
|
323
|
+
// member ever reaches the checkout. The host opens the checkout as the lent member.
|
|
324
|
+
const hosted = isHostedWorker();
|
|
325
|
+
const order = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
326
|
+
const snapshotId = order.paymentGatewayOrderId ?? "";
|
|
327
|
+
if (!snapshotId && !hosted) {
|
|
323
328
|
return derive({ ...state, ...cartConsumedByOrder() });
|
|
324
329
|
}
|
|
325
330
|
|
|
@@ -335,6 +340,9 @@ const svc = defineService<State, {
|
|
|
335
340
|
return !!reference && cartPricing.pricing.continueCheckoutGuard({ kind: "storesProduct", id: reference.catalogItemId }).allowed;
|
|
336
341
|
});
|
|
337
342
|
},
|
|
343
|
+
// Hosted (Spaces): the host opens the checkout as the lent member — Headless redirect
|
|
344
|
+
// sessions do not take a Member instance.
|
|
345
|
+
...(hosted ? { openCashier: hostedCheckoutOpener(checkoutId) } : {}),
|
|
338
346
|
cashierUrl: async () => {
|
|
339
347
|
const redirect = await createRedirectSession({
|
|
340
348
|
ecomCheckout: { checkoutId },
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
|
|
2
|
+
// pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
|
|
3
|
+
// instance, so the HOST opens the site's checkout signed in as that member. The worker names only
|
|
4
|
+
// its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
|
|
5
|
+
// the site's `createSessionCookie` → checkout. The session token never reaches this worker.
|
|
6
|
+
|
|
7
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
8
|
+
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
9
|
+
|
|
10
|
+
/** The host verbs (registered by the host shell, never by the app itself). The first prepares the
|
|
11
|
+
* checkout and answers a one-time ticket; the second opens it. Two steps so the caller's charge guard
|
|
12
|
+
* is re-read after the host's network round trips and right before the checkout opens — the prepared
|
|
13
|
+
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
|
+
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
|
+
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
16
|
+
|
|
17
|
+
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
|
+
export function isHostedWorker(): boolean {
|
|
19
|
+
return hostSiteFromInput((globalThis as { himalaya?: { input?: unknown } }).himalaya?.input) !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
23
|
+
|
|
24
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
25
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
26
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
27
|
+
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
28
|
+
return async (stillMayCharge) => {
|
|
29
|
+
try {
|
|
30
|
+
const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
|
|
31
|
+
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
|
+
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
|
+
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
34
|
+
if (!(await stillMayCharge())) return "withdrawn";
|
|
35
|
+
const result = await dispatch(HOSTED_CHECKOUT_OPEN_ACTION, { ticket }) as HostAnswer;
|
|
36
|
+
const opened = result?.opened ?? result?.payload?.opened;
|
|
37
|
+
return opened === true || opened === "true";
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -6,6 +6,7 @@ import { createCheckoutFromCurrentCart, createOrder, getCurrentCart, removeLineI
|
|
|
6
6
|
import { createRedirectSession } from "@himalaya/wix-redirects";
|
|
7
7
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
8
8
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
9
|
+
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
9
10
|
|
|
10
11
|
/** Wix Stores appDefId — the vertical that owns a cart/ecom order, and therefore the
|
|
11
12
|
* `appId` its cashier order is paid under. (Same constant Wix's own tooling uses.) */
|
|
@@ -317,9 +318,13 @@ const svc = defineService<State, {
|
|
|
317
318
|
if (!checkout.checkoutId) return derive({ ...state, checkoutHint: copyParam("cart.checkoutIdMissingMessage", "Wix did not return a checkout id.") });
|
|
318
319
|
const checkoutId = checkout.checkoutId;
|
|
319
320
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
// Hosted (Spaces): the site's own checkout page creates the order (RN's
|
|
322
|
+
// route) — creating it here first fails on a site with no payment method set up, before the
|
|
323
|
+
// member ever reaches the checkout. The host opens the checkout as the lent member.
|
|
324
|
+
const hosted = isHostedWorker();
|
|
325
|
+
const order = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
326
|
+
const snapshotId = order.paymentGatewayOrderId ?? "";
|
|
327
|
+
if (!snapshotId && !hosted) {
|
|
323
328
|
return derive({ ...state, ...cartConsumedByOrder() });
|
|
324
329
|
}
|
|
325
330
|
|
|
@@ -335,6 +340,9 @@ const svc = defineService<State, {
|
|
|
335
340
|
return !!reference && cartPricing.pricing.continueCheckoutGuard({ kind: "storesProduct", id: reference.catalogItemId }).allowed;
|
|
336
341
|
});
|
|
337
342
|
},
|
|
343
|
+
// Hosted (Spaces): the host opens the checkout as the lent member — Headless redirect
|
|
344
|
+
// sessions do not take a Member instance.
|
|
345
|
+
...(hosted ? { openCashier: hostedCheckoutOpener(checkoutId) } : {}),
|
|
338
346
|
cashierUrl: async () => {
|
|
339
347
|
const redirect = await createRedirectSession({
|
|
340
348
|
ecomCheckout: { checkoutId },
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
|
|
2
|
+
// pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
|
|
3
|
+
// instance, so the HOST opens the site's checkout signed in as that member. The worker names only
|
|
4
|
+
// its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
|
|
5
|
+
// the site's `createSessionCookie` → checkout. The session token never reaches this worker.
|
|
6
|
+
|
|
7
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
8
|
+
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
9
|
+
|
|
10
|
+
/** The host verbs (registered by the host shell, never by the app itself). The first prepares the
|
|
11
|
+
* checkout and answers a one-time ticket; the second opens it. Two steps so the caller's charge guard
|
|
12
|
+
* is re-read after the host's network round trips and right before the checkout opens — the prepared
|
|
13
|
+
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
|
+
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
|
+
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
16
|
+
|
|
17
|
+
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
|
+
export function isHostedWorker(): boolean {
|
|
19
|
+
return hostSiteFromInput((globalThis as { himalaya?: { input?: unknown } }).himalaya?.input) !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
23
|
+
|
|
24
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
25
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
26
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
27
|
+
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
28
|
+
return async (stillMayCharge) => {
|
|
29
|
+
try {
|
|
30
|
+
const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
|
|
31
|
+
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
|
+
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
|
+
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
34
|
+
if (!(await stillMayCharge())) return "withdrawn";
|
|
35
|
+
const result = await dispatch(HOSTED_CHECKOUT_OPEN_ACTION, { ticket }) as HostAnswer;
|
|
36
|
+
const opened = result?.opened ?? result?.payload?.opened;
|
|
37
|
+
return opened === true || opened === "true";
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -6,6 +6,7 @@ import { createCheckoutFromCurrentCart, createOrder, getCurrentCart, removeLineI
|
|
|
6
6
|
import { createRedirectSession } from "@himalaya/wix-redirects";
|
|
7
7
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
8
8
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
9
|
+
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
9
10
|
|
|
10
11
|
/** Wix Stores appDefId — the vertical that owns a cart/ecom order, and therefore the
|
|
11
12
|
* `appId` its cashier order is paid under. (Same constant Wix's own tooling uses.) */
|
|
@@ -317,9 +318,13 @@ const svc = defineService<State, {
|
|
|
317
318
|
if (!checkout.checkoutId) return derive({ ...state, checkoutHint: copyParam("cart.checkoutIdMissingMessage", "Wix did not return a checkout id.") });
|
|
318
319
|
const checkoutId = checkout.checkoutId;
|
|
319
320
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
// Hosted (Spaces): the site's own checkout page creates the order (RN's
|
|
322
|
+
// route) — creating it here first fails on a site with no payment method set up, before the
|
|
323
|
+
// member ever reaches the checkout. The host opens the checkout as the lent member.
|
|
324
|
+
const hosted = isHostedWorker();
|
|
325
|
+
const order = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
326
|
+
const snapshotId = order.paymentGatewayOrderId ?? "";
|
|
327
|
+
if (!snapshotId && !hosted) {
|
|
323
328
|
return derive({ ...state, ...cartConsumedByOrder() });
|
|
324
329
|
}
|
|
325
330
|
|
|
@@ -335,6 +340,9 @@ const svc = defineService<State, {
|
|
|
335
340
|
return !!reference && cartPricing.pricing.continueCheckoutGuard({ kind: "storesProduct", id: reference.catalogItemId }).allowed;
|
|
336
341
|
});
|
|
337
342
|
},
|
|
343
|
+
// Hosted (Spaces): the host opens the checkout as the lent member — Headless redirect
|
|
344
|
+
// sessions do not take a Member instance.
|
|
345
|
+
...(hosted ? { openCashier: hostedCheckoutOpener(checkoutId) } : {}),
|
|
338
346
|
cashierUrl: async () => {
|
|
339
347
|
const redirect = await createRedirectSession({
|
|
340
348
|
ecomCheckout: { checkoutId },
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
|
|
2
|
+
// pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
|
|
3
|
+
// instance, so the HOST opens the site's checkout signed in as that member. The worker names only
|
|
4
|
+
// its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
|
|
5
|
+
// the site's `createSessionCookie` → checkout. The session token never reaches this worker.
|
|
6
|
+
|
|
7
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
8
|
+
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
9
|
+
|
|
10
|
+
/** The host verbs (registered by the host shell, never by the app itself). The first prepares the
|
|
11
|
+
* checkout and answers a one-time ticket; the second opens it. Two steps so the caller's charge guard
|
|
12
|
+
* is re-read after the host's network round trips and right before the checkout opens — the prepared
|
|
13
|
+
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
|
+
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
|
+
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
16
|
+
|
|
17
|
+
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
|
+
export function isHostedWorker(): boolean {
|
|
19
|
+
return hostSiteFromInput((globalThis as { himalaya?: { input?: unknown } }).himalaya?.input) !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
23
|
+
|
|
24
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
25
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
26
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
27
|
+
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
28
|
+
return async (stillMayCharge) => {
|
|
29
|
+
try {
|
|
30
|
+
const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
|
|
31
|
+
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
|
+
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
|
+
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
34
|
+
if (!(await stillMayCharge())) return "withdrawn";
|
|
35
|
+
const result = await dispatch(HOSTED_CHECKOUT_OPEN_ACTION, { ticket }) as HostAnswer;
|
|
36
|
+
const opened = result?.opened ?? result?.payload?.opened;
|
|
37
|
+
return opened === true || opened === "true";
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -9,6 +9,7 @@ import { bookSlot, getService, queryServiceAvailability, type WixBookingSlotFull
|
|
|
9
9
|
import { createCheckout, createOrder } from "@himalaya/wix-ecom";
|
|
10
10
|
import { createRedirectSession, type WixSlot } from "@himalaya/wix-redirects";
|
|
11
11
|
import { payCashierOrder } from "@himalaya/checkout";
|
|
12
|
+
import { hostedCheckoutOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
12
13
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
13
14
|
import { displayName, getMyData, type WixMyData } from "@himalaya/wix-users";
|
|
14
15
|
import { formatServicePrice, serviceIsChargeable } from "../_shared/wix-service-price.js";
|
|
@@ -662,9 +663,12 @@ const svc = defineService<S, {
|
|
|
662
663
|
// 3. Create the Wix-managed order — mints the cashier snapshot when a
|
|
663
664
|
// balance is due. No paymentGatewayOrderId ⇒ nothing to charge
|
|
664
665
|
// (covered / zero total) ⇒ the booking is already confirmed.
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
666
|
+
// Hosted (Spaces): the site's own checkout page creates the order (as
|
|
667
|
+
// RN does) — creating it here first fails on a site with no payment method set up.
|
|
668
|
+
const hosted = isHostedWorker();
|
|
669
|
+
const orderRes = hosted ? { paymentGatewayOrderId: "" } : await createOrder(checkoutId);
|
|
670
|
+
const snapshotId = orderRes.paymentGatewayOrderId ?? "";
|
|
671
|
+
if (!snapshotId && !hosted) {
|
|
668
672
|
return derive({ ...pending, bookingInFlight: false, successMessage: copyParam("time.successMessage", "Booked. Your confirmation is ready in My Bookings."), message: "" });
|
|
669
673
|
}
|
|
670
674
|
// 4. Collect payment IN-APP — wallet when this app has one wired, otherwise
|
|
@@ -676,6 +680,8 @@ const svc = defineService<S, {
|
|
|
676
680
|
appId: WIX_BOOKINGS_APP_ID,
|
|
677
681
|
snapshotId,
|
|
678
682
|
msid: await resolveMsid(),
|
|
683
|
+
// Hosted (Spaces): the host opens this booking's checkout as the lent member.
|
|
684
|
+
...(hosted ? { openCashier: hostedCheckoutOpener(checkoutId) } : {}),
|
|
679
685
|
cashierUrl: async () => {
|
|
680
686
|
const redirect = await createRedirectSession({
|
|
681
687
|
bookingsCheckout: { slotAvailability: { slot: slot as unknown as WixSlot } },
|