@wix/himalaya-cli 0.1346.0 → 0.1349.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 -4
- 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
|
@@ -7706,7 +7706,6 @@ function resolveTranslation(value, catalog3, sourceCatalog) {
|
|
|
7706
7706
|
var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
|
|
7707
7707
|
var init_bindings = __esm({
|
|
7708
7708
|
"../../core/ts/src/bindings.ts"() {
|
|
7709
|
-
"use strict";
|
|
7710
7709
|
BINDING_SIGILS = ["service", "flag", "item", "t"];
|
|
7711
7710
|
BINDING_SIGIL_RE = new RegExp(
|
|
7712
7711
|
`^\\$(?:${BINDING_SIGILS.filter((s2) => s2 !== "t").join("|")})\\b|^\\$t:`
|
|
@@ -22951,6 +22950,7 @@ __export(gen_native_config_exports, {
|
|
|
22951
22950
|
LABEL_MODULES_ANDROID_INCLUDE: () => LABEL_MODULES_ANDROID_INCLUDE,
|
|
22952
22951
|
LABEL_MODULES_ANDROID_INSTALLERS: () => LABEL_MODULES_ANDROID_INSTALLERS,
|
|
22953
22952
|
LABEL_MODULES_ANDROID_LINK: () => LABEL_MODULES_ANDROID_LINK,
|
|
22953
|
+
LABEL_MODULES_IOS_FLAGS: () => LABEL_MODULES_IOS_FLAGS,
|
|
22954
22954
|
LABEL_MODULES_IOS_LINK: () => LABEL_MODULES_IOS_LINK,
|
|
22955
22955
|
LABEL_MODULES_IOS_PACKAGE: () => LABEL_MODULES_IOS_PACKAGE,
|
|
22956
22956
|
LABEL_MODULES_SWIFT_IMPORT: () => LABEL_MODULES_SWIFT_IMPORT,
|
|
@@ -22963,7 +22963,9 @@ __export(gen_native_config_exports, {
|
|
|
22963
22963
|
androidPlanFor: () => androidPlanFor,
|
|
22964
22964
|
androidSettingsGradle: () => androidSettingsGradle,
|
|
22965
22965
|
androidTvLeanbackPlan: () => androidTvLeanbackPlan,
|
|
22966
|
+
applyModuleFlags: () => applyModuleFlags,
|
|
22966
22967
|
applyToFile: () => applyToFile,
|
|
22968
|
+
assertModuleFlagsMarkers: () => assertModuleFlagsMarkers,
|
|
22967
22969
|
assertSingleMarkerPair: () => assertSingleMarkerPair,
|
|
22968
22970
|
assertSingleModuleInstall: () => assertSingleModuleInstall,
|
|
22969
22971
|
automotiveAppDescPath: () => automotiveAppDescPath,
|
|
@@ -22983,6 +22985,7 @@ __export(gen_native_config_exports, {
|
|
|
22983
22985
|
iosPlanFor: () => iosPlanFor,
|
|
22984
22986
|
kotlinBootSourcesUnder: () => kotlinBootSourcesUnder,
|
|
22985
22987
|
kotlinFilesWithModuleInstall: () => kotlinFilesWithModuleInstall,
|
|
22988
|
+
linkedFlag: () => linkedFlag,
|
|
22986
22989
|
loadOverrides: () => loadOverrides,
|
|
22987
22990
|
macosPlanFor: () => macosPlanFor,
|
|
22988
22991
|
moduleLinkRenderers: () => moduleLinkRenderers,
|
|
@@ -23001,6 +23004,7 @@ __export(gen_native_config_exports, {
|
|
|
23001
23004
|
renderModulesAndroidInstallers: () => renderModulesAndroidInstallers,
|
|
23002
23005
|
renderModulesAndroidLink: () => renderModulesAndroidLink,
|
|
23003
23006
|
renderModulesAndroidLinkBinary: () => renderModulesAndroidLinkBinary,
|
|
23007
|
+
renderModulesIosFlags: () => renderModulesIosFlags,
|
|
23004
23008
|
renderModulesIosLink: () => renderModulesIosLink,
|
|
23005
23009
|
renderModulesIosLinkBinary: () => renderModulesIosLinkBinary,
|
|
23006
23010
|
renderModulesIosPackage: () => renderModulesIosPackage,
|
|
@@ -23226,13 +23230,16 @@ function renderModulesAndroidLinkBinary(projects, coreVersion, indent) {
|
|
|
23226
23230
|
function moduleLinkRenderers(modules, binary) {
|
|
23227
23231
|
if (!binary) {
|
|
23228
23232
|
return {
|
|
23233
|
+
iosLinked: () => modules.spm,
|
|
23229
23234
|
iosPackage: (i2) => renderModulesIosPackage(modules.spm, i2),
|
|
23230
23235
|
iosLink: (i2) => renderModulesIosLink(modules.spm, i2),
|
|
23231
23236
|
androidInclude: (i2) => renderModulesAndroidInclude(modules.gradle, i2),
|
|
23232
23237
|
androidLink: (i2) => renderModulesAndroidLink(modules.gradle, i2)
|
|
23233
23238
|
};
|
|
23234
23239
|
}
|
|
23240
|
+
const iosLinked = () => modules.spm.length && coreVersionHasModules(binary.coreVersion()) ? modules.spm : [];
|
|
23235
23241
|
return {
|
|
23242
|
+
iosLinked,
|
|
23236
23243
|
iosPackage: (i2) => modules.spm.length && coreVersionHasModules(binary.coreVersion()) ? renderModulesIosPackageBinary(modules.spm, binary.corePackagePath?.(), i2) : [],
|
|
23237
23244
|
// Same version rule as Android: a pre-split core carries the capabilities and has no products.
|
|
23238
23245
|
iosLink: (i2) => modules.spm.length && coreVersionHasModules(binary.coreVersion()) ? renderModulesIosLinkBinary(modules.spm, i2) : [],
|
|
@@ -23246,14 +23253,17 @@ function moduleLinkRenderers(modules, binary) {
|
|
|
23246
23253
|
}
|
|
23247
23254
|
};
|
|
23248
23255
|
}
|
|
23256
|
+
function renderModulesIosFlags(products, indent) {
|
|
23257
|
+
return [`${indent}OTHER_SWIFT_FLAGS: "${["$(inherited)", ...products.map((p2) => `-D${linkedFlag(p2)}`)].join(" ")}"`];
|
|
23258
|
+
}
|
|
23249
23259
|
function renderModulesSwiftImport(indent) {
|
|
23250
23260
|
return NATIVE_MODULES.flatMap(
|
|
23251
|
-
(m2) => m2.ios ? [`${indent}#if canImport(${m2.ios.product})`, `${indent}import ${m2.ios.product}`, `${indent}#endif`] : []
|
|
23261
|
+
(m2) => m2.ios ? [`${indent}#if canImport(${m2.ios.product}) && ${linkedFlag(m2.ios.product)}`, `${indent}import ${m2.ios.product}`, `${indent}#endif`] : []
|
|
23252
23262
|
);
|
|
23253
23263
|
}
|
|
23254
23264
|
function renderModulesSwiftInstall(indent) {
|
|
23255
23265
|
return NATIVE_MODULES.flatMap(
|
|
23256
|
-
(m2) => m2.ios ? [`${indent}#if canImport(${m2.ios.product})`, `${indent}${m2.ios.product}.install()`, `${indent}#endif`] : []
|
|
23266
|
+
(m2) => m2.ios ? [`${indent}#if canImport(${m2.ios.product}) && ${linkedFlag(m2.ios.product)}`, `${indent}${m2.ios.product}.install()`, `${indent}#endif`] : []
|
|
23257
23267
|
);
|
|
23258
23268
|
}
|
|
23259
23269
|
function renderModulesAndroidInstallers(indent) {
|
|
@@ -23433,6 +23443,19 @@ function applyToFile(path2, kind, label2, render, check) {
|
|
|
23433
23443
|
writeFileSync13(path2, after);
|
|
23434
23444
|
return { path: path2, outcome: "written" };
|
|
23435
23445
|
}
|
|
23446
|
+
function assertModuleFlagsMarkers(paths) {
|
|
23447
|
+
for (const path2 of paths) {
|
|
23448
|
+
if (!existsSync34(path2)) continue;
|
|
23449
|
+
const text9 = readFileSync33(path2, "utf8");
|
|
23450
|
+
if (hasMarkers(text9, "yaml", LABEL_MODULES_IOS_LINK) && !hasMarkers(text9, "yaml", LABEL_MODULES_IOS_FLAGS)) {
|
|
23451
|
+
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).`);
|
|
23452
|
+
}
|
|
23453
|
+
}
|
|
23454
|
+
}
|
|
23455
|
+
function applyModuleFlags(path2, products, check) {
|
|
23456
|
+
assertModuleFlagsMarkers([path2]);
|
|
23457
|
+
return applyToFile(path2, "yaml", LABEL_MODULES_IOS_FLAGS, (i2) => renderModulesIosFlags(products, i2), check);
|
|
23458
|
+
}
|
|
23436
23459
|
function applyWholeFile(path2, content, check) {
|
|
23437
23460
|
const current = existsSync34(path2) ? readFileSync33(path2, "utf8") : null;
|
|
23438
23461
|
if (current === content) return { path: path2, outcome: "unchanged" };
|
|
@@ -23485,6 +23508,7 @@ function genNativeConfig(app, opts = {}) {
|
|
|
23485
23508
|
const check = opts.check ?? false;
|
|
23486
23509
|
assertSingleModuleInstall(app, swiftFilesWithModuleMarkers(app));
|
|
23487
23510
|
assertSingleModuleInstall(app, kotlinFilesWithModuleInstall(app));
|
|
23511
|
+
assertModuleFlagsMarkers([iosProjectYml(app), macosProjectYml(app)]);
|
|
23488
23512
|
const configPath = appConfigTs(app);
|
|
23489
23513
|
const configTs = existsSync34(configPath) ? readFileSync33(configPath, "utf8") : "";
|
|
23490
23514
|
const capabilities = parseStringArray(configTs, "capabilities");
|
|
@@ -23589,9 +23613,11 @@ function genNativeConfig(app, opts = {}) {
|
|
|
23589
23613
|
// published AARs (#4055): no source package, no settings include.
|
|
23590
23614
|
applyToFile(iosProjectYml(app), "yaml", LABEL_MODULES_IOS_PACKAGE, moduleLinks.iosPackage, check),
|
|
23591
23615
|
applyToFile(iosProjectYml(app), "yaml", LABEL_MODULES_IOS_LINK, moduleLinks.iosLink, check),
|
|
23616
|
+
applyModuleFlags(iosProjectYml(app), moduleLinks.iosLinked(), check),
|
|
23592
23617
|
// macOS (RFC 0046) reuses the iOS boot, so it links the same macOS-capable modules.
|
|
23593
23618
|
applyToFile(macosProjectYml(app), "yaml", LABEL_MODULES_IOS_PACKAGE, (i2) => renderModulesIosPackage(modules.macos, i2), check),
|
|
23594
23619
|
applyToFile(macosProjectYml(app), "yaml", LABEL_MODULES_IOS_LINK, (i2) => renderModulesIosLink(modules.macos, i2), check),
|
|
23620
|
+
applyModuleFlags(macosProjectYml(app), modules.macos, check),
|
|
23595
23621
|
applyToFile(androidSettingsGradle(app), "slash", LABEL_MODULES_ANDROID_INCLUDE, moduleLinks.androidInclude, check),
|
|
23596
23622
|
applyToFile(androidAppGradle(app), "slash", LABEL_MODULES_ANDROID_LINK, moduleLinks.androidLink, check),
|
|
23597
23623
|
// watchOS (RFC 0047): the watch runtime mode/serve, declared once in config.ts. A watch
|
|
@@ -23685,7 +23711,7 @@ function genNativeConfig(app, opts = {}) {
|
|
|
23685
23711
|
);
|
|
23686
23712
|
return { app, buildSet: ids, files, drift: files.some((f2) => f2.outcome === "drift") };
|
|
23687
23713
|
}
|
|
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;
|
|
23714
|
+
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
23715
|
var init_gen_native_config = __esm({
|
|
23690
23716
|
"../native-config/gen-native-config.ts"() {
|
|
23691
23717
|
init_src2();
|
|
@@ -23708,6 +23734,7 @@ var init_gen_native_config = __esm({
|
|
|
23708
23734
|
LABEL_MODULES_IOS_LINK = "himalaya:modules:link";
|
|
23709
23735
|
LABEL_MODULES_ANDROID_INCLUDE = "himalaya:modules:include";
|
|
23710
23736
|
LABEL_MODULES_ANDROID_LINK = "himalaya:modules:dep";
|
|
23737
|
+
LABEL_MODULES_IOS_FLAGS = "himalaya:modules:flags";
|
|
23711
23738
|
LABEL_MODULES_SWIFT_IMPORT = "himalaya:modules:import";
|
|
23712
23739
|
LABEL_MODULES_SWIFT_INSTALL = "himalaya:modules:install";
|
|
23713
23740
|
LABEL_MODULES_ANDROID_INSTALLERS = "himalaya:modules:installers";
|
|
@@ -23725,6 +23752,7 @@ var init_gen_native_config = __esm({
|
|
|
23725
23752
|
ALL_ORIENTATIONS = ["portrait", "portraitUpsideDown", "landscapeLeft", "landscapeRight"];
|
|
23726
23753
|
HEALTH_PERMISSION_COPY_META = "io.himalaya.health.permissionCopy";
|
|
23727
23754
|
FROM_APP_PLATFORM_DIR = "../../../";
|
|
23755
|
+
linkedFlag = (product) => `HIMALAYA_LINKED_${product}`;
|
|
23728
23756
|
CORE_ANDROID_CAPABILITY_MODULES = join34(
|
|
23729
23757
|
dirname21(fileURLToPath14(import.meta.url)),
|
|
23730
23758
|
"../../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.1349.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": "9279a00f8a3383291b5d42997cd4086dac34b4ccf1c4b62d3869dd44"
|
|
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 } },
|