@openlup/platform 0.22.0 → 0.24.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 (241) hide show
  1. package/CHANGELOG.md +440 -158
  2. package/README.md +141 -589
  3. package/dist/fulfillment/adapterConformance.js +1 -1
  4. package/dist/fulfillment/bindings.js +1 -1
  5. package/dist/fulfillment/contracts/conformance.d.ts +3 -1
  6. package/dist/fulfillment/contracts/dispatch.d.ts +45 -3
  7. package/dist/fulfillment/contracts/index.d.ts +3 -3
  8. package/dist/fulfillment/contracts/observation.d.ts +21 -1
  9. package/dist/fulfillment/contracts/readback.d.ts +21 -0
  10. package/dist/fulfillment/contracts/store.d.ts +3 -0
  11. package/dist/fulfillment/contracts/sync.d.ts +58 -2
  12. package/dist/fulfillment/dispatchProjection.d.ts +20 -1
  13. package/dist/fulfillment/dispatchProjection.js +97 -6
  14. package/dist/fulfillment/index.d.ts +3 -2
  15. package/dist/fulfillment/index.js +2 -2
  16. package/dist/fulfillment/migrations.d.ts +5 -0
  17. package/dist/fulfillment/migrations.js +1 -0
  18. package/dist/fulfillment/observationProjection.d.ts +24 -3
  19. package/dist/fulfillment/observationProjection.js +65 -12
  20. package/dist/fulfillment/omnipack/client.d.ts +3 -1
  21. package/dist/fulfillment/omnipack/client.js +31 -13
  22. package/dist/fulfillment/omnipack/index.d.ts +4 -1
  23. package/dist/fulfillment/omnipack/index.js +107 -45
  24. package/dist/fulfillment/omnipack/mappers.d.ts +7 -1
  25. package/dist/fulfillment/omnipack/mappers.js +24 -2
  26. package/dist/fulfillment/omnipack/observation.d.ts +11 -4
  27. package/dist/fulfillment/omnipack/observation.js +44 -29
  28. package/dist/fulfillment/omnipack/reconciliation.d.ts +15 -0
  29. package/dist/fulfillment/omnipack/reconciliation.js +55 -0
  30. package/dist/fulfillment/postgresDispatchStore.js +1 -1
  31. package/dist/fulfillment/postgresShipmentSpine.d.ts +8 -1
  32. package/dist/fulfillment/postgresShipmentSpine.js +17 -3
  33. package/dist/fulfillment/postgresStockSyncPort.js +6 -3
  34. package/dist/fulfillment/providerDispatchAcceptance.d.ts +1 -0
  35. package/dist/fulfillment/providerDispatchAcceptance.js +8 -2
  36. package/dist/fulfillment/providerDispatchWorker.d.ts +51 -0
  37. package/dist/fulfillment/providerDispatchWorker.js +65 -20
  38. package/dist/fulfillment/providerReconciliationWorker.d.ts +27 -19
  39. package/dist/fulfillment/providerReconciliationWorker.js +75 -19
  40. package/dist/fulfillment/providerStockSyncWorker.d.ts +5 -1
  41. package/dist/fulfillment/providerStockSyncWorker.js +229 -120
  42. package/dist/fulfillment/providerTrackingReferences.d.ts +3 -2
  43. package/dist/fulfillment/verifiedIngress.d.ts +38 -11
  44. package/dist/fulfillment/verifiedIngress.js +36 -8
  45. package/dist/invoicing/accountingCanonicalProviderLines.d.ts +5 -0
  46. package/dist/invoicing/accountingCanonicalProviderLines.js +104 -0
  47. package/dist/invoicing/accountingLineAllocation.d.ts +2 -0
  48. package/dist/invoicing/accountingLineAllocation.js +69 -0
  49. package/dist/invoicing/adapterConformance.d.ts +42 -0
  50. package/dist/invoicing/adapterConformance.js +296 -0
  51. package/dist/invoicing/artifacts.d.ts +7 -0
  52. package/dist/invoicing/artifacts.js +20 -0
  53. package/dist/invoicing/bindings.d.ts +46 -0
  54. package/dist/invoicing/bindings.js +69 -0
  55. package/dist/invoicing/capabilities.d.ts +11 -0
  56. package/dist/invoicing/capabilities.js +10 -0
  57. package/dist/invoicing/channelPolicy.d.ts +28 -0
  58. package/dist/invoicing/channelPolicy.js +27 -0
  59. package/dist/invoicing/continuity.d.ts +12 -0
  60. package/dist/invoicing/continuity.js +18 -0
  61. package/dist/invoicing/contracts/capabilities.d.ts +40 -0
  62. package/dist/invoicing/contracts/capabilities.js +1 -0
  63. package/dist/invoicing/contracts/companyLookup.d.ts +49 -0
  64. package/dist/invoicing/contracts/companyLookup.js +1 -0
  65. package/dist/invoicing/contracts/delivery.d.ts +74 -0
  66. package/dist/invoicing/contracts/delivery.js +1 -0
  67. package/dist/invoicing/contracts/facts.d.ts +110 -0
  68. package/dist/invoicing/contracts/facts.js +1 -0
  69. package/dist/invoicing/contracts/host.d.ts +245 -0
  70. package/dist/invoicing/contracts/host.js +1 -0
  71. package/dist/invoicing/contracts/index.d.ts +16 -0
  72. package/dist/invoicing/contracts/index.js +1 -0
  73. package/dist/invoicing/contracts/provider.d.ts +126 -0
  74. package/dist/invoicing/contracts/provider.js +1 -0
  75. package/dist/invoicing/contracts/settlement.d.ts +66 -0
  76. package/dist/invoicing/contracts/settlement.js +1 -0
  77. package/dist/invoicing/contracts/store.d.ts +277 -0
  78. package/dist/invoicing/contracts/store.js +1 -0
  79. package/dist/invoicing/contribution.d.ts +21 -0
  80. package/dist/invoicing/contribution.js +19 -0
  81. package/dist/invoicing/correction.d.ts +42 -0
  82. package/dist/invoicing/correction.js +44 -0
  83. package/dist/invoicing/delivery.d.ts +85 -0
  84. package/dist/invoicing/delivery.js +79 -0
  85. package/dist/invoicing/documentHistory.d.ts +50 -0
  86. package/dist/invoicing/documentHistory.js +213 -0
  87. package/dist/invoicing/documentSync.d.ts +24 -0
  88. package/dist/invoicing/documentSync.js +26 -0
  89. package/dist/invoicing/dutyCollector.d.ts +15 -0
  90. package/dist/invoicing/dutyCollector.js +35 -0
  91. package/dist/invoicing/fulfillmentTrigger.d.ts +56 -0
  92. package/dist/invoicing/fulfillmentTrigger.js +90 -0
  93. package/dist/invoicing/handoff.d.ts +37 -0
  94. package/dist/invoicing/handoff.js +62 -0
  95. package/dist/invoicing/index.d.ts +39 -0
  96. package/dist/invoicing/index.js +37 -0
  97. package/dist/invoicing/issue.d.ts +39 -0
  98. package/dist/invoicing/issue.js +55 -0
  99. package/dist/invoicing/migrations.d.ts +48 -0
  100. package/dist/invoicing/migrations.js +25 -0
  101. package/dist/invoicing/nativeUnsupported.d.ts +9 -0
  102. package/dist/invoicing/nativeUnsupported.js +22 -0
  103. package/dist/invoicing/paymentReadback.d.ts +20 -0
  104. package/dist/invoicing/paymentReadback.js +47 -0
  105. package/dist/invoicing/postgresInvoiceStore.d.ts +55 -0
  106. package/dist/invoicing/postgresInvoiceStore.js +344 -0
  107. package/dist/invoicing/readModel.d.ts +74 -0
  108. package/dist/invoicing/readModel.js +90 -0
  109. package/dist/invoicing/readback.d.ts +4 -0
  110. package/dist/invoicing/readback.js +30 -0
  111. package/dist/invoicing/reversal.d.ts +37 -0
  112. package/dist/invoicing/reversal.js +28 -0
  113. package/dist/invoicing/sellerRevision.d.ts +19 -0
  114. package/dist/invoicing/sellerRevision.js +14 -0
  115. package/dist/invoicing/settlementSync.d.ts +18 -0
  116. package/dist/invoicing/settlementSync.js +32 -0
  117. package/dist/invoicing/snapshot.d.ts +52 -0
  118. package/dist/invoicing/snapshot.js +141 -0
  119. package/dist/subscriptions/automaticRenewal.d.ts +44 -0
  120. package/dist/subscriptions/automaticRenewal.js +81 -0
  121. package/dist/subscriptions/automaticRenewalExecution.d.ts +21 -0
  122. package/dist/subscriptions/automaticRenewalExecution.js +44 -0
  123. package/dist/subscriptions/contracts/commands.d.ts +115 -0
  124. package/dist/subscriptions/contracts/commands.js +1 -0
  125. package/dist/subscriptions/contracts/dunning.d.ts +133 -0
  126. package/dist/subscriptions/contracts/dunning.js +1 -0
  127. package/dist/subscriptions/contracts/index.d.ts +6 -0
  128. package/dist/subscriptions/contracts/index.js +1 -0
  129. package/dist/subscriptions/contracts/messages.d.ts +43 -0
  130. package/dist/subscriptions/contracts/messages.js +1 -0
  131. package/dist/subscriptions/contracts/renewal.d.ts +533 -0
  132. package/dist/subscriptions/contracts/renewal.js +1 -0
  133. package/dist/subscriptions/contracts/stockWait.d.ts +73 -0
  134. package/dist/subscriptions/contracts/stockWait.js +1 -0
  135. package/dist/subscriptions/index.d.ts +39 -0
  136. package/dist/subscriptions/index.js +17 -0
  137. package/dist/subscriptions/paymentOutcomes.d.ts +8 -0
  138. package/dist/subscriptions/paymentOutcomes.js +27 -0
  139. package/dist/subscriptions/postgresRenewalStore.d.ts +20 -0
  140. package/dist/subscriptions/postgresRenewalStore.js +61 -0
  141. package/dist/subscriptions/postgresRetryStore.d.ts +78 -0
  142. package/dist/subscriptions/postgresRetryStore.js +142 -0
  143. package/dist/subscriptions/renewalCycleFailureMechanism.d.ts +34 -0
  144. package/dist/subscriptions/renewalCycleFailureMechanism.js +79 -0
  145. package/dist/subscriptions/renewalCycleMechanism.d.ts +46 -0
  146. package/dist/subscriptions/renewalCycleMechanism.js +157 -0
  147. package/dist/subscriptions/renewalCycleRefusalMechanism.d.ts +41 -0
  148. package/dist/subscriptions/renewalCycleRefusalMechanism.js +160 -0
  149. package/dist/subscriptions/renewalIdentity.d.ts +3 -0
  150. package/dist/subscriptions/renewalIdentity.js +4 -0
  151. package/package.json +23 -3
  152. package/release-gates.json +74 -7
  153. package/sql/20260809210000_accounting_document_rail.sql +889 -0
  154. package/sql/20260811150000_accounting_payout_rail.sql +333 -0
  155. package/sql/20260812130000_accounting_document_lifecycle_rail.sql +460 -0
  156. package/sql/20261022160000_subscription_retry_lifecycle.sql +328 -0
  157. package/sql/20261022165000_fulfillment_stock_evidence_replay_reopen.sql +30 -0
  158. package/sql/20261022190000_invoicing_durable_duties.sql +494 -0
  159. package/sql/20261022191000_invoicing_document_effects.sql +249 -0
  160. package/sql/20261022192000_invoicing_payment_decoupling.sql +144 -0
  161. package/sql/20261022195000_invoicing_seller_revision.sql +65 -0
  162. package/src/fulfillment/adapterConformance.ts +1 -1
  163. package/src/fulfillment/bindings.ts +1 -1
  164. package/src/fulfillment/contracts/conformance.ts +3 -1
  165. package/src/fulfillment/contracts/dispatch.ts +24 -5
  166. package/src/fulfillment/contracts/index.ts +3 -3
  167. package/src/fulfillment/contracts/observation.ts +11 -3
  168. package/src/fulfillment/contracts/readback.ts +13 -0
  169. package/src/fulfillment/contracts/store.ts +1 -0
  170. package/src/fulfillment/contracts/sync.ts +13 -2
  171. package/src/fulfillment/dispatchProjection.ts +94 -7
  172. package/src/fulfillment/index.ts +3 -2
  173. package/src/fulfillment/migrations.ts +1 -0
  174. package/src/fulfillment/observationProjection.ts +64 -13
  175. package/src/fulfillment/omnipack/client.ts +32 -13
  176. package/src/fulfillment/omnipack/index.ts +99 -39
  177. package/src/fulfillment/omnipack/mappers.ts +23 -2
  178. package/src/fulfillment/omnipack/observation.ts +45 -27
  179. package/src/fulfillment/omnipack/reconciliation.ts +56 -0
  180. package/src/fulfillment/postgresDispatchStore.ts +1 -1
  181. package/src/fulfillment/postgresShipmentSpine.ts +21 -4
  182. package/src/fulfillment/postgresStockSyncPort.ts +6 -3
  183. package/src/fulfillment/providerDispatchAcceptance.ts +9 -3
  184. package/src/fulfillment/providerDispatchWorker.ts +61 -20
  185. package/src/fulfillment/providerReconciliationWorker.ts +81 -25
  186. package/src/fulfillment/providerStockSyncWorker.ts +222 -126
  187. package/src/fulfillment/providerTrackingReferences.ts +3 -2
  188. package/src/fulfillment/verifiedIngress.ts +44 -11
  189. package/src/invoicing/accountingCanonicalProviderLines.ts +134 -0
  190. package/src/invoicing/accountingLineAllocation.ts +69 -0
  191. package/src/invoicing/adapterConformance.ts +249 -0
  192. package/src/invoicing/artifacts.ts +19 -0
  193. package/src/invoicing/bindings.ts +80 -0
  194. package/src/invoicing/capabilities.ts +9 -0
  195. package/src/invoicing/channelPolicy.ts +50 -0
  196. package/src/invoicing/continuity.ts +15 -0
  197. package/src/invoicing/contracts/capabilities.ts +15 -0
  198. package/src/invoicing/contracts/companyLookup.ts +13 -0
  199. package/src/invoicing/contracts/delivery.ts +24 -0
  200. package/src/invoicing/contracts/facts.ts +71 -0
  201. package/src/invoicing/contracts/host.ts +84 -0
  202. package/src/invoicing/contracts/index.ts +16 -0
  203. package/src/invoicing/contracts/provider.ts +78 -0
  204. package/src/invoicing/contracts/settlement.ts +39 -0
  205. package/src/invoicing/contracts/store.ts +102 -0
  206. package/src/invoicing/contribution.ts +28 -0
  207. package/src/invoicing/correction.ts +56 -0
  208. package/src/invoicing/delivery.ts +122 -0
  209. package/src/invoicing/documentHistory.ts +301 -0
  210. package/src/invoicing/documentSync.ts +51 -0
  211. package/src/invoicing/dutyCollector.ts +31 -0
  212. package/src/invoicing/fulfillmentTrigger.ts +144 -0
  213. package/src/invoicing/handoff.ts +71 -0
  214. package/src/invoicing/index.ts +39 -0
  215. package/src/invoicing/issue.ts +64 -0
  216. package/src/invoicing/migrations.ts +25 -0
  217. package/src/invoicing/nativeUnsupported.ts +24 -0
  218. package/src/invoicing/paymentReadback.ts +70 -0
  219. package/src/invoicing/postgresInvoiceStore.ts +334 -0
  220. package/src/invoicing/readModel.ts +80 -0
  221. package/src/invoicing/readback.ts +26 -0
  222. package/src/invoicing/reversal.ts +34 -0
  223. package/src/invoicing/sellerRevision.ts +20 -0
  224. package/src/invoicing/settlementSync.ts +37 -0
  225. package/src/invoicing/snapshot.ts +192 -0
  226. package/src/subscriptions/automaticRenewal.ts +87 -0
  227. package/src/subscriptions/automaticRenewalExecution.ts +39 -0
  228. package/src/subscriptions/contracts/commands.ts +46 -0
  229. package/src/subscriptions/contracts/dunning.ts +48 -0
  230. package/src/subscriptions/contracts/index.ts +6 -0
  231. package/src/subscriptions/contracts/messages.ts +18 -0
  232. package/src/subscriptions/contracts/renewal.ts +325 -0
  233. package/src/subscriptions/contracts/stockWait.ts +34 -0
  234. package/src/subscriptions/index.ts +24 -0
  235. package/src/subscriptions/paymentOutcomes.ts +23 -0
  236. package/src/subscriptions/postgresRenewalStore.ts +76 -0
  237. package/src/subscriptions/postgresRetryStore.ts +174 -0
  238. package/src/subscriptions/renewalCycleFailureMechanism.ts +119 -0
  239. package/src/subscriptions/renewalCycleMechanism.ts +225 -0
  240. package/src/subscriptions/renewalCycleRefusalMechanism.ts +215 -0
  241. package/src/subscriptions/renewalIdentity.ts +5 -0
