@wix/himalaya-cli 0.1558.0 → 0.1559.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.
@@ -164,7 +164,7 @@ export declare function queryParticipants(programId: string, opts?: {
164
164
  limit?: number;
165
165
  }, readOptions?: WixCallOptions): Promise<WixQueryParticipantsResponse>;
166
166
  /** Participant lifecycle state. */
167
- export type WixParticipantState = "JOINED" | "PAYMENT_PENDING" | "APPROVAL_PENDING" | "COMPLETED" | "INVITED" | "REMOVED" | string;
167
+ export type WixParticipantState = "JOINED" | "PAYMENT_PENDING" | "ENROLLMENT_PENDING" | "APPROVAL_PENDING" | "COMPLETED" | "INVITED" | "REMOVED" | string;
168
168
  export interface WixParticipantPaymentPending {
169
169
  /** The payable handle for the cashier rail, when the program charges. */
170
170
  purchaseFlowId?: string;
@@ -172,7 +172,7 @@ export interface WixParticipantPaymentPending {
172
172
  snapshotId?: string;
173
173
  }
174
174
  /**
175
- * The participant JoinProgram hands back — a DIFFERENT entity from the roster
175
+ * The compatibility result joinProgram returns — a DIFFERENT entity from the roster
176
176
  * `WixParticipant` above, which is why it gets its own name. This one describes the join
177
177
  * outcome (`state`, `paymentPendingOptions`); the roster one describes who the member is
178
178
  * and how far they have got (`member`, `progress`, `enrollmentStatus`).
@@ -195,7 +195,16 @@ export interface WixJoinedParticipant {
195
195
  export interface WixJoinProgramResponse {
196
196
  participant?: WixJoinedParticipant;
197
197
  }
198
- /** Join a program as the signed-in member. Branch on `participant.state`. */
199
- export declare function joinProgram(programId: string): Promise<WixJoinProgramResponse>;
200
- /** The cashier handle to settle, or "" when the join needs no payment. */
198
+ /**
199
+ * Join as the signed-in member through Join Applications. Read the current
200
+ * application first so an existing application is resumed without another create.
201
+ * Approved paid applications prepare
202
+ * SINGLE_PAYMENT and return the Wix Payments order as paymentPendingOptions.orderId.
203
+ * mayPreparePayment rechecks the caller's checkout guard before creating that order.
204
+ * @scope member
205
+ */
206
+ export declare function joinProgram(programId: string, options?: {
207
+ mayPreparePayment?: () => boolean;
208
+ }): Promise<WixJoinProgramResponse>;
209
+ /** The cashier handle to settle, or "" when no payable order is available. */
201
210
  export declare function payableIdOf(participant?: WixJoinedParticipant): string;
@@ -29,13 +29,41 @@ function queryParticipants(programId, opts, readOptions) {
29
29
  };
30
30
  return wixCall("POST", "/online-programs/v3/participants/query", { query, fields: ["PROGRESS"] }, readOptions);
31
31
  }
