@mj-biz-apps/orders-core-entities-server 5.20.0 → 5.22.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.
Files changed (59) hide show
  1. package/dist/AccessOverride.d.ts +90 -0
  2. package/dist/AccessOverride.d.ts.map +1 -0
  3. package/dist/AccessOverride.js +331 -0
  4. package/dist/AccessOverride.js.map +1 -0
  5. package/dist/AccessOverrideOperations.d.ts +19 -0
  6. package/dist/AccessOverrideOperations.d.ts.map +1 -0
  7. package/dist/AccessOverrideOperations.js +46 -0
  8. package/dist/AccessOverrideOperations.js.map +1 -0
  9. package/dist/AccountingBridge.d.ts +56 -0
  10. package/dist/AccountingBridge.d.ts.map +1 -1
  11. package/dist/AccountingBridge.js +73 -1
  12. package/dist/AccountingBridge.js.map +1 -1
  13. package/dist/CheckoutAccountStep.d.ts +147 -0
  14. package/dist/CheckoutAccountStep.d.ts.map +1 -0
  15. package/dist/CheckoutAccountStep.js +368 -0
  16. package/dist/CheckoutAccountStep.js.map +1 -0
  17. package/dist/CheckoutSessionService.d.ts +12 -2
  18. package/dist/CheckoutSessionService.d.ts.map +1 -1
  19. package/dist/CheckoutSessionService.js +89 -2
  20. package/dist/CheckoutSessionService.js.map +1 -1
  21. package/dist/DetectUnattestedProgressOperation.d.ts +52 -0
  22. package/dist/DetectUnattestedProgressOperation.d.ts.map +1 -0
  23. package/dist/DetectUnattestedProgressOperation.js +186 -0
  24. package/dist/DetectUnattestedProgressOperation.js.map +1 -0
  25. package/dist/EntitlementAccessOverrideEntityServer.d.ts +33 -0
  26. package/dist/EntitlementAccessOverrideEntityServer.d.ts.map +1 -0
  27. package/dist/EntitlementAccessOverrideEntityServer.js +118 -0
  28. package/dist/EntitlementAccessOverrideEntityServer.js.map +1 -0
  29. package/dist/EntitlementBehavior.d.ts +71 -0
  30. package/dist/EntitlementBehavior.d.ts.map +1 -1
  31. package/dist/EntitlementBehavior.js +73 -0
  32. package/dist/EntitlementBehavior.js.map +1 -1
  33. package/dist/EntitlementRead.d.ts +1 -0
  34. package/dist/EntitlementRead.d.ts.map +1 -1
  35. package/dist/EntitlementRead.js +36 -23
  36. package/dist/EntitlementRead.js.map +1 -1
  37. package/dist/GetProgressWorklistOperation.d.ts +6 -0
  38. package/dist/GetProgressWorklistOperation.d.ts.map +1 -1
  39. package/dist/GetProgressWorklistOperation.js +19 -2
  40. package/dist/GetProgressWorklistOperation.js.map +1 -1
  41. package/dist/OrderEntityServer.js +2 -2
  42. package/dist/OrderEntityServer.js.map +1 -1
  43. package/dist/PaymentGatedAccess.d.ts +44 -6
  44. package/dist/PaymentGatedAccess.d.ts.map +1 -1
  45. package/dist/PaymentGatedAccess.js +142 -10
  46. package/dist/PaymentGatedAccess.js.map +1 -1
  47. package/dist/ProgressJudgmentCall.d.ts +69 -0
  48. package/dist/ProgressJudgmentCall.d.ts.map +1 -0
  49. package/dist/ProgressJudgmentCall.js +60 -0
  50. package/dist/ProgressJudgmentCall.js.map +1 -0
  51. package/dist/RecordProgressOperation.d.ts +3 -1
  52. package/dist/RecordProgressOperation.d.ts.map +1 -1
  53. package/dist/RecordProgressOperation.js +21 -1
  54. package/dist/RecordProgressOperation.js.map +1 -1
  55. package/dist/index.d.ts +12 -4
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +12 -3
  58. package/dist/index.js.map +1 -1
  59. package/package.json +4 -2
@@ -17,7 +17,7 @@
17
17
  * resolves by key — but that is a different package (`accounting-server`, wired up by the host app's
18
18
  * resolver paths) and is unaffected by how this one is imported.
19
19
  */
20
- import { BaseRemotableOperation, Metadata } from '@memberjunction/core';
20
+ import { BaseRemotableOperation, LogStatus, Metadata } from '@memberjunction/core';
21
21
  import { MJGlobal } from '@memberjunction/global';
22
22
  import { AccountingEngineBase, pickActiveLinkIndex } from '@mj-biz-apps/accounting-engine-base';
23
23
  import { GLAccountResolver } from './GLAccountResolver.js';
