@openlup/platform 0.27.0 → 0.29.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 (232) hide show
  1. package/CHANGELOG.md +386 -127
  2. package/README.md +251 -9
  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 +2 -0
  78. package/dist/composition/index.js +2 -0
  79. package/dist/composition/marketingContribution.d.ts +21 -0
  80. package/dist/composition/marketingContribution.js +29 -0
  81. package/dist/fulfillment/handlerSchemas.d.ts +40 -40
  82. package/dist/identity/customerContracts.d.ts +2 -2
  83. package/dist/invoicing/snapshot.d.ts +1 -1
  84. package/dist/marketing/acquisitionCaseSchemas.d.ts +103 -0
  85. package/dist/marketing/acquisitionCaseSchemas.js +53 -0
  86. package/dist/marketing/acquisitionEvidenceSchemas.d.ts +105 -0
  87. package/dist/marketing/acquisitionEvidenceSchemas.js +55 -0
  88. package/dist/marketing/analytics/dataLayer.d.ts +18 -0
  89. package/dist/marketing/analytics/dataLayer.js +111 -0
  90. package/dist/marketing/analytics/noop.d.ts +9 -0
  91. package/dist/marketing/analytics/noop.js +8 -0
  92. package/dist/marketing/analytics/purchaseFacts.d.ts +4 -0
  93. package/dist/marketing/analytics/purchaseFacts.js +69 -0
  94. package/dist/marketing/analytics/routePolicy.d.ts +10 -0
  95. package/dist/marketing/analytics/routePolicy.js +59 -0
  96. package/dist/marketing/consentEvidence.d.ts +8 -0
  97. package/dist/marketing/consentEvidence.js +32 -0
  98. package/dist/marketing/contactLegal.d.ts +14 -0
  99. package/dist/marketing/contactLegal.js +19 -0
  100. package/dist/marketing/contracts/acquisition.d.ts +88 -0
  101. package/dist/marketing/contracts/acquisition.js +1 -0
  102. package/dist/marketing/contracts/acquisitionPorts.d.ts +49 -0
  103. package/dist/marketing/contracts/acquisitionPorts.js +1 -0
  104. package/dist/marketing/contracts/analytics.d.ts +144 -0
  105. package/dist/marketing/contracts/analytics.js +1 -0
  106. package/dist/marketing/contracts/contactLegal.d.ts +53 -0
  107. package/dist/marketing/contracts/contactLegal.js +1 -0
  108. package/dist/marketing/contracts/index.d.ts +6 -0
  109. package/dist/marketing/contracts/index.js +1 -0
  110. package/dist/marketing/contracts/prelaunch.d.ts +39 -0
  111. package/dist/marketing/contracts/prelaunch.js +1 -0
  112. package/dist/marketing/index.d.ts +16 -0
  113. package/dist/marketing/index.js +14 -0
  114. package/dist/marketing/prelaunchAcquisitionSchemas.d.ts +116 -0
  115. package/dist/marketing/prelaunchAcquisitionSchemas.js +51 -0
  116. package/dist/marketing/seo/jsonLd.d.ts +157 -0
  117. package/dist/marketing/seo/jsonLd.js +127 -0
  118. package/dist/marketing/seo/metadata.d.ts +41 -0
  119. package/dist/marketing/seo/metadata.js +36 -0
  120. package/dist/marketing/seo/serializeJsonLd.d.ts +1 -0
  121. package/dist/marketing/seo/serializeJsonLd.js +6 -0
  122. package/dist/marketing/seo/urls.d.ts +14 -0
  123. package/dist/marketing/seo/urls.js +23 -0
  124. package/dist/marketing/server/acquisitionCaseDirect.d.ts +4 -0
  125. package/dist/marketing/server/acquisitionCaseDirect.js +88 -0
  126. package/dist/marketing/server/acquisitionCaseHandlers.d.ts +28 -0
  127. package/dist/marketing/server/acquisitionCaseHandlers.js +40 -0
  128. package/dist/marketing/server/acquisitionCasePorts.d.ts +42 -0
  129. package/dist/marketing/server/acquisitionCasePorts.js +1 -0
  130. package/dist/marketing/server/acquisitionEvidenceDirect.d.ts +5 -0
  131. package/dist/marketing/server/acquisitionEvidenceDirect.js +100 -0
  132. package/dist/marketing/server/analyticsContext.d.ts +3 -0
  133. package/dist/marketing/server/analyticsContext.js +65 -0
  134. package/dist/marketing/server/analyticsContextPorts.d.ts +31 -0
  135. package/dist/marketing/server/analyticsContextPorts.js +1 -0
  136. package/dist/marketing/server/bindings.d.ts +14 -0
  137. package/dist/marketing/server/bindings.js +18 -0
  138. package/dist/marketing/server/directPorts.d.ts +26 -0
  139. package/dist/marketing/server/directPorts.js +1 -0
  140. package/dist/marketing/server/index.d.ts +14 -0
  141. package/dist/marketing/server/index.js +11 -0
  142. package/dist/marketing/server/postgres/acquisitionCaseRoutines.d.ts +10 -0
  143. package/dist/marketing/server/postgres/acquisitionCaseRoutines.js +204 -0
  144. package/dist/marketing/server/postgres/acquisitionEvidenceRoutines.d.ts +10 -0
  145. package/dist/marketing/server/postgres/acquisitionEvidenceRoutines.js +107 -0
  146. package/dist/marketing/server/postgres/index.d.ts +3 -0
  147. package/dist/marketing/server/postgres/index.js +3 -0
  148. package/dist/marketing/server/prelaunchAcquisitionDirect.d.ts +4 -0
  149. package/dist/marketing/server/prelaunchAcquisitionDirect.js +122 -0
  150. package/dist/marketing/server/privacyContributor.d.ts +66 -0
  151. package/dist/marketing/server/privacyContributor.js +87 -0
  152. package/dist/marketing/server/research/adminSurveyResponsesHandler.d.ts +9 -0
  153. package/dist/marketing/server/research/adminSurveyResponsesHandler.js +53 -0
  154. package/dist/marketing/server/research/managedSurveyResponseSubmit.d.ts +39 -0
  155. package/dist/marketing/server/research/managedSurveyResponseSubmit.js +102 -0
  156. package/dist/marketing/server/research/surveyResponseSubmitHandler.d.ts +8 -0
  157. package/dist/marketing/server/research/surveyResponseSubmitHandler.js +41 -0
  158. package/dist/marketing/validation.d.ts +2 -0
  159. package/dist/marketing/validation.js +3 -0
  160. package/dist/pricing/acquisitionQuoteBinding.d.ts +1 -1
  161. package/dist/pricing/catalogFactsSchemas.d.ts +1 -1
  162. package/dist/pricing/quotePromotionEvidence.d.ts +1 -1
  163. package/dist/pricing/quoteSchemas.d.ts +56 -56
  164. package/dist/promotions/adminPromotionCodesContracts.d.ts +30 -30
  165. package/dist/promotions/adminPromotionsContracts.d.ts +14 -14
  166. package/package.json +32 -2
  167. package/release-gates.json +99 -4
  168. package/sql/20261026120000_account_support_operator_authority.sql +693 -0
  169. package/src/account-support/accountReadFacade.ts +15 -0
  170. package/src/account-support/accountReadModels.ts +371 -0
  171. package/src/account-support/bindings.ts +28 -0
  172. package/src/account-support/contracts/account.ts +306 -0
  173. package/src/account-support/contracts/capabilities.ts +30 -0
  174. package/src/account-support/contracts/commands.ts +243 -0
  175. package/src/account-support/contracts/customer360.ts +150 -0
  176. package/src/account-support/contracts/index.ts +141 -0
  177. package/src/account-support/contracts/invoice.ts +44 -0
  178. package/src/account-support/contracts/operator.ts +123 -0
  179. package/src/account-support/contribution.ts +35 -0
  180. package/src/account-support/customer360.ts +268 -0
  181. package/src/account-support/customerAccountReadDiagnostics.ts +83 -0
  182. package/src/account-support/index.ts +72 -0
  183. package/src/account-support/invoiceAccess.ts +63 -0
  184. package/src/account-support/migrations.ts +7 -0
  185. package/src/account-support/operatorAuthority.ts +393 -0
  186. package/src/account-support/operatorRecovery.ts +136 -0
  187. package/src/account-support/orderHistory.ts +309 -0
  188. package/src/account-support/paymentMethodComposition.ts +8 -0
  189. package/src/account-support/postgresOperatorAuthority.ts +92 -0
  190. package/src/account-support/selfService.ts +83 -0
  191. package/src/account-support/subscriptionCommandCodec.ts +48 -0
  192. package/src/composition/accountSupportContribution.ts +28 -0
  193. package/src/composition/index.ts +2 -0
  194. package/src/composition/marketingContribution.ts +37 -0
  195. package/src/marketing/acquisitionCaseSchemas.ts +75 -0
  196. package/src/marketing/acquisitionEvidenceSchemas.ts +72 -0
  197. package/src/marketing/analytics/dataLayer.ts +100 -0
  198. package/src/marketing/analytics/noop.ts +14 -0
  199. package/src/marketing/analytics/purchaseFacts.ts +63 -0
  200. package/src/marketing/analytics/routePolicy.ts +55 -0
  201. package/src/marketing/consentEvidence.ts +36 -0
  202. package/src/marketing/contactLegal.ts +22 -0
  203. package/src/marketing/contracts/acquisition.ts +50 -0
  204. package/src/marketing/contracts/acquisitionPorts.ts +48 -0
  205. package/src/marketing/contracts/analytics.ts +88 -0
  206. package/src/marketing/contracts/contactLegal.ts +41 -0
  207. package/src/marketing/contracts/index.ts +17 -0
  208. package/src/marketing/contracts/prelaunch.ts +15 -0
  209. package/src/marketing/index.ts +16 -0
  210. package/src/marketing/prelaunchAcquisitionSchemas.ts +64 -0
  211. package/src/marketing/seo/jsonLd.ts +153 -0
  212. package/src/marketing/seo/metadata.ts +62 -0
  213. package/src/marketing/seo/serializeJsonLd.ts +6 -0
  214. package/src/marketing/seo/urls.ts +34 -0
  215. package/src/marketing/server/acquisitionCaseDirect.ts +67 -0
  216. package/src/marketing/server/acquisitionCaseHandlers.ts +57 -0
  217. package/src/marketing/server/acquisitionCasePorts.ts +42 -0
  218. package/src/marketing/server/acquisitionEvidenceDirect.ts +76 -0
  219. package/src/marketing/server/analyticsContext.ts +55 -0
  220. package/src/marketing/server/analyticsContextPorts.ts +28 -0
  221. package/src/marketing/server/bindings.ts +27 -0
  222. package/src/marketing/server/directPorts.ts +27 -0
  223. package/src/marketing/server/index.ts +14 -0
  224. package/src/marketing/server/postgres/acquisitionCaseRoutines.ts +218 -0
  225. package/src/marketing/server/postgres/acquisitionEvidenceRoutines.ts +103 -0
  226. package/src/marketing/server/postgres/index.ts +3 -0
  227. package/src/marketing/server/prelaunchAcquisitionDirect.ts +121 -0
  228. package/src/marketing/server/privacyContributor.ts +123 -0
  229. package/src/marketing/server/research/adminSurveyResponsesHandler.ts +78 -0
  230. package/src/marketing/server/research/managedSurveyResponseSubmit.ts +158 -0
  231. package/src/marketing/server/research/surveyResponseSubmitHandler.ts +57 -0
  232. package/src/marketing/validation.ts +3 -0