32
- function joinProgram(programId) {
33
- if (!programId) throw new Error("joinProgram: programId is required");
34
- return wixCall(
35
- "POST",
36
- `/online-programs/v3/participants/${encodeURIComponent(programId)}/join`,
37
- {}
38
- );
32
+ function joinProgramError(code, message) {
33
+ return Object.assign(new Error(`joinProgram: ${message}`), { code });
34
+ }
35
+ var JOIN_APPLICATIONS = "/online-programs/participants/v3/join-applications";
36
+ async function joinProgram(programId, options) {
37
+ if (!programId) throw joinProgramError("program_id_required", "programId is required");
38
+ let response;
39
+ let currentReadSucceeded = false;
40
+ try {
41
+ response = await wixCall("GET", `${JOIN_APPLICATIONS}/current/${encodeURIComponent(programId)}`);
42
+ currentReadSucceeded = true;
43
+ } catch {
44
+ }
45
+ if (!currentReadSucceeded) {
46
+ response = await wixCall("POST", JOIN_APPLICATIONS, { joinApplication: { programId } });
47
+ }
48
+ let application = response?.joinApplication;
49
+ if (!application?.id || application.programId !== programId) throw joinProgramError("invalid_join_application", "invalid join application");
50
+ if (application.status === "APPROVED" && ["PAYMENT_PENDING", "PAYMENT_FAILED", "PAYMENT_CANCELED"].includes(application.paymentStatus ?? "")) {
51
+ if (options?.mayPreparePayment && !options.mayPreparePayment()) throw joinProgramError("payment_preparation_withdrawn", "payment preparation withdrawn");
52
+ const prepared = await wixCall("POST", `${JOIN_APPLICATIONS}/${encodeURIComponent(application.id)}/prepare-payment`, { paymentType: "SINGLE_PAYMENT" });
53
+ if (prepared?.joinApplication?.id !== application.id || prepared.joinApplication.programId !== programId) throw joinProgramError("invalid_prepared_application", "invalid prepared application");
54
+ application = prepared.joinApplication;
55
+ }
56
+ const participant = { programId, memberId: application.memberId };
57
+ if (application.status === "APPROVAL_PENDING") return { participant: { ...participant, state: "APPROVAL_PENDING" } };
58
+ if (application.status !== "APPROVED") throw joinProgramError("join_application_not_approved", "application not approved");
59
+ if (application.paymentStatus === "PENDING_OWNER_APPROVAL") return { participant: { ...participant, state: "ENROLLMENT_PENDING" } };
60
+ if (["NO_PAYMENT_REQUIRED", "PAYMENT_SUCCESSFUL"].includes(application.paymentStatus ?? "")) {
61
+ return { participant: application.participantId ? { ...participant, id: application.participantId, state: "JOINED" } : { ...participant, state: "ENROLLMENT_PENDING" } };
62
+ }
63
+ if (application.paymentStatus === "PAYMENT_IN_PROGRESS" && application.paymentInfo?.paymentOrderId) {
64
+ return { participant: { ...participant, state: "PAYMENT_PENDING", paymentPendingOptions: { orderId: application.paymentInfo.paymentOrderId } } };
65
+ }
66
+ throw joinProgramError("join_application_not_ready", "enrollment or payment is not ready");
39
67
  }
40
68
  function payableIdOf(participant) {
41
69
  const pending = participant?.paymentPendingOptions;
@@ -23983,7 +23983,7 @@
23983
23983
  "name": "WixParticipantState",
23984
23984
  "kind": "type",
23985
23985
  "jsdoc": "/** Participant lifecycle state. */",
23986
- "decl": "export type WixParticipantState =\n | \"JOINED\"\n | \"PAYMENT_PENDING\"\n | \"APPROVAL_PENDING\"\n | \"COMPLETED\"\n | \"INVITED\"\n | \"REMOVED\"\n | string;"
23986
+ "decl": "export type WixParticipantState =\n | \"JOINED\"\n | \"PAYMENT_PENDING\"\n | \"ENROLLMENT_PENDING\"\n | \"APPROVAL_PENDING\"\n | \"COMPLETED\"\n | \"INVITED\"\n | \"REMOVED\"\n | string;"
23987
23987
  },
23988
23988
  {
23989
23989
  "name": "WixParticipantPaymentPending",
@@ -23994,7 +23994,7 @@
23994
23994
  {
23995
23995
  "name": "WixJoinedParticipant",
23996
23996
  "kind": "interface",
23997
- "jsdoc": "/**\n * The participant JoinProgram hands back — a DIFFERENT entity from the roster\n * `WixParticipant` above, which is why it gets its own name. This one describes the join\n * outcome (`state`, `paymentPendingOptions`); the roster one describes who the member is\n * and how far they have got (`member`, `progress`, `enrollmentStatus`).\n *\n * Both were spelled `WixParticipant` until #2289, so TypeScript MERGED the two declarations\n * instead of keeping them apart. That is wrong twice over: the merged type carries every\n * field of both, so it describes neither response — and the merge is itself an error,\n * because `id` was required in the roster shape and optional here (TS2687 + TS2717).\n */",
23997
+ "jsdoc": "/**\n * The compatibility result joinProgram returns — a DIFFERENT entity from the roster\n * `WixParticipant` above, which is why it gets its own name. This one describes the join\n * outcome (`state`, `paymentPendingOptions`); the roster one describes who the member is\n * and how far they have got (`member`, `progress`, `enrollmentStatus`).\n *\n * Both were spelled `WixParticipant` until #2289, so TypeScript MERGED the two declarations\n * instead of keeping them apart. That is wrong twice over: the merged type carries every\n * field of both, so it describes neither response — and the merge is itself an error,\n * because `id` was required in the roster shape and optional here (TS2687 + TS2717).\n */",
23998
23998
  "decl": "export interface WixJoinedParticipant {\n id?: string;\n programId?: string;\n memberId?: string;\n state?: WixParticipantState;\n paymentPendingOptions?: WixParticipantPaymentPending;\n joinedOptions?: { joinedDate?: string };\n}"
23999
23999
  },
24000
24000
  {
@@ -24006,14 +24006,14 @@
24006
24006
  {
24007
24007
  "name": "joinProgram",
24008
24008
  "kind": "function",
24009
- "jsdoc": "/** Join a program as the signed-in member. Branch on `participant.state`. */",
24010
- "decl": "export function joinProgram(programId: string): Promise<WixJoinProgramResponse>;",
24011
- "http": "POST /online-programs/v3/participants/{…}/join"
24009
+ "jsdoc": "/**\n * Join as the signed-in member through Join Applications. Read the current\n * application first so an existing application is resumed without another create.\n * Approved paid applications prepare\n * SINGLE_PAYMENT and return the Wix Payments order as paymentPendingOptions.orderId.\n * mayPreparePayment rechecks the caller's checkout guard before creating that order.\n * @scope member\n */",
24010
+ "decl": "export async function joinProgram(programId: string, options?: { mayPreparePayment?: () => boolean }): Promise<WixJoinProgramResponse>;",
24011
+ "scope": "member"
24012
24012
  },
24013
24013
  {
24014
24014
  "name": "payableIdOf",
24015
24015
  "kind": "function",
24016
- "jsdoc": "/** The cashier handle to settle, or \"\" when the join needs no payment. */",
24016
+ "jsdoc": "/** The cashier handle to settle, or \"\" when no payable order is available. */",
24017
24017
  "decl": "export function payableIdOf(participant?: WixJoinedParticipant): string;"
24018
24018
  }
24019
24019
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1558.0",
3
+ "version": "0.1559.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": "3e25c3d6c9c9f3a1d3abe87df97e8a1f848ce1a712d321126ce42454"
74
+ "falconPackageHash": "10b9cfbdf8247c393305b2341b984702640668ff76c32fbc09ae2d8e"
75
75
  }
@@ -9,7 +9,7 @@ import { withOnlineWrites } from "../_shared/online-actions.js";
9
9
  // error card.
10
10
  //
11
11
  // Phase 2d — enrolling is NATIVE. `joinProgram` covers all three program kinds in
12
- // one call and the outcome is read off `participant.state`, not assumed:
12
+ // the Join Applications API; its compatibility result exposes only confirmed outcomes:
13
13
  // a free/open program comes back JOINED with nothing to pay, a paid one comes back
14
14
  // PAYMENT_PENDING and settles on the shared in-app pay rail, and a private one comes
15
15
  // back APPROVAL_PENDING (the owner approves; there is nothing to charge). Any failure
@@ -190,18 +190,26 @@ interface CommerceGuards {
190
190
  async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<string> {
191
191
  const blocked = () => iapPriceCopy("program").purchaseUnavailable;
192
192
  if (!guards.mayStart()) return blocked();
193
+ let preparationWithdrawn = false;
193
194
  try {
194
- const joined = await joinProgram(inputId());
195
+ const joined = await joinProgram(inputId(), { mayPreparePayment: () => {
196
+ if (!guards.mayContinue()) preparationWithdrawn = true;
197
+ return !preparationWithdrawn;
198
+ } });
195
199
  const participant = joined.participant;
196
200
  const programState = (participant?.state ?? "").toUpperCase();
197
201
  if (programState === "APPROVAL_PENDING") {
198
202
  return copyParam("program.approvalPendingMessage", "Request sent — the host will approve your place.");
199
203
  }
204
+ if (programState === "ENROLLMENT_PENDING") {
205
+ return copyParam("program.enrollmentPendingMessage", "Your enrollment is processing — check back shortly.");
206
+ }
200
207
  const payable = payableIdOf(participant);
201
- if (!payable) {
202
- // Free / open program (or one already settled) ⇒ nothing to charge.
208
+ if (programState === "JOINED") {
209
+ // Only a confirmed participant is enrolled; absent payment data is not success.
203
210
  return copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
204
211
  }
212
+ if (programState !== "PAYMENT_PENDING" || !payable) throw new Error("Program enrollment is not ready");
205
213
  const msid = await wixPlatformSession.signedInstanceStatus().then((s) => s.msid ?? "").catch(() => "");
206
214
  // Re-read before each step that commits money: a route that settles meanwhile wins.
207
215
  let withdrawn = false;
@@ -221,12 +229,8 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
221
229
  // which is precisely the dead-end the two-rail design exists to prevent
222
230
  // (and which the cart/plans/events flows each guard with a test).
223
231
  //
224
- // There is no `programsCheckout` redirect intent. `ecomOrderPaymentRequest`
225
- // is the only payment-request-shaped one, and a program's `purchaseFlowId`
226
- // is a payment-request handle, so that is what we hand it. If that mapping
227
- // is ever wrong the call throws, we degrade to the same site-directing
228
- // message as before, and nothing regresses — but when it is right, a
229
- // wallet-less site can actually sell the program IN-APP.
232
+ // Join Applications returns a Wix Payments order id; the payment-request
233
+ // redirect uses that same order handle on the hosted rail.
230
234
  cashierUrl: async () => {
231
235
  if (!stillAllowed()) return undefined;
232
236
  const redirect = await createRedirectSession({
@@ -237,10 +241,13 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
237
241
  },
238
242
  });
239
243
  if (withdrawn) return blocked();
240
- if (pay.kind === "paid") return copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
244
+ if (pay.kind === "paid") return pay.pending
245
+ ? copyParam("program.paymentPendingMessage", "Payment is processing — enrollment will be confirmed after payment.")
246
+ : copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
241
247
  if (pay.kind === "handoff") return "";
242
248
  if (pay.kind === "cancelled") return copyParam("program.joinCancelledMessage", "Payment cancelled — you haven't enrolled.");
243
249
  } catch {
250
+ if (preparationWithdrawn) return blocked();
244
251
  // fall through to the site-directing message
245
252
  }
246
253
  return copyTemplate("program.joinOnSiteMessage", "Enrolment happens on the website — visit the site to join {name}.", { name });
@@ -9,7 +9,7 @@ import { withOnlineWrites } from "../_shared/online-actions.js";
9
9
  // error card.
10
10
  //
11
11
  // Phase 2d — enrolling is NATIVE. `joinProgram` covers all three program kinds in
12
- // one call and the outcome is read off `participant.state`, not assumed:
12
+ // the Join Applications API; its compatibility result exposes only confirmed outcomes:
13
13
  // a free/open program comes back JOINED with nothing to pay, a paid one comes back
14
14
  // PAYMENT_PENDING and settles on the shared in-app pay rail, and a private one comes
15
15
  // back APPROVAL_PENDING (the owner approves; there is nothing to charge). Any failure
@@ -190,18 +190,26 @@ interface CommerceGuards {
190
190
  async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<string> {
191
191
  const blocked = () => iapPriceCopy("program").purchaseUnavailable;
192
192
  if (!guards.mayStart()) return blocked();
193
+ let preparationWithdrawn = false;
193
194
  try {
194
- const joined = await joinProgram(inputId());
195
+ const joined = await joinProgram(inputId(), { mayPreparePayment: () => {
196
+ if (!guards.mayContinue()) preparationWithdrawn = true;
197
+ return !preparationWithdrawn;
198
+ } });
195
199
  const participant = joined.participant;
196
200
  const programState = (participant?.state ?? "").toUpperCase();
197
201
  if (programState === "APPROVAL_PENDING") {
198
202
  return copyParam("program.approvalPendingMessage", "Request sent — the host will approve your place.");
199
203
  }
204
+ if (programState === "ENROLLMENT_PENDING") {
205
+ return copyParam("program.enrollmentPendingMessage", "Your enrollment is processing — check back shortly.");
206
+ }
200
207
  const payable = payableIdOf(participant);
201
- if (!payable) {
202
- // Free / open program (or one already settled) ⇒ nothing to charge.
208
+ if (programState === "JOINED") {
209
+ // Only a confirmed participant is enrolled; absent payment data is not success.
203
210
  return copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
204
211
  }
212
+ if (programState !== "PAYMENT_PENDING" || !payable) throw new Error("Program enrollment is not ready");
205
213
  const msid = await wixPlatformSession.signedInstanceStatus().then((s) => s.msid ?? "").catch(() => "");
206
214
  // Re-read before each step that commits money: a route that settles meanwhile wins.
207
215
  let withdrawn = false;
@@ -221,12 +229,8 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
221
229
  // which is precisely the dead-end the two-rail design exists to prevent
222
230
  // (and which the cart/plans/events flows each guard with a test).
223
231
  //
224
- // There is no `programsCheckout` redirect intent. `ecomOrderPaymentRequest`
225
- // is the only payment-request-shaped one, and a program's `purchaseFlowId`
226
- // is a payment-request handle, so that is what we hand it. If that mapping
227
- // is ever wrong the call throws, we degrade to the same site-directing
228
- // message as before, and nothing regresses — but when it is right, a
229
- // wallet-less site can actually sell the program IN-APP.
232
+ // Join Applications returns a Wix Payments order id; the payment-request
233
+ // redirect uses that same order handle on the hosted rail.
230
234
  cashierUrl: async () => {
231
235
  if (!stillAllowed()) return undefined;
232
236
  const redirect = await createRedirectSession({
@@ -237,10 +241,13 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
237
241
  },
238
242
  });
239
243
  if (withdrawn) return blocked();
240
- if (pay.kind === "paid") return copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
244
+ if (pay.kind === "paid") return pay.pending
245
+ ? copyParam("program.paymentPendingMessage", "Payment is processing — enrollment will be confirmed after payment.")
246
+ : copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
241
247
  if (pay.kind === "handoff") return "";
242
248
  if (pay.kind === "cancelled") return copyParam("program.joinCancelledMessage", "Payment cancelled — you haven't enrolled.");
243
249
  } catch {
250
+ if (preparationWithdrawn) return blocked();
244
251
  // fall through to the site-directing message
245
252
  }
246
253
  return copyTemplate("program.joinOnSiteMessage", "Enrolment happens on the website — visit the site to join {name}.", { name });