@wix/himalaya-cli 0.1354.0 → 0.1356.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/cli.mjs +2 -0
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/branded-lite/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +31 -2
- package/templates/community-club/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/community-club/tier5-src/himi-events-detail/index.ts +31 -2
- package/templates/events-meetups/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +31 -2
- package/templates/gym-fitness/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +31 -2
- package/templates/nonprofit-cause/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +31 -2
- package/templates/podcast-creator/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +31 -2
- package/templates/portfolio-creative/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +31 -2
- package/templates/restaurant/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/restaurant/tier5-src/himi-events-detail/index.ts +31 -2
- package/templates/retail-store/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/yoga-studio/tier5-src/_shared/hosted-checkout.ts +36 -10
- package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +31 -2
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
// Hosted checkout
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Hosted checkout. A Branded Lite hosted in a host shell (Spaces) pays as the member the host
|
|
2
|
+
// lent it: Headless `createRedirectSession` does not take a Member instance, so the HOST opens the site's
|
|
3
|
+
// checkout signed in as that member. The worker names only its ecom checkout; the host reads Wix's
|
|
4
|
+
// checkout URL, mints a one-time member session and opens the site's `createSessionCookie` → checkout.
|
|
5
|
+
// The session token never reaches this worker.
|
|
6
|
+
//
|
|
7
|
+
// Classic Wix Events tickets and classic Pricing Plans have no ecom checkout: for those the
|
|
8
|
+
// worker names the page by id (`himi.host.memberSitePage`) and the host opens the site's own event or
|
|
9
|
+
// plans page the same way, signed in — where the member picks and pays on the site.
|
|
6
10
|
|
|
7
11
|
import { dispatch } from "@himalaya/dispatch";
|
|
8
12
|
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
@@ -13,6 +17,12 @@ import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
|
13
17
|
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
18
|
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
19
|
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
20
|
+
/** Prepares a site PAGE (not an ecom checkout) the same way; opened with [HOSTED_CHECKOUT_OPEN_ACTION]. */
|
|
21
|
+
export const HOSTED_SITE_PAGE_ACTION = "himi.host.memberSitePage";
|
|
22
|
+
|
|
23
|
+
/** A site page a hosted worker may ask its host for — ids only, never a URL: the host reads the URL
|
|
24
|
+
* from Wix itself and decides whether the member may be signed in there. */
|
|
25
|
+
export type HostedSitePage = { page: "event"; eventId: string } | { page: "plans"; planId?: string };
|
|
16
26
|
|
|
17
27
|
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
28
|
export function isHostedWorker(): boolean {
|
|
@@ -20,14 +30,14 @@ export function isHostedWorker(): boolean {
|
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
33
|
+
type HostOpener = (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
|
|
23
34
|
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
35
|
+
/** Prepare with [action] [params] → re-read the guard → open by ticket. A host without the verbs, or one
|
|
36
|
+
* that refused (Wix named no page, an unsafe URL, no account session), → false, never a throw. */
|
|
37
|
+
function hostOpener(action: string, params: Record<string, string>): HostOpener {
|
|
28
38
|
return async (stillMayCharge) => {
|
|
29
39
|
try {
|
|
30
|
-
const prepared = await dispatch(
|
|
40
|
+
const prepared = await dispatch(action, params) as HostAnswer;
|
|
31
41
|
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
42
|
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
43
|
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
@@ -40,3 +50,19 @@ export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () =>
|
|
|
40
50
|
}
|
|
41
51
|
};
|
|
42
52
|
}
|
|
53
|
+
|
|
54
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
55
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
56
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
57
|
+
export function hostedCheckoutOpener(checkoutId: string): HostOpener {
|
|
58
|
+
return hostOpener(HOSTED_CHECKOUT_ACTION, { checkoutId });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The site's own [page] opened by the host, signed in as the lent member where the host deems it safe
|
|
62
|
+
* (a visitor otherwise) — the same two steps and answers as [hostedCheckoutOpener]. */
|
|
63
|
+
export function hostedSitePageOpener(page: HostedSitePage): HostOpener {
|
|
64
|
+
const params: Record<string, string> = page.page === "event"
|
|
65
|
+
? { page: "event", eventId: page.eventId }
|
|
66
|
+
: { page: "plans", ...(page.planId ? { planId: page.planId } : {}) };
|
|
67
|
+
return hostOpener(HOSTED_SITE_PAGE_ACTION, params);
|
|
68
|
+
}
|
|
@@ -35,7 +35,10 @@ import {
|
|
|
35
35
|
type EventRow,
|
|
36
36
|
} from "../_shared/himi-events";
|
|
37
37
|
import { initials } from "../_shared/himi-text.js";
|
|
38
|
-
import { isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
38
|
+
import { hostedSitePageOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
39
|
+
|
|
40
|
+
/** A hosted signed-in event-page open is in flight (see openRegistration). */
|
|
41
|
+
let hostedOpenInFlight = false;
|
|
39
42
|
import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
40
43
|
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
41
44
|
import { richBody } from "../_shared/rich-body.js";
|
|
@@ -579,7 +582,8 @@ const svc = defineService<S, {
|
|
|
579
582
|
// `checkoutReservation` needs a buyer + guest form this screen does not collect, and the
|
|
580
583
|
// redirect session needs a headless OAuth client a hosted app does not have. Skip both —
|
|
581
584
|
// the native chain would also leave a ticket reservation held for nothing — and open the
|
|
582
|
-
// event's own page, where the member registers on the site
|
|
585
|
+
// event's own page, where the member registers on the site: signed in by the host where it can
|
|
586
|
+
// (`himi.host.memberSitePage`), else as a visitor.
|
|
583
587
|
if (s.registrationKind === "tickets" && s.id && !isHostedWorker()) {
|
|
584
588
|
let reservationId = "";
|
|
585
589
|
try {
|
|
@@ -658,6 +662,31 @@ const svc = defineService<S, {
|
|
|
658
662
|
// fall through to registrationUrl
|
|
659
663
|
}
|
|
660
664
|
}
|
|
665
|
+
// Hosted: the HOST opens the event's own page signed in as the lent member — ids only; it
|
|
666
|
+
// reads the page URL from Wix and refuses (a custom domain → a visitor page; no published Events
|
|
667
|
+
// page → nothing) on its own. Nothing here can change between its prepare and its open (the event
|
|
668
|
+
// is fixed and the page is where the member chooses and pays), so the guard has nothing to re-read.
|
|
669
|
+
// A host that refused or failed leaves the visitor page below.
|
|
670
|
+
if (isHostedWorker() && s.id && (s.registrationKind === "tickets" || s.registrationKind === "rsvp")) {
|
|
671
|
+
// One signed-in open at a time: the host keeps a single one-time ticket, so a second tap would
|
|
672
|
+
// replace the first tap's ticket and drop that tap into the signed-OUT visitor page below.
|
|
673
|
+
if (hostedOpenInFlight) return s;
|
|
674
|
+
hostedOpenInFlight = true;
|
|
675
|
+
let opened: boolean | "withdrawn";
|
|
676
|
+
try {
|
|
677
|
+
opened = await hostedSitePageOpener({ page: "event", eventId: s.id })(async () => true);
|
|
678
|
+
} finally {
|
|
679
|
+
hostedOpenInFlight = false;
|
|
680
|
+
}
|
|
681
|
+
if (opened === true) {
|
|
682
|
+
return derive({
|
|
683
|
+
...s,
|
|
684
|
+
actionMessage: s.registrationKind === "tickets"
|
|
685
|
+
? copyParam("event.checkoutOpened", "Complete checkout in the browser to get your tickets.")
|
|
686
|
+
: "",
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
661
690
|
if (!s.registrationUrl) {
|
|
662
691
|
return derive({ ...s, actionMessage: copyParam("event.registrationOnPage", "Registration for this event happens on the event page.") });
|
|
663
692
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
// Hosted checkout
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Hosted checkout. A Branded Lite hosted in a host shell (Spaces) pays as the member the host
|
|
2
|
+
// lent it: Headless `createRedirectSession` does not take a Member instance, so the HOST opens the site's
|
|
3
|
+
// checkout signed in as that member. The worker names only its ecom checkout; the host reads Wix's
|
|
4
|
+
// checkout URL, mints a one-time member session and opens the site's `createSessionCookie` → checkout.
|
|
5
|
+
// The session token never reaches this worker.
|
|
6
|
+
//
|
|
7
|
+
// Classic Wix Events tickets and classic Pricing Plans have no ecom checkout: for those the
|
|
8
|
+
// worker names the page by id (`himi.host.memberSitePage`) and the host opens the site's own event or
|
|
9
|
+
// plans page the same way, signed in — where the member picks and pays on the site.
|
|
6
10
|
|
|
7
11
|
import { dispatch } from "@himalaya/dispatch";
|
|
8
12
|
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
@@ -13,6 +17,12 @@ import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
|
13
17
|
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
18
|
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
19
|
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
20
|
+
/** Prepares a site PAGE (not an ecom checkout) the same way; opened with [HOSTED_CHECKOUT_OPEN_ACTION]. */
|
|
21
|
+
export const HOSTED_SITE_PAGE_ACTION = "himi.host.memberSitePage";
|
|
22
|
+
|
|
23
|
+
/** A site page a hosted worker may ask its host for — ids only, never a URL: the host reads the URL
|
|
24
|
+
* from Wix itself and decides whether the member may be signed in there. */
|
|
25
|
+
export type HostedSitePage = { page: "event"; eventId: string } | { page: "plans"; planId?: string };
|
|
16
26
|
|
|
17
27
|
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
28
|
export function isHostedWorker(): boolean {
|
|
@@ -20,14 +30,14 @@ export function isHostedWorker(): boolean {
|
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
33
|
+
type HostOpener = (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
|
|
23
34
|
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
35
|
+
/** Prepare with [action] [params] → re-read the guard → open by ticket. A host without the verbs, or one
|
|
36
|
+
* that refused (Wix named no page, an unsafe URL, no account session), → false, never a throw. */
|
|
37
|
+
function hostOpener(action: string, params: Record<string, string>): HostOpener {
|
|
28
38
|
return async (stillMayCharge) => {
|
|
29
39
|
try {
|
|
30
|
-
const prepared = await dispatch(
|
|
40
|
+
const prepared = await dispatch(action, params) as HostAnswer;
|
|
31
41
|
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
42
|
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
43
|
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
@@ -40,3 +50,19 @@ export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () =>
|
|
|
40
50
|
}
|
|
41
51
|
};
|
|
42
52
|
}
|
|
53
|
+
|
|
54
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
55
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
56
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
57
|
+
export function hostedCheckoutOpener(checkoutId: string): HostOpener {
|
|
58
|
+
return hostOpener(HOSTED_CHECKOUT_ACTION, { checkoutId });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The site's own [page] opened by the host, signed in as the lent member where the host deems it safe
|
|
62
|
+
* (a visitor otherwise) — the same two steps and answers as [hostedCheckoutOpener]. */
|
|
63
|
+
export function hostedSitePageOpener(page: HostedSitePage): HostOpener {
|
|
64
|
+
const params: Record<string, string> = page.page === "event"
|
|
65
|
+
? { page: "event", eventId: page.eventId }
|
|
66
|
+
: { page: "plans", ...(page.planId ? { planId: page.planId } : {}) };
|
|
67
|
+
return hostOpener(HOSTED_SITE_PAGE_ACTION, params);
|
|
68
|
+
}
|
|
@@ -35,7 +35,10 @@ import {
|
|
|
35
35
|
type EventRow,
|
|
36
36
|
} from "../_shared/himi-events";
|
|
37
37
|
import { initials } from "../_shared/himi-text.js";
|
|
38
|
-
import { isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
38
|
+
import { hostedSitePageOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
39
|
+
|
|
40
|
+
/** A hosted signed-in event-page open is in flight (see openRegistration). */
|
|
41
|
+
let hostedOpenInFlight = false;
|
|
39
42
|
import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
40
43
|
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
41
44
|
import { richBody } from "../_shared/rich-body.js";
|
|
@@ -579,7 +582,8 @@ const svc = defineService<S, {
|
|
|
579
582
|
// `checkoutReservation` needs a buyer + guest form this screen does not collect, and the
|
|
580
583
|
// redirect session needs a headless OAuth client a hosted app does not have. Skip both —
|
|
581
584
|
// the native chain would also leave a ticket reservation held for nothing — and open the
|
|
582
|
-
// event's own page, where the member registers on the site
|
|
585
|
+
// event's own page, where the member registers on the site: signed in by the host where it can
|
|
586
|
+
// (`himi.host.memberSitePage`), else as a visitor.
|
|
583
587
|
if (s.registrationKind === "tickets" && s.id && !isHostedWorker()) {
|
|
584
588
|
let reservationId = "";
|
|
585
589
|
try {
|
|
@@ -658,6 +662,31 @@ const svc = defineService<S, {
|
|
|
658
662
|
// fall through to registrationUrl
|
|
659
663
|
}
|
|
660
664
|
}
|
|
665
|
+
// Hosted: the HOST opens the event's own page signed in as the lent member — ids only; it
|
|
666
|
+
// reads the page URL from Wix and refuses (a custom domain → a visitor page; no published Events
|
|
667
|
+
// page → nothing) on its own. Nothing here can change between its prepare and its open (the event
|
|
668
|
+
// is fixed and the page is where the member chooses and pays), so the guard has nothing to re-read.
|
|
669
|
+
// A host that refused or failed leaves the visitor page below.
|
|
670
|
+
if (isHostedWorker() && s.id && (s.registrationKind === "tickets" || s.registrationKind === "rsvp")) {
|
|
671
|
+
// One signed-in open at a time: the host keeps a single one-time ticket, so a second tap would
|
|
672
|
+
// replace the first tap's ticket and drop that tap into the signed-OUT visitor page below.
|
|
673
|
+
if (hostedOpenInFlight) return s;
|
|
674
|
+
hostedOpenInFlight = true;
|
|
675
|
+
let opened: boolean | "withdrawn";
|
|
676
|
+
try {
|
|
677
|
+
opened = await hostedSitePageOpener({ page: "event", eventId: s.id })(async () => true);
|
|
678
|
+
} finally {
|
|
679
|
+
hostedOpenInFlight = false;
|
|
680
|
+
}
|
|
681
|
+
if (opened === true) {
|
|
682
|
+
return derive({
|
|
683
|
+
...s,
|
|
684
|
+
actionMessage: s.registrationKind === "tickets"
|
|
685
|
+
? copyParam("event.checkoutOpened", "Complete checkout in the browser to get your tickets.")
|
|
686
|
+
: "",
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
661
690
|
if (!s.registrationUrl) {
|
|
662
691
|
return derive({ ...s, actionMessage: copyParam("event.registrationOnPage", "Registration for this event happens on the event page.") });
|
|
663
692
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
// Hosted checkout
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Hosted checkout. A Branded Lite hosted in a host shell (Spaces) pays as the member the host
|
|
2
|
+
// lent it: Headless `createRedirectSession` does not take a Member instance, so the HOST opens the site's
|
|
3
|
+
// checkout signed in as that member. The worker names only its ecom checkout; the host reads Wix's
|
|
4
|
+
// checkout URL, mints a one-time member session and opens the site's `createSessionCookie` → checkout.
|
|
5
|
+
// The session token never reaches this worker.
|
|
6
|
+
//
|
|
7
|
+
// Classic Wix Events tickets and classic Pricing Plans have no ecom checkout: for those the
|
|
8
|
+
// worker names the page by id (`himi.host.memberSitePage`) and the host opens the site's own event or
|
|
9
|
+
// plans page the same way, signed in — where the member picks and pays on the site.
|
|
6
10
|
|
|
7
11
|
import { dispatch } from "@himalaya/dispatch";
|
|
8
12
|
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
@@ -13,6 +17,12 @@ import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
|
13
17
|
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
18
|
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
19
|
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
20
|
+
/** Prepares a site PAGE (not an ecom checkout) the same way; opened with [HOSTED_CHECKOUT_OPEN_ACTION]. */
|
|
21
|
+
export const HOSTED_SITE_PAGE_ACTION = "himi.host.memberSitePage";
|
|
22
|
+
|
|
23
|
+
/** A site page a hosted worker may ask its host for — ids only, never a URL: the host reads the URL
|
|
24
|
+
* from Wix itself and decides whether the member may be signed in there. */
|
|
25
|
+
export type HostedSitePage = { page: "event"; eventId: string } | { page: "plans"; planId?: string };
|
|
16
26
|
|
|
17
27
|
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
28
|
export function isHostedWorker(): boolean {
|
|
@@ -20,14 +30,14 @@ export function isHostedWorker(): boolean {
|
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
33
|
+
type HostOpener = (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
|
|
23
34
|
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
35
|
+
/** Prepare with [action] [params] → re-read the guard → open by ticket. A host without the verbs, or one
|
|
36
|
+
* that refused (Wix named no page, an unsafe URL, no account session), → false, never a throw. */
|
|
37
|
+
function hostOpener(action: string, params: Record<string, string>): HostOpener {
|
|
28
38
|
return async (stillMayCharge) => {
|
|
29
39
|
try {
|
|
30
|
-
const prepared = await dispatch(
|
|
40
|
+
const prepared = await dispatch(action, params) as HostAnswer;
|
|
31
41
|
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
42
|
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
43
|
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
@@ -40,3 +50,19 @@ export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () =>
|
|
|
40
50
|
}
|
|
41
51
|
};
|
|
42
52
|
}
|
|
53
|
+
|
|
54
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
55
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
56
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
57
|
+
export function hostedCheckoutOpener(checkoutId: string): HostOpener {
|
|
58
|
+
return hostOpener(HOSTED_CHECKOUT_ACTION, { checkoutId });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The site's own [page] opened by the host, signed in as the lent member where the host deems it safe
|
|
62
|
+
* (a visitor otherwise) — the same two steps and answers as [hostedCheckoutOpener]. */
|
|
63
|
+
export function hostedSitePageOpener(page: HostedSitePage): HostOpener {
|
|
64
|
+
const params: Record<string, string> = page.page === "event"
|
|
65
|
+
? { page: "event", eventId: page.eventId }
|
|
66
|
+
: { page: "plans", ...(page.planId ? { planId: page.planId } : {}) };
|
|
67
|
+
return hostOpener(HOSTED_SITE_PAGE_ACTION, params);
|
|
68
|
+
}
|
|
@@ -35,7 +35,10 @@ import {
|
|
|
35
35
|
type EventRow,
|
|
36
36
|
} from "../_shared/himi-events";
|
|
37
37
|
import { initials } from "../_shared/himi-text.js";
|
|
38
|
-
import { isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
38
|
+
import { hostedSitePageOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
39
|
+
|
|
40
|
+
/** A hosted signed-in event-page open is in flight (see openRegistration). */
|
|
41
|
+
let hostedOpenInFlight = false;
|
|
39
42
|
import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
40
43
|
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
41
44
|
import { richBody } from "../_shared/rich-body.js";
|
|
@@ -579,7 +582,8 @@ const svc = defineService<S, {
|
|
|
579
582
|
// `checkoutReservation` needs a buyer + guest form this screen does not collect, and the
|
|
580
583
|
// redirect session needs a headless OAuth client a hosted app does not have. Skip both —
|
|
581
584
|
// the native chain would also leave a ticket reservation held for nothing — and open the
|
|
582
|
-
// event's own page, where the member registers on the site
|
|
585
|
+
// event's own page, where the member registers on the site: signed in by the host where it can
|
|
586
|
+
// (`himi.host.memberSitePage`), else as a visitor.
|
|
583
587
|
if (s.registrationKind === "tickets" && s.id && !isHostedWorker()) {
|
|
584
588
|
let reservationId = "";
|
|
585
589
|
try {
|
|
@@ -658,6 +662,31 @@ const svc = defineService<S, {
|
|
|
658
662
|
// fall through to registrationUrl
|
|
659
663
|
}
|
|
660
664
|
}
|
|
665
|
+
// Hosted: the HOST opens the event's own page signed in as the lent member — ids only; it
|
|
666
|
+
// reads the page URL from Wix and refuses (a custom domain → a visitor page; no published Events
|
|
667
|
+
// page → nothing) on its own. Nothing here can change between its prepare and its open (the event
|
|
668
|
+
// is fixed and the page is where the member chooses and pays), so the guard has nothing to re-read.
|
|
669
|
+
// A host that refused or failed leaves the visitor page below.
|
|
670
|
+
if (isHostedWorker() && s.id && (s.registrationKind === "tickets" || s.registrationKind === "rsvp")) {
|
|
671
|
+
// One signed-in open at a time: the host keeps a single one-time ticket, so a second tap would
|
|
672
|
+
// replace the first tap's ticket and drop that tap into the signed-OUT visitor page below.
|
|
673
|
+
if (hostedOpenInFlight) return s;
|
|
674
|
+
hostedOpenInFlight = true;
|
|
675
|
+
let opened: boolean | "withdrawn";
|
|
676
|
+
try {
|
|
677
|
+
opened = await hostedSitePageOpener({ page: "event", eventId: s.id })(async () => true);
|
|
678
|
+
} finally {
|
|
679
|
+
hostedOpenInFlight = false;
|
|
680
|
+
}
|
|
681
|
+
if (opened === true) {
|
|
682
|
+
return derive({
|
|
683
|
+
...s,
|
|
684
|
+
actionMessage: s.registrationKind === "tickets"
|
|
685
|
+
? copyParam("event.checkoutOpened", "Complete checkout in the browser to get your tickets.")
|
|
686
|
+
: "",
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
661
690
|
if (!s.registrationUrl) {
|
|
662
691
|
return derive({ ...s, actionMessage: copyParam("event.registrationOnPage", "Registration for this event happens on the event page.") });
|
|
663
692
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
// Hosted checkout
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Hosted checkout. A Branded Lite hosted in a host shell (Spaces) pays as the member the host
|
|
2
|
+
// lent it: Headless `createRedirectSession` does not take a Member instance, so the HOST opens the site's
|
|
3
|
+
// checkout signed in as that member. The worker names only its ecom checkout; the host reads Wix's
|
|
4
|
+
// checkout URL, mints a one-time member session and opens the site's `createSessionCookie` → checkout.
|
|
5
|
+
// The session token never reaches this worker.
|
|
6
|
+
//
|
|
7
|
+
// Classic Wix Events tickets and classic Pricing Plans have no ecom checkout: for those the
|
|
8
|
+
// worker names the page by id (`himi.host.memberSitePage`) and the host opens the site's own event or
|
|
9
|
+
// plans page the same way, signed in — where the member picks and pays on the site.
|
|
6
10
|
|
|
7
11
|
import { dispatch } from "@himalaya/dispatch";
|
|
8
12
|
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
@@ -13,6 +17,12 @@ import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
|
13
17
|
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
18
|
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
19
|
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
20
|
+
/** Prepares a site PAGE (not an ecom checkout) the same way; opened with [HOSTED_CHECKOUT_OPEN_ACTION]. */
|
|
21
|
+
export const HOSTED_SITE_PAGE_ACTION = "himi.host.memberSitePage";
|
|
22
|
+
|
|
23
|
+
/** A site page a hosted worker may ask its host for — ids only, never a URL: the host reads the URL
|
|
24
|
+
* from Wix itself and decides whether the member may be signed in there. */
|
|
25
|
+
export type HostedSitePage = { page: "event"; eventId: string } | { page: "plans"; planId?: string };
|
|
16
26
|
|
|
17
27
|
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
28
|
export function isHostedWorker(): boolean {
|
|
@@ -20,14 +30,14 @@ export function isHostedWorker(): boolean {
|
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
33
|
+
type HostOpener = (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
|
|
23
34
|
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
35
|
+
/** Prepare with [action] [params] → re-read the guard → open by ticket. A host without the verbs, or one
|
|
36
|
+
* that refused (Wix named no page, an unsafe URL, no account session), → false, never a throw. */
|
|
37
|
+
function hostOpener(action: string, params: Record<string, string>): HostOpener {
|
|
28
38
|
return async (stillMayCharge) => {
|
|
29
39
|
try {
|
|
30
|
-
const prepared = await dispatch(
|
|
40
|
+
const prepared = await dispatch(action, params) as HostAnswer;
|
|
31
41
|
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
42
|
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
43
|
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
@@ -40,3 +50,19 @@ export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () =>
|
|
|
40
50
|
}
|
|
41
51
|
};
|
|
42
52
|
}
|
|
53
|
+
|
|
54
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
55
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
56
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
57
|
+
export function hostedCheckoutOpener(checkoutId: string): HostOpener {
|
|
58
|
+
return hostOpener(HOSTED_CHECKOUT_ACTION, { checkoutId });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The site's own [page] opened by the host, signed in as the lent member where the host deems it safe
|
|
62
|
+
* (a visitor otherwise) — the same two steps and answers as [hostedCheckoutOpener]. */
|
|
63
|
+
export function hostedSitePageOpener(page: HostedSitePage): HostOpener {
|
|
64
|
+
const params: Record<string, string> = page.page === "event"
|
|
65
|
+
? { page: "event", eventId: page.eventId }
|
|
66
|
+
: { page: "plans", ...(page.planId ? { planId: page.planId } : {}) };
|
|
67
|
+
return hostOpener(HOSTED_SITE_PAGE_ACTION, params);
|
|
68
|
+
}
|
|
@@ -35,7 +35,10 @@ import {
|
|
|
35
35
|
type EventRow,
|
|
36
36
|
} from "../_shared/himi-events";
|
|
37
37
|
import { initials } from "../_shared/himi-text.js";
|
|
38
|
-
import { isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
38
|
+
import { hostedSitePageOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
|
|
39
|
+
|
|
40
|
+
/** A hosted signed-in event-page open is in flight (see openRegistration). */
|
|
41
|
+
let hostedOpenInFlight = false;
|
|
39
42
|
import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
40
43
|
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
41
44
|
import { richBody } from "../_shared/rich-body.js";
|
|
@@ -579,7 +582,8 @@ const svc = defineService<S, {
|
|
|
579
582
|
// `checkoutReservation` needs a buyer + guest form this screen does not collect, and the
|
|
580
583
|
// redirect session needs a headless OAuth client a hosted app does not have. Skip both —
|
|
581
584
|
// the native chain would also leave a ticket reservation held for nothing — and open the
|
|
582
|
-
// event's own page, where the member registers on the site
|
|
585
|
+
// event's own page, where the member registers on the site: signed in by the host where it can
|
|
586
|
+
// (`himi.host.memberSitePage`), else as a visitor.
|
|
583
587
|
if (s.registrationKind === "tickets" && s.id && !isHostedWorker()) {
|
|
584
588
|
let reservationId = "";
|
|
585
589
|
try {
|
|
@@ -658,6 +662,31 @@ const svc = defineService<S, {
|
|
|
658
662
|
// fall through to registrationUrl
|
|
659
663
|
}
|
|
660
664
|
}
|
|
665
|
+
// Hosted: the HOST opens the event's own page signed in as the lent member — ids only; it
|
|
666
|
+
// reads the page URL from Wix and refuses (a custom domain → a visitor page; no published Events
|
|
667
|
+
// page → nothing) on its own. Nothing here can change between its prepare and its open (the event
|
|
668
|
+
// is fixed and the page is where the member chooses and pays), so the guard has nothing to re-read.
|
|
669
|
+
// A host that refused or failed leaves the visitor page below.
|
|
670
|
+
if (isHostedWorker() && s.id && (s.registrationKind === "tickets" || s.registrationKind === "rsvp")) {
|
|
671
|
+
// One signed-in open at a time: the host keeps a single one-time ticket, so a second tap would
|
|
672
|
+
// replace the first tap's ticket and drop that tap into the signed-OUT visitor page below.
|
|
673
|
+
if (hostedOpenInFlight) return s;
|
|
674
|
+
hostedOpenInFlight = true;
|
|
675
|
+
let opened: boolean | "withdrawn";
|
|
676
|
+
try {
|
|
677
|
+
opened = await hostedSitePageOpener({ page: "event", eventId: s.id })(async () => true);
|
|
678
|
+
} finally {
|
|
679
|
+
hostedOpenInFlight = false;
|
|
680
|
+
}
|
|
681
|
+
if (opened === true) {
|
|
682
|
+
return derive({
|
|
683
|
+
...s,
|
|
684
|
+
actionMessage: s.registrationKind === "tickets"
|
|
685
|
+
? copyParam("event.checkoutOpened", "Complete checkout in the browser to get your tickets.")
|
|
686
|
+
: "",
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
661
690
|
if (!s.registrationUrl) {
|
|
662
691
|
return derive({ ...s, actionMessage: copyParam("event.registrationOnPage", "Registration for this event happens on the event page.") });
|
|
663
692
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
// Hosted checkout
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Hosted checkout. A Branded Lite hosted in a host shell (Spaces) pays as the member the host
|
|
2
|
+
// lent it: Headless `createRedirectSession` does not take a Member instance, so the HOST opens the site's
|
|
3
|
+
// checkout signed in as that member. The worker names only its ecom checkout; the host reads Wix's
|
|
4
|
+
// checkout URL, mints a one-time member session and opens the site's `createSessionCookie` → checkout.
|
|
5
|
+
// The session token never reaches this worker.
|
|
6
|
+
//
|
|
7
|
+
// Classic Wix Events tickets and classic Pricing Plans have no ecom checkout: for those the
|
|
8
|
+
// worker names the page by id (`himi.host.memberSitePage`) and the host opens the site's own event or
|
|
9
|
+
// plans page the same way, signed in — where the member picks and pays on the site.
|
|
6
10
|
|
|
7
11
|
import { dispatch } from "@himalaya/dispatch";
|
|
8
12
|
import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
@@ -13,6 +17,12 @@ import { hostSiteFromInput } from "./branded-lite-hosted.js";
|
|
|
13
17
|
* URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
|
|
14
18
|
export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
|
|
15
19
|
export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
|
|
20
|
+
/** Prepares a site PAGE (not an ecom checkout) the same way; opened with [HOSTED_CHECKOUT_OPEN_ACTION]. */
|
|
21
|
+
export const HOSTED_SITE_PAGE_ACTION = "himi.host.memberSitePage";
|
|
22
|
+
|
|
23
|
+
/** A site page a hosted worker may ask its host for — ids only, never a URL: the host reads the URL
|
|
24
|
+
* from Wix itself and decides whether the member may be signed in there. */
|
|
25
|
+
export type HostedSitePage = { page: "event"; eventId: string } | { page: "plans"; planId?: string };
|
|
16
26
|
|
|
17
27
|
/** Whether this worker runs inside a host that opened a site for it. */
|
|
18
28
|
export function isHostedWorker(): boolean {
|
|
@@ -20,14 +30,14 @@ export function isHostedWorker(): boolean {
|
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
|
|
33
|
+
type HostOpener = (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
|
|
23
34
|
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
|
|
35
|
+
/** Prepare with [action] [params] → re-read the guard → open by ticket. A host without the verbs, or one
|
|
36
|
+
* that refused (Wix named no page, an unsafe URL, no account session), → false, never a throw. */
|
|
37
|
+
function hostOpener(action: string, params: Record<string, string>): HostOpener {
|
|
28
38
|
return async (stillMayCharge) => {
|
|
29
39
|
try {
|
|
30
|
-
const prepared = await dispatch(
|
|
40
|
+
const prepared = await dispatch(action, params) as HostAnswer;
|
|
31
41
|
const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
|
|
32
42
|
if (typeof ticket !== "string" || !ticket) return false;
|
|
33
43
|
// An unopened ticket simply expires in the host: nothing to cancel.
|
|
@@ -40,3 +50,19 @@ export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () =>
|
|
|
40
50
|
}
|
|
41
51
|
};
|
|
42
52
|
}
|
|
53
|
+
|
|
54
|
+
/** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
|
|
55
|
+
* lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
|
|
56
|
+
* opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
|
|
57
|
+
export function hostedCheckoutOpener(checkoutId: string): HostOpener {
|
|
58
|
+
return hostOpener(HOSTED_CHECKOUT_ACTION, { checkoutId });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The site's own [page] opened by the host, signed in as the lent member where the host deems it safe
|
|
62
|
+
* (a visitor otherwise) — the same two steps and answers as [hostedCheckoutOpener]. */
|
|
63
|
+
export function hostedSitePageOpener(page: HostedSitePage): HostOpener {
|
|
64
|
+
const params: Record<string, string> = page.page === "event"
|
|
65
|
+
? { page: "event", eventId: page.eventId }
|
|
66
|
+
: { page: "plans", ...(page.planId ? { planId: page.planId } : {}) };
|
|
67
|
+
return hostOpener(HOSTED_SITE_PAGE_ACTION, params);
|
|
68
|
+
}
|