@openlup/platform 0.27.0 → 0.28.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 (115) hide show
  1. package/CHANGELOG.md +167 -109
  2. package/README.md +111 -8
  3. package/dist/account-support/accountReadFacade.d.ts +8 -0
  4. package/dist/account-support/accountReadFacade.js +10 -0
  5. package/dist/account-support/accountReadModels.d.ts +69 -0
  6. package/dist/account-support/accountReadModels.js +272 -0
  7. package/dist/account-support/bindings.d.ts +23 -0
  8. package/dist/account-support/bindings.js +16 -0
  9. package/dist/account-support/contracts/account.d.ts +281 -0
  10. package/dist/account-support/contracts/account.js +1 -0
  11. package/dist/account-support/contracts/capabilities.d.ts +44 -0
  12. package/dist/account-support/contracts/capabilities.js +1 -0
  13. package/dist/account-support/contracts/commands.d.ts +307 -0
  14. package/dist/account-support/contracts/commands.js +1 -0
  15. package/dist/account-support/contracts/customer360.d.ts +154 -0
  16. package/dist/account-support/contracts/customer360.js +1 -0
  17. package/dist/account-support/contracts/index.d.ts +10 -0
  18. package/dist/account-support/contracts/index.js +1 -0
  19. package/dist/account-support/contracts/invoice.d.ts +79 -0
  20. package/dist/account-support/contracts/invoice.js +1 -0
  21. package/dist/account-support/contracts/operator.d.ts +289 -0
  22. package/dist/account-support/contracts/operator.js +1 -0
  23. package/dist/account-support/contribution.d.ts +24 -0
  24. package/dist/account-support/contribution.js +22 -0
  25. package/dist/account-support/customer360.d.ts +106 -0
  26. package/dist/account-support/customer360.js +163 -0
  27. package/dist/account-support/customerAccountReadDiagnostics.d.ts +11 -0
  28. package/dist/account-support/customerAccountReadDiagnostics.js +71 -0
  29. package/dist/account-support/index.d.ts +26 -0
  30. package/dist/account-support/index.js +15 -0
  31. package/dist/account-support/invoiceAccess.d.ts +57 -0
  32. package/dist/account-support/invoiceAccess.js +40 -0
  33. package/dist/account-support/migrations.d.ts +14 -0
  34. package/dist/account-support/migrations.js +7 -0
  35. package/dist/account-support/operatorAuthority.d.ts +67 -0
  36. package/dist/account-support/operatorAuthority.js +379 -0
  37. package/dist/account-support/operatorRecovery.d.ts +66 -0
  38. package/dist/account-support/operatorRecovery.js +129 -0
  39. package/dist/account-support/orderHistory.d.ts +50 -0
  40. package/dist/account-support/orderHistory.js +188 -0
  41. package/dist/account-support/paymentMethodComposition.d.ts +5 -0
  42. package/dist/account-support/paymentMethodComposition.js +4 -0
  43. package/dist/account-support/postgresOperatorAuthority.d.ts +16 -0
  44. package/dist/account-support/postgresOperatorAuthority.js +93 -0
  45. package/dist/account-support/selfService.d.ts +6 -0
  46. package/dist/account-support/selfService.js +81 -0
  47. package/dist/account-support/subscriptionCommandCodec.d.ts +10 -0
  48. package/dist/account-support/subscriptionCommandCodec.js +38 -0
  49. package/dist/catalog/draftSchemas.d.ts +1 -1
  50. package/dist/catalog/foundationSchemas.d.ts +3 -3
  51. package/dist/catalog/resolvedSkuProjection.d.ts +2 -2
  52. package/dist/catalog/sellableSchemas.d.ts +2 -2
  53. package/dist/commerce/checkoutCommandSchemas.d.ts +2 -2
  54. package/dist/commerce/checkoutResumeSchemas.d.ts +6 -6
  55. package/dist/commerce/checkoutSchemas.d.ts +12 -12
  56. package/dist/commerce/commerceReturnsPort.d.ts +2 -2
  57. package/dist/commerce/configuratorIntentSchemas.d.ts +2 -2
  58. package/dist/commerce/contractPrimitives.d.ts +8 -8
  59. package/dist/commerce/customerDefaultsSnapshotSchemas.d.ts +4 -4
  60. package/dist/commerce/model/omsReadModelHelpers.d.ts +4 -4
  61. package/dist/commerce/offerAvailabilitySchemas.d.ts +1 -1
  62. package/dist/commerce/oms/omsContractBase.d.ts +15 -15
  63. package/dist/commerce/oms/omsContractReadAuxSchemas.d.ts +11 -11
  64. package/dist/commerce/oms/omsContractReadSchemas.d.ts +121 -121
  65. package/dist/commerce/oms/omsContractRequests.d.ts +13 -13
  66. package/dist/commerce/oms/omsControlPlane.d.ts +22 -22
  67. package/dist/commerce/oms/omsFulfillmentContractSchemas.d.ts +12 -12
  68. package/dist/commerce/oms/omsFulfillmentDebugSchemas.d.ts +12 -12
  69. package/dist/commerce/orderReviewContracts.d.ts +33 -33
  70. package/dist/commerce/outboxEventSchemas.d.ts +4 -4
  71. package/dist/commerce/schemas.d.ts +21 -21
  72. package/dist/commerce/server/orderReviewHandlers.d.ts +10 -10
  73. package/dist/communications/consentPurposeRegistry.d.ts +1 -1
  74. package/dist/communications/newsletterIntegrationContracts.d.ts +8 -8
  75. package/dist/composition/accountSupportContribution.d.ts +18 -0
  76. package/dist/composition/accountSupportContribution.js +21 -0
  77. package/dist/composition/index.d.ts +1 -0
  78. package/dist/composition/index.js +1 -0
  79. package/dist/fulfillment/handlerSchemas.d.ts +40 -40
  80. package/dist/identity/customerContracts.d.ts +2 -2
  81. package/dist/invoicing/snapshot.d.ts +1 -1
  82. package/dist/pricing/acquisitionQuoteBinding.d.ts +1 -1
  83. package/dist/pricing/catalogFactsSchemas.d.ts +1 -1
  84. package/dist/pricing/quotePromotionEvidence.d.ts +1 -1
  85. package/dist/pricing/quoteSchemas.d.ts +56 -56
  86. package/dist/promotions/adminPromotionCodesContracts.d.ts +30 -30
  87. package/dist/promotions/adminPromotionsContracts.d.ts +14 -14
  88. package/package.json +12 -2
  89. package/release-gates.json +30 -3
  90. package/sql/20261026120000_account_support_operator_authority.sql +693 -0
  91. package/src/account-support/accountReadFacade.ts +15 -0
  92. package/src/account-support/accountReadModels.ts +371 -0
  93. package/src/account-support/bindings.ts +28 -0
  94. package/src/account-support/contracts/account.ts +306 -0
  95. package/src/account-support/contracts/capabilities.ts +30 -0
  96. package/src/account-support/contracts/commands.ts +243 -0
  97. package/src/account-support/contracts/customer360.ts +150 -0
  98. package/src/account-support/contracts/index.ts +141 -0
  99. package/src/account-support/contracts/invoice.ts +44 -0
  100. package/src/account-support/contracts/operator.ts +123 -0
  101. package/src/account-support/contribution.ts +35 -0
  102. package/src/account-support/customer360.ts +268 -0
  103. package/src/account-support/customerAccountReadDiagnostics.ts +83 -0
  104. package/src/account-support/index.ts +72 -0
  105. package/src/account-support/invoiceAccess.ts +63 -0
  106. package/src/account-support/migrations.ts +7 -0
  107. package/src/account-support/operatorAuthority.ts +393 -0
  108. package/src/account-support/operatorRecovery.ts +136 -0
  109. package/src/account-support/orderHistory.ts +309 -0
  110. package/src/account-support/paymentMethodComposition.ts +8 -0
  111. package/src/account-support/postgresOperatorAuthority.ts +92 -0
  112. package/src/account-support/selfService.ts +83 -0
  113. package/src/account-support/subscriptionCommandCodec.ts +48 -0
  114. package/src/composition/accountSupportContribution.ts +28 -0
  115. package/src/composition/index.ts +1 -0
