@openlup/platform 0.21.0 → 0.23.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 (349) hide show
  1. package/CHANGELOG.md +432 -146
  2. package/README.md +82 -589
  3. package/dist/composition/composeBundle.d.ts +7 -0
  4. package/dist/composition/composeBundle.js +2 -0
  5. package/dist/composition/fulfillmentContribution.d.ts +21 -0
  6. package/dist/composition/fulfillmentContribution.js +19 -0
  7. package/dist/composition/index.d.ts +1 -0
  8. package/dist/composition/index.js +1 -0
  9. package/dist/fulfillment/adapterConformance.d.ts +3 -0
  10. package/dist/fulfillment/adapterConformance.js +223 -0
  11. package/dist/fulfillment/bindings.d.ts +3 -0
  12. package/dist/fulfillment/bindings.js +29 -0
  13. package/dist/fulfillment/commerceFulfillmentHandlers.d.ts +29 -0
  14. package/dist/fulfillment/commerceFulfillmentHandlers.js +184 -0
  15. package/dist/fulfillment/commerceFulfillmentReadModel.d.ts +86 -0
  16. package/dist/fulfillment/commerceFulfillmentReadModel.js +131 -0
  17. package/dist/fulfillment/contracts/alignment.d.ts +25 -0
  18. package/dist/fulfillment/contracts/alignment.js +1 -0
  19. package/dist/fulfillment/contracts/bindings.d.ts +51 -0
  20. package/dist/fulfillment/contracts/bindings.js +1 -0
  21. package/dist/fulfillment/contracts/capabilities.d.ts +29 -0
  22. package/dist/fulfillment/contracts/capabilities.js +1 -0
  23. package/dist/fulfillment/contracts/conformance.d.ts +95 -0
  24. package/dist/fulfillment/contracts/conformance.js +1 -0
  25. package/dist/fulfillment/contracts/customerAlignment.d.ts +6 -0
  26. package/dist/fulfillment/contracts/customerAlignment.js +1 -0
  27. package/dist/fulfillment/contracts/dispatch.d.ts +143 -0
  28. package/dist/fulfillment/contracts/dispatch.js +1 -0
  29. package/dist/fulfillment/contracts/index.d.ts +18 -0
  30. package/dist/fulfillment/contracts/index.js +1 -0
  31. package/dist/fulfillment/contracts/label.d.ts +35 -0
  32. package/dist/fulfillment/contracts/label.js +1 -0
  33. package/dist/fulfillment/contracts/nativeUnsupported.d.ts +6 -0
  34. package/dist/fulfillment/contracts/nativeUnsupported.js +1 -0
  35. package/dist/fulfillment/contracts/observation.d.ts +72 -0
  36. package/dist/fulfillment/contracts/observation.js +1 -0
  37. package/dist/fulfillment/contracts/operator.d.ts +64 -0
  38. package/dist/fulfillment/contracts/operator.js +1 -0
  39. package/dist/fulfillment/contracts/read.d.ts +110 -0
  40. package/dist/fulfillment/contracts/read.js +1 -0
  41. package/dist/fulfillment/contracts/readback.d.ts +59 -0
  42. package/dist/fulfillment/contracts/readback.js +1 -0
  43. package/dist/fulfillment/contracts/replacement.d.ts +34 -0
  44. package/dist/fulfillment/contracts/replacement.js +1 -0
  45. package/dist/fulfillment/contracts/store.d.ts +89 -0
  46. package/dist/fulfillment/contracts/store.js +1 -0
  47. package/dist/fulfillment/contracts/sync.d.ts +226 -0
  48. package/dist/fulfillment/contracts/sync.js +1 -0
  49. package/dist/fulfillment/currentFulfillmentParcel.d.ts +2 -0
  50. package/dist/fulfillment/currentFulfillmentParcel.js +1 -0
  51. package/dist/fulfillment/delayReadModel.d.ts +11 -0
  52. package/dist/fulfillment/delayReadModel.js +45 -0
  53. package/dist/fulfillment/deliveryAlignmentReadModel.d.ts +4 -0
  54. package/dist/fulfillment/deliveryAlignmentReadModel.js +12 -0
  55. package/dist/fulfillment/dispatchProjection.d.ts +26 -0
  56. package/dist/fulfillment/dispatchProjection.js +107 -0
  57. package/dist/fulfillment/fulfillmentObservationEffects.d.ts +66 -0
  58. package/dist/fulfillment/fulfillmentObservationEffects.js +36 -0
  59. package/dist/fulfillment/handlerSchemas.d.ts +647 -0
  60. package/dist/fulfillment/handlerSchemas.js +213 -0
  61. package/dist/fulfillment/handlerSupport.d.ts +33 -0
  62. package/dist/fulfillment/handlerSupport.js +25 -0
  63. package/dist/fulfillment/index.d.ts +38 -0
  64. package/dist/fulfillment/index.js +37 -0
  65. package/dist/fulfillment/labelLifecycle.d.ts +24 -0
  66. package/dist/fulfillment/labelLifecycle.js +9 -0
  67. package/dist/fulfillment/lowStockEvidenceHandler.d.ts +36 -0
  68. package/dist/fulfillment/lowStockEvidenceHandler.js +46 -0
  69. package/dist/fulfillment/migrations.d.ts +32 -0
  70. package/dist/fulfillment/migrations.js +9 -0
  71. package/dist/fulfillment/nativeUnsupported.d.ts +10 -0
  72. package/dist/fulfillment/nativeUnsupported.js +18 -0
  73. package/dist/fulfillment/normalizedFulfillmentFacts.d.ts +2 -0
  74. package/dist/fulfillment/normalizedFulfillmentFacts.js +1 -0
  75. package/dist/fulfillment/observationProjection.d.ts +32 -0
  76. package/dist/fulfillment/observationProjection.js +90 -0
  77. package/dist/fulfillment/omnipack/capabilities.d.ts +3 -0
  78. package/dist/fulfillment/omnipack/capabilities.js +16 -0
  79. package/dist/fulfillment/omnipack/client.d.ts +39 -0
  80. package/dist/fulfillment/omnipack/client.js +172 -0
  81. package/dist/fulfillment/omnipack/index.d.ts +55 -0
  82. package/dist/fulfillment/omnipack/index.js +408 -0
  83. package/dist/fulfillment/omnipack/mappers.d.ts +79 -0
  84. package/dist/fulfillment/omnipack/mappers.js +199 -0
  85. package/dist/fulfillment/omnipack/observation.d.ts +28 -0
  86. package/dist/fulfillment/omnipack/observation.js +72 -0
  87. package/dist/fulfillment/omnipack/omnipackDispatchPayload.d.ts +36 -0
  88. package/dist/fulfillment/omnipack/omnipackDispatchPayload.js +63 -0
  89. package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.d.ts +83 -0
  90. package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.js +113 -0
  91. package/dist/fulfillment/omnipack/omnipackProductPackPayload.d.ts +11 -0
  92. package/dist/fulfillment/omnipack/omnipackProductPackPayload.js +38 -0
  93. package/dist/fulfillment/omnipack/omnipackProductPayload.d.ts +18 -0
  94. package/dist/fulfillment/omnipack/omnipackProductPayload.js +51 -0
  95. package/dist/fulfillment/omnipack/omnipackReconciliationPayload.d.ts +1 -0
  96. package/dist/fulfillment/omnipack/omnipackReconciliationPayload.js +3 -0
  97. package/dist/fulfillment/omnipack/omnipackStatusVocabulary.d.ts +4 -0
  98. package/dist/fulfillment/omnipack/omnipackStatusVocabulary.js +87 -0
  99. package/dist/fulfillment/omnipack/omnipackWebhookPayload.d.ts +9 -0
  100. package/dist/fulfillment/omnipack/omnipackWebhookPayload.js +76 -0
  101. package/dist/fulfillment/omnipack/productSyncMappers.d.ts +18 -0
  102. package/dist/fulfillment/omnipack/productSyncMappers.js +43 -0
  103. package/dist/fulfillment/omnipack/providerError.d.ts +25 -0
  104. package/dist/fulfillment/omnipack/providerError.js +47 -0
  105. package/dist/fulfillment/omnipack/reconciliation.d.ts +15 -0
  106. package/dist/fulfillment/omnipack/reconciliation.js +55 -0
  107. package/dist/fulfillment/omnipack/trackingReferences.d.ts +16 -0
  108. package/dist/fulfillment/omnipack/trackingReferences.js +52 -0
  109. package/dist/fulfillment/paidFulfillmentRecovery.d.ts +67 -0
  110. package/dist/fulfillment/paidFulfillmentRecovery.js +154 -0
  111. package/dist/fulfillment/paidHandoff.d.ts +27 -0
  112. package/dist/fulfillment/paidHandoff.js +86 -0
  113. package/dist/fulfillment/paidHandoffPorts.d.ts +24 -0
  114. package/dist/fulfillment/paidHandoffPorts.js +3 -0
  115. package/dist/fulfillment/postgresDeliveryAlignment.d.ts +4 -0
  116. package/dist/fulfillment/postgresDeliveryAlignment.js +12 -0
  117. package/dist/fulfillment/postgresDispatchStore.d.ts +16 -0
  118. package/dist/fulfillment/postgresDispatchStore.js +44 -0
  119. package/dist/fulfillment/postgresEvidenceReadPort.d.ts +25 -0
  120. package/dist/fulfillment/postgresEvidenceReadPort.js +45 -0
  121. package/dist/fulfillment/postgresPaidFulfillmentRecovery.d.ts +25 -0
  122. package/dist/fulfillment/postgresPaidFulfillmentRecovery.js +26 -0
  123. package/dist/fulfillment/postgresPaidHandoffStore.d.ts +4 -0
  124. package/dist/fulfillment/postgresPaidHandoffStore.js +10 -0
  125. package/dist/fulfillment/postgresShipmentSpine.d.ts +70 -0
  126. package/dist/fulfillment/postgresShipmentSpine.js +246 -0
  127. package/dist/fulfillment/postgresStockSyncPort.d.ts +8 -0
  128. package/dist/fulfillment/postgresStockSyncPort.js +66 -0
  129. package/dist/fulfillment/productReconciliationEvidenceHandler.d.ts +36 -0
  130. package/dist/fulfillment/productReconciliationEvidenceHandler.js +50 -0
  131. package/dist/fulfillment/providerCapabilities.d.ts +9 -0
  132. package/dist/fulfillment/providerCapabilities.js +17 -0
  133. package/dist/fulfillment/providerCommandLedger.d.ts +143 -0
  134. package/dist/fulfillment/providerCommandLedger.js +14 -0
  135. package/dist/fulfillment/providerDispatchAcceptance.d.ts +27 -0
  136. package/dist/fulfillment/providerDispatchAcceptance.js +29 -0
  137. package/dist/fulfillment/providerDispatchError.d.ts +6 -0
  138. package/dist/fulfillment/providerDispatchError.js +5 -0
  139. package/dist/fulfillment/providerDispatchWorker.d.ts +44 -0
  140. package/dist/fulfillment/providerDispatchWorker.js +199 -0
  141. package/dist/fulfillment/providerProductSyncWorker.d.ts +28 -0
  142. package/dist/fulfillment/providerProductSyncWorker.js +122 -0
  143. package/dist/fulfillment/providerReconciliationError.d.ts +9 -0
  144. package/dist/fulfillment/providerReconciliationError.js +17 -0
  145. package/dist/fulfillment/providerReconciliationTerminalSkip.d.ts +2 -0
  146. package/dist/fulfillment/providerReconciliationTerminalSkip.js +6 -0
  147. package/dist/fulfillment/providerReconciliationTimestamp.d.ts +1 -0
  148. package/dist/fulfillment/providerReconciliationTimestamp.js +27 -0
  149. package/dist/fulfillment/providerReconciliationWorker.d.ts +90 -0
  150. package/dist/fulfillment/providerReconciliationWorker.js +207 -0
  151. package/dist/fulfillment/providerStockAvailability.d.ts +13 -0
  152. package/dist/fulfillment/providerStockAvailability.js +51 -0
  153. package/dist/fulfillment/providerStockSyncWorker.d.ts +41 -0
  154. package/dist/fulfillment/providerStockSyncWorker.js +295 -0
  155. package/dist/fulfillment/providerTrackingReferences.d.ts +16 -0
  156. package/dist/fulfillment/providerTrackingReferences.js +23 -0
  157. package/dist/fulfillment/replacementRefusals.d.ts +5 -0
  158. package/dist/fulfillment/replacementRefusals.js +38 -0
  159. package/dist/fulfillment/replacementShipmentHandler.d.ts +5 -0
  160. package/dist/fulfillment/replacementShipmentHandler.js +46 -0
  161. package/dist/fulfillment/schemaCapabilities.d.ts +13 -0
  162. package/dist/fulfillment/schemaCapabilities.js +7 -0
  163. package/dist/fulfillment/shipmentsOverviewHandler.d.ts +16 -0
  164. package/dist/fulfillment/shipmentsOverviewHandler.js +54 -0
  165. package/dist/fulfillment/statusMap.d.ts +2 -0
  166. package/dist/fulfillment/statusMap.js +1 -0
  167. package/dist/fulfillment/trackingProgress.d.ts +1 -0
  168. package/dist/fulfillment/trackingProgress.js +1 -0
  169. package/dist/fulfillment/verifiedIngress.d.ts +79 -0
  170. package/dist/fulfillment/verifiedIngress.js +94 -0
  171. package/dist/invoicing/capabilities.d.ts +11 -0
  172. package/dist/invoicing/capabilities.js +10 -0
  173. package/dist/invoicing/contracts/capabilities.d.ts +40 -0
  174. package/dist/invoicing/contracts/capabilities.js +1 -0
  175. package/dist/invoicing/contracts/companyLookup.d.ts +49 -0
  176. package/dist/invoicing/contracts/companyLookup.js +1 -0
  177. package/dist/invoicing/contracts/delivery.d.ts +74 -0
  178. package/dist/invoicing/contracts/delivery.js +1 -0
  179. package/dist/invoicing/contracts/facts.d.ts +100 -0
  180. package/dist/invoicing/contracts/facts.js +1 -0
  181. package/dist/invoicing/contracts/host.d.ts +246 -0
  182. package/dist/invoicing/contracts/host.js +1 -0
  183. package/dist/invoicing/contracts/index.d.ts +16 -0
  184. package/dist/invoicing/contracts/index.js +1 -0
  185. package/dist/invoicing/contracts/provider.d.ts +126 -0
  186. package/dist/invoicing/contracts/provider.js +1 -0
  187. package/dist/invoicing/contracts/settlement.d.ts +66 -0
  188. package/dist/invoicing/contracts/settlement.js +1 -0
  189. package/dist/invoicing/contracts/store.d.ts +267 -0
  190. package/dist/invoicing/contracts/store.js +1 -0
  191. package/dist/invoicing/index.d.ts +1 -0
  192. package/dist/invoicing/index.js +1 -0
  193. package/dist/payments/stripe/index.d.ts +25 -0
  194. package/dist/payments/stripe/index.js +25 -0
  195. package/dist/payments/tpay/index.d.ts +30 -0
  196. package/dist/payments/tpay/index.js +30 -0
  197. package/dist/subscriptions/automaticRenewal.d.ts +44 -0
  198. package/dist/subscriptions/automaticRenewal.js +81 -0
  199. package/dist/subscriptions/automaticRenewalExecution.d.ts +21 -0
  200. package/dist/subscriptions/automaticRenewalExecution.js +44 -0
  201. package/dist/subscriptions/contracts/commands.d.ts +115 -0
  202. package/dist/subscriptions/contracts/commands.js +1 -0
  203. package/dist/subscriptions/contracts/dunning.d.ts +133 -0
  204. package/dist/subscriptions/contracts/dunning.js +1 -0
  205. package/dist/subscriptions/contracts/index.d.ts +6 -0
  206. package/dist/subscriptions/contracts/index.js +1 -0
  207. package/dist/subscriptions/contracts/messages.d.ts +43 -0
  208. package/dist/subscriptions/contracts/messages.js +1 -0
  209. package/dist/subscriptions/contracts/renewal.d.ts +533 -0
  210. package/dist/subscriptions/contracts/renewal.js +1 -0
  211. package/dist/subscriptions/contracts/stockWait.d.ts +73 -0
  212. package/dist/subscriptions/contracts/stockWait.js +1 -0
  213. package/dist/subscriptions/index.d.ts +39 -0
  214. package/dist/subscriptions/index.js +17 -0
  215. package/dist/subscriptions/paymentOutcomes.d.ts +8 -0
  216. package/dist/subscriptions/paymentOutcomes.js +27 -0
  217. package/dist/subscriptions/postgresRenewalStore.d.ts +20 -0
  218. package/dist/subscriptions/postgresRenewalStore.js +61 -0
  219. package/dist/subscriptions/postgresRetryStore.d.ts +78 -0
  220. package/dist/subscriptions/postgresRetryStore.js +142 -0
  221. package/dist/subscriptions/renewalCycleFailureMechanism.d.ts +34 -0
  222. package/dist/subscriptions/renewalCycleFailureMechanism.js +79 -0
  223. package/dist/subscriptions/renewalCycleMechanism.d.ts +46 -0
  224. package/dist/subscriptions/renewalCycleMechanism.js +157 -0
  225. package/dist/subscriptions/renewalCycleRefusalMechanism.d.ts +41 -0
  226. package/dist/subscriptions/renewalCycleRefusalMechanism.js +160 -0
  227. package/dist/subscriptions/renewalIdentity.d.ts +3 -0
  228. package/dist/subscriptions/renewalIdentity.js +4 -0
  229. package/package.json +37 -2
  230. package/release-gates.json +97 -3
  231. package/sql/20260808210000_fulfillment_shipment_rail.sql +810 -0
  232. package/sql/20260819150000_fulfillment_shipment_sequence.sql +98 -0
  233. package/sql/20261022080000_fulfillment_command_ledger.sql +786 -0
  234. package/sql/20261022090000_fulfillment_delivery_alignment.sql +1106 -0
  235. package/sql/20261022160000_subscription_retry_lifecycle.sql +328 -0
  236. package/sql/20261022165000_fulfillment_stock_evidence_replay_reopen.sql +30 -0
  237. package/src/composition/composeBundle.ts +4 -0
  238. package/src/composition/fulfillmentContribution.ts +28 -0
  239. package/src/composition/index.ts +1 -0
  240. package/src/fulfillment/adapterConformance.ts +195 -0
  241. package/src/fulfillment/bindings.ts +26 -0
  242. package/src/fulfillment/commerceFulfillmentHandlers.ts +263 -0
  243. package/src/fulfillment/commerceFulfillmentReadModel.ts +257 -0
  244. package/src/fulfillment/contracts/alignment.ts +13 -0
  245. package/src/fulfillment/contracts/bindings.ts +30 -0
  246. package/src/fulfillment/contracts/capabilities.ts +15 -0
  247. package/src/fulfillment/contracts/conformance.ts +63 -0
  248. package/src/fulfillment/contracts/customerAlignment.ts +4 -0
  249. package/src/fulfillment/contracts/dispatch.ts +73 -0
  250. package/src/fulfillment/contracts/index.ts +18 -0
  251. package/src/fulfillment/contracts/label.ts +9 -0
  252. package/src/fulfillment/contracts/nativeUnsupported.ts +6 -0
  253. package/src/fulfillment/contracts/observation.ts +26 -0
  254. package/src/fulfillment/contracts/operator.ts +31 -0
  255. package/src/fulfillment/contracts/read.ts +42 -0
  256. package/src/fulfillment/contracts/readback.ts +24 -0
  257. package/src/fulfillment/contracts/replacement.ts +15 -0
  258. package/src/fulfillment/contracts/store.ts +27 -0
  259. package/src/fulfillment/contracts/sync.ts +72 -0
  260. package/src/fulfillment/currentFulfillmentParcel.ts +2 -0
  261. package/src/fulfillment/delayReadModel.ts +60 -0
  262. package/src/fulfillment/deliveryAlignmentReadModel.ts +15 -0
  263. package/src/fulfillment/dispatchProjection.ts +105 -0
  264. package/src/fulfillment/fulfillmentObservationEffects.ts +86 -0
  265. package/src/fulfillment/handlerSchemas.ts +255 -0
  266. package/src/fulfillment/handlerSupport.ts +25 -0
  267. package/src/fulfillment/index.ts +39 -0
  268. package/src/fulfillment/labelLifecycle.ts +11 -0
  269. package/src/fulfillment/lowStockEvidenceHandler.ts +60 -0
  270. package/src/fulfillment/migrations.ts +9 -0
  271. package/src/fulfillment/nativeUnsupported.ts +23 -0
  272. package/src/fulfillment/normalizedFulfillmentFacts.ts +2 -0
  273. package/src/fulfillment/observationProjection.ts +92 -0
  274. package/src/fulfillment/omnipack/capabilities.ts +18 -0
  275. package/src/fulfillment/omnipack/client.ts +270 -0
  276. package/src/fulfillment/omnipack/index.ts +369 -0
  277. package/src/fulfillment/omnipack/mappers.ts +282 -0
  278. package/src/fulfillment/omnipack/observation.ts +79 -0
  279. package/src/fulfillment/omnipack/omnipackDispatchPayload.ts +93 -0
  280. package/src/fulfillment/omnipack/omnipackOutboundOrderPayload.ts +194 -0
  281. package/src/fulfillment/omnipack/omnipackProductPackPayload.ts +47 -0
  282. package/src/fulfillment/omnipack/omnipackProductPayload.ts +67 -0
  283. package/src/fulfillment/omnipack/omnipackReconciliationPayload.ts +7 -0
  284. package/src/fulfillment/omnipack/omnipackStatusVocabulary.ts +103 -0
  285. package/src/fulfillment/omnipack/omnipackWebhookPayload.ts +73 -0
  286. package/src/fulfillment/omnipack/productSyncMappers.ts +74 -0
  287. package/src/fulfillment/omnipack/providerError.ts +64 -0
  288. package/src/fulfillment/omnipack/reconciliation.ts +56 -0
  289. package/src/fulfillment/omnipack/trackingReferences.ts +77 -0
  290. package/src/fulfillment/paidFulfillmentRecovery.ts +252 -0
  291. package/src/fulfillment/paidHandoff.ts +122 -0
  292. package/src/fulfillment/paidHandoffPorts.ts +21 -0
  293. package/src/fulfillment/postgresDeliveryAlignment.ts +14 -0
  294. package/src/fulfillment/postgresDispatchStore.ts +55 -0
  295. package/src/fulfillment/postgresEvidenceReadPort.ts +59 -0
  296. package/src/fulfillment/postgresPaidFulfillmentRecovery.ts +29 -0
  297. package/src/fulfillment/postgresPaidHandoffStore.ts +11 -0
  298. package/src/fulfillment/postgresShipmentSpine.ts +338 -0
  299. package/src/fulfillment/postgresStockSyncPort.ts +68 -0
  300. package/src/fulfillment/productReconciliationEvidenceHandler.ts +64 -0
  301. package/src/fulfillment/providerCapabilities.ts +18 -0
  302. package/src/fulfillment/providerCommandLedger.ts +53 -0
  303. package/src/fulfillment/providerDispatchAcceptance.ts +37 -0
  304. package/src/fulfillment/providerDispatchError.ts +7 -0
  305. package/src/fulfillment/providerDispatchWorker.ts +159 -0
  306. package/src/fulfillment/providerProductSyncWorker.ts +154 -0
  307. package/src/fulfillment/providerReconciliationError.ts +20 -0
  308. package/src/fulfillment/providerReconciliationTerminalSkip.ts +8 -0
  309. package/src/fulfillment/providerReconciliationTimestamp.ts +34 -0
  310. package/src/fulfillment/providerReconciliationWorker.ts +206 -0
  311. package/src/fulfillment/providerStockAvailability.ts +60 -0
  312. package/src/fulfillment/providerStockSyncWorker.ts +318 -0
  313. package/src/fulfillment/providerTrackingReferences.ts +32 -0
  314. package/src/fulfillment/replacementRefusals.ts +43 -0
  315. package/src/fulfillment/replacementShipmentHandler.ts +47 -0
  316. package/src/fulfillment/schemaCapabilities.ts +7 -0
  317. package/src/fulfillment/shipmentsOverviewHandler.ts +73 -0
  318. package/src/fulfillment/statusMap.ts +2 -0
  319. package/src/fulfillment/trackingProgress.ts +1 -0
  320. package/src/fulfillment/verifiedIngress.ts +113 -0
  321. package/src/invoicing/capabilities.ts +9 -0
  322. package/src/invoicing/contracts/capabilities.ts +15 -0
  323. package/src/invoicing/contracts/companyLookup.ts +13 -0
  324. package/src/invoicing/contracts/delivery.ts +24 -0
  325. package/src/invoicing/contracts/facts.ts +60 -0
  326. package/src/invoicing/contracts/host.ts +85 -0
  327. package/src/invoicing/contracts/index.ts +16 -0
  328. package/src/invoicing/contracts/provider.ts +78 -0
  329. package/src/invoicing/contracts/settlement.ts +39 -0
  330. package/src/invoicing/contracts/store.ts +98 -0
  331. package/src/invoicing/index.ts +1 -0
  332. package/src/payments/stripe/index.ts +30 -0
  333. package/src/payments/tpay/index.ts +36 -0
  334. package/src/subscriptions/automaticRenewal.ts +87 -0
  335. package/src/subscriptions/automaticRenewalExecution.ts +39 -0
  336. package/src/subscriptions/contracts/commands.ts +46 -0
  337. package/src/subscriptions/contracts/dunning.ts +48 -0
  338. package/src/subscriptions/contracts/index.ts +6 -0
  339. package/src/subscriptions/contracts/messages.ts +18 -0
  340. package/src/subscriptions/contracts/renewal.ts +325 -0
  341. package/src/subscriptions/contracts/stockWait.ts +34 -0
  342. package/src/subscriptions/index.ts +24 -0
  343. package/src/subscriptions/paymentOutcomes.ts +23 -0
  344. package/src/subscriptions/postgresRenewalStore.ts +76 -0
  345. package/src/subscriptions/postgresRetryStore.ts +174 -0
  346. package/src/subscriptions/renewalCycleFailureMechanism.ts +119 -0
  347. package/src/subscriptions/renewalCycleMechanism.ts +225 -0
  348. package/src/subscriptions/renewalCycleRefusalMechanism.ts +215 -0
  349. package/src/subscriptions/renewalIdentity.ts +5 -0