@@ -0,0 +1,18 @@
1
+ export type SubscriptionLifecycleFact = {
2
+ factId: string; idempotencyKey: string; subscriptionId: string; clientId: string;
3
+ cycleId: string | null; caseId: string | null; orderId: string | null;
4
+ occurredAt: string; firstRecordedAt: string;
5
+ } & (
6
+ | { kind: "payment_failed" | "payment_expired"; retryAttempt: number; nextRetryAt: string | null; failureClass: string | null; amountMinor: number | null; currency: string | null }
7
+ | { kind: "payment_recovered"; paymentEvidenceRef: string }
8
+ | { kind: "stock_wait_started" | "stock_wait_resumed"; frozenFingerprint: string }
9
+ | { kind: "winback_eligible"; cancelledAt: string; paymentMethodRef: string }
10
+ );
11
+ /** M04 receives a descriptor; host owns copy, recipient, consent and transport. */
12
+ export interface SubscriptionLifecycleMessageDescriptor {
13
+ fact: SubscriptionLifecycleFact; messageKey: string; templateSlug: string;
14
+ recipientRef: string; locale: string; recoveryUrlPath: string | null;
15
+ }
16
+ export interface SubscriptionLifecycleMessagePort {
17
+ enqueue(descriptor: SubscriptionLifecycleMessageDescriptor): Promise<{ notificationId: string; replayed: boolean }>;
18
+ }
@@ -0,0 +1,325 @@
1
+ import type {
2
+ PaymentAttemptStatus, PaymentExecutionBaseInput, PaymentExecutionBaseResult,
3
+ PaymentProviderCapabilityDescriptor, ProviderAttemptIdentity,
4
+ } from "@openlup/core/payment";
5
+
6
+ /** Structural hosted-cycle contracts; native vocabulary is bound by the caller. */
7
+ export type SubscriptionRenewalOutcome = "charged" | "requires_action" | "failed" | "skipped";
8
+ export interface HostedRenewalDue {
9
+ subscriptionId: string; nextCycleAt: string; providerKind: string;
10
+ providerMethodRef: string | null;
11
+ }
12
+ export interface SubscriptionRenewalChargeResult {
13
+ subscriptionId: string; outcome: SubscriptionRenewalOutcome; cycleId: string | null;
14
+ cycleNumber: number | null; orderId: string | null; paymentIntentId: string | null;
15
+ attemptStatus: PaymentAttemptStatus | null; replayed: boolean; dunningCaseId?: string | null;
16
+ retryAttempt?: number | null; applyReplayed?: boolean; dunningPropagationFailed?: boolean; reason?: string;
17
+ admissionDeferralReference?: string;
18
+ }
19
+ export interface SubscriptionCycleSnapshotResult {
20
+ cycleNumber: number; retryAttempt: number; providerAttemptSequence: number;
21
+ templateSnapshot: Record<string, unknown>; pricingSnapshot: Record<string, unknown>;
22
+ orderSnapshot: Record<string, unknown>;
23
+ }
24
+ export interface SubscriptionCycleOrderInput {
25
+ idempotencyKey: string; subscriptionId: string; cycleNumber: number; scheduledAt: string;
26
+ templateSnapshot: Record<string, unknown>; pricingSnapshot: Record<string, unknown>;
27
+ orderSnapshot: Record<string, unknown>;
28
+ }
29
+ export interface SubscriptionCycleOrderResult {
30
+ cycleId: string; orderRef: string; orderUuid: string; paymentRef: string; replayed: boolean;
31
+ }
32
+ export interface SubscriptionReservationPreflightResult {
33
+ ok: boolean; reason: string | null; itemsChecked: number; reacquired: number;
34
+ }
35
+ export interface SubscriptionRenewalPersistencePort<M extends string | null | undefined = string | null | undefined> {
36
+ buildCycleSnapshots(input: { subscriptionId: string; scheduledAt: string }): Promise<SubscriptionCycleSnapshotResult>;
37
+ createCycleOrder(input: SubscriptionCycleOrderInput): Promise<SubscriptionCycleOrderResult>;
38
+ preflightReservation(input: { orderId: string; now: string }): Promise<SubscriptionReservationPreflightResult>;
39
+ readMandateRecurringModel(providerKind: string, providerMethodRef: string | null): Promise<M>;
40
+ noteRowOutcome(input: { subscriptionId: string; scheduledAt: string; errorKey: string | null }): Promise<void>;
41
+ }
42
+
43
+ export type FailurePropagationKind =
44
+ | "off_session_requires_action"
45
+ | "provider_declined"
46
+ | "preflight_block";
47
+ export interface FailurePropagationContext {
48
+ kind: FailurePropagationKind;
49
+ executionIdempotencyKey: string;
50
+ providerIdempotencyKey: string;
51
+ providerRequestFingerprint: string;
52
+ paymentIntentId: string;
53
+ cycleId: string;
54
+ subscriptionId: string;
55
+ orderUuid: string;
56
+ providerKind: string;
57
+ failureReason: string;
58
+ failureClassification?: { failureClass: string; decidedBy: string } | null;
59
+ occurredAt: string;
60
+ attemptAlreadyPrepared?: boolean;
61
+ }
62
+ export interface CycleRetryState {
63
+ retryAttempt: number | null;
64
+ nextRetryAt: string | null;
65
+ rowPresent: boolean;
66
+ }
67
+ export type DunningCaseOpenOutcome =
68
+ | { status: "opened"; caseId: string | null }
69
+ | { status: "rejected"; reason: string; code?: string }
70
+ | { status: "unavailable"; reason: string };
71
+ export interface CycleChargeFailurePropagationPort {
72
+ recordBlockedPreflightAttempt(input: {
73
+ idempotencyKey: string;
74
+ paymentIntentId: string;
75
+ providerKind: string;
76
+ providerIdempotencyKey: string;
77
+ providerRequestFingerprint: string;
78
+ failureReason: string;
79
+ }): Promise<void>;
80
+ readDurableAttemptInstant(input: {
81
+ paymentIntentId: string;
82
+ attemptIdempotencyKey: string;
83
+ }): Promise<string | null>;
84
+ applyFailedResult(input: {
85
+ idempotencyKey: string;
86
+ paymentIntentId: string;
87
+ occurredAt: string;
88
+ failureReason: string;
89
+ failureClassification: { failureClass: string; decidedBy: string };
90
+ }): Promise<{ replayed: boolean }>;
91
+ readCycleRetryState(cycleId: string): Promise<CycleRetryState>;
92
+ openDunningCase(input: {
93
+ idempotencyKey: string;
94
+ cycleId: string;
95
+ subscriptionId: string;
96
+ orderUuid: string;
97
+ paymentIntentId: string;
98
+ retryAttempt: number;
99
+ nextRetryAt: string | null;
100
+ failureReason: string;
101
+ occurredAt: string;
102
+ failureClass: string;
103
+ }): Promise<DunningCaseOpenOutcome>;
104
+ }
105
+ export interface FailurePropagationResult {
106
+ dunningCaseId: string | null;
107
+ retryAttempt: number | null;
108
+ applyReplayed: boolean;
109
+ dunningPropagationFailed: boolean;
110
+ }
111
+
112
+ export type RenewalPrepareInput<P extends string> =
113
+ Parameters<RenewalPreparedAttemptPersistencePort<P, string>["prepareProviderAttempt"]>[0];
114
+ export type RenewalAttemptResult<N extends string> =
115
+ Awaited<ReturnType<RenewalPreparedAttemptPersistencePort<string, N>["finalizeProviderAttempt"]>>;
116
+ export interface RenewalIntentResult { paymentIntentId: string; replayed: boolean }
117
+ export interface RenewalPaymentPort<CI, IR extends RenewalIntentResult, P extends string, N extends string>
118
+ extends RenewalPreparedAttemptPersistencePort<P, N> {
119
+ createIntent(input: CI): Promise<IR>;
120
+ }
121
+ export interface RenewalExecutionPort<I extends PaymentExecutionBaseInput, R extends PaymentExecutionBaseResult> {
122
+ execute(input: I): Promise<R>;
123
+ }
124
+ export interface RenewalDeliveryAdmission { allowed: boolean; state: string; reason: string }
125
+ export type RenewalAllowedAdmission = RenewalDeliveryAdmission & { allowed: true };
126
+ export interface RenewalDeliveryClient {
127
+ admitDeliveryAlignment(input: { subscriptionId: string; scheduledAt: string; asOf: string }): Promise<RenewalDeliveryAdmission>;
128
+ }
129
+ export interface RenewalFailureDeps {
130
+ chargeFailurePropagation: CycleChargeFailurePropagationPort;
131
+ now?: () => string;
132
+ }
133
+ export interface RenewalPreflightDeps<CI, IR extends RenewalIntentResult, M extends string | null | undefined>
134
+ extends RenewalFailureDeps {
135
+ persistence: SubscriptionRenewalPersistencePort<M>;
136
+ paymentPort: { createIntent(input: CI): Promise<IR> };
137
+ }
138
+ export interface RenewalCycleDeps<CI, IR extends RenewalIntentResult, P extends string, N extends string,
139
+ I extends PaymentExecutionBaseInput, R extends PaymentExecutionBaseResult, M extends string | null | undefined>
140
+ extends RenewalPreflightDeps<CI, IR, M> {
141
+ paymentPort: RenewalPaymentPort<CI, IR, P, N>;
142
+ executionPort: RenewalExecutionPort<I, R>;
143
+ deliveryAlignment: RenewalDeliveryClient;
144
+ capability: PaymentProviderCapabilityDescriptor;
145
+ deliveryAlignmentAdmission?: RenewalAllowedAdmission;
146
+ }
147
+ /** Local references, never a snapshot of the original due/ports or their getters. */
148
+ export interface RenewalExecutionContext<IR extends RenewalIntentResult, M extends string | null | undefined, F extends string> {
149
+ snapshots: SubscriptionCycleSnapshotResult;
150
+ cycleOrder: SubscriptionCycleOrderResult;
151
+ intent: IR;
152
+ totalGrossMinor: number;
153
+ executionIdempotencyKey: string;
154
+ attemptIdentity: ProviderAttemptIdentity;
155
+ providerFlow: F;
156
+ paymentMethodRecurringModel: M | undefined;
157
+ }
158
+ export interface RenewalOutcomeContext {
159
+ executionIdempotencyKey: string;
160
+ attemptIdentity: ProviderAttemptIdentity;
161
+ intent: RenewalIntentResult;
162
+ cycleOrder: Pick<SubscriptionCycleOrderResult, "cycleId" | "orderUuid" | "replayed">;
163
+ snapshots: Pick<SubscriptionCycleSnapshotResult, "cycleNumber">;
164
+ finalizedAttempt: { status: PaymentAttemptStatus };
165
+ }
166
+ export interface RenewalReplayContext {
167
+ cycleId: string; cycleNumber: number; orderId: string; paymentIntentId: string;
168
+ executionIdempotencyKey: string; providerIdempotencyKey: string;
169
+ providerRequestFingerprint: string; providerKind: string;
170
+ }
171
+
172
+ /** Durable preparation facts; provider admission belongs to the caller. */
173
+ export interface RenewalPreparedAttemptPrepareInput<P extends string = string> {
174
+ idempotencyKey: string;
175
+ paymentIntentId: string;
176
+ provider: P;
177
+ providerIdempotencyKey: string;
178
+ providerRequestFingerprint: string;
179
+ providerFlow: string;
180
+ paymentMethodRef?: string | null;
181
+ requestPayload: Record<string, unknown>;
182
+ }
183
+
184
+ export interface RenewalPreparedAttemptPersistenceResult<N extends string = string> {
185
+ paymentAttemptId: string;
186
+ status: PaymentAttemptStatus;
187
+ replayed: boolean;
188
+ providerAttemptId: string | null;
189
+ providerSessionId: string | null;
190
+ nextActionKind: N | null;
191
+ }
192
+
193
+ export interface RenewalPreparedAttemptFinalizeInput<N extends string = string> extends Pick<
194
+ PaymentExecutionBaseResult,
195
+ "providerAttemptId" | "providerSessionId" | "attemptStatus" | "requestPayload" | "responsePayload"
196
+ > {
197
+ idempotencyKey: string;
198
+ paymentIntentId: string;
199
+ paymentAttemptId: string;
200
+ providerIdempotencyKey: string;
201
+ providerRequestFingerprint: string;
202
+ nextActionKind: N | null;
203
+ }
204
+
205
+ /** Only persistence capabilities needed by interactive attempt ordering. */
206
+ export interface RenewalPreparedAttemptPersistencePort<P extends string = string, N extends string = string> {
207
+ prepareProviderAttempt(input: RenewalPreparedAttemptPrepareInput<P>): Promise<RenewalPreparedAttemptPersistenceResult<N>>;
208
+ finalizeProviderAttempt(input: RenewalPreparedAttemptFinalizeInput<N>): Promise<RenewalPreparedAttemptPersistenceResult<N>>;
209
+ }
210
+
211
+
212
+ /** Original text is an identity input; instant is for comparisons only. */
213
+ export interface RenewalSchedule {
214
+ subscriptionId: string;
215
+ scheduledAt: string;
216
+ scheduledInstant: number;
217
+ }
218
+ export interface RenewalIdentity {
219
+ cycleIdempotencyKey: string;
220
+ executionIdempotencyKey: string;
221
+ externalIdempotencyKey: string;
222
+ requestFingerprint: string;
223
+ }
224
+ export type RenewalAdmission =
225
+ | { allowed: true; state: string; reason: string }
226
+ | { allowed: false; state: string; reason: string; deferred: true; caseRef?: string }
227
+ | { allowed: false; state: string; reason: string; deferred: false };
228
+ export interface RenewalLine {
229
+ lineOrdinal: number; sku: string; quantity: number; unitAmountMinor: number;
230
+ }
231
+ /** Logical numbering is evidence-bound, never a universal nativeCounter + 1. */
232
+ export type RenewalCycleCorrelation =
233
+ | { status: "correlated"; logicalCycleNumber: number; nativeRenewalCounter: number | null; activationCycleId: string | null; evidenceRef: string }
234
+ | { status: "ambiguous"; logicalCycleNumber: null; nativeRenewalCounter: number | null; activationCycleId: string | null; reason: string };
235
+ export interface RenewalTemplateSnapshot {
236
+ templateId: string; version: number; cadenceDays: number; lines: readonly RenewalLine[];
237
+ }
238
+ export interface RenewalPricingSnapshot {
239
+ currency: string; totalAmountMinor: number; shippingAmountMinor: number;
240
+ agreementPolicy: "lock_until_edit"; proration: "none";
241
+ }
242
+ export interface RenewalOrderSnapshot {
243
+ shippingAddressRef: string | null; shippingQuoteRef: string | null;
244
+ acquisitionRef: string | null; lines: readonly RenewalLine[];
245
+ }
246
+ export interface RenewalFrozenSnapshot extends RenewalSchedule {
247
+ contractVersion: "platform.subscription.renewal.v1";
248
+ clientId: string; frozenRevision: string; frozenFingerprint: string;
249
+ cycle: RenewalCycleCorrelation; retryAttempt: number; externalAttemptSequence: number;
250
+ templateSnapshot: RenewalTemplateSnapshot;
251
+ pricingSnapshot: RenewalPricingSnapshot;
252
+ orderSnapshot: RenewalOrderSnapshot;
253
+ stockSourceKey: string; settlementChannelKey: string; authorizationRef: string;
254
+ payerAccountRef: string | null; unattendedChargeAuthorizedAt: string;
255
+ }
256
+ export interface RenewalDue extends RenewalSchedule {
257
+ clientId: string; cadenceDays: number; cycle: RenewalCycleCorrelation; currency: string;
258
+ stockSourceKey: string; settlementChannelKey: string; authorizationRef: string;
259
+ payerAccountRef: string | null; unattendedChargeAuthorizedAt: string;
260
+ deliveryAdmission: "allowed" | "blocked"; deliveryBlockReason: string | null;
261
+ }
262
+ export type RenewalOperationState = "claimed" | "reserved" | "attempt_prepared" | "succeeded" | "refused";
263
+ export interface RenewalReadback extends RenewalSchedule {
264
+ contractVersion: "platform.subscription.renewal.v1";
265
+ acquired: boolean; replayed: boolean; reason: string;
266
+ operationId: string; identityKey: string; operationFingerprint: string;
267
+ cycle: RenewalCycleCorrelation; state: RenewalOperationState;
268
+ claimToken: string; claimGeneration: number; leaseExpiresAt: string;
269
+ cycleSnapshot: RenewalFrozenSnapshot;
270
+ externalAttemptRef: string | null; externalAttemptAcknowledgedAt: string | null;
271
+ terminalOutcome: "succeeded" | "refused" | null; terminalReason: string | null; terminalAt: string | null;
272
+ reservationId: string | null; cycleId: string | null; orderId: string | null; settlementIntentId: string | null;
273
+ }
274
+ /** M07 verified truth. Nullable money is not positive matching evidence. */
275
+ export interface RenewalPaymentEvidence {
276
+ status: "succeeded" | "failed" | "pending" | "unknown";
277
+ disposition: "observed" | "ambiguous" | "late_positive";
278
+ operationId: string; eventKey: string; requestFingerprint: string;
279
+ verifiedEvidenceRef: string; externalAttemptRef: string | null;
280
+ occurredAt: string | null; observedAt: string; firstRecordedAt: string;
281
+ amountMinor: number | null; currency: string | null; reason: string | null;
282
+ }
283
+ /** Payment execution is outside all SQL transactions. */
284
+ export type RenewalTransactionUnits =
285
+ | { graph: "native"; prepare: "cycle_order_items_settlement_reservation_attempt"; outcome: "settlement_outcome_schedule_counter_reservation"; recovery: "settlement_cycle_receipt_case_counter" }
286
+ | { graph: "managed"; prepare: "create_cycle_order_then_prepare_attempt"; failure: "record_attempt_then_apply_result_then_open_case"; recovery: "authorize_method_then_schedule" };
287
+ export interface RenewalReservationResult {
288
+ reserved: boolean; replayed: boolean; reservationId: string | null;
289
+ status: "held" | "committed" | "consumed" | "released" | "expired" | "refused";
290
+ reason: string | null;
291
+ }
292
+ export interface RenewalPreparedSettlement {
293
+ contractVersion: "platform.subscription.renewal.v1"; replayed: boolean;
294
+ operationId: string; operationFingerprint: string; cycleId: string;
295
+ orderId: string; settlementIntentId: string; state: "attempt_prepared";
296
+ }
297
+ export interface RenewalStorePort {
298
+ readonly transactions: RenewalTransactionUnits;
299
+ listDue(limit: number, asOf: string): Promise<RenewalDue[]>;
300
+ buildCycleSnapshots(input: RenewalSchedule): Promise<RenewalFrozenSnapshot>;
301
+ readMandateEvidence(input: RenewalSchedule): Promise<{ authorized: boolean; authorizationRef: string; authorizedAt: string | null }>;
302
+ admitDeliveryAlignment(input: RenewalSchedule & { occurredAt: string }): Promise<RenewalAdmission>;
303
+ claimDue(input: { snapshot: RenewalFrozenSnapshot; workerId: string; leaseSeconds?: number; now: string }): Promise<RenewalReadback>;
304
+ readRenewal(identityKey: string): Promise<RenewalReadback | null>;
305
+ refuseBeforePayment(input: { operationId: string; claimToken: string; reason: string; occurredAt: string }): Promise<RenewalReadback>;
306
+ }
307
+ export interface RenewalSettlementPort {
308
+ prepareRenewal(input: { operationId: string; claimToken: string; now: string }): Promise<RenewalPreparedSettlement>;
309
+ acknowledgeExternalAttempt(input: { operationId: string; operationFingerprint: string; externalAttemptRef: string; acknowledgedAt: string }): Promise<RenewalReadback>;
310
+ recordTerminalOutcome(input: { operationId: string; eventKey: string; operationFingerprint: string; outcome: "succeeded" | "refused"; externalRef?: string | null; reason?: string | null; occurredAt: string }): Promise<RenewalReadback>;
311
+ }
312
+ export interface RenewalExecutionRequest extends RenewalIdentity {
313
+ paymentIntentId: string; amountMinor: number; currency: string; mode: string;
314
+ orderRef: string; orderId: string; flow: string; customerRef: string | null;
315
+ paymentMethodRef: string; recurringModel: string | null; clientId: string;
316
+ payer: { email: string | null; name: string | null } | null;
317
+ }
318
+ export interface RenewalPaymentRail {
319
+ execute(input: RenewalExecutionRequest): Promise<{ status: "succeeded" | "failed" | "requires_action" | "pending" | "unknown"; externalAttemptRef: string | null; failure?: { reason: string; failureClass: string } }>;
320
+ findExternalAttempt(input: { settlementIntentId: string }): Promise<string | null>;
321
+ readOutcome(input: { externalAttemptRef: string; settlementIntentId: string; orderId: string; subscriptionId: string; amountMinor: number; currency: string }): Promise<RenewalPaymentEvidence>;
322
+ }
323
+ export type RenewalResult =
324
+ | { outcome: "deferred"; subscriptionId: string; scheduledAt: string; reason: string; caseRef?: string }
325
+ | { outcome: "charged" | "requires_action" | "failed" | "skipped"; subscriptionId: string; cycleId: string | null; cycleNumber: number | null; orderId: string | null; paymentIntentId: string | null; attemptStatus: string | null; replayed: boolean; dunningCaseId?: string | null; retryAttempt?: number | null; applyReplayed?: boolean; dunningPropagationFailed?: boolean; reason?: string };
@@ -0,0 +1,34 @@
1
+ import type { RenewalFrozenSnapshot, RenewalReadback, RenewalSchedule } from "./renewal.js";
2
+ export interface StockWaitRecoveryBinding {
3
+ caseId: string | null; cycleId: string | null; orderId: string | null;
4
+ paymentId: string | null; engineIdempotencyKey: string | null;
5
+ }
6
+ export interface StockWaitDeficit {
7
+ skuId: string; sku: string; displayName: string; size: string | null;
8
+ required: number; reserved: number; available: number; missing: number;
9
+ }
10
+ export interface StockWaitCase extends RenewalSchedule {
11
+ caseId: string; clientId: string; state: "waiting" | "stock_secured" | "resumed" | "closed";
12
+ frozenSnapshot: RenewalFrozenSnapshot; frozenFingerprint: string;
13
+ binding: StockWaitRecoveryBinding; deficits: readonly StockWaitDeficit[];
14
+ fulfillmentRef: string | null; blockReason: string | null;
15
+ openedAt: string; checkedAt: string | null; selectedAt: string | null;
16
+ securedAt: string | null; resumedAt: string | null; closedAt: string | null;
17
+ startedNoticeKey: string; resumedNoticeKey: string; noticeEligible: boolean;
18
+ }
19
+ /** All-line reserve/reacquire and the wait transition share a DB transaction.
20
+ * A shortage rolls back the subtransaction, retaining previous commitments;
21
+ * record the wait outside that failed subtransaction. Technical failure is unknown.
22
+ */
23
+ export interface StockWaitStorePort {
24
+ listCandidates(input: { limit: number; asOf: string }): Promise<StockWaitCase[]>;
25
+ readFrozenCase(input: StockWaitRecoveryBinding): Promise<StockWaitCase | null>;
26
+ reserveAndTransition(input: { caseId: string; frozenFingerprint: string; now: string }): Promise<StockWaitCase>;
27
+ /** Only elapsed, unprepared, unacknowledged, stock-only refusals may reset.
28
+ * Requires unchanged schedule/fingerprint and no payment artifacts or outcomes.
29
+ * Rotate fencing generation atomically with reacquisition. Never reset ambiguity.
30
+ */
31
+ recoverRefusedSchedule(input: { operationId: string; caseId: string; claimGeneration: number; frozenFingerprint: string; scheduledAt: string; now: string }): Promise<
32
+ | { outcome: "recovered"; operation: RenewalReadback; replayed: boolean }
33
+ | { outcome: "refused"; reason: "not_stock_only" | "external_ownership" | "stale_snapshot" | "lease_active" | "inventory_unavailable" }>;
34
+ }
@@ -0,0 +1,24 @@
1
+ export { renewalIdentity } from "./renewalIdentity.js";
2
+ export const NATIVE_SUBSCRIPTION_CAPABILITIES = Object.freeze({
3
+ graph: "native",
4
+ multiCyclePrepaid: Object.freeze({ supported: false, refusalCode: "multi_cycle_prepaid_unsupported" }),
5
+ genericBundleCommands: Object.freeze({ supported: false, refusalCode: "native_generic_bundle_commands_unsupported" }),
6
+ commandBoundReceipts: Object.freeze({ supported: false, refusalCode: "native_command_bound_receipts_unsupported" }),
7
+ preparedAttemptStore: Object.freeze({ supported: false, refusalCode: "native_prepared_attempt_store_unsupported" }),
8
+ retryRecovery: "selected", stockRecovery: "unavailable",
9
+ deliveryAdmission: "required", executorCount: 1, defaultEnabled: false,
10
+ } as const satisfies import("./contracts/commands.js").SubscriptionCapabilities);
11
+ export { executeRenewalCycle, prepareRenewalAttempt, checkRenewalDeliveryAdmission } from "./renewalCycleMechanism.js";
12
+ export type { RenewalCyclePolicy } from "./renewalCycleMechanism.js";
13
+ export { recordRenewalPreflightBlock, replayRenewalAttempt, declinedRenewalOutcome, requiresActionRenewalOutcome } from "./renewalCycleRefusalMechanism.js";
14
+ export type { RenewalPreflightPolicy, RenewalReplayPolicy, RenewalOutcomePolicy } from "./renewalCycleRefusalMechanism.js";
15
+ export { propagateRenewalFailure } from "./renewalCycleFailureMechanism.js";
16
+ export type { RenewalFailurePolicy } from "./renewalCycleFailureMechanism.js";
17
+ export { runAutomaticSubscriptionRenewals, runOneAutomaticSubscriptionRenewal, correlatedLogicalCycle } from "./automaticRenewal.js";
18
+ export type { AutomaticRenewalRunStatus, AutomaticRenewalRunResult, AutomaticRenewalRunDeps, AutomaticRenewalPolicy } from "./automaticRenewal.js";
19
+ export { createPostgresRenewalStore } from "./postgresRenewalStore.js";
20
+ export type { NativeRenewalCodec, SubscriptionQueryExecutor } from "./postgresRenewalStore.js";
21
+ export { createPostgresRetryStore } from "./postgresRetryStore.js";
22
+ export type { NativeRetryFailure, NativeRetryRequest, NativeRetryPositive, NativeRetryPreparation, NativeRetryRow } from "./postgresRetryStore.js";
23
+ export { createPostgresRetryLifecycleBinding } from "./postgresRetryStore.js";
24
+ export type { NativeRetryLifecycleBinding } from "./postgresRetryStore.js";
@@ -0,0 +1,23 @@
1
+ import type { RenewalPaymentRail, RenewalReadback } from "./contracts/renewal.js";
2
+ import type { AutomaticRenewalRunDeps, AutomaticRenewalRunResult } from "./automaticRenewal.js";
3
+ import { automaticRenewalResult, handoffRefusal } from "./automaticRenewalExecution.js";
4
+ /** H4: only explicit verified matching M07 evidence may become positive local truth. */
5
+ export async function readAndApplyProviderOutcome(deps: AutomaticRenewalRunDeps, operation: RenewalReadback, prepared: { cycleId: string; orderId: string; settlementIntentId: string }, rail: RenewalPaymentRail, externalAttemptRef: string, occurredAt: string): Promise<AutomaticRenewalRunResult> {
6
+ const money = operation.cycleSnapshot.pricingSnapshot;
7
+ const observed = await rail.readOutcome({ externalAttemptRef, settlementIntentId: prepared.settlementIntentId, orderId: prepared.orderId, subscriptionId: operation.subscriptionId, amountMinor: money.totalAmountMinor, currency: money.currency });
8
+ if (observed.amountMinor !== null && observed.amountMinor !== money.totalAmountMinor) throw new Error("subscription_renewal_provider_amount_mismatch");
9
+ if (observed.currency !== null && observed.currency.toUpperCase() !== money.currency.toUpperCase()) throw new Error("subscription_renewal_provider_currency_mismatch");
10
+ if (observed.status === "pending" || observed.status === "unknown") return automaticRenewalResult(operation, "awaiting_provider", `provider_${observed.status}`);
11
+ if (observed.operationId !== operation.operationId || observed.requestFingerprint !== operation.operationFingerprint
12
+ || observed.externalAttemptRef !== externalAttemptRef || !observed.verifiedEvidenceRef || !observed.eventKey
13
+ || !observed.firstRecordedAt || !observed.observedAt) throw new Error("subscription_renewal_verified_evidence_required");
14
+ if (observed.disposition === "ambiguous") return automaticRenewalResult(operation, "awaiting_provider", "provider_ambiguous");
15
+ const terminalAt = observed.occurredAt ?? occurredAt;
16
+ if (observed.status === "failed") {
17
+ const reason = observed.reason ?? "provider_failed";
18
+ return handoffRefusal(deps, operation, prepared, externalAttemptRef, reason, deps.policy.classifyFailure(reason), terminalAt);
19
+ }
20
+ if (observed.amountMinor === null || observed.currency === null || observed.occurredAt === null) return automaticRenewalResult(operation, "awaiting_provider", "provider_positive_evidence_incomplete");
21
+ await deps.settlement.recordTerminalOutcome({ operationId: operation.operationId, eventKey: `${operation.identityKey}:provider-success:${externalAttemptRef}`, operationFingerprint: operation.operationFingerprint, outcome: "succeeded", externalRef: externalAttemptRef, occurredAt: terminalAt });
22
+ return automaticRenewalResult(operation, "succeeded", null);
23
+ }
@@ -0,0 +1,76 @@
1
+ import type { RenewalDue, RenewalFrozenSnapshot, RenewalReadback, RenewalSchedule, RenewalStorePort } from "./contracts/renewal.js";
2
+
3
+ /** Transaction-bound executors may be supplied; no driver, environment or account is selected here. */
4
+ export interface SubscriptionQueryExecutor {
5
+ query(text: string, values?: unknown[]): Promise<{ rows: Record<string, unknown>[] }>;
6
+ }
7
+ /** The host preserves its native wire snapshot, identity dialect and evidence-bound numbering. */
8
+ export interface NativeRenewalCodec {
9
+ due(row: Record<string, unknown>): RenewalDue;
10
+ freeze(row: Record<string, unknown>, schedule: RenewalSchedule): RenewalFrozenSnapshot;
11
+ encode(snapshot: RenewalFrozenSnapshot): Record<string, unknown>;
12
+ decode(snapshot: Record<string, unknown>): RenewalFrozenSnapshot;
13
+ readback(row: Record<string, unknown>): RenewalReadback;
14
+ identity(snapshot: RenewalFrozenSnapshot): string;
15
+ fingerprint(snapshot: RenewalFrozenSnapshot): string;
16
+ }
17
+ const SNAPSHOT_SQL = `SELECT s.id AS subscription_id,s.client_id,s.next_cycle_at::text AS scheduled_at,t.*,
18
+ COALESCE((SELECT jsonb_agg(jsonb_build_object('lineOrdinal',l.line_ordinal,'sku',l.sku,'quantity',l.quantity,
19
+ 'unitAmountMinor',l.unit_amount_minor) ORDER BY l.line_ordinal) FROM public.subscription_renewal_term_lines l
20
+ WHERE l.subscription_id=s.id),'[]'::jsonb) AS lines
21
+ FROM public.subscriptions s JOIN public.subscription_renewal_terms t ON t.subscription_id=s.id
22
+ WHERE s.id=$1::uuid AND s.next_cycle_at=$2::timestamptz`;
23
+ export function subscriptionRecord(value: unknown): Record<string, unknown> {
24
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("subscription_renewal_response_invalid");
25
+ return value as Record<string, unknown>;
26
+ }
27
+ /** Native claim/prepare remain their original SQL transaction units. G23 is composed by the host. */
28
+ export function createPostgresRenewalStore(executor: SubscriptionQueryExecutor, codec: NativeRenewalCodec): RenewalStorePort {
29
+ async function header(input: RenewalSchedule) {
30
+ const result = await executor.query(SNAPSHOT_SQL, [input.subscriptionId, input.scheduledAt]);
31
+ if (!result.rows[0]) throw new Error("subscription_renewal_snapshot_not_found");
32
+ return result.rows[0];
33
+ }
34
+ async function response(sql: string, values: unknown[]) {
35
+ return codec.readback(subscriptionRecord((await executor.query(sql, values)).rows[0]?.response));
36
+ }
37
+ return {
38
+ transactions: { graph: "native", prepare: "cycle_order_items_settlement_reservation_attempt",
39
+ outcome: "settlement_outcome_schedule_counter_reservation", recovery: "settlement_cycle_receipt_case_counter" },
40
+ async listDue(limit, asOf) {
41
+ const result = await executor.query(`SELECT due.*,due.scheduled_at::text AS scheduled_at
42
+ FROM public.subscription_list_due_renewals($1::timestamptz,$2::integer) due`, [asOf, limit]);
43
+ return result.rows.map(row => codec.due(row));
44
+ },
45
+ async buildCycleSnapshots(input) {
46
+ const stored = await executor.query(`SELECT cycle_snapshot FROM public.subscription_renewal_operations
47
+ WHERE subscription_id=$1::uuid AND scheduled_at=$2::timestamptz`, [input.subscriptionId, input.scheduledAt]);
48
+ return stored.rows[0] ? codec.decode(subscriptionRecord(stored.rows[0].cycle_snapshot)) : codec.freeze(await header(input), input);
49
+ },
50
+ async readMandateEvidence(input) {
51
+ const row = await header(input);
52
+ return { authorized: true, authorizationRef: String(row.authorization_ref),
53
+ authorizedAt: row.unattended_charge_authorized_at instanceof Date
54
+ ? row.unattended_charge_authorized_at.toISOString() : String(row.unattended_charge_authorized_at) };
55
+ },
56
+ async admitDeliveryAlignment(input) {
57
+ const row = await header(input);
58
+ if (row.delivery_admission !== "allowed" && row.delivery_admission !== "blocked") throw new Error("subscription_renewal_delivery_admission_invalid");
59
+ return row.delivery_admission === "allowed"
60
+ ? { allowed: true, state: "static", reason: "allowed" }
61
+ : { allowed: false, state: "static", reason: String(row.delivery_block_reason ?? "delivery_alignment_blocked"), deferred: false };
62
+ },
63
+ async claimDue(input) {
64
+ return response(`SELECT public.subscription_claim_due_renewal($1::text,$2::text,$3::jsonb,$4::text,$5::integer,$6::timestamptz) AS response`,
65
+ [codec.identity(input.snapshot), codec.fingerprint(input.snapshot), JSON.stringify(codec.encode(input.snapshot)), input.workerId, input.leaseSeconds ?? 300, input.now]);
66
+ },
67
+ async readRenewal(identityKey) {
68
+ const value = (await executor.query("SELECT public.subscription_read_renewal($1::text) AS response", [identityKey])).rows[0]?.response;
69
+ return value == null ? null : codec.readback(subscriptionRecord(value));
70
+ },
71
+ async refuseBeforePayment(input) {
72
+ return response("SELECT public.subscription_refuse_renewal_preflight($1::uuid,$2::uuid,$3::text,$4::timestamptz) AS response",
73
+ [input.operationId, input.claimToken, input.reason, input.occurredAt]);
74
+ },
75
+ };
76
+ }