@@ -0,0 +1,188 @@
1
+ import { currentParcelPerOrder, rowsForParcel } from "@openlup/core/fulfillment";
2
+ import { deriveOrderMoney, customerSafeEvidenceToken, customerStepForTerminalOrder, customerStepFromTimedSignals, resolveProviderExceptionRecovery } from "../commerce/index.js";
3
+ import { resolveDeliverySelectionEvidence, summarizeDeliverySelection } from "../shipping/index.js";
4
+ export const CUSTOMER_ORDERS_CONTRACT_VERSION = "customer.orders.v2";
5
+ export async function readCustomerOrderSummaries(store, clientId, limit, projection) {
6
+ const rows = await store.readCustomerOrders(clientId, ["cancelled", "expired"], limit);
7
+ const maps = await companions(store, clientId, rows.map(row => text(row.id)), projection);
8
+ return {
9
+ contractVersion: CUSTOMER_ORDERS_CONTRACT_VERSION,
10
+ orders: rows.map(row => summary(row, maps, projection))
11
+ };
12
+ }
13
+ export async function readCustomerOrderDetail(store, clientId, orderId, projection) {
14
+ const row = await store.readCustomerOrder(clientId, orderId);
15
+ if (!row)
16
+ return null;
17
+ const [lines, maps] = await Promise.all([store.readOrderLines(orderId), companions(store, clientId, [orderId], projection)]);
18
+ const money = deriveOrderMoney(toOrderMoneyHeader(row), lines.map(toOrderMoneyItem));
19
+ return {
20
+ contractVersion: CUSTOMER_ORDERS_CONTRACT_VERSION,
21
+ order: {
22
+ ...summary(row, maps, projection, money),
23
+ subtotal: customerOrderMoney(money.subtotal, money.currency),
24
+ discount: customerOrderMoney(money.productDiscount, money.currency),
25
+ shipping: customerOrderMoney(money.shippingEffective, money.currency),
26
+ tax: customerOrderMoney(money.tax, money.currency),
27
+ lines: mapCustomerOrderLines(lines, money.lines, money.currency, projection.presentation),
28
+ fulfillment: maps.fulfillment.get(orderId) ?? null
29
+ }
30
+ };
31
+ }
32
+ async function companions(store, clientId, ids, projection) {
33
+ const [payments, parcels, tracking, operations, evidence, holds, active, invoices] = await Promise.all([
34
+ store.readPaymentStatuses(ids),
35
+ store.readFulfillments(clientId, ids),
36
+ store.readTracking(ids),
37
+ store.readFulfillmentOperations(ids),
38
+ store.readStatusEvidence(ids),
39
+ store.readReleasedProviderExceptionHolds(ids),
40
+ store.readActiveHoldOrderIds(ids),
41
+ store.readInvoiceSummaries(ids),
42
+ ]);
43
+ const released = await store.readReleasedProviderExceptionEvidence(holds);
44
+ for (const [id, rows] of released) {
45
+ const known = new Set((evidence.get(id) ?? []).map(row => text(row.id)));
46
+ evidence.set(id, [...(evidence.get(id) ?? []), ...rows.filter(row => !known.has(text(row.id)))]);
47
+ }
48
+ const recovery = new Map();
49
+ for (const parcel of parcels) {
50
+ const id = text(parcel.order_id);
51
+ if (!id)
52
+ continue;
53
+ recovery.set(id, resolveProviderExceptionRecovery({
54
+ fulfillmentOrderId: nullableText(parcel.id),
55
+ releasedHolds: holds.get(id) ?? [],
56
+ automaticHoldSource: projection.automaticHoldSource,
57
+ automaticReleaseSource: projection.automaticReleaseSource,
58
+ statusEvidence: (evidence.get(id) ?? []).map(row => ({
59
+ id: nullableText(row.id),
60
+ fulfillmentOrderId: nullableText(row.fulfillment_order_id),
61
+ providerStatus: nullableText(row.provider_status),
62
+ localStatus: nullableText(row.local_status),
63
+ token: customerSafeEvidenceToken(nullableText(row.local_status), nullableText(row.provider_status)),
64
+ occurredAt: nullableText(row.occurred_at)
65
+ }))
66
+ }));
67
+ }
68
+ const readTracking = (id, parcelId) => projection.fulfillmentEvidence({
69
+ trackingRefs: rowsForParcel(tracking.get(id) ?? [], parcelId),
70
+ statusEvidenceRows: evidence.get(id) ?? [],
71
+ operationRows: operations.get(id) ?? [],
72
+ recovery: recovery.get(id)
73
+ });
74
+ const fulfillment = new Map();
75
+ for (const [id, parcel] of currentParcelPerOrder(parcels)) {
76
+ const details = readTracking(id, text(parcel.id)), op = operations.get(id)?.[0];
77
+ fulfillment.set(id, {
78
+ fulfillmentOrderId: text(parcel.id) || null,
79
+ status: nullableText(parcel.status),
80
+ providerKind: nullableText(parcel.provider_kind) ?? details.trackingReferences[0]?.providerKind ?? null,
81
+ trackingNumber: details.trackingNumber,
82
+ trackingNumbers: details.trackingNumbers,
83
+ trackingReferences: details.trackingReferences,
84
+ lastEventType: nullableText(op?.operation_type) ?? details.trackingTimeline[0]?.eventType ?? null,
85
+ updatedAt: nullableText(parcel.updated_at) ?? nullableText(op?.occurred_at),
86
+ trackingTimeline: details.trackingTimeline
87
+ });
88
+ }
89
+ return { payments, evidence, recovery, active, invoices, fulfillment, readTracking };
90
+ }
91
+ function summary(row, maps, projection, money = deriveOrderMoney(toOrderMoneyHeader(row))) {
92
+ const id = text(row.id), parcel = maps.fulfillment.get(id), tracking = maps.readTracking(id, parcel?.fulfillmentOrderId ?? null), history = maps.invoices.get(id) ?? null, invoice = history?.invoice ?? null;
93
+ const metadata = record(row.metadata);
94
+ const requested = nullableText(record(metadata.invoiceBuyerSnapshot).taxId) !== null
95
+ || nullableText(record(record(metadata.runtimeFinalize).invoiceBuyerSnapshot).taxId) !== null;
96
+ const customerFulfillmentStep = maps.active.has(id)
97
+ ? "exception"
98
+ : customerStepForTerminalOrder(text(row.status))
99
+ ?? customerStepFromTimedSignals([text(row.status), parcel?.status ?? null], (maps.evidence.get(id) ?? []).map(value => ({
100
+ id: nullableText(value.id),
101
+ token: customerSafeEvidenceToken(nullableText(value.local_status), nullableText(value.provider_status)),
102
+ occurredAt: nullableText(value.occurred_at)
103
+ })), maps.recovery.get(id));
104
+ return {
105
+ orderId: id,
106
+ orderRef: `order_${id}`,
107
+ orderNumber: nullableText(row.order_number) ?? projection.orderReference(id),
108
+ subscriptionId: nullableText(row.subscription_id),
109
+ status: text(row.status),
110
+ paymentStatus: maps.payments.get(id) ?? null,
111
+ fulfillmentStatus: parcel?.status ?? null,
112
+ customerFulfillmentStep,
113
+ total: customerOrderMoney(money.total, money.currency),
114
+ ...tracking,
115
+ deliverySelection: summarizeDeliverySelection(resolveDeliverySelectionEvidence({ orderMetadata: row.metadata }).selection),
116
+ invoice,
117
+ invoiceDocuments: history?.invoiceDocuments ?? [],
118
+ invoiceRequestStatus: invoice?.downloadAvailable ? "available" : history || requested ? "preparing" : "not_requested",
119
+ createdAt: text(row.created_at),
120
+ updatedAt: text(row.updated_at)
121
+ };
122
+ }
123
+ export function mapCustomerOrderLines(rows, moneyLines, currency, presentation) {
124
+ return rows.map((row, index) => {
125
+ const money = moneyLines[index], total = money?.catalogTotal ?? Number(row.total_cents ?? 0), list = lineListTotalMinor(record(row.product_snapshot)), saving = list !== null && list > total;
126
+ return {
127
+ lineId: text(row.id),
128
+ skuId: nullableText(row.sku_id),
129
+ ...presentation.display(row),
130
+ quantity: Number(row.quantity),
131
+ unitPrice: customerOrderMoney(money?.catalogUnit ?? row.unit_price_cents, currency),
132
+ total: customerOrderMoney(total, currency),
133
+ listTotal: saving ? customerOrderMoney(list, currency) : null,
134
+ discount: saving ? customerOrderMoney(list - total, currency) : null
135
+ };
136
+ });
137
+ }
138
+ export function lineListTotalMinor(snapshot) {
139
+ const components = record(snapshot.quoteLine).pricingComponents;
140
+ if (!Array.isArray(components))
141
+ return null;
142
+ let result = 0, found = false;
143
+ for (const value of components) {
144
+ const component = record(value);
145
+ if (component.componentType === "base_unit") {
146
+ result += Number(component.amountMinor ?? 0);
147
+ found = true;
148
+ }
149
+ }
150
+ return found ? result : null;
151
+ }
152
+ export function customerOrderMoney(value, currency) {
153
+ return { amountMinor: Number(value ?? 0), currency };
154
+ }
155
+ export function toOrderMoneyHeader(row) {
156
+ return {
157
+ id: text(row.id),
158
+ currency: text(row.currency),
159
+ subtotal_cents: Number(row.subtotal_cents),
160
+ discount_cents: Number(row.discount_cents),
161
+ shipping_cents: Number(row.shipping_cents),
162
+ shipping_discount_cents: Number(row.shipping_discount_cents ?? 0),
163
+ tax_cents: Number(row.tax_cents),
164
+ total_cents: Number(row.total_cents)
165
+ };
166
+ }
167
+ export function toOrderMoneyItem(row) {
168
+ const number = (value) => typeof value === "number" ? value : null;
169
+ return {
170
+ id: text(row.id),
171
+ quantity: Number(row.quantity),
172
+ unit_price_cents: Number(row.unit_price_cents),
173
+ total_cents: Number(row.total_cents),
174
+ discount_allocated_cents: number(row.discount_allocated_cents),
175
+ effective_total_cents: number(row.effective_total_cents),
176
+ effective_net_cents: number(row.effective_net_cents),
177
+ vat_rate_bps: Number(row.vat_rate_bps)
178
+ };
179
+ }
180
+ function text(value) {
181
+ return typeof value === "string" ? value : "";
182
+ }
183
+ function nullableText(value) {
184
+ return typeof value === "string" && value ? value : null;
185
+ }
186
+ function record(value) {
187
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
188
+ }
@@ -0,0 +1,5 @@
1
+ import type { MethodSetupInput, MethodSetupPort, MethodSetupResult } from "./contracts/index.js";
2
+ /** M07 collection only; ready, replayed and unsupported remain the owner's result. */
3
+ export declare function createPaymentMethodComposition<Handoff>(port: MethodSetupPort<Handoff>): {
4
+ execute(userId: string, request: MethodSetupInput): Promise<MethodSetupResult<Handoff>>;
5
+ };
@@ -0,0 +1,4 @@
1
+ /** M07 collection only; ready, replayed and unsupported remain the owner's result. */
2
+ export function createPaymentMethodComposition(port) {
3
+ return { execute(_userId, request) { return port.setupMethod(request); } };
4
+ }
@@ -0,0 +1,16 @@
1
+ import type { NativeOperatorAuthorityPort } from "./contracts/index.js";
2
+ import { type OperatorAuthorityOptions } from "./operatorAuthority.js";
3
+ /** Narrow structural subset of a query executor; the host owns connection and transaction. */
4
+ export interface OperatorQueryExecutor {
5
+ query(sql: string, values?: unknown[]): Promise<{
6
+ rows: Record<string, unknown>[];
7
+ }>;
8
+ }
9
+ export interface OperatorTransactionExecutor {
10
+ run<T>(work: (executor: OperatorQueryExecutor) => Promise<T>): Promise<T>;
11
+ }
12
+ export declare const OPERATOR_AUTHORITY_UNAVAILABLE: {
13
+ readonly outcome: "refused";
14
+ readonly refusalCode: "operator_subscription_authority_unavailable";
15
+ };
16
+ export declare function createPostgresOperatorAuthority(lane: OperatorTransactionExecutor, options: OperatorAuthorityOptions): NativeOperatorAuthorityPort;
@@ -0,0 +1,93 @@
1
+ import { nativeSubscriptionCommand, leadAbsorption, recoveryResult, operatorRecoveryMaterial, CustomerSupportCommandRequiresHumanError, CustomerSupportOperatorInactiveError, CustomerSupportCommandInvalidError, CustomerSupportCommandIdempotencyConflictError, OperatorSubscriptionAuthorityUnavailableError } from "./operatorAuthority.js";
2
+ export const OPERATOR_AUTHORITY_UNAVAILABLE = {
3
+ outcome: "refused", refusalCode: "operator_subscription_authority_unavailable",
4
+ };
5
+ /** Native SQLSTATE mapping preserves the existing direct adapter's opaque fallback. */
6
+ function nativeCommandFailure(error, fallback) {
7
+ const raw = (error ?? {});
8
+ const code = typeof raw.code === "string" ? raw.code : "";
9
+ const message = typeof raw.message === "string" ? raw.message : "";
10
+ if (message.includes("customer_support_command_requires_human"))
11
+ return new CustomerSupportCommandRequiresHumanError();
12
+ if (code === "42501" || message.includes("communications_operator_inactive"))
13
+ return new CustomerSupportOperatorInactiveError();
14
+ if (code === "22023" || /customer_support_(subscription_command|absorb_lead)_invalid/.test(message))
15
+ return new CustomerSupportCommandInvalidError();
16
+ if (code === "23505" || message.includes("customer_support_idempotency_conflict"))
17
+ return new CustomerSupportCommandIdempotencyConflictError();
18
+ if (code === "42883" || /does not exist/i.test(message))
19
+ return new OperatorSubscriptionAuthorityUnavailableError();
20
+ return new Error(fallback);
21
+ }
22
+ export function createPostgresOperatorAuthority(lane, options) {
23
+ const operatorId = options.operatorId.trim();
24
+ if (!operatorId)
25
+ throw new Error("customer_support_operator_required");
26
+ const check = (input) => {
27
+ if (input.operatorId !== operatorId)
28
+ throw new Error("customer_support_operator_scope_mismatch");
29
+ };
30
+ const call = async (sql, values, fallback) => {
31
+ try {
32
+ return await lane.run(async (executor) => {
33
+ const { rows } = await executor.query(sql, values);
34
+ if (rows.length !== 1 || !("result" in rows[0]))
35
+ throw new Error("customer_support_routine_response_invalid");
36
+ return rows[0].result;
37
+ });
38
+ }
39
+ catch (error) {
40
+ throw nativeCommandFailure(error, fallback);
41
+ }
42
+ };
43
+ return {
44
+ async applySubscriptionAction(input) {
45
+ check(input);
46
+ if (input.subscriptionAction === "change_shipping_address")
47
+ return OPERATOR_AUTHORITY_UNAVAILABLE;
48
+ const result = await call("SELECT public.customer_support_apply_subscription_action_v1($1,$2,$3,$4,$5,$6,$7) AS result", [
49
+ operatorId, input.subscriptionId, input.subscriptionAction, input.payload ?? {}, input.expectedVersion,
50
+ input.idempotencyKey, new Date(options.now()).toISOString(),
51
+ ], "customer_support_subscription_command_failed");
52
+ return nativeSubscriptionCommand(result);
53
+ },
54
+ async correctSubjectEmail(input) { check(input); return OPERATOR_AUTHORITY_UNAVAILABLE; },
55
+ async correctSubjectPhone(input) { check(input); return OPERATOR_AUTHORITY_UNAVAILABLE; },
56
+ async linkSubjectAccount(input) { check(input); return OPERATOR_AUTHORITY_UNAVAILABLE; },
57
+ async absorbLead(input) {
58
+ check(input);
59
+ const result = await call("SELECT public.customer_support_absorb_lead_v1($1,$2,$3,$4,$5,$6) AS result", [
60
+ operatorId, input.customerId, input.leadId, input.expectedLeadEmail, input.idempotencyKey, new Date(options.now()).toISOString(),
61
+ ], "customer_support_lead_absorption_failed");
62
+ return leadAbsorption(result);
63
+ },
64
+ async issueRecovery(input) {
65
+ check(input);
66
+ const material = await operatorRecoveryMaterial(input, options);
67
+ // Preserve recovery's special conflict mapping and opaque non-conflict failure.
68
+ let result;
69
+ try {
70
+ result = await lane.run(async (executor) => {
71
+ const { rows } = await executor.query("SELECT public.customer_support_issue_recovery($1,$2,$3,$4,$5,$6,$7,$8) AS result", [
72
+ operatorId, input.subjectId, input.caseId, input.idempotencyKey, material.payloadFingerprint,
73
+ material.tokenHash, material.expiresAt, material.recoveryPath,
74
+ ]);
75
+ if (rows.length !== 1 || !("result" in rows[0]))
76
+ throw new Error("customer_support_routine_response_invalid");
77
+ return rows[0].result;
78
+ });
79
+ }
80
+ catch (error) {
81
+ const raw = (error ?? {});
82
+ if (typeof raw.message === "string" && raw.message.includes("customer_support_command_requires_human")) {
83
+ throw new CustomerSupportCommandRequiresHumanError();
84
+ }
85
+ if (raw.code === "23505" || (typeof raw.message === "string" && raw.message.includes("idempotency_conflict"))) {
86
+ return { outcome: "conflict", conflictCode: "idempotency_conflict" };
87
+ }
88
+ throw new Error("customer_support_recovery_failed");
89
+ }
90
+ return recoveryResult(result);
91
+ },
92
+ };
93
+ }
@@ -0,0 +1,6 @@
1
+ import type { SelfServiceCommandFacade, SelfServiceCommandFacadeInput } from "./contracts/index.js";
2
+ /** Neutral per-request graph selection. The host supplies all owner implementations. */
3
+ export declare function createSelfServiceCommandFacade<Request extends {
4
+ subscriptionId: string;
5
+ action: string;
6
+ }, PreviewRequest, Preview, Receipt, ProfileRequest, ProfileResult, MethodRequest, MethodResult, CommerceRequest, CommerceResult, RecoveryRequest, RecoveryResult, ControlSnapshot = unknown>(input: SelfServiceCommandFacadeInput<Request, PreviewRequest, Preview, Receipt, ProfileRequest, ProfileResult, MethodRequest, MethodResult, CommerceRequest, CommerceResult, RecoveryRequest, RecoveryResult, ControlSnapshot>): SelfServiceCommandFacade<Request, PreviewRequest, Preview, Receipt, ProfileRequest, ProfileResult, MethodRequest, MethodResult, CommerceRequest, CommerceResult, RecoveryRequest, RecoveryResult, ControlSnapshot>;
@@ -0,0 +1,81 @@
1
+ const HOST_BINDING_REQUIRED = Object.freeze({ supported: false, refusalCode: "host_binding_required" });
2
+ /** Neutral per-request graph selection. The host supplies all owner implementations. */
3
+ export function createSelfServiceCommandFacade(input) {
4
+ const context = { ...input.context }, native = input.nativeLegacySubscription;
5
+ function requireMethods(lane, methods) {
6
+ if (lane !== undefined && (!lane || methods.some(method => typeof lane[method] !== "function"))) {
7
+ throw new Error("customer_self_service_lane_configuration_required");
8
+ }
9
+ }
10
+ requireMethods(input.managedSubscription, ["previewAction", "applyAction"]);
11
+ requireMethods(native, ["ownershipRefusal"]);
12
+ if (native) {
13
+ if (!native.explicitPause || !native.control)
14
+ throw new Error("customer_self_service_lane_configuration_required");
15
+ requireMethods(native.explicitPause, ["applyAction"]);
16
+ requireMethods(native.control, ["applyAction", "previewAction", "hasSubscription", "getSnapshot"]);
17
+ }
18
+ requireMethods(input.nativeRawSubscription, ["preview", "apply"]);
19
+ requireMethods(input.nativeSubscription, ["preview", "apply"]);
20
+ requireMethods(input.profile, ["updateProfile"]);
21
+ requireMethods(input.paymentMethod, ["execute"]);
22
+ requireMethods(input.commerce, ["execute"]);
23
+ requireMethods(input.recovery, ["startPaymentRecovery"]);
24
+ if ((!input.paymentMethod && input.capabilities.paymentMethodSetup.supported)
25
+ || (!input.commerce && (input.capabilities.commerceRecovery.supported || input.capabilities.configuredPurchase.supported))
26
+ || (!(input.managedSubscription || native || input.nativeRawSubscription || input.nativeSubscription)
27
+ && (input.capabilities.subscriptions.genericBundleCommands.supported || input.capabilities.subscriptions.commandBoundReceipts.supported))) {
28
+ throw new Error("customer_self_service_lane_configuration_required");
29
+ }
30
+ if (input.capabilities.graph !== context.graph
31
+ || (input.managedSubscription && context.graph !== "managed")
32
+ || ((native || input.nativeRawSubscription || input.nativeSubscription) && context.graph !== "native")) {
33
+ throw new Error("customer_graph_mismatch");
34
+ }
35
+ function verify(principalId, clientId) {
36
+ if (principalId !== context.principalId || clientId !== context.clientId)
37
+ throw new Error("customer_principal_mismatch");
38
+ }
39
+ const managed = input.managedSubscription, raw = input.nativeRawSubscription, typed = input.nativeSubscription;
40
+ const profile = input.profile, method = input.paymentMethod, commerce = input.commerce, recovery = input.recovery;
41
+ const methodUnavailable = input.capabilities.paymentMethodSetup.supported ? undefined : input.capabilities.paymentMethodSetup;
42
+ const commerceUnavailable = input.capabilities.commerceRecovery.supported ? undefined : input.capabilities.commerceRecovery;
43
+ return {
44
+ capabilities: input.capabilities,
45
+ managedSubscription: {
46
+ async previewAction(userId, request) { verify(userId, context.clientId); return managed ? managed.previewAction(userId, request) : HOST_BINDING_REQUIRED; },
47
+ async applyAction(userId, request) { verify(userId, context.clientId); return managed ? managed.applyAction(userId, request) : HOST_BINDING_REQUIRED; },
48
+ },
49
+ nativeLegacySubscription: {
50
+ async previewAction(userId, request) { verify(userId, context.clientId); return native ? native.control.previewAction(userId, request) : HOST_BINDING_REQUIRED; },
51
+ async applyAction(userId, request) {
52
+ verify(userId, context.clientId);
53
+ if (!native)
54
+ return HOST_BINDING_REQUIRED;
55
+ if (request.action === "pause")
56
+ return native.explicitPause.applyAction(userId, request);
57
+ if (request.action !== "update_bundle")
58
+ return native.control.applyAction(userId, request);
59
+ const canonical = { ...request, subscriptionId: request.subscriptionId.toLowerCase() };
60
+ if (!await native.control.hasSubscription(userId, canonical.subscriptionId))
61
+ throw native.ownershipRefusal();
62
+ return native.control.applyAction(userId, canonical);
63
+ },
64
+ },
65
+ subscriptionControl: {
66
+ async getSnapshot(userId) { verify(userId, context.clientId); return native ? native.control.getSnapshot(userId) : HOST_BINDING_REQUIRED; },
67
+ },
68
+ nativeRawSubscription: {
69
+ async preview(command) { verify(command.actorId, command.clientId); return raw ? raw.preview(command) : HOST_BINDING_REQUIRED; },
70
+ async apply(command) { verify(command.actorId, command.clientId); return raw ? raw.apply(command) : HOST_BINDING_REQUIRED; },
71
+ },
72
+ nativeSubscription: {
73
+ async preview(command) { verify(command.actor.actorId, command.actor.clientId); return typed ? typed.preview(command) : HOST_BINDING_REQUIRED; },
74
+ async apply(command) { verify(command.actor.actorId, command.actor.clientId); return typed ? typed.apply(command) : HOST_BINDING_REQUIRED; },
75
+ },
76
+ profile: { async updateProfile(userId, request) { verify(userId, context.clientId); return profile ? profile.updateProfile(userId, request) : HOST_BINDING_REQUIRED; } },
77
+ paymentMethod: { async execute(userId, request) { verify(userId, context.clientId); return method ? method.execute(userId, request) : methodUnavailable; } },
78
+ commerce: { async execute(userId, request) { verify(userId, context.clientId); return commerce ? commerce.execute(userId, request) : commerceUnavailable; } },
79
+ recovery: { async startPaymentRecovery(userId, request) { verify(userId, context.clientId); return recovery ? recovery.startPaymentRecovery(userId, request) : HOST_BINDING_REQUIRED; } },
80
+ };
81
+ }
@@ -0,0 +1,10 @@
1
+ import type { CustomerContext, LegacySubscriptionRequest, NativePreviewProjection, NativeSubscriptionCommand, NativeSubscriptionPreview, RawSubscriptionCommands, SubscriptionDriver } from "./contracts/index.js";
2
+ /** Encode only verified identity and the host's resolved request; no verb aliases. */
3
+ export declare function encodeNativeSubscriptionCommand<Action extends string, Payload extends Record<string, unknown>>(context: CustomerContext, request: LegacySubscriptionRequest<Action, Payload>, now: string): NativeSubscriptionCommand;
4
+ export declare function decodeNativeSubscriptionPreview<Preview>(result: Record<string, unknown>, project: (record: NativeSubscriptionPreview) => Preview): NativePreviewProjection<Preview>;
5
+ /** Preview and apply are separate transactions. Apply never creates a quote. */
6
+ export declare function createNativeSubscriptionDriver<Action extends string, Payload extends Record<string, unknown>, Preview>(input: {
7
+ owner: RawSubscriptionCommands;
8
+ now(): string;
9
+ previewProjection(record: NativeSubscriptionPreview): Preview;
10
+ }): SubscriptionDriver<LegacySubscriptionRequest<Action, Payload>, NativePreviewProjection<Preview>, Record<string, unknown>>;
@@ -0,0 +1,38 @@
1
+ /** Encode only verified identity and the host's resolved request; no verb aliases. */
2
+ export function encodeNativeSubscriptionCommand(context, request, now) {
3
+ return { actorId: context.principalId, clientId: context.clientId, idempotencyKey: request.idempotencyKey,
4
+ subscriptionId: request.subscriptionId, action: request.action, payload: request.payload, now };
5
+ }
6
+ export function decodeNativeSubscriptionPreview(result, project) {
7
+ if (result.status !== "previewed")
8
+ return { kind: "owner", result };
9
+ if (result.canApply !== true
10
+ || !Number.isSafeInteger(result.templateVersion) || Number(result.templateVersion) < 1
11
+ || typeof result.quoteHash !== "string" || !/^[a-f0-9]{64}$/.test(result.quoteHash)
12
+ || typeof result.quoteExpiresAt !== "string" || !Number.isFinite(Date.parse(result.quoteExpiresAt))
13
+ || !Number.isSafeInteger(result.currentTotalMinor) || Number(result.currentTotalMinor) < 0
14
+ || !Number.isSafeInteger(result.newTotalMinor) || Number(result.newTotalMinor) < 0
15
+ || !Array.isArray(result.lines) || result.lines.length === 0 || !result.lines.every(line => line !== null && typeof line === "object" && typeof line.variantId === "string"
16
+ && Number.isSafeInteger(line.quantity) && line.quantity > 0 && typeof line.isAddon === "boolean"
17
+ && Number.isSafeInteger(line.sortOrder) && line.sortOrder >= 0
18
+ && Number.isSafeInteger(line.unitPriceMinor) && line.unitPriceMinor >= 0 && typeof line.currency === "string")
19
+ || result.priceAgreementPolicy === null || typeof result.priceAgreementPolicy !== "object"
20
+ || !("kind" in result.priceAgreementPolicy) || result.priceAgreementPolicy.kind !== "lock_until_edit"
21
+ || !("proration" in result.priceAgreementPolicy) || result.priceAgreementPolicy.proration !== "none"
22
+ || !("cycleEffect" in result.priceAgreementPolicy) || result.priceAgreementPolicy.cycleEffect !== "future_unlocked_cycles_only") {
23
+ throw new Error("subscription_command_preview_invalid");
24
+ }
25
+ return { kind: "projected", preview: project(result) };
26
+ }
27
+ /** Preview and apply are separate transactions. Apply never creates a quote. */
28
+ export function createNativeSubscriptionDriver(input) {
29
+ return {
30
+ async preview(context, request) {
31
+ const command = encodeNativeSubscriptionCommand(context, request, input.now());
32
+ const payload = { ...command.payload };
33
+ delete payload.acceptedQuoteHash;
34
+ return decodeNativeSubscriptionPreview(await input.owner.preview({ ...command, payload }), input.previewProjection);
35
+ },
36
+ apply(context, request) { return input.owner.apply(encodeNativeSubscriptionCommand(context, request, input.now())); },
37
+ };
38
+ }
@@ -315,8 +315,8 @@ export declare const catalogDraftRefusalSchema: z.ZodObject<{
315
315
  }, z.core.$strict>;