@@ -0,0 +1,282 @@
1
+ import {
2
+ mapOmnipackTrackingReferences,
3
+ type OmnipackTrackingReferenceEvidence,
4
+ } from "./trackingReferences.js";
5
+
6
+ export interface OmnipackOrderCreatedEvidence {
7
+ provider: "omnipack";
8
+ providerOrderId: string;
9
+ raw: Record<string, unknown>;
10
+ }
11
+
12
+ export interface OmnipackProductCreatedEvidence {
13
+ provider: "omnipack";
14
+ sku: string;
15
+ raw: Record<string, unknown>;
16
+ }
17
+
18
+ export interface OmnipackStockEvidence {
19
+ provider: "omnipack";
20
+ sku: string;
21
+ totalQuantity: number;
22
+ forSaleQuantity: number;
23
+ reservedOrUnavailableQuantity: number;
24
+ stockTruth: "external_stock_master_with_local_reservations";
25
+ }
26
+
27
+ export interface OmnipackStockMovementEvidence {
28
+ provider: "omnipack";
29
+ sku: string;
30
+ occurredAt: string | null;
31
+ quantity: number;
32
+ operationType: string | null;
33
+ warehouseDocumentNumber: string | null;
34
+ lotNumber: string | null;
35
+ expirationDate: string | null;
36
+ }
37
+
38
+ export interface OmnipackFulfilmentEvidence {
39
+ provider: "omnipack";
40
+ providerOrderId: string | null;
41
+ fulfilmentNumber: string;
42
+ externalNumber: string | null;
43
+ orderNumber: string | null;
44
+ createdAt: string | null;
45
+ updatedAt: string | null;
46
+ status: string | null;
47
+ subStatus: string | null;
48
+ trackingNumbers: string[];
49
+ trackingReferences: OmnipackTrackingReferenceEvidence[];
50
+ items: Array<{ sku: string; quantity: number; lotNumber: string | null; expirationDate: string | null }>;
51
+ }
52
+
53
+ export interface OmnipackFulfilmentRequestEvidence {
54
+ provider: "omnipack";
55
+ warehouseDocumentNumber: string;
56
+ warehouseName: string | null;
57
+ state: string | null;
58
+ }
59
+
60
+ export interface OmnipackWebhookEvidence {
61
+ rejectedTrackingRefs?: number;
62
+ rejectedServices?: number;
63
+ rejectedTrackingUrls?: number;
64
+ provider: "omnipack";
65
+ event: string;
66
+ providerOrderId: string | null;
67
+ orderNumber: string | null;
68
+ fulfilmentNumber: string | null;
69
+ occurredAt: string | null;
70
+ trackingNumbers: string[];
71
+ trackingReferences: OmnipackTrackingReferenceEvidence[];
72
+ shippingMethods: string[];
73
+ }
74
+
75
+ export function mapOmnipackOrderCreatedResponse(body: unknown): OmnipackOrderCreatedEvidence {
76
+ const response = asRecord(body);
77
+ const providerOrderId = readString(response.orderId);
78
+ if (!providerOrderId) throw new Error("omnipack_order_response_invalid");
79
+ return {
80
+ provider: "omnipack",
81
+ providerOrderId,
82
+ raw: sanitizeOmnipackPayload(response),
83
+ };
84
+ }
85
+
86
+ // Catalog load (test-readiness). The product/inbound responses are evidence only — we read
87
+ // back authoritative stock via getStock. We tolerate a sparse response body and fall back to
88
+ // the request-side sku/reference so an idempotent re-create still yields usable evidence.
89
+ export function mapOmnipackProductCreatedResponse(body: unknown, requestSku: string): OmnipackProductCreatedEvidence {
90
+ const response = asRecord(body);
91
+ return {
92
+ provider: "omnipack",
93
+ sku: readString(response.sku) || requestSku,
94
+ raw: sanitizeOmnipackPayload(response),
95
+ };
96
+ }
97
+
98
+ export function mapOmnipackStockResponse(body: unknown): OmnipackStockEvidence[] {
99
+ const stockItems = readArray(asRecord(asRecord(body)._embedded).stockItems);
100
+ return stockItems.map((item) => {
101
+ const row = asRecord(item);
102
+ const totalQuantity = readNumber(row.totalQuantity);
103
+ const forSaleQuantity = readNumber(row.forSaleQuantity);
104
+ const unavailable = [
105
+ row.expireSoonQuantity,
106
+ row.faultyQuantity,
107
+ row.complaintsQuantity,
108
+ row.withheldQuantity,
109
+ row.packagingQuantity,
110
+ row.processingQuantity,
111
+ row.unpackingQuantity,
112
+ ].map(readNumber).reduce((sum, value) => sum + value, 0);
113
+ return {
114
+ provider: "omnipack",
115
+ sku: readString(row.sku),
116
+ totalQuantity,
117
+ forSaleQuantity,
118
+ reservedOrUnavailableQuantity: unavailable,
119
+ stockTruth: "external_stock_master_with_local_reservations",
120
+ };
121
+ });
122
+ }
123
+
124
+ export function mapOmnipackStockMovementsResponse(body: unknown): OmnipackStockMovementEvidence[] {
125
+ return readArray(asRecord(body)._embedded).map((item) => {
126
+ const row = asRecord(item);
127
+ return {
128
+ provider: "omnipack",
129
+ sku: readString(row.sku),
130
+ occurredAt: readNullableString(row.occurredAt),
131
+ quantity: readNumber(row.quantity),
132
+ operationType: readNullableString(row.operationType),
133
+ warehouseDocumentNumber: readNullableString(row.warehouseDocumentNumber),
134
+ lotNumber: readNullableString(row.productLotNumber),
135
+ expirationDate: readNullableString(row.expirationDate),
136
+ };
137
+ });
138
+ }
139
+
140
+ export function mapOmnipackFulfilmentsResponse(body: unknown): OmnipackFulfilmentEvidence[] {
141
+ const fulfilments = readArray(asRecord(asRecord(body)._embedded).fulfilments);
142
+ return fulfilments.map((item) => {
143
+ const row = asRecord(item);
144
+ const trackingReferences = trackingReferencesFrom(row);
145
+ return {
146
+ provider: "omnipack",
147
+ providerOrderId: readNullableString(row.orderId) ?? readNullableString(row.providerOrderId),
148
+ fulfilmentNumber: readString(row.fulfilmentNumber),
149
+ externalNumber: readNullableString(row.externalNumber),
150
+ orderNumber: readNullableString(row.orderNumber),
151
+ createdAt: readNullableString(row.createdAt),
152
+ updatedAt: readNullableString(row.updatedAt),
153
+ status: readNullableString(row.status),
154
+ subStatus: readNullableString(row.subStatus),
155
+ trackingNumbers: trackingReferences.map((ref) => ref.trackingNumber),
156
+ trackingReferences,
157
+ items: readArray(row.items).map((rawItem) => {
158
+ const fulfilmentItem = asRecord(rawItem);
159
+ return {
160
+ sku: readString(fulfilmentItem.sku),
161
+ quantity: readNumber(fulfilmentItem.quantity),
162
+ lotNumber: readNullableString(fulfilmentItem.productLotNumber),
163
+ expirationDate: readNullableString(fulfilmentItem.expirationDate),
164
+ };
165
+ }),
166
+ };
167
+ });
168
+ }
169
+
170
+ export function mapOmnipackFulfilmentRequestsResponse(body: unknown): OmnipackFulfilmentRequestEvidence[] {
171
+ const requests = readArray(asRecord(asRecord(body)._embedded).fulfilmentRequests);
172
+ return requests.map((item) => {
173
+ const row = asRecord(item);
174
+ return {
175
+ provider: "omnipack",
176
+ warehouseDocumentNumber: readString(row.warehouseDocumentNumber),
177
+ warehouseName: readNullableString(row.warehouseName),
178
+ state: readNullableString(row.state),
179
+ };
180
+ });
181
+ }
182
+
183
+ export function mapOmnipackWebhookPayload(body: unknown, fallbackEvent = "", admitTrackingNumber?: (value: string) => boolean,
184
+ admitMetadata?: { service(value: string): boolean; url(value: string): boolean }): OmnipackWebhookEvidence {
185
+ const payload = asRecord(body);
186
+ const shipments = readArray(payload.shipments).map(asRecord);
187
+ let rejectedTrackingRefs = 0, rejectedServices = 0, rejectedTrackingUrls = 0;
188
+ // Count every supplied spelling before extraction, tracking refusal or first-wins deduplication.
189
+ if (admitMetadata) for (const row of shipments) {
190
+ for (const value of [row.shippingMethod, row.service]) {
191
+ if (value != null && (typeof value !== "string" || !admitMetadata.service(value))) rejectedServices++;
192
+ }
193
+ for (const value of [row.trackingUrl, row.trackingURL, row.trackingLink, row.publicTrackingUrl]) {
194
+ if (value != null && (typeof value !== "string" || !admitMetadata.url(value))) rejectedTrackingUrls++;
195
+ }
196
+ }
197
+ // Admit and count original entries before the contributed extractor trims/deduplicates.
198
+ const admitted = admitTrackingNumber ? shipments.filter(row => {
199
+ if (admitTrackingNumber(readString(row.trackingNo) || readString(row.trackingNumber))) return true;
200
+ rejectedTrackingRefs++; return false;
201
+ }) : shipments;
202
+ const trackingReferences = trackingReferencesFrom({ shipments: admitted });
203
+ return {
204
+ provider: "omnipack",
205
+ event: readString(payload.event) || fallbackEvent,
206
+ ...(rejectedTrackingRefs ? { rejectedTrackingRefs } : {}),
207
+ ...(admitMetadata ? { rejectedServices, rejectedTrackingUrls } : {}),
208
+ providerOrderId: readNullableString(payload.orderId) ?? readNullableString(payload.providerOrderId),
209
+ orderNumber: readNullableString(payload.orderNumber),
210
+ fulfilmentNumber: readNullableString(payload.fulfilmentNumber),
211
+ occurredAt: readNullableString(payload.occurredAt),
212
+ trackingNumbers: trackingReferences.map((ref) => ref.trackingNumber),
213
+ trackingReferences,
214
+ shippingMethods: shipments.map((shipment) => readString(shipment.shippingMethod)).filter(Boolean),
215
+ };
216
+ }
217
+
218
+ export function sanitizeOmnipackPayload(input: Record<string, unknown>): Record<string, unknown> {
219
+ const blocked = new Set([
220
+ "authorization",
221
+ "Authorization",
222
+ "password",
223
+ "email",
224
+ "phone",
225
+ "firstName",
226
+ "lastName",
227
+ "name",
228
+ "address",
229
+ "street",
230
+ "streetNumber",
231
+ "houseNo",
232
+ "flatNo",
233
+ "city",
234
+ "postalCode",
235
+ ]);
236
+ return sanitizeRecord(input, blocked);
237
+ }
238
+
239
+ function sanitizeRecord(input: Record<string, unknown>, blocked: Set<string>): Record<string, unknown> {
240
+ return Object.fromEntries(
241
+ Object.entries(input)
242
+ .filter(([key]) => !blocked.has(key))
243
+ .map(([key, value]) => [key, sanitizeValue(value, blocked)]),
244
+ );
245
+ }
246
+
247
+ function sanitizeValue(input: unknown, blocked: Set<string>): unknown {
248
+ if (Array.isArray(input)) return input.map((value) => sanitizeValue(value, blocked));
249
+ if (input && typeof input === "object") return sanitizeRecord(input as Record<string, unknown>, blocked);
250
+ return input;
251
+ }
252
+
253
+ function asRecord(input: unknown): Record<string, unknown> {
254
+ return input && typeof input === "object" && !Array.isArray(input) ? input as Record<string, unknown> : {};
255
+ }
256
+
257
+ function readArray(input: unknown): unknown[] {
258
+ return Array.isArray(input) ? input : [];
259
+ }
260
+
261
+ function readString(input: unknown): string {
262
+ return typeof input === "string" ? input : "";
263
+ }
264
+
265
+ function readNullableString(input: unknown): string | null {
266
+ const value = readString(input);
267
+ return value || null;
268
+ }
269
+
270
+ function readNumber(input: unknown): number {
271
+ return typeof input === "number" && Number.isFinite(input) ? input : 0;
272
+ }
273
+
274
+ function trackingReferencesFrom(row: Record<string, unknown>): OmnipackTrackingReferenceEvidence[] {
275
+ return mapOmnipackTrackingReferences({
276
+ shipments: readArray(row.shipments),
277
+ trackingNumbers: readArray(row.trackingNumbers),
278
+ readString,
279
+ readNullableString,
280
+ asRecord,
281
+ });
282
+ }
@@ -0,0 +1,79 @@
1
+ import { admittedReference, reference } from "../dispatchProjection.js";
2
+ import { projectObservation, publicTrackingUrl } from "../observationProjection.js";
3
+ export { reference } from "../dispatchProjection.js";
4
+ import { createHash } from "node:crypto";
5
+ import { parseNormalizedFulfillmentFact } from "@openlup/core/fulfillment";
6
+ import type { CanonicalLocalFulfillmentStatus, FulfillmentObservation, FulfillmentObservationCodecs, FulfillmentRefusal } from "../contracts/index.js";
7
+ import type { OmnipackWebhookEvidence } from "./mappers.js";
8
+ import type { OmnipackTrackingReferenceEvidence } from "./trackingReferences.js";
9
+ import { buildProviderEventId, EVENT_TO_STATUS, type OmnipackWebhookRouteEvent } from "./omnipackWebhookPayload.js";
10
+ import { localStatusFor } from "./omnipackStatusVocabulary.js";
11
+
12
+ export const refusal = (reason: FulfillmentRefusal["reason"], phase: FulfillmentRefusal["phase"], retryable = false): FulfillmentRefusal => ({ reason, phase, retryable });
13
+ export const digest = (bytes: string | Uint8Array): string => createHash("sha256").update(bytes).digest("hex");
14
+ /** Share admission with the durable sink, including explicit deny allowlists. */
15
+ export function correlationReference(value: string | null | undefined, codecs: OmnipackObservationCodecs = {}): string | null {
16
+ return admittedReference(value, codecs.approveReference);
17
+ }
18
+ export interface OmnipackObservationCodecs extends FulfillmentObservationCodecs {
19
+ normalizeCarrier?(input: { carrierRef: string | null; serviceRef: string | null }): string | null;
20
+ }
21
+ export function timestamp(value: string | null): string | null {
22
+ return value && /^\d{4}-\d\d-\d\dT/u.test(value) && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : null;
23
+ }
24
+ export function eventKind(value: string): OmnipackWebhookRouteEvent | null {
25
+ return Object.hasOwn(EVENT_TO_STATUS, value) ? value as OmnipackWebhookRouteEvent : null;
26
+ }
27
+ function tracking(refs: OmnipackTrackingReferenceEvidence[], codecs: OmnipackObservationCodecs, approve: (url: string) => boolean) {
28
+ let rejectedTrackingRefs = 0, rejectedServices = 0, rejectedTrackingUrls = 0;
29
+ const trackingRefs: FulfillmentObservation["trackingRefs"][number][] = [];
30
+ for (const item of refs) {
31
+ if (item.service !== null && !correlationReference(item.service, codecs)) rejectedServices++;
32
+ if (item.trackingUrl !== null && !publicTrackingUrl(item.trackingUrl, approve, codecs.trackingUrl)) rejectedTrackingUrls++;
33
+ const trackingNumber = correlationReference(item.trackingNumber, codecs);
34
+ if (!trackingNumber) { rejectedTrackingRefs++; continue; }
35
+ // Service admission precedes any derivation. A refused service cannot leak a prefix.
36
+ const serviceRef = correlationReference(item.service, codecs);
37
+ const carrierRef = item.service !== null && !serviceRef ? null : reference(codecs.normalizeCarrier
38
+ ? codecs.normalizeCarrier({ carrierRef: item.carrier, serviceRef })
39
+ : serviceRef?.split("_")[0]?.toLowerCase());
40
+ trackingRefs.push({ trackingNumber, carrierRef, serviceRef: item.service, publicUrl: item.trackingUrl });
41
+ }
42
+ return { trackingRefs, rejectedTrackingRefs, rejectedServices, rejectedTrackingUrls };
43
+ }
44
+ export function observation(input: {
45
+ evidence: OmnipackWebhookEvidence; event: FulfillmentObservation["eventKind"]; status?: CanonicalLocalFulfillmentStatus;
46
+ receivedAt: string; rawDigest: string; evidenceRef: `evidence:${string}`;
47
+ source: "provider_webhook" | "provider_reconciliation"; fulfillmentRef?: string;
48
+ approveTrackingUrl: (url: string) => boolean;
49
+ observationCodecs?: OmnipackObservationCodecs;
50
+ }): FulfillmentObservation {
51
+ const evidence = input.evidence;
52
+ const codecs = input.observationCodecs ?? {};
53
+ const externalRef = correlationReference(evidence.providerOrderId, codecs);
54
+ const providerFulfillmentRef = correlationReference(evidence.fulfilmentNumber, codecs);
55
+ const orderNumber = correlationReference(evidence.orderNumber, codecs);
56
+ const fulfillmentRef = reference(input.fulfillmentRef) ?? providerFulfillmentRef ?? externalRef ?? orderNumber;
57
+ if (!fulfillmentRef || input.source === "provider_webhook" && [evidence.providerOrderId, evidence.orderNumber, evidence.fulfilmentNumber].some(value => value !== null && !correlationReference(value, codecs))) throw refusal("missing_correlation", "normalize");
58
+ const occurredAt = timestamp(evidence.occurredAt);
59
+ if (evidence.occurredAt && !occurredAt) throw refusal("malformed", "normalize");
60
+ // Compose the key with the provider's
61
+ // original bytes, including minute precision and offsets, rather than the UTC fact.
62
+ const key = buildProviderEventId(evidence);
63
+ const normalizedTracking = tracking(evidence.trackingReferences, codecs, input.approveTrackingUrl);
64
+ const rejectedTrackingRefs = normalizedTracking.rejectedTrackingRefs + (evidence.rejectedTrackingRefs ?? 0)
65
+ + (evidence.rejectedServices ?? normalizedTracking.rejectedServices);
66
+ const status = input.status ?? localStatusFor(EVENT_TO_STATUS[input.event as OmnipackWebhookRouteEvent]);
67
+ const fact = parseNormalizedFulfillmentFact({
68
+ schemaVersion: 1, mapperVersion: "1", factId: digest(key), providerKind: "omnipack", fulfillmentReference: reference(fulfillmentRef) ?? digest(fulfillmentRef),
69
+ canonicalStatus: status, provenance: { source: input.source, authority: "provider_asserted" },
70
+ occurredAt, receivedAt: input.receivedAt,
71
+ rawEvent: { reference: input.evidenceRef, digestAlgorithm: "sha256", digest: input.rawDigest },
72
+ }, { isCanonicalStatus: (value): value is CanonicalLocalFulfillmentStatus =>
73
+ ["provider_received", "picking", "packed", "in_transit", "delivered", "exception", "cancelled"].includes(value) });
74
+ return projectObservation({ fact, eventKind: input.event, eventKey: key, firstReceivedAt: input.receivedAt,
75
+ correlation: { orderRef: null, fulfillmentRef: reference(input.fulfillmentRef), externalRef: evidence.providerOrderId, orderNumber: evidence.orderNumber, providerFulfillmentRef: evidence.fulfilmentNumber, parcelRef: null },
76
+ trackingRefs: normalizedTracking.trackingRefs, diagnostics: { admission: {
77
+ rejectedTrackingRefs, rejectedTrackingUrls: evidence.rejectedTrackingUrls ?? normalizedTracking.rejectedTrackingUrls,
78
+ } } }, input.approveTrackingUrl, codecs);
79
+ }
@@ -0,0 +1,93 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ buildOmnipackOutboundOrderEvidence,
4
+ buildOmnipackOutboundOrderPayload,
5
+ type OmnipackOutboundOrderPayload,
6
+ type OmnipackOutboundDeliverySelection,
7
+ } from "./omnipackOutboundOrderPayload.js";
8
+ // Structural slice consumed by the donor codec; persistence and orchestration stay outside.
9
+ export interface OmnipackDispatchCandidate {
10
+ orderId: string;
11
+ orderNumber: string | null;
12
+ deliveryContact: {
13
+ revision: number;
14
+ recipientName: string | null;
15
+ contactEmail: string | null;
16
+ contactPhone: string | null;
17
+ line1: string;
18
+ city: string;
19
+ postalCode: string;
20
+ country: string;
21
+ selectedDelivery: OmnipackOutboundDeliverySelection | null;
22
+ } | null;
23
+ lines: Array<{ sku: string; quantity: number; productSnapshot: Record<string, unknown> }>;
24
+ }
25
+
26
+ export { buildOmnipackOutboundOrderEvidence as buildSanitizedEvidence };
27
+
28
+ export function buildOmnipackDispatchCommand(candidate: OmnipackDispatchCandidate) {
29
+ const providerPayload = buildOmnipackDispatchPayloadFromCandidate(candidate);
30
+ const sanitizedRequest = {
31
+ ...buildOmnipackOutboundOrderEvidence(providerPayload),
32
+ deliveryContactRevision: candidate.deliveryContact?.revision ?? 0,
33
+ };
34
+ const serialized = serializeOmnipackDispatchPayload(providerPayload);
35
+ return {
36
+ providerPayload,
37
+ body: serialized.body,
38
+ sanitizedRequest,
39
+ // The hash commits to the request that may create a real provider effect.
40
+ // Only its digest is persisted; the PII-bearing input is never evidence.
41
+ requestFingerprint: serialized.requestFingerprint,
42
+ };
43
+ }
44
+
45
+ export function buildOmnipackDispatchPayloadFromCandidate(
46
+ candidate: OmnipackDispatchCandidate,
47
+ ): OmnipackOutboundOrderPayload {
48
+ const contact = candidate.deliveryContact;
49
+ if (!contact) throw new Error("omnipack_order_payload_missing_delivery_contact");
50
+ return buildOmnipackOutboundOrderPayload({
51
+ orderNumber: candidate.orderNumber ?? candidate.orderId,
52
+ deliverySelection: contact.selectedDelivery,
53
+ recipient: {
54
+ name: contact.recipientName ?? "",
55
+ email: contact.contactEmail,
56
+ phone: contact.contactPhone,
57
+ },
58
+ address: {
59
+ line1: contact.line1,
60
+ city: contact.city,
61
+ postalCode: contact.postalCode,
62
+ country: contact.country,
63
+ },
64
+ items: candidate.lines.map((line) => ({
65
+ sku: line.sku,
66
+ quantity: line.quantity,
67
+ lotNumber: nullableText(line.productSnapshot.lotNumber ?? line.productSnapshot.lotCode),
68
+ expirationDate: nullableText(line.productSnapshot.expirationDate ?? line.productSnapshot.expiresAt),
69
+ })),
70
+ });
71
+ }
72
+
73
+ export function stableDispatchHash(value: unknown): string {
74
+ return serializeOmnipackDispatchPayload(value).requestFingerprint;
75
+ }
76
+
77
+ // Materialize once: the digest and createOrder consume these exact UTF-8 bytes.
78
+ export function serializeOmnipackDispatchPayload(value: unknown): { body: string; requestFingerprint: string } {
79
+ const body = JSON.stringify(sortRecord(value));
80
+ return { body, requestFingerprint: createHash("sha256").update(body).digest("hex") };
81
+ }
82
+
83
+ function sortRecord(value: unknown): unknown {
84
+ if (Array.isArray(value)) return value.map(sortRecord);
85
+ if (!value || typeof value !== "object") return value;
86
+ return Object.fromEntries(
87
+ Object.entries(value as Record<string, unknown>).sort().map(([key, child]) => [key, sortRecord(child)]),
88
+ );
89
+ }
90
+
91
+ function nullableText(value: unknown): string | null {
92
+ return typeof value === "string" && value.trim() ? value.trim() : null;
93
+ }
@@ -0,0 +1,194 @@
1
+ export type OmnipackOutboundDeliveryKind = "courier" | "parcel-locker";
2
+
3
+ export interface OmnipackOutboundOrderInput {
4
+ orderNumber: string;
5
+ description?: string | null;
6
+ additionalCustomerOrderNumber?: string | null;
7
+ deliverySelection: OmnipackOutboundDeliverySelection | null;
8
+ recipient: {
9
+ name: string;
10
+ email: string | null;
11
+ phone: string | null;
12
+ };
13
+ address: {
14
+ line1: string;
15
+ city: string;
16
+ postalCode: string;
17
+ country: string;
18
+ };
19
+ items: Array<{
20
+ sku: string;
21
+ quantity: number;
22
+ lotNumber?: string | null;
23
+ expirationDate?: string | null;
24
+ }>;
25
+ orderValue?: { amount: number; currency: string } | null;
26
+ }
27
+
28
+ export interface OmnipackOutboundDeliverySelection {
29
+ providerKind?: string | null;
30
+ carrierKind?: string | null;
31
+ carrierCode?: string | null;
32
+ serviceCode?: string | null;
33
+ service?: string | null;
34
+ deliveryKind?: string | null;
35
+ kind?: string | null;
36
+ pickupPoint?: {
37
+ id?: string | null;
38
+ pointId?: string | null;
39
+ name?: string | null;
40
+ address?: {
41
+ line1?: string | null;
42
+ postalCode?: string | null;
43
+ city?: string | null;
44
+ country?: string | null;
45
+ } | null;
46
+ } | null;
47
+ }
48
+
49
+ export interface OmnipackOutboundOrderPayload {
50
+ orderNumber: string;
51
+ description?: string;
52
+ additionalCustomerOrderNumber?: string;
53
+ items: Array<{
54
+ sku: string;
55
+ quantity: number;
56
+ lotNumber?: string;
57
+ expirationDate?: string;
58
+ }>;
59
+ shippingDetails: {
60
+ carrier: string;
61
+ service: string;
62
+ address: {
63
+ name: string;
64
+ street: string;
65
+ city: string;
66
+ postCode: string;
67
+ phone: string;
68
+ email?: string;
69
+ country: string;
70
+ };
71
+ pickUpPoint?: string;
72
+ };
73
+ orderValue?: { amount: number; currency: string };
74
+ }
75
+
76
+ export class OmnipackOutboundOrderPayloadError extends Error {
77
+ constructor(readonly code: string) {
78
+ super(code);
79
+ this.name = "OmnipackOutboundOrderPayloadError";
80
+ }
81
+ }
82
+
83
+ export function buildOmnipackOutboundOrderPayload(input: OmnipackOutboundOrderInput): OmnipackOutboundOrderPayload {
84
+ const orderNumber = requiredText(input.orderNumber, "omnipack_order_payload_missing_order_number");
85
+ const selection = input.deliverySelection;
86
+ const providerKind = optionalText(selection?.providerKind);
87
+ if (providerKind !== "omnipack") {
88
+ throw new OmnipackOutboundOrderPayloadError("omnipack_order_payload_missing_omnipack_delivery_selection");
89
+ }
90
+ const service = requiredText(selection?.serviceCode ?? selection?.service, "omnipack_order_payload_missing_service");
91
+ const carrier = service;
92
+ const deliveryKind = readDeliveryKind(selection);
93
+ const pickUpPoint = readPickUpPoint(deliveryKind, selection);
94
+ const items = input.items.map((item) => {
95
+ const lotNumber = optionalText(item.lotNumber);
96
+ const expirationDate = optionalText(item.expirationDate);
97
+ return {
98
+ sku: requiredText(item.sku, "omnipack_order_payload_missing_sku"),
99
+ quantity: requiredQuantity(item.quantity),
100
+ ...(lotNumber ? { lotNumber } : {}),
101
+ ...(expirationDate ? { expirationDate } : {}),
102
+ };
103
+ });
104
+ if (items.length === 0) {
105
+ throw new OmnipackOutboundOrderPayloadError("omnipack_order_payload_missing_items");
106
+ }
107
+
108
+ return compactRecord({
109
+ orderNumber,
110
+ description: optionalText(input.description),
111
+ additionalCustomerOrderNumber: optionalText(input.additionalCustomerOrderNumber),
112
+ items,
113
+ shippingDetails: compactRecord({
114
+ carrier,
115
+ service,
116
+ address: compactRecord({
117
+ name: requiredText(input.recipient.name, "omnipack_order_payload_missing_recipient_name"),
118
+ street: requiredText(input.address.line1, "omnipack_order_payload_missing_address_street"),
119
+ city: requiredText(input.address.city, "omnipack_order_payload_missing_address_city"),
120
+ postCode: requiredText(input.address.postalCode, "omnipack_order_payload_missing_address_post_code"),
121
+ phone: requiredText(input.recipient.phone, "omnipack_order_payload_missing_recipient_phone"),
122
+ email: optionalText(input.recipient.email),
123
+ country: requiredText(input.address.country, "omnipack_order_payload_missing_address_country"),
124
+ }),
125
+ pickUpPoint,
126
+ }),
127
+ orderValue: validOrderValue(input.orderValue),
128
+ }) as OmnipackOutboundOrderPayload;
129
+ }
130
+
131
+ export function buildOmnipackOutboundOrderEvidence(payload: OmnipackOutboundOrderPayload): Record<string, unknown> {
132
+ return {
133
+ provider: "omnipack",
134
+ requestKind: "outbound_order",
135
+ orderNumber: payload.orderNumber,
136
+ carrier: payload.shippingDetails.carrier,
137
+ service: payload.shippingDetails.service,
138
+ pickUpPoint: payload.shippingDetails.pickUpPoint ?? null,
139
+ itemCount: payload.items.length,
140
+ items: payload.items.map((item) => ({
141
+ sku: item.sku,
142
+ quantity: item.quantity,
143
+ lotNumber: item.lotNumber ?? null,
144
+ expirationDate: item.expirationDate ?? null,
145
+ })),
146
+ stockTruth: "external_stock_master_with_local_reservations",
147
+ };
148
+ }
149
+
150
+ function readDeliveryKind(selection: OmnipackOutboundDeliverySelection | null): OmnipackOutboundDeliveryKind {
151
+ const value = optionalText(selection?.deliveryKind ?? selection?.kind);
152
+ if (value === "courier" || value === "parcel-locker") return value;
153
+ throw new OmnipackOutboundOrderPayloadError("omnipack_order_payload_missing_delivery_kind");
154
+ }
155
+
156
+ function readPickUpPoint(
157
+ deliveryKind: OmnipackOutboundDeliveryKind,
158
+ selection: OmnipackOutboundDeliverySelection | null,
159
+ ): string | undefined {
160
+ if (deliveryKind !== "parcel-locker") return undefined;
161
+ const point = optionalText(selection?.pickupPoint?.id ?? selection?.pickupPoint?.pointId);
162
+ if (!point) throw new OmnipackOutboundOrderPayloadError("omnipack_order_payload_missing_pickup_point");
163
+ return point;
164
+ }
165
+
166
+ function requiredText(value: unknown, code: string): string {
167
+ const text = optionalText(value);
168
+ if (!text) throw new OmnipackOutboundOrderPayloadError(code);
169
+ return text;
170
+ }
171
+
172
+ function optionalText(value: unknown): string | undefined {
173
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
174
+ }
175
+
176
+ function requiredQuantity(value: unknown): number {
177
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
178
+ throw new OmnipackOutboundOrderPayloadError("omnipack_order_payload_invalid_quantity");
179
+ }
180
+ return value;
181
+ }
182
+
183
+ function validOrderValue(value: OmnipackOutboundOrderInput["orderValue"]): OmnipackOutboundOrderPayload["orderValue"] | undefined {
184
+ if (!value) return undefined;
185
+ if (typeof value.amount !== "number" || value.amount <= 0 || !Number.isFinite(value.amount)) {
186
+ throw new OmnipackOutboundOrderPayloadError("omnipack_order_payload_invalid_order_value");
187
+ }
188
+ const currency = requiredText(value.currency, "omnipack_order_payload_missing_order_value_currency");
189
+ return { amount: value.amount, currency };
190
+ }
191
+
192
+ function compactRecord<T extends Record<string, unknown>>(record: T): T {
193
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)) as T;
194
+ }