@@ -167,4 +167,76 @@ export async function SubmitJournalEntryDrafts(drafts, what, provider, user) {
167
167
  }
168
168
  return payload;
169
169
  }
170
+ /**
171
+ * `TryCreateInstance`, not `CreateInstance`: for an unregistered key `CreateInstance` hands back a
172
+ * hollow `BaseRemotableOperation` rather than null, so a null check alone would never fire and the
173
+ * call would route with no operation key.
174
+ */
175
+ function resolveAccountingOperation(key, what) {
176
+ const resolved = MJGlobal.Instance.ClassFactory.TryCreateInstance(BaseRemotableOperation, key);
177
+ const op = resolved.Resolved ? resolved.Instance : null;
178
+ if (!op) {
179
+ throw new Error(`The '${key}' operation is not registered. The BizApps Accounting server package must be loaded before orders can ${what}.`);
180
+ }
181
+ return op;
182
+ }
183
+ /**
184
+ * The ACTIVE configuration of one finance exception type, or null when accounting has no such type
185
+ * or has switched it off — in which case the caller skips. No defaults are invented here: the
186
+ * thresholds are finance's to set, and a guessed one would raise a list nobody asked for.
187
+ *
188
+ * @throws when the operation is unregistered or the read fails
189
+ */
190
+ export async function GetActiveFinanceExceptionType(code, provider, user) {
191
+ const key = 'Accounting.GetFinanceExceptionTypes';
192
+ const op = resolveAccountingOperation(key, `read the ${code} exception type`);
193
+ const result = await op.Execute({ Codes: [code] }, { provider, user });
194
+ if (!result.Success) {
195
+ throw new Error(`${key} did not execute: ${result.ErrorMessage ?? result.ResultCode ?? 'unknown error'}`);
196
+ }
197
+ const payload = result.Output;
198
+ if (!payload)
199
+ throw new Error(`${key} returned no payload.`);
200
+ if (!payload.Success) {
201
+ throw new Error(`${key} failed. ${(payload.Errors ?? []).map((e) => `${e.Code}: ${e.Message}`).join('; ')}`);
202
+ }
203
+ const type = (payload.Types ?? []).find((t) => t.Code === code);
204
+ if (!type) {
205
+ LogStatus(`Finance exception type ${code} is not defined in accounting; nothing is raised for it.`);
206
+ return null;
207
+ }
208
+ if (!type.IsActive) {
209
+ LogStatus(`Finance exception type ${code} is inactive; nothing is raised for it.`);
210
+ return null;
211
+ }
212
+ return { ...type, Configuration: type.Configuration ?? {} };
213
+ }
214
+ /**
215
+ * Raise finance exceptions into accounting's review list and return its payload.
216
+ *
217
+ * SIDE EFFECT, DELIBERATELY NAMED: this writes review rows. It joins the CALLER'S transaction when
218
+ * there is one, so a raise made at save time commits or rolls back with the save. Idempotent on
219
+ * (type, dedupe key) on accounting's side: a repeat returns `Created: false`.
220
+ *
221
+ * @param what names the act in the error message
222
+ * @throws when the operation is unregistered, the call fails, or accounting refuses the batch
223
+ */
224
+ export async function RaiseFinanceExceptions(exceptions, what, provider, user) {
225
+ const key = 'Accounting.RaiseFinanceExceptions';
226
+ const op = resolveAccountingOperation(key, `raise ${what}`);
227
+ const result = await op.Execute({ Exceptions: exceptions }, { provider, user });
228
+ if (!result.Success) {
229
+ throw new Error(`${key} did not execute for ${what}: ${result.ErrorMessage ?? result.ResultCode ?? 'unknown error'}`);
230
+ }
231
+ const payload = result.Output;
232
+ if (!payload)
233
+ throw new Error(`${key} returned no payload for ${what}.`);
234
+ if (!payload.Success) {
235
+ const detail = (payload.Errors ?? [])
236
+ .map((e) => `${e.Index != null ? `#${e.Index} ` : ''}${e.Code}: ${e.Message}`)
237
+ .join('; ');
238
+ throw new Error(`Raising ${what} failed. ${detail}`);
239
+ }
240
+ return payload;
241
+ }
170
242
  //# sourceMappingURL=AccountingBridge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AccountingBridge.js","sourceRoot":"","sources":["../src/AccountingBridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AccountingBridge.js","sourceRoot":"","sources":["../src/AccountingBridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @fileoverview The account step a self-serve checkout can run after payment (#292).
3
+ *
4
+ * Some hosts need the buyer to leave checkout with a working login on the host's own identity
5
+ * provider. Orders does not know that provider, so it offers a seam: a `CheckoutAccountStep`
6
+ * subclass registered through the ClassFactory. Once `/complete` has confirmed the order, the widget
7
+ * asks for the account step through `/checkout/account`, and the host is asked to ensure an account
8
+ * for the buyer. It answers one of:
9
+ * - `Created` — a new account; the buyer may set its password in the widget, once. The account
10
+ * must not sign in until the host has verified the buyer's e-mail (see the class doc).
11
+ * - `Exists` — an account was already there; it is never touched, and the buyer is told to sign in.
12
+ * - `Failed` — the host could not answer; the buyer is shown the host's message.
13
+ * - `NotApplicable` — this checkout is not one the host makes accounts for (another company's
14
+ * widget, say); the session has no account step, exactly as if no host were registered.
15
+ *
16
+ * With nothing registered the ClassFactory returns this base class itself, which means the step is
17
+ * off: the checkout behaves exactly as it did before the step existed.
18
+ *
19
+ * The outcome is kept in the session's MetadataJSON, so a later call (the buyer reloading the page,
20
+ * or retrying after `Failed`) gets the same answer, and the password
21
+ * call is only honoured for a session whose account this checkout created. The password itself is
22
+ * handed straight to the host and never stored or logged.
23
+ */
24
+ import { UserInfo } from '@memberjunction/core';
25
+ /** How many times a buyer may try a password the host refuses before the step closes. */
26
+ export declare const MAX_CHECKOUT_PASSWORD_ATTEMPTS = 5;
27
+ /** Longest password passed to the host. The host's own policy decides what is acceptable. */
28
+ export declare const MAX_CHECKOUT_PASSWORD_LENGTH = 256;
29
+ /** Default minutes after the account is created during which the buyer may set its password here. */
30
+ export declare const DEFAULT_CHECKOUT_PASSWORD_WINDOW_MINUTES = 5;
31
+ /** Default seconds Orders waits for the host before treating a call as unanswered. */
32
+ export declare const DEFAULT_CHECKOUT_ACCOUNT_HOST_TIMEOUT_SECONDS = 10;
33
+ /** What the buyer can be shown. */
34
+ export type CheckoutAccountOutcome = 'Created' | 'Exists' | 'Failed';
35
+ /** What a host can answer: a buyer-facing outcome, or that the step does not apply to this checkout. */
36
+ export type CheckoutAccountHostOutcome = CheckoutAccountOutcome | 'NotApplicable';
37
+ /** Everything the host is told about the buyer. */
38
+ export interface CheckoutAccountContext {
39
+ /** The buyer's e-mail as the checkout captured it. */
40
+ Email: string;
41
+ FirstName: string | null;
42
+ LastName: string | null;
43
+ /** The payer Person the checkout resolved or created. */
44
+ PersonID: string | null;
45
+ /** The confirmed order. */
46
+ OrderID: string;
47
+ /** The selling company of the checkout widget. */
48
+ CompanyID: string | null;
49
+ SessionID: string;
50
+ /** When the checkout session began: a host can use it to tell an account made by this checkout from an older one. */
51
+ SessionCreatedAt: Date | null;
52
+ ContextUser?: UserInfo;
53
+ }
54
+ /** The host's answer to {@link CheckoutAccountStep.EnsureAccount}. */
55
+ export interface CheckoutAccountResult {
56
+ Outcome: CheckoutAccountHostOutcome;
57
+ /** Shown to the buyer. Worded for the buyer, never containing secrets. */
58
+ Message?: string;
59
+ /**
60
+ * With `Created`: the host has sent, or will send, a verification e-mail, and the widget tells
61
+ * the buyer to use it before signing in.
62
+ */
63
+ VerificationRequired?: boolean;
64
+ }
65
+ /** The host's answer to {@link CheckoutAccountStep.SetPassword}. */
66
+ export interface CheckoutPasswordResult {
67
+ Success: boolean;
68
+ /** Shown to the buyer when the password was refused, e.g. the host's password policy. */
69
+ Message?: string;
70
+ }
71
+ /**
72
+ * The host seam. Register a subclass with `@RegisterClass(CheckoutAccountStep)` and reference its
73
+ * class from the server bootstrap so the decorator is not tree-shaken away. The highest-priority
74
+ * registration wins.
75
+ *
76
+ * `SetPassword` is only called for a session whose `EnsureAccount` answered `Created`, within
77
+ * `PasswordWindowMinutes` of that answer. Orders cannot see the identity provider, and the checkout
78
+ * never proves the buyer owns the e-mail, so these rules are the host's to hold:
79
+ * - Never change an account the checkout did not create for this purchase, in either method.
80
+ * - An account answered `Created` must not sign in until the host has verified the buyer's e-mail,
81
+ * for example with a link sent to it. Anyone can check out with someone else's e-mail and set
82
+ * the password here; verification is what keeps that from becoming a working login.
83
+ * - Until the e-mail is verified, do not link the new login to `PersonID`. That Person was
84
+ * matched by e-mail alone, and may be an existing member with orders and memberships.
85
+ * - When `EnsureAccount` finds an account it created earlier for the same `SessionID`, answer
86
+ * `Created`, not `Exists`. A call Orders gave up on (`HostTimeoutSeconds`) is recorded as
87
+ * `Failed` and asked again, and the host may have finished creating the account meanwhile.
88
+ */
89
+ export declare class CheckoutAccountStep {
90
+ /**
91
+ * How long after `Created` the buyer may set the password in the widget. After it, the buyer
92
+ * signs in or resets the password through the host instead.
93
+ */
94
+ get PasswordWindowMinutes(): number;
95
+ /**
96
+ * How long Orders waits for `EnsureAccount` or `SetPassword`. An `EnsureAccount` that runs over
97
+ * is recorded as `Failed`; a `SetPassword` that runs over closes the password form, since the
98
+ * password may have been set.
99
+ */
100
+ get HostTimeoutSeconds(): number;
101
+ EnsureAccount(_context: CheckoutAccountContext): Promise<CheckoutAccountResult>;
102
+ SetPassword(_context: CheckoutAccountContext & {
103
+ Password: string;
104
+ }): Promise<CheckoutPasswordResult>;
105
+ }
106
+ /** The registered host step, or null when none is registered and the step is off. */
107
+ export declare function ResolveCheckoutAccountStep(): CheckoutAccountStep | null;
108
+ /** True when a host registered an account step, so the widget should ask for it after `/complete`. */
109
+ export declare function HasCheckoutAccountStep(): boolean;
110
+ /** The account step as the widget receives it. */
111
+ export interface CheckoutAccountStatus {
112
+ Outcome: CheckoutAccountOutcome;
113
+ Message?: string;
114
+ /** True while the buyer may still set a password: the outcome was `Created` and none is set yet. */
115
+ CanSetPassword: boolean;
116
+ /** True when the host will e-mail the buyer a link to verify the account before it signs in. */
117
+ VerificationRequired: boolean;
118
+ }
119
+ export interface CheckoutAccountResponse {
120
+ Success: boolean;
121
+ ErrorMessage?: string;
122
+ /** Absent when no host step is registered — the checkout has no account step. */
123
+ Account?: CheckoutAccountStatus;
124
+ }
125
+ /**
126
+ * Run the account step for a confirmed checkout, or return the outcome it already reached.
127
+ *
128
+ * `Created`, `Exists` and `NotApplicable` are final: asking again returns the recorded answer rather
129
+ * than asking the host, which would now find the account this checkout created and call it
130
+ * existing. Only `Failed` is asked again. `NotApplicable` is answered as if no step were registered.
131
+ * A host step that throws, or does not answer within `HostTimeoutSeconds`, is recorded as `Failed`;
132
+ * it never affects the paid order.
133
+ */
134
+ export declare function EnsureCheckoutAccount(sessionID: string, clientSessionKey: string, contextUser?: UserInfo): Promise<CheckoutAccountResponse>;
135
+ /**
136
+ * Set the password of the account this checkout created.
137
+ *
138
+ * Refused unless the session's recorded outcome is `Created`, no password has been set or is being
139
+ * set, the buyer has attempts left, and the step's password window has not closed. A refusal by the
140
+ * host (its password policy) counts as an attempt.
141
+ *
142
+ * The attempt is recorded before the password is passed on, and a refusal is returned if that
143
+ * cannot be saved. So once the host may have set a password, no second one is accepted, even when
144
+ * its answer is lost or cannot be recorded.
145
+ */
146
+ export declare function SetCheckoutAccountPassword(sessionID: string, clientSessionKey: string, password: unknown, contextUser?: UserInfo): Promise<CheckoutAccountResponse>;
147
+ //# sourceMappingURL=CheckoutAccountStep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CheckoutAccountStep.d.ts","sourceRoot":"","sources":["../src/CheckoutAccountStep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,368 @@
1
+ /**
2
+ * @fileoverview The account step a self-serve checkout can run after payment (#292).
3
+ *
4
+ * Some hosts need the buyer to leave checkout with a working login on the host's own identity
5
+ * provider. Orders does not know that provider, so it offers a seam: a `CheckoutAccountStep`
6
+ * subclass registered through the ClassFactory. Once `/complete` has confirmed the order, the widget
7
+ * asks for the account step through `/checkout/account`, and the host is asked to ensure an account
8
+ * for the buyer. It answers one of:
9
+ * - `Created` — a new account; the buyer may set its password in the widget, once. The account
10
+ * must not sign in until the host has verified the buyer's e-mail (see the class doc).
11
+ * - `Exists` — an account was already there; it is never touched, and the buyer is told to sign in.
12
+ * - `Failed` — the host could not answer; the buyer is shown the host's message.
13
+ * - `NotApplicable` — this checkout is not one the host makes accounts for (another company's
14
+ * widget, say); the session has no account step, exactly as if no host were registered.
15
+ *
16
+ * With nothing registered the ClassFactory returns this base class itself, which means the step is
17
+ * off: the checkout behaves exactly as it did before the step existed.
18
+ *
19
+ * The outcome is kept in the session's MetadataJSON, so a later call (the buyer reloading the page,
20
+ * or retrying after `Failed`) gets the same answer, and the password
21
+ * call is only honoured for a session whose account this checkout created. The password itself is
22
+ * handed straight to the host and never stored or logged.
23
+ */
24
+ import { CompositeKey, LogError, Metadata } from '@memberjunction/core';
25
+ import { MJGlobal } from '@memberjunction/global';
26
+ import { RequireUUID } from './sql-guards.js';
27
+ const CHECKOUT_SESSION_ENTITY = 'MJ_BizApps_Orders: Checkout Sessions';
28
+ const CHECKOUT_WIDGET_ENTITY = 'MJ_BizApps_Orders: Checkout Widgets';
29
+ const PERSON_ENTITY = 'MJ_BizApps_Common: People';
30
+ /** How many times a buyer may try a password the host refuses before the step closes. */
31
+ export const MAX_CHECKOUT_PASSWORD_ATTEMPTS = 5;
32
+ /** Longest password passed to the host. The host's own policy decides what is acceptable. */
33
+ export const MAX_CHECKOUT_PASSWORD_LENGTH = 256;
34
+ /** Default minutes after the account is created during which the buyer may set its password here. */
35
+ export const DEFAULT_CHECKOUT_PASSWORD_WINDOW_MINUTES = 5;
36
+ /** Default seconds Orders waits for the host before treating a call as unanswered. */
37
+ export const DEFAULT_CHECKOUT_ACCOUNT_HOST_TIMEOUT_SECONDS = 10;
38
+ /**
39
+ * The host seam. Register a subclass with `@RegisterClass(CheckoutAccountStep)` and reference its
40
+ * class from the server bootstrap so the decorator is not tree-shaken away. The highest-priority
41
+ * registration wins.
42
+ *
43
+ * `SetPassword` is only called for a session whose `EnsureAccount` answered `Created`, within
44
+ * `PasswordWindowMinutes` of that answer. Orders cannot see the identity provider, and the checkout
45
+ * never proves the buyer owns the e-mail, so these rules are the host's to hold:
46
+ * - Never change an account the checkout did not create for this purchase, in either method.
47
+ * - An account answered `Created` must not sign in until the host has verified the buyer's e-mail,
48
+ * for example with a link sent to it. Anyone can check out with someone else's e-mail and set
49
+ * the password here; verification is what keeps that from becoming a working login.
50
+ * - Until the e-mail is verified, do not link the new login to `PersonID`. That Person was
51
+ * matched by e-mail alone, and may be an existing member with orders and memberships.
52
+ * - When `EnsureAccount` finds an account it created earlier for the same `SessionID`, answer
53
+ * `Created`, not `Exists`. A call Orders gave up on (`HostTimeoutSeconds`) is recorded as
54
+ * `Failed` and asked again, and the host may have finished creating the account meanwhile.
55
+ */
56
+ export class CheckoutAccountStep {
57
+ /**
58
+ * How long after `Created` the buyer may set the password in the widget. After it, the buyer
59
+ * signs in or resets the password through the host instead.
60
+ */
61
+ get PasswordWindowMinutes() {
62
+ return DEFAULT_CHECKOUT_PASSWORD_WINDOW_MINUTES;
63
+ }
64
+ /**
65
+ * How long Orders waits for `EnsureAccount` or `SetPassword`. An `EnsureAccount` that runs over
66
+ * is recorded as `Failed`; a `SetPassword` that runs over closes the password form, since the
67
+ * password may have been set.
68
+ */
69
+ get HostTimeoutSeconds() {
70
+ return DEFAULT_CHECKOUT_ACCOUNT_HOST_TIMEOUT_SECONDS;
71
+ }
72
+ async EnsureAccount(_context) {
73
+ return { Outcome: 'Failed' };
74
+ }
75
+ async SetPassword(_context) {
76
+ return { Success: false };
77
+ }
78
+ }
79
+ /** The registered host step, or null when none is registered and the step is off. */
80
+ export function ResolveCheckoutAccountStep() {
81
+ const step = MJGlobal.Instance.ClassFactory.CreateInstance(CheckoutAccountStep);
82
+ if (!step || Object.getPrototypeOf(step) === CheckoutAccountStep.prototype) {
83
+ return null;
84
+ }
85
+ return step;
86
+ }
87
+ /** True when a host registered an account step, so the widget should ask for it after `/complete`. */
88
+ export function HasCheckoutAccountStep() {
89
+ return ResolveCheckoutAccountStep() !== null;
90
+ }
91
+ const GENERIC_FAILURE = 'We could not set up your account just now. Your order is confirmed. Please try again in a moment.';
92
+ /** Calls on the same session run one at a time in this process, so a retry cannot overlap a call still in progress. */
93
+ const sessionQueues = new Map();
94
+ async function oneAtATime(sessionID, work) {
95
+ const key = sessionID.toLowerCase();
96
+ const previous = sessionQueues.get(key) ?? Promise.resolve();
97
+ const current = previous.catch(() => undefined).then(work);
98
+ sessionQueues.set(key, current);
99
+ try {
100
+ return await current;
101
+ }
102
+ finally {
103
+ if (sessionQueues.get(key) === current)
104
+ sessionQueues.delete(key);
105
+ }
106
+ }
107
+ const TIMED_OUT = Symbol('timed out');
108
+ /** The host's answer, or {@link TIMED_OUT} when it does not answer within the step's timeout. */
109
+ async function withHostTimeout(step, call) {
110
+ const seconds = step.HostTimeoutSeconds;
111
+ const ms = (Number.isFinite(seconds) && seconds > 0 ? seconds : DEFAULT_CHECKOUT_ACCOUNT_HOST_TIMEOUT_SECONDS) * 1000;
112
+ let timer;
113
+ try {
114
+ return await Promise.race([
115
+ call(),
116
+ new Promise((resolve) => {
117
+ timer = setTimeout(() => resolve(TIMED_OUT), ms);
118
+ }),
119
+ ]);
120
+ }
121
+ finally {
122
+ clearTimeout(timer);
123
+ }
124
+ }
125
+ function keyMatches(session, presentedKey) {
126
+ const stored = session.ClientSessionKey ?? '';
127
+ const presented = (presentedKey ?? '').trim();
128
+ if (!stored || !presented || stored.length !== presented.length)
129
+ return false;
130
+ let diff = 0;
131
+ for (let i = 0; i < stored.length; i++)
132
+ diff |= stored.charCodeAt(i) ^ presented.charCodeAt(i);
133
+ return diff === 0;
134
+ }
135
+ function readMetadata(session) {
136
+ if (!session.MetadataJSON)
137
+ return {};
138
+ try {
139
+ const parsed = JSON.parse(session.MetadataJSON);
140
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
141
+ }
142
+ catch {
143
+ return {};
144
+ }
145
+ }
146
+ function readAccountState(session) {
147
+ const account = readMetadata(session)['Account'];
148
+ return account && typeof account === 'object' && typeof account.Outcome === 'string' ? account : null;
149
+ }
150
+ async function saveAccountState(session, state) {
151
+ session.MetadataJSON = JSON.stringify({ ...readMetadata(session), Account: state });
152
+ if (!(await session.Save())) {
153
+ LogError(`[CheckoutAccountStep] could not record the account step on session ${session.ID}: ${session.LatestResult?.CompleteMessage ?? 'unknown error'}`);
154
+ return false;
155
+ }
156
+ return true;
157
+ }
158
+ /** True while `now` is inside the step's password window, counted from when the outcome was decided. */
159
+ function withinPasswordWindow(state, step, now = new Date()) {
160
+ const decided = Date.parse(state.DecidedAt);
161
+ const minutes = step.PasswordWindowMinutes;
162
+ if (!Number.isFinite(decided) || !Number.isFinite(minutes) || minutes <= 0)
163
+ return false;
164
+ return now.getTime() <= decided + minutes * 60 * 1000;
165
+ }
166
+ function toStatus(state, step) {
167
+ return {
168
+ Outcome: state.Outcome,
169
+ Message: state.Message,
170
+ CanSetPassword: state.Outcome === 'Created' &&
171
+ !state.PasswordSet &&
172
+ !state.PasswordPendingSince &&
173
+ (state.PasswordAttempts ?? 0) < MAX_CHECKOUT_PASSWORD_ATTEMPTS &&
174
+ withinPasswordWindow(state, step),
175
+ VerificationRequired: state.Outcome === 'Created' && state.VerificationRequired === true,
176
+ };
177
+ }
178
+ /** The recorded state, unless the step does not apply to this session. */
179
+ function buyerFacing(state) {
180
+ return state && state.Outcome !== 'NotApplicable' ? state : null;
181
+ }
182
+ /** The session, when the key matches and the checkout has confirmed its order; otherwise the refusal. */
183
+ async function loadConfirmedSession(sessionID, clientSessionKey, contextUser) {
184
+ try {
185
+ RequireUUID(sessionID, 'sessionId');
186
+ }
187
+ catch {
188
+ return { Refusal: { Success: false, ErrorMessage: 'Checkout session not found' } };
189
+ }
190
+ const md = new Metadata();
191
+ const session = await md.GetEntityObject(CHECKOUT_SESSION_ENTITY, contextUser);
192
+ if (!(await session.Load(sessionID))) {
193
+ return { Refusal: { Success: false, ErrorMessage: 'Checkout session not found' } };
194
+ }
195
+ if (!keyMatches(session, clientSessionKey)) {
196
+ return { Refusal: { Success: false, ErrorMessage: 'Checkout session key does not match' } };
197
+ }
198
+ if (session.Status !== 'Confirmed' || !session.DraftOrderID) {
199
+ return { Refusal: { Success: false, ErrorMessage: 'This checkout has not completed yet.' } };
200
+ }
201
+ return { Session: session };
202
+ }
203
+ async function buildContext(session, contextUser) {
204
+ const md = new Metadata();
205
+ let firstName = null;
206
+ let lastName = null;
207
+ if (session.PersonID) {
208
+ const person = await md.GetEntityObject(PERSON_ENTITY, contextUser);
209
+ if (await person.InnerLoad(CompositeKey.FromID(session.PersonID))) {
210
+ firstName = person.Get('FirstName') ?? null;
211
+ lastName = person.Get('LastName') ?? null;
212
+ }
213
+ }
214
+ let companyID = null;
215
+ const widget = await md.GetEntityObject(CHECKOUT_WIDGET_ENTITY, contextUser);
216
+ if (session.CheckoutWidgetID && (await widget.InnerLoad(CompositeKey.FromID(session.CheckoutWidgetID)))) {
217
+ companyID = widget.Get('CompanyID') ?? null;
218
+ }
219
+ return {
220
+ Email: session.Email ?? '',
221
+ FirstName: firstName,
222
+ LastName: lastName,
223
+ PersonID: session.PersonID ?? null,
224
+ OrderID: session.DraftOrderID,
225
+ CompanyID: companyID,
226
+ SessionID: session.ID,
227
+ SessionCreatedAt: session.__mj_CreatedAt ? new Date(session.__mj_CreatedAt) : null,
228
+ ContextUser: contextUser,
229
+ };
230
+ }
231
+ /**
232
+ * Run the account step for a confirmed checkout, or return the outcome it already reached.
233
+ *
234
+ * `Created`, `Exists` and `NotApplicable` are final: asking again returns the recorded answer rather
235
+ * than asking the host, which would now find the account this checkout created and call it
236
+ * existing. Only `Failed` is asked again. `NotApplicable` is answered as if no step were registered.
237
+ * A host step that throws, or does not answer within `HostTimeoutSeconds`, is recorded as `Failed`;
238
+ * it never affects the paid order.
239
+ */
240
+ export async function EnsureCheckoutAccount(sessionID, clientSessionKey, contextUser) {
241
+ const step = ResolveCheckoutAccountStep();
242
+ if (!step)
243
+ return { Success: true };
244
+ return oneAtATime(sessionID, () => ensureAccount(step, sessionID, clientSessionKey, contextUser));
245
+ }
246
+ async function ensureAccount(step, sessionID, clientSessionKey, contextUser) {
247
+ const loaded = await loadConfirmedSession(sessionID, clientSessionKey, contextUser);
248
+ if ('Refusal' in loaded)
249
+ return loaded.Refusal;
250
+ const session = loaded.Session;
251
+ const existing = readAccountState(session);
252
+ if (existing?.Outcome === 'NotApplicable')
253
+ return { Success: true };
254
+ const shown = buyerFacing(existing);
255
+ if (shown && shown.Outcome !== 'Failed') {
256
+ return { Success: true, Account: toStatus(shown, step) };
257
+ }
258
+ const context = await buildContext(session, contextUser);
259
+ let result;
260
+ if (!context.Email) {
261
+ result = { Outcome: 'Failed', Message: GENERIC_FAILURE };
262
+ }
263
+ else {
264
+ try {
265
+ const answer = await withHostTimeout(step, () => step.EnsureAccount(context));
266
+ if (answer === TIMED_OUT) {
267
+ LogError(`[CheckoutAccountStep] ${step.constructor.name}.EnsureAccount did not answer within ${step.HostTimeoutSeconds}s for session ${session.ID}`);
268
+ result = { Outcome: 'Failed' };
269
+ }
270
+ else {
271
+ result = answer;
272
+ }
273
+ }
274
+ catch (err) {
275
+ LogError(`[CheckoutAccountStep] ${step.constructor.name}.EnsureAccount threw for session ${session.ID}: ${err instanceof Error ? err.message : String(err)}`);
276
+ result = { Outcome: 'Failed' };
277
+ }
278
+ }
279
+ const outcome = ['Created', 'Exists', 'Failed', 'NotApplicable'].includes(result?.Outcome)
280
+ ? result.Outcome
281
+ : 'Failed';
282
+ if (outcome === 'NotApplicable') {
283
+ await saveAccountState(session, { Outcome: outcome, DecidedAt: new Date().toISOString() });
284
+ return { Success: true };
285
+ }
286
+ const state = {
287
+ Outcome: outcome,
288
+ Message: result?.Message ?? (outcome === 'Failed' ? GENERIC_FAILURE : undefined),
289
+ DecidedAt: new Date().toISOString(),
290
+ VerificationRequired: outcome === 'Created' && result?.VerificationRequired === true,
291
+ PasswordSet: false,
292
+ PasswordAttempts: 0,
293
+ };
294
+ await saveAccountState(session, state);
295
+ return { Success: true, Account: toStatus(state, step) };
296
+ }
297
+ /**
298
+ * Set the password of the account this checkout created.
299
+ *
300
+ * Refused unless the session's recorded outcome is `Created`, no password has been set or is being
301
+ * set, the buyer has attempts left, and the step's password window has not closed. A refusal by the
302
+ * host (its password policy) counts as an attempt.
303
+ *
304
+ * The attempt is recorded before the password is passed on, and a refusal is returned if that
305
+ * cannot be saved. So once the host may have set a password, no second one is accepted, even when
306
+ * its answer is lost or cannot be recorded.
307
+ */
308
+ export async function SetCheckoutAccountPassword(sessionID, clientSessionKey, password, contextUser) {
309
+ const step = ResolveCheckoutAccountStep();
310
+ if (!step)
311
+ return { Success: false, ErrorMessage: 'This checkout does not create accounts.' };
312
+ return oneAtATime(sessionID, () => setPassword(step, sessionID, clientSessionKey, password, contextUser));
313
+ }
314
+ const PASSWORD_MAY_BE_SET = 'We could not confirm your password was set. Sign in with the e-mail you used, or reset your password if that does not work.';
315
+ async function setPassword(step, sessionID, clientSessionKey, password, contextUser) {
316
+ const loaded = await loadConfirmedSession(sessionID, clientSessionKey, contextUser);
317
+ if ('Refusal' in loaded)
318
+ return loaded.Refusal;
319
+ const session = loaded.Session;
320
+ const state = buyerFacing(readAccountState(session));
321
+ if (!state || !toStatus(state, step).CanSetPassword) {
322
+ const open = state?.Outcome === 'Created' && !state.PasswordSet;
323
+ const errorMessage = open && state.PasswordPendingSince
324
+ ? PASSWORD_MAY_BE_SET
325
+ : open && !withinPasswordWindow(state, step)
326
+ ? 'The time to set a password here has passed. Reset your password with the e-mail you used to sign in.'
327
+ : 'A password cannot be set here. Sign in, or reset your password, with the e-mail you used.';
328
+ return { Success: false, ErrorMessage: errorMessage, Account: state ? toStatus(state, step) : undefined };
329
+ }
330
+ if (typeof password !== 'string' || password.length === 0 || password.length > MAX_CHECKOUT_PASSWORD_LENGTH) {
331
+ return { Success: false, ErrorMessage: 'Please enter a password.', Account: toStatus(state, step) };
332
+ }
333
+ const pending = {
334
+ ...state,
335
+ PasswordAttempts: (state.PasswordAttempts ?? 0) + 1,
336
+ PasswordPendingSince: new Date().toISOString(),
337
+ };
338
+ if (!(await saveAccountState(session, pending))) {
339
+ return { Success: false, ErrorMessage: 'That password could not be set right now. Please try again.', Account: toStatus(state, step) };
340
+ }
341
+ let result;
342
+ try {
343
+ const context = await buildContext(session, contextUser);
344
+ const answer = await withHostTimeout(step, () => step.SetPassword({ ...context, Password: password }));
345
+ if (answer === TIMED_OUT) {
346
+ // The host may still set it, so the attempt stays pending and the form closes.
347
+ LogError(`[CheckoutAccountStep] ${step.constructor.name}.SetPassword did not answer within ${step.HostTimeoutSeconds}s for session ${session.ID}`);
348
+ return { Success: false, ErrorMessage: PASSWORD_MAY_BE_SET, Account: toStatus(pending, step) };
349
+ }
350
+ result = answer;
351
+ }
352
+ catch (err) {
353
+ LogError(`[CheckoutAccountStep] ${step.constructor.name}.SetPassword threw for session ${session.ID}: ${err instanceof Error ? err.message : String(err)}`);
354
+ result = { Success: false };
355
+ }
356
+ const { PasswordPendingSince: _answered, ...rest } = pending;
357
+ const next = { ...rest, PasswordSet: result?.Success === true };
358
+ // A save that fails here leaves the attempt pending, which keeps the form closed.
359
+ await saveAccountState(session, next);
360
+ return result?.Success === true
361
+ ? { Success: true, Account: toStatus(next, step) }
362
+ : {
363
+ Success: false,
364
+ ErrorMessage: result?.Message ?? 'That password could not be set. Please try another.',
365
+ Account: toStatus(next, step),
366
+ };
367
+ }
368
+ //# sourceMappingURL=CheckoutAccountStep.js.map