@@ -0,0 +1,272 @@
1
+ export const CUSTOMER_ACCOUNT_V2_CONTRACT_VERSION = "customer.account.v3";
2
+ /** Two observation stages, including the original nested subscription fan-out. Auth/ownership precedes this call. */
3
+ export async function readAccountSnapshot(sources, policy, client) {
4
+ const clientId = text(client.id);
5
+ const [extension, allRows, addresses, ordererProfiles, paymentPreferences, deliveryPreferences, orders, events] = await Promise.all([
6
+ sources.readExtension(clientId),
7
+ sources.readSubscriptionRows(clientId, 50),
8
+ sources.readAddresses(clientId),
9
+ sources.readOrdererProfiles(clientId),
10
+ sources.readPaymentPreferences(clientId),
11
+ sources.readDeliveryPreferences(clientId),
12
+ sources.readOrders(clientId, 20),
13
+ sources.readEvents(clientId),
14
+ ]);
15
+ const rows = allRows.filter(isCustomerVisibleSubscription);
16
+ const ids = rows.map(row => text(row.id));
17
+ const [subscriptions, states] = await Promise.all([
18
+ assemble(sources, policy, rows, clientId, nullableText(client.email)),
19
+ sources.readActionStates(ids),
20
+ ]);
21
+ return {
22
+ contractVersion: CUSTOMER_ACCOUNT_V2_CONTRACT_VERSION,
23
+ profile: profile(client),
24
+ extension,
25
+ subscriptions,
26
+ addresses,
27
+ ordererProfiles,
28
+ billingProfiles: ordererProfiles,
29
+ paymentPreferences,
30
+ deliveryPreferences,
31
+ recentOrders: orders.orders,
32
+ events,
33
+ actionRequired: buildActionRequired(subscriptions, orders.orders, states, policy.canUsePaymentMethodForRenewal)
34
+ };
35
+ }
36
+ async function assemble(sources, policy, rows, clientId, clientEmail) {
37
+ const ids = rows.map(row => text(row.id));
38
+ const cycles = new Map(rows.map(row => [text(row.id), nullableText(row.next_cycle_at)]));
39
+ const [lines, blockers, pauses, evidence, alignments, next] = await Promise.all([
40
+ sources.readSubscriptionLines(ids),
41
+ sources.readSubscriptionBlockers(ids, cycles),
42
+ sources.readPauseWindows(ids),
43
+ sources.readPaymentEvidence(ids),
44
+ sources.readDeliveryAlignments(cycles),
45
+ sources.readNextCharges(rows),
46
+ ]);
47
+ return rows.map(row => {
48
+ const subscriptionId = text(row.id), status = text(row.status);
49
+ const cutoff = editCutoff(nullableText(row.next_cycle_at), typeof row.edit_window_hours === "number" ? row.edit_window_hours : null);
50
+ const paymentMethodStatus = policy.paymentMethodStatus({ clientId, clientEmail, row, evidence: evidence.get(subscriptionId) });
51
+ const edit = computeEditState({
52
+ subscriptionId,
53
+ status,
54
+ nextCycleAt: nullableText(row.next_cycle_at),
55
+ editCutoffAt: cutoff,
56
+ paymentMethodKind: nullableText(row.payment_method_kind),
57
+ paymentMethodRefPresent: Boolean(nullableText(row.payment_method_ref)),
58
+ paymentMethodStatus
59
+ }, blockers, policy.canUsePaymentMethodForRenewal);
60
+ const items = lines.get(subscriptionId) ?? [], pause = pauses.get(subscriptionId);
61
+ return {
62
+ subscriptionId,
63
+ shippingAddressId: nullableText(row.shipping_address_id),
64
+ status,
65
+ pausePreset: pause?.pausePreset ?? null,
66
+ pauseStartedAt: pause?.pauseStartedAt ?? null,
67
+ pauseEndsAt: pause?.pauseEndsAt ?? null,
68
+ cadenceDays: Number(row.cadence_days),
69
+ nextCycleAt: nullableText(row.next_cycle_at),
70
+ ...(alignments.has(subscriptionId) ? { deliveryAlignment: alignments.get(subscriptionId) } : {}),
71
+ editCutoffAt: cutoff,
72
+ canEditUpcomingPackage: edit.canEdit,
73
+ editBlockedReason: edit.reason,
74
+ paymentMethodKind: nullableText(row.payment_method_kind),
75
+ paymentMethodStatus,
76
+ templateVersion: Number(row.template_version),
77
+ sizeConstraint: record(row.size_constraint),
78
+ packageSummary: policy.packageSummary(items, row.size_constraint),
79
+ ...policy.withNextCharge(recurringPrice(items), next.get(subscriptionId)),
80
+ lines: items
81
+ };
82
+ });
83
+ }
84
+ export function isCustomerVisibleSubscription(row) {
85
+ return ["active", "paused", "pending_activation"].includes(text(row.status))
86
+ || (row.status === "cancelled" && row.cancellation_source === "customer_self_service");
87
+ }
88
+ export function recurringPrice(lines) {
89
+ if (!lines.length)
90
+ return null;
91
+ let amount = 0, currency = null;
92
+ for (const line of lines) {
93
+ const money = line.lineSubtotal;
94
+ if (typeof money?.amountMinor !== "number" || typeof money.currency !== "string"
95
+ || (currency !== null && currency !== money.currency))
96
+ return null;
97
+ currency = money.currency;
98
+ amount += money.amountMinor;
99
+ }
100
+ if (currency === null)
101
+ return null;
102
+ return {
103
+ subtotalGross: { amountMinor: amount, currency },
104
+ totalGross: { amountMinor: amount, currency },
105
+ currency,
106
+ source: "frozen_quote_line"
107
+ };
108
+ }
109
+ export function editCutoff(nextCycleAt, editWindowHours) {
110
+ if (!nextCycleAt)
111
+ return null;
112
+ const date = new Date(nextCycleAt);
113
+ date.setHours(date.getHours() - (editWindowHours ?? 72));
114
+ return date.toISOString();
115
+ }
116
+ export function computeEditState(input, blockers, canUse) {
117
+ if (input.status !== "active")
118
+ return { canEdit: false, reason: "not_active" };
119
+ const reason = blockers.get(input.subscriptionId);
120
+ if (reason)
121
+ return { canEdit: false, reason };
122
+ const usable = input.paymentMethodStatus
123
+ ? canUse(input.paymentMethodStatus)
124
+ : Boolean(input.paymentMethodKind && input.paymentMethodRefPresent);
125
+ if (!usable)
126
+ return { canEdit: false, reason: "missing_payment_method" };
127
+ if (input.editCutoffAt && new Date(input.editCutoffAt).getTime() <= Date.now())
128
+ return { canEdit: false, reason: "edit_window_closed" };
129
+ return { canEdit: true, reason: null };
130
+ }
131
+ export function buildActionRequired(subscriptions, orders, states = new Map(), canUse) {
132
+ const actions = [];
133
+ for (const s of subscriptions) {
134
+ const state = states.get(s.subscriptionId);
135
+ const base = {
136
+ entityType: "subscription",
137
+ entityId: s.subscriptionId,
138
+ subscriptionId: s.subscriptionId,
139
+ orderId: state?.orderId ?? null,
140
+ failureCause: state?.failureCause ?? "unknown",
141
+ dueAt: state?.dueAt ?? s.nextCycleAt,
142
+ nextRetryAt: state?.nextRetryAt ?? null,
143
+ recoveryEligible: state?.recoveryEligible ?? false
144
+ };
145
+ if (state?.blockedReason === "subscription_activation_missing_mandate") {
146
+ actions.push({
147
+ ...base,
148
+ actionId: `subscription:${s.subscriptionId}:activation_missing_mandate`,
149
+ kind: "payment_method_missing",
150
+ severity: "critical",
151
+ messageCode: "subscription_activation_missing_mandate",
152
+ blockedReason: "subscription_activation_missing_mandate",
153
+ failureCause: "unknown",
154
+ title: "First order paid, subscription needs a payment method",
155
+ body: "The first order is paid. Add a card to enable future renewals without charging that order again.",
156
+ cta: "repair_payment",
157
+ recoveryEligible: false,
158
+ dueAt: state.dueAt,
159
+ nextRetryAt: null
160
+ });
161
+ continue;
162
+ }
163
+ if (s.status !== "active" && s.status !== "paused")
164
+ continue;
165
+ if (state?.blockedReason === "payment_expired") {
166
+ actions.push({
167
+ ...base,
168
+ actionId: `subscription:${s.subscriptionId}:payment_expired`,
169
+ kind: "payment_recovery",
170
+ severity: "critical",
171
+ messageCode: "payment_expired",
172
+ blockedReason: "payment_expired",
173
+ title: "Subscription paused after payment could not be collected",
174
+ body: "Every retry has been used, so nothing else will be attempted. Update the payment method to resume; the unpaid cycle is skipped and a new one starts from the resume date.",
175
+ cta: "repair_payment",
176
+ dueAt: state.dueAt,
177
+ nextRetryAt: null
178
+ });
179
+ continue;
180
+ }
181
+ if (state) {
182
+ actions.push({
183
+ ...base,
184
+ actionId: `subscription:${s.subscriptionId}:payment_blocked`,
185
+ kind: state.recoveryEligible ? "payment_recovery" : "subscription_blocked",
186
+ severity: "critical",
187
+ messageCode: state.blockedReason ?? "payment_blocked",
188
+ blockedReason: state.blockedReason,
189
+ title: "Payment needs attention",
190
+ body: state.recoveryEligible
191
+ ? "Your subscription is blocked until the payment method is recovered."
192
+ : "Your subscription has a payment issue that needs support review.",
193
+ cta: state.recoveryEligible ? "repair_payment" : "contact_support"
194
+ });
195
+ continue;
196
+ }
197
+ if (!(s.paymentMethodStatus ? canUse(s.paymentMethodStatus) : Boolean(s.paymentMethodKind)))
198
+ actions.push({
199
+ ...base,
200
+ actionId: `subscription:${s.subscriptionId}:payment_method_missing`,
201
+ kind: "payment_method_missing",
202
+ severity: "warning",
203
+ messageCode: "missing_payment_method",
204
+ blockedReason: "missing_payment_method",
205
+ title: "Payment method missing",
206
+ body: "Add or recover a payment method before the next subscription cycle.",
207
+ cta: "contact_support"
208
+ });
209
+ }
210
+ for (const order of orders) {
211
+ const status = order.paymentStatus?.toLowerCase() ?? "", failed = ["failed", "payment_failed", "requires_payment_method"].includes(status), required = ["requires_action", "requires_confirmation"].includes(status);
212
+ if (failed || required)
213
+ actions.push({
214
+ actionId: `order:${order.orderId}:${required ? "requires_action" : "payment_failed"}`,
215
+ kind: "order_attention",
216
+ severity: "critical",
217
+ entityType: "order",
218
+ entityId: order.orderId,
219
+ subscriptionId: null,
220
+ orderId: order.orderId,
221
+ messageCode: required ? "payment_requires_action" : "payment_failed",
222
+ blockedReason: required ? "payment_requires_action" : "payment_failed",
223
+ failureCause: "unknown",
224
+ title: required ? "Payment requires action" : "Payment failed",
225
+ body: "Open the order details to review the payment status and next step.",
226
+ cta: "view_order",
227
+ recoveryEligible: false,
228
+ dueAt: order.updatedAt,
229
+ nextRetryAt: null
230
+ });
231
+ const invoice = order.invoice;
232
+ if (invoice && !invoice.downloadAvailable && ["issued", "accepted"].includes(invoice.status))
233
+ actions.push({
234
+ actionId: `invoice:${invoice.invoiceId}:pdf_unavailable`,
235
+ kind: "invoice_attention",
236
+ severity: "info",
237
+ entityType: "invoice",
238
+ entityId: invoice.invoiceId,
239
+ subscriptionId: null,
240
+ orderId: order.orderId,
241
+ messageCode: "invoice_pdf_unavailable",
242
+ blockedReason: "invoice_pdf_unavailable",
243
+ failureCause: "unknown",
244
+ title: "Invoice PDF unavailable",
245
+ body: "The invoice is visible, but the PDF cannot be downloaded from the account yet.",
246
+ cta: "contact_support",
247
+ recoveryEligible: false,
248
+ dueAt: invoice.issuedAt ?? invoice.createdAt,
249
+ nextRetryAt: null
250
+ });
251
+ }
252
+ return actions.slice(0, 8);
253
+ }
254
+ function profile(row) {
255
+ return {
256
+ clientId: text(row.id),
257
+ email: text(row.email),
258
+ firstName: nullableText(row.first_name),
259
+ lastName: nullableText(row.last_name),
260
+ phone: nullableText(row.phone),
261
+ lifecycleStage: text(row.lifecycle_stage)
262
+ };
263
+ }
264
+ function text(value) {
265
+ return typeof value === "string" ? value : "";
266
+ }
267
+ function nullableText(value) {
268
+ return typeof value === "string" && value ? value : null;
269
+ }
270
+ function record(value) {
271
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
272
+ }
@@ -0,0 +1,23 @@
1
+ import type { AccountSupportCapabilities } from "./contracts/index.js";
2
+ /** Already bound owner methods; the host retains authentication and dispatch. */
3
+ export type AccountSupportCallable = (...args: never[]) => unknown;
4
+ export type AccountSupportOperations = Readonly<Record<string, AccountSupportCallable | null>>;
5
+ export type AccountSupportHostBindingRequired = {
6
+ readonly kind: "unsupported";
7
+ readonly code: "host_binding_required";
8
+ };
9
+ type CommerceArguments<F> = F extends AccountSupportCallable ? Parameters<F> : unknown[];
10
+ /** Capture one graph and one executor per logical operation, without calling either. */
11
+ export declare function createAccountSupportBindings<O extends AccountSupportOperations, C extends AccountSupportOperations = AccountSupportOperations>(input: {
12
+ graph: "managed" | "native";
13
+ capabilities: AccountSupportCapabilities;
14
+ operations: O;
15
+ commerce?: C | null;
16
+ }): Readonly<{
17
+ graph: "managed" | "native";
18
+ capabilities: Readonly<AccountSupportCapabilities>;
19
+ operations: Readonly<O>;
20
+ /** No draft commerce import or fallback executor. An absent record/member is explicit. */
21
+ commerce<K extends keyof C & string>(key: K, ...args: CommerceArguments<C[K]>): Promise<Awaited<ReturnType<Extract<C[K], AccountSupportCallable>>> | AccountSupportHostBindingRequired>;
22
+ }>;
23
+ export {};
@@ -0,0 +1,16 @@
1
+ const HOST_BINDING_REQUIRED = Object.freeze({ kind: "unsupported", code: "host_binding_required" });
2
+ /** Capture one graph and one executor per logical operation, without calling either. */
3
+ export function createAccountSupportBindings(input) {
4
+ if (input.graph !== input.capabilities.graph)
5
+ throw new Error("customer_graph_mismatch");
6
+ const capabilities = Object.freeze({ ...input.capabilities, subscriptions: Object.freeze({ ...input.capabilities.subscriptions }) });
7
+ const operations = Object.freeze({ ...input.operations });
8
+ const commerce = Object.freeze({ ...input.commerce });
9
+ return Object.freeze({ graph: input.graph, capabilities, operations,
10
+ /** No draft commerce import or fallback executor. An absent record/member is explicit. */
11
+ async commerce(key, ...args) {
12
+ const operation = Object.hasOwn(commerce, key) ? commerce[key] : null;
13
+ return operation ? operation(...args) : HOST_BINDING_REQUIRED;
14
+ },
15
+ });
16
+ }
@@ -0,0 +1,281 @@
1
+ /** Host-verified selection; never an authorization claim from a request body. */
2
+ export interface CustomerContext {
3
+ principalId: string;
4
+ clientId: string;
5
+ graph: "managed" | "native";
6
+ }
7
+ export type AccountReadRefusal = "FORBIDDEN" | "NOT_FOUND" | "UPSTREAM_UNAVAILABLE" | "INVALID_RESPONSE";
8
+ /** Owner DTOs stay host supplied, including private profile and market fields.
9
+ * Null and thrown failures retain their existing host mappings. No cursor is promised.
10
+ */
11
+ export interface AccountReadPort<Account, OrderList, OrderDetail> {
12
+ getAccount(userId: string): Promise<Account | null>;
13
+ listOrders(userId: string, input: {
14
+ limit: number;
15
+ }): Promise<OrderList | null>;
16
+ getOrderDetail(userId: string, orderId: string): Promise<OrderDetail | null>;
17
+ }
18
+ import type { AccountSupportMoney, SubscriptionPaymentMethodStatus } from "./commands.js";
19
+ import type { CustomerDocumentDeliveryStatus } from "./invoice.js";
20
+ export interface AccountProfile {
21
+ clientId: string;
22
+ email: string;
23
+ firstName: string | null;
24
+ lastName: string | null;
25
+ phone: string | null;
26
+ lifecycleStage: "lead" | "tester" | "customer" | "inactive";
27
+ }
28
+ export interface AccountAddress {
29
+ addressId: string;
30
+ kind: "shipping" | "billing" | "both";
31
+ label: string | null;
32
+ recipientName: string | null;
33
+ contactPhone: string | null;
34
+ companyName: string | null;
35
+ taxId: string | null;
36
+ line1: string;
37
+ line2: string | null;
38
+ city: string;
39
+ postalCode: string;
40
+ country: string;
41
+ isDefault: boolean;
42
+ deliveryNotes: string | null;
43
+ courierInstructions: string | null;
44
+ lastUsedAt: string | null;
45
+ createdAt: string;
46
+ updatedAt: string;
47
+ }
48
+ export interface AccountBillingProfile {
49
+ profileId: string;
50
+ label: string | null;
51
+ fullName: string;
52
+ email: string;
53
+ phone: string | null;
54
+ companyName: string | null;
55
+ taxId: string | null;
56
+ companyVerificationLevel?: "registry_verified" | "provider_verified" | "manual_unverified" | "invalid" | null;
57
+ companyIdentitySource?: string | null;
58
+ companyIdentityEvidenceHash?: string | null;
59
+ isDefault: boolean;
60
+ createdAt: string;
61
+ updatedAt: string;
62
+ }
63
+ /** Method/provider vocabulary is supplied by the selected host, never a platform provider list. */
64
+ export interface AccountPaymentPreference<Method extends string = string> {
65
+ scope: "one_time" | "subscription" | "any";
66
+ methodKind: Method;
67
+ lastSelectedAt: string;
68
+ }
69
+ export interface AccountPickupPoint {
70
+ id: string;
71
+ name: string;
72
+ address: {
73
+ line1: string;
74
+ postalCode: string;
75
+ city: string;
76
+ country: string;
77
+ } | null;
78
+ }
79
+ export interface AccountDeliverySelection {
80
+ deliveryKind: "courier" | "parcel-locker";
81
+ providerKind: string | null;
82
+ carrierKind: string | null;
83
+ carrierCode: string | null;
84
+ serviceCode: string | null;
85
+ pickupPoint: AccountPickupPoint | null;
86
+ }
87
+ export interface AccountDeliveryPreference<Provider extends string = string> extends AccountDeliverySelection {
88
+ scope: "one_time" | "subscription" | "any";
89
+ providerKind: Provider;
90
+ carrierKind: string;
91
+ carrierCode: string;
92
+ serviceCode: string;
93
+ lastSelectedAt: string;
94
+ }
95
+ export interface AccountEvent {
96
+ eventId: string;
97
+ eventType: string;
98
+ entityType: string;
99
+ entityId: string | null;
100
+ occurredAt: string;
101
+ }
102
+ export interface AccountSubscriptionLine {
103
+ lineId: string;
104
+ variantId: string;
105
+ qty: number;
106
+ sortOrder: number;
107
+ isAddon: boolean;
108
+ title: string | null;
109
+ sku: string | null;
110
+ productSlug?: string | null;
111
+ displayLabel?: string | null;
112
+ accentColor?: string | null;
113
+ unitPrice?: AccountSupportMoney | null;
114
+ lineSubtotal?: AccountSupportMoney | null;
115
+ }
116
+ export interface AccountSubscription {
117
+ subscriptionId: string;
118
+ shippingAddressId: string | null;
119
+ status: "pending_activation" | "active" | "paused" | "cancelled" | "activation_failed" | "completed";
120
+ pausePreset: "2_weeks" | "1_month" | "indefinite" | null;
121
+ pauseStartedAt: string | null;
122
+ pauseEndsAt: string | null;
123
+ cadenceDays: number;
124
+ nextCycleAt: string | null;
125
+ deliveryAlignment?: {
126
+ state: "protected" | "aligned";
127
+ };
128
+ editCutoffAt: string | null;
129
+ canEditUpcomingPackage: boolean;
130
+ editBlockedReason: "not_active" | "edit_window_closed" | "cycle_locked" | "payment_blocked" | "missing_payment_method" | null;
131
+ paymentMethodKind: string | null;
132
+ paymentMethodStatus?: SubscriptionPaymentMethodStatus;
133
+ templateVersion: number;
134
+ sizeConstraint: Record<string, unknown> | null;
135
+ packageSummary: string | null;
136
+ recurringPrice: {
137
+ subtotalGross: AccountSupportMoney;
138
+ totalGross: AccountSupportMoney;
139
+ currency: string;
140
+ source: "frozen_quote_line" | "missing";
141
+ } | null;
142
+ nextCharge?: {
143
+ totalGross: AccountSupportMoney;
144
+ starterStage: "delivery2" | "graduation" | null;
145
+ } | null;
146
+ lines: AccountSubscriptionLine[];
147
+ }
148
+ export interface AccountTrackingReference {
149
+ providerKind: string;
150
+ trackingNumber: string;
151
+ trackingUrl: string | null;
152
+ carrierKind: string | null;
153
+ service: string | null;
154
+ updatedAt: string | null;
155
+ }
156
+ export interface AccountTrackingEvent {
157
+ eventType: string;
158
+ label: string;
159
+ occurredAt: string | null;
160
+ source: "webhook" | "reconciliation" | "manual" | "simulator" | "fulfillment";
161
+ }
162
+ export interface AccountInvoiceSummary {
163
+ invoiceId: string;
164
+ orderId: string;
165
+ invoiceRef: string;
166
+ status: string;
167
+ providerInvoiceNumber: string | null;
168
+ ksefNumber: string | null;
169
+ ksefStatus: string;
170
+ documentDeliveryStatus: CustomerDocumentDeliveryStatus;
171
+ totalGross: AccountSupportMoney;
172
+ issuedAt: string | null;
173
+ downloadAvailable: boolean;
174
+ downloadUrl: string | null;
175
+ createdAt: string;
176
+ }
177
+ export interface AccountInvoiceDocument extends Omit<AccountInvoiceSummary, "totalGross"> {
178
+ documentKey: string;
179
+ role: "original" | "correction" | "replacement";
180
+ artifact: "invoice" | "correction";
181
+ isCurrent: boolean;
182
+ totalGross: AccountSupportMoney | null;
183
+ }
184
+ export interface AccountOrderSummary {
185
+ orderId: string;
186
+ orderRef: string;
187
+ orderNumber: string | null;
188
+ subscriptionId: string | null;
189
+ status: string;
190
+ paymentStatus: string | null;
191
+ fulfillmentStatus: string | null;
192
+ customerFulfillmentStep?: "paid" | "accepted" | "packing" | "transit" | "delivered" | "exception" | "cancelled";
193
+ total: AccountSupportMoney;
194
+ trackingNumber: string | null;
195
+ trackingNumbers: string[];
196
+ trackingUrl: string | null;
197
+ carrierKind: string | null;
198
+ service: string | null;
199
+ trackingReferences: AccountTrackingReference[];
200
+ trackingTimeline: AccountTrackingEvent[];
201
+ deliverySelection: AccountDeliverySelection | null;
202
+ invoice: AccountInvoiceSummary | null;
203
+ invoiceDocuments: AccountInvoiceDocument[];
204
+ invoiceRequestStatus: "not_requested" | "preparing" | "available";
205
+ createdAt: string;
206
+ updatedAt: string;
207
+ }
208
+ export interface AccountOrderLine {
209
+ lineId: string;
210
+ skuId: string | null;
211
+ title: string;
212
+ quantity: number;
213
+ unitPrice: AccountSupportMoney;
214
+ total: AccountSupportMoney;
215
+ variantName: string | null;
216
+ productSlug?: string | null;
217
+ displayLabel?: string | null;
218
+ accentColor?: string | null;
219
+ listTotal?: AccountSupportMoney | null;
220
+ discount?: AccountSupportMoney | null;
221
+ }
222
+ export interface AccountFulfillmentSummary {
223
+ fulfillmentOrderId: string | null;
224
+ status: string | null;
225
+ providerKind: string | null;
226
+ trackingNumber: string | null;
227
+ trackingNumbers: string[];
228
+ trackingReferences: AccountTrackingReference[];
229
+ lastEventType: string | null;
230
+ updatedAt: string | null;
231
+ trackingTimeline: AccountTrackingEvent[];
232
+ }
233
+ export interface AccountOrderDetail extends AccountOrderSummary {
234
+ subtotal: AccountSupportMoney;
235
+ discount: AccountSupportMoney;
236
+ shipping: AccountSupportMoney;
237
+ tax: AccountSupportMoney;
238
+ lines: AccountOrderLine[];
239
+ fulfillment: AccountFulfillmentSummary | null;
240
+ }
241
+ export interface AccountOrderList {
242
+ contractVersion: "customer.orders.v2";
243
+ orders: AccountOrderSummary[];
244
+ }
245
+ export interface AccountOrderDetailResponse {
246
+ contractVersion: "customer.orders.v2";
247
+ order: AccountOrderDetail;
248
+ }
249
+ export interface AccountActionRequired {
250
+ actionId: string;
251
+ kind: "payment_recovery" | "payment_method_missing" | "subscription_blocked" | "order_attention" | "invoice_attention";
252
+ severity: "info" | "warning" | "critical";
253
+ entityType: "subscription" | "order" | "invoice" | "account";
254
+ entityId: string | null;
255
+ subscriptionId: string | null;
256
+ orderId: string | null;
257
+ messageCode: AccountActionMessage;
258
+ blockedReason: AccountActionMessage | null;
259
+ title: string;
260
+ body: string | null;
261
+ cta: "repair_payment" | "view_subscription" | "view_order" | "contact_support" | null;
262
+ failureCause: "needs_confirmation" | "method_cannot_recur" | "method_dead" | "method_data_invalid" | "method_missing" | "unknown";
263
+ recoveryEligible: boolean;
264
+ dueAt: string | null;
265
+ nextRetryAt: string | null;
266
+ }
267
+ export type AccountActionMessage = "payment_blocked" | "payment_expired" | "missing_payment_method" | "payment_failed" | "payment_requires_action" | "subscription_edit_blocked" | "invoice_pdf_unavailable" | "subscription_activation_missing_mandate";
268
+ export interface AccountSnapshot<Extension = unknown> {
269
+ contractVersion: "customer.account.v3";
270
+ profile: AccountProfile;
271
+ extension: Extension;
272
+ subscriptions: AccountSubscription[];
273
+ addresses: AccountAddress[];
274
+ ordererProfiles: AccountBillingProfile[];
275
+ billingProfiles: AccountBillingProfile[];
276
+ paymentPreferences: AccountPaymentPreference[];
277
+ deliveryPreferences: AccountDeliveryPreference[];
278
+ recentOrders: AccountOrderSummary[];
279
+ events: AccountEvent[];
280
+ actionRequired: AccountActionRequired[];
281
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import type { SubscriptionCapabilities } from "./commands.js";
2
+ export type AccountSupportCapability<Code extends string> = {
3
+ supported: true;
4
+ } | {
5
+ supported: false;
6
+ refusalCode: Code;
7
+ };
8
+ export type AccountSupportLaneUnavailable<Code extends string> = Extract<AccountSupportCapability<Code>, {
9
+ supported: false;
10
+ }>;
11
+ /** Explicit driver selections; importing the module installs no capability. */
12
+ export interface AccountSupportCapabilities {
13
+ graph: "managed" | "native";
14
+ accountRead: AccountSupportCapability<"host_binding_required">;
15
+ customer360Read: AccountSupportCapability<"host_binding_required">;
16
+ invoiceAccess: AccountSupportCapability<"host_binding_required">;
17
+ paymentMethodSetup: AccountSupportCapability<"unsupported">;
18
+ commerceRecovery: AccountSupportCapability<"host_binding_required">;
19
+ configuredPurchase: AccountSupportCapability<"host_binding_required">;
20
+ operatorSubscription: AccountSupportCapability<"operator_subscription_authority_unavailable">;
21
+ operatorContact: AccountSupportCapability<"operator_subscription_authority_unavailable">;
22
+ subscriptions: SubscriptionCapabilities;
23
+ communicationGifts: AccountSupportCapability<"unsupported_preference">;
24
+ }
25
+ /** Per-action/subcase truth, independent of the host's unrelated read selections. */
26
+ export interface OperatorAuthorityCapabilities {
27
+ graph: "managed" | "native";
28
+ email: AccountSupportCapability<"operator_subscription_authority_unavailable">;
29
+ phone: AccountSupportCapability<"operator_subscription_authority_unavailable">;
30
+ link: AccountSupportCapability<"operator_subscription_authority_unavailable">;
31
+ address: AccountSupportCapability<"operator_subscription_authority_unavailable">;
32
+ shortBand: AccountSupportCapability<"operator_subscription_authority_unavailable">;
33
+ unpaidResume: AccountSupportCapability<"operator_subscription_authority_unavailable">;
34
+ preparedAttemptPause: AccountSupportCapability<"operator_subscription_authority_unavailable">;
35
+ pause: {
36
+ supported: true;
37
+ };
38
+ resume: {
39
+ supported: true;
40
+ };
41
+ slide: {
42
+ supported: true;
43
+ };
44
+ }
@@ -0,0 +1 @@
1
+ export {};