316
316
  export declare const catalogDraftApplyResultSchema: z.ZodUnion<readonly [z.ZodObject<{
317
317
  outcome: z.ZodEnum<{
318
- committed: "committed";
319
318
  replayed: "replayed";
319
+ committed: "committed";
320
320
  }>;
321
321
  record: z.ZodObject<{
322
322
  draftId: z.ZodGUID;
@@ -19,8 +19,8 @@ export declare const catalogSkuIdentifierSchema: z.ZodObject<{
19
19
  issuer: z.ZodString;
20
20
  normalizedValue: z.ZodString;
21
21
  kind: z.ZodEnum<{
22
- gtin: "gtin";
23
22
  other: "other";
23
+ gtin: "gtin";
24
24
  }>;
25
25
  packKind: z.ZodEnum<{
26
26
  unit: "unit";
@@ -55,8 +55,8 @@ export declare const neutralSkuEnvelopeV1Schema: z.ZodObject<{
55
55
  }, z.core.$strict>;
56
56
  sellability: z.ZodObject<{
57
57
  status: z.ZodEnum<{
58
- draft: "draft";
59
58
  active: "active";
59
+ draft: "draft";
60
60
  archived: "archived";
61
61
  }>;
62
62
  oneTime: z.ZodBoolean;
@@ -78,8 +78,8 @@ export declare const neutralSkuEnvelopeV1Schema: z.ZodObject<{
78
78
  issuer: z.ZodString;
79
79
  normalizedValue: z.ZodString;
80
80
  kind: z.ZodEnum<{
81
- gtin: "gtin";
82
81
  other: "other";
82
+ gtin: "gtin";
83
83
  }>;
84
84
  packKind: z.ZodEnum<{
85
85
  unit: "unit";
@@ -25,8 +25,8 @@ export declare const resolvedSkuProjectionSchema: z.ZodObject<{
25
25
  }, z.core.$strict>;
26
26
  sellability: z.ZodObject<{
27
27
  status: z.ZodEnum<{
28
- draft: "draft";
29
28
  active: "active";
29
+ draft: "draft";
30
30
  archived: "archived";
31
31
  }>;
32
32
  oneTime: z.ZodBoolean;
@@ -48,8 +48,8 @@ export declare const resolvedSkuProjectionSchema: z.ZodObject<{
48
48
  issuer: z.ZodString;
49
49
  normalizedValue: z.ZodString;
50
50
  kind: z.ZodEnum<{
51
- gtin: "gtin";
52
51
  other: "other";
52
+ gtin: "gtin";
53
53
  }>;
54
54
  packKind: z.ZodEnum<{
55
55
  unit: "unit";
@@ -9,8 +9,8 @@ export declare const sellableCatalogItemSchema: z.ZodObject<{
9
9
  currency: z.ZodString;
10
10
  }, z.core.$strict>;
11
11
  permittedPurchaseModes: z.ZodArray<z.ZodEnum<{
12
- subscription: "subscription";
13
12
  one_time: "one_time";
13
+ subscription: "subscription";
14
14
  }>>;
15
15
  }, z.core.$strict>;
16
16
  export declare const sellableCatalogProfileSchema: z.ZodObject<{
@@ -39,8 +39,8 @@ export declare const sellableCatalogListResponseSchema: z.ZodObject<{
39
39
  currency: z.ZodString;
40
40
  }, z.core.$strict>;
41
41
  permittedPurchaseModes: z.ZodArray<z.ZodEnum<{
42
- subscription: "subscription";
43
42
  one_time: "one_time";
43
+ subscription: "subscription";
44
44
  }>>;
45
45
  }, z.core.$strict>>;
46
46
  }, z.core.$strict>;
@@ -10,8 +10,8 @@ export declare function createCheckoutCommandSchemas<IntentStatus extends z.ZodT
10
10
  version: z.ZodLiteral<"commerce.checkout_command.v1">;
11
11
  idempotencyKey: z.ZodString;
12
12
  mode: z.ZodEnum<{
13
- subscription: "subscription";
14
13
  one_time: "one_time";
14
+ subscription: "subscription";
15
15
  }>;
16
16
  lines: z.ZodArray<z.ZodObject<{
17
17
  sku: z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
@@ -37,8 +37,8 @@ export declare function createCheckoutCommandSchemas<IntentStatus extends z.ZodT
37
37
  version: z.ZodLiteral<"commerce.checkout_command.v1">;
38
38
  idempotencyKey: z.ZodString;
39
39
  mode: z.ZodEnum<{
40
- subscription: "subscription";
41
40
  one_time: "one_time";
41
+ subscription: "subscription";
42
42
  }>;
43
43
  lines: z.ZodArray<z.ZodObject<{
44
44
  sku: z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
@@ -2,8 +2,8 @@ import { z } from "zod";
2
2
  export declare const CHECKOUT_RESUME_CONTRACT_VERSION = "commerce.checkout_resume.v1";
3
3
  export declare const checkoutResumeTokenSchema: z.ZodString;
4
4
  export declare const checkoutResumeModeSchema: z.ZodEnum<{
5
- subscription: "subscription";
6
5
  one_time: "one_time";
6
+ subscription: "subscription";
7
7
  }>;
8
8
  export declare const checkoutResumeCompletionCoreSchema: z.ZodObject<{
9
9
  productSelection: z.ZodDefault<z.ZodBoolean>;
@@ -25,8 +25,8 @@ export declare function createCheckoutResumeSchemas<Section extends z.ZodType<st
25
25
  checkoutResumeDraftStateSchema: z.ZodObject<{
26
26
  version: z.ZodLiteral<"commerce.checkout_resume.v1">;
27
27
  mode: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
28
- subscription: "subscription";
29
28
  one_time: "one_time";
29
+ subscription: "subscription";
30
30
  }>>>;
31
31
  cadenceDays: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
32
32
  cart: z.ZodDefault<z.ZodObject<{
@@ -47,8 +47,8 @@ export declare function createCheckoutResumeSchemas<Section extends z.ZodType<st
47
47
  draftState: z.ZodObject<{
48
48
  version: z.ZodLiteral<"commerce.checkout_resume.v1">;
49
49
  mode: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
50
- subscription: "subscription";
51
50
  one_time: "one_time";
51
+ subscription: "subscription";
52
52
  }>>>;
53
53
  cadenceDays: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
54
54
  cart: z.ZodDefault<z.ZodObject<{
@@ -74,8 +74,8 @@ export declare function createCheckoutResumeSchemas<Section extends z.ZodType<st
74
74
  draftState: z.ZodObject<{
75
75
  version: z.ZodLiteral<"commerce.checkout_resume.v1">;
76
76
  mode: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
77
- subscription: "subscription";
78
77
  one_time: "one_time";
78
+ subscription: "subscription";
79
79
  }>>>;
80
80
  cadenceDays: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
81
81
  cart: z.ZodDefault<z.ZodObject<{
@@ -104,8 +104,8 @@ export declare function createCheckoutResumeSchemas<Section extends z.ZodType<st
104
104
  draftState: z.ZodObject<{
105
105
  version: z.ZodLiteral<"commerce.checkout_resume.v1">;
106
106
  mode: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
107
- subscription: "subscription";
108
107
  one_time: "one_time";
108
+ subscription: "subscription";
109
109
  }>>>;
110
110
  cadenceDays: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
111
111
  cart: z.ZodDefault<z.ZodObject<{
@@ -134,8 +134,8 @@ export declare function createCheckoutResumeSchemas<Section extends z.ZodType<st
134
134
  draftState: z.ZodObject<{
135
135
  version: z.ZodLiteral<"commerce.checkout_resume.v1">;
136
136
  mode: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
137
- subscription: "subscription";
138
137
  one_time: "one_time";
138
+ subscription: "subscription";
139
139
  }>>>;
140
140
  cadenceDays: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
141
141
  cart: z.ZodDefault<z.ZodObject<{