@openlup/platform 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +272 -278
- package/README.md +195 -6
- package/dist/composition/composeBundle.d.ts +13 -0
- package/dist/composition/composeBundle.js +4 -0
- package/dist/composition/fulfillmentContribution.d.ts +21 -0
- package/dist/composition/fulfillmentContribution.js +19 -0
- package/dist/composition/index.d.ts +1 -0
- package/dist/composition/index.js +2 -0
- package/dist/fulfillment/adapterConformance.d.ts +3 -0
- package/dist/fulfillment/adapterConformance.js +223 -0
- package/dist/fulfillment/bindings.d.ts +3 -0
- package/dist/fulfillment/bindings.js +29 -0
- package/dist/fulfillment/commerceFulfillmentHandlers.d.ts +29 -0
- package/dist/fulfillment/commerceFulfillmentHandlers.js +184 -0
- package/dist/fulfillment/commerceFulfillmentReadModel.d.ts +86 -0
- package/dist/fulfillment/commerceFulfillmentReadModel.js +131 -0
- package/dist/fulfillment/contracts/alignment.d.ts +25 -0
- package/dist/fulfillment/contracts/alignment.js +1 -0
- package/dist/fulfillment/contracts/bindings.d.ts +51 -0
- package/dist/fulfillment/contracts/bindings.js +1 -0
- package/dist/fulfillment/contracts/capabilities.d.ts +29 -0
- package/dist/fulfillment/contracts/capabilities.js +1 -0
- package/dist/fulfillment/contracts/conformance.d.ts +93 -0
- package/dist/fulfillment/contracts/conformance.js +1 -0
- package/dist/fulfillment/contracts/customerAlignment.d.ts +6 -0
- package/dist/fulfillment/contracts/customerAlignment.js +1 -0
- package/dist/fulfillment/contracts/dispatch.d.ts +105 -0
- package/dist/fulfillment/contracts/dispatch.js +1 -0
- package/dist/fulfillment/contracts/index.d.ts +18 -0
- package/dist/fulfillment/contracts/index.js +1 -0
- package/dist/fulfillment/contracts/label.d.ts +35 -0
- package/dist/fulfillment/contracts/label.js +1 -0
- package/dist/fulfillment/contracts/nativeUnsupported.d.ts +6 -0
- package/dist/fulfillment/contracts/nativeUnsupported.js +1 -0
- package/dist/fulfillment/contracts/observation.d.ts +52 -0
- package/dist/fulfillment/contracts/observation.js +1 -0
- package/dist/fulfillment/contracts/operator.d.ts +64 -0
- package/dist/fulfillment/contracts/operator.js +1 -0
- package/dist/fulfillment/contracts/read.d.ts +110 -0
- package/dist/fulfillment/contracts/read.js +1 -0
- package/dist/fulfillment/contracts/readback.d.ts +38 -0
- package/dist/fulfillment/contracts/readback.js +1 -0
- package/dist/fulfillment/contracts/replacement.d.ts +34 -0
- package/dist/fulfillment/contracts/replacement.js +1 -0
- package/dist/fulfillment/contracts/store.d.ts +86 -0
- package/dist/fulfillment/contracts/store.js +1 -0
- package/dist/fulfillment/contracts/sync.d.ts +170 -0
- package/dist/fulfillment/contracts/sync.js +1 -0
- package/dist/fulfillment/currentFulfillmentParcel.d.ts +2 -0
- package/dist/fulfillment/currentFulfillmentParcel.js +1 -0
- package/dist/fulfillment/delayReadModel.d.ts +11 -0
- package/dist/fulfillment/delayReadModel.js +45 -0
- package/dist/fulfillment/deliveryAlignmentReadModel.d.ts +4 -0
- package/dist/fulfillment/deliveryAlignmentReadModel.js +12 -0
- package/dist/fulfillment/dispatchProjection.d.ts +7 -0
- package/dist/fulfillment/dispatchProjection.js +18 -0
- package/dist/fulfillment/fulfillmentObservationEffects.d.ts +66 -0
- package/dist/fulfillment/fulfillmentObservationEffects.js +36 -0
- package/dist/fulfillment/handlerSchemas.d.ts +647 -0
- package/dist/fulfillment/handlerSchemas.js +213 -0
- package/dist/fulfillment/handlerSupport.d.ts +33 -0
- package/dist/fulfillment/handlerSupport.js +25 -0
- package/dist/fulfillment/index.d.ts +37 -0
- package/dist/fulfillment/index.js +37 -0
- package/dist/fulfillment/labelLifecycle.d.ts +24 -0
- package/dist/fulfillment/labelLifecycle.js +9 -0
- package/dist/fulfillment/lowStockEvidenceHandler.d.ts +36 -0
- package/dist/fulfillment/lowStockEvidenceHandler.js +46 -0
- package/dist/fulfillment/migrations.d.ts +27 -0
- package/dist/fulfillment/migrations.js +8 -0
- package/dist/fulfillment/nativeUnsupported.d.ts +10 -0
- package/dist/fulfillment/nativeUnsupported.js +18 -0
- package/dist/fulfillment/normalizedFulfillmentFacts.d.ts +2 -0
- package/dist/fulfillment/normalizedFulfillmentFacts.js +1 -0
- package/dist/fulfillment/observationProjection.d.ts +11 -0
- package/dist/fulfillment/observationProjection.js +37 -0
- package/dist/fulfillment/omnipack/capabilities.d.ts +3 -0
- package/dist/fulfillment/omnipack/capabilities.js +16 -0
- package/dist/fulfillment/omnipack/client.d.ts +37 -0
- package/dist/fulfillment/omnipack/client.js +162 -0
- package/dist/fulfillment/omnipack/index.d.ts +52 -0
- package/dist/fulfillment/omnipack/index.js +349 -0
- package/dist/fulfillment/omnipack/mappers.d.ts +73 -0
- package/dist/fulfillment/omnipack/mappers.js +177 -0
- package/dist/fulfillment/omnipack/observation.d.ts +21 -0
- package/dist/fulfillment/omnipack/observation.js +57 -0
- package/dist/fulfillment/omnipack/omnipackDispatchPayload.d.ts +36 -0
- package/dist/fulfillment/omnipack/omnipackDispatchPayload.js +63 -0
- package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.d.ts +83 -0
- package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.js +113 -0
- package/dist/fulfillment/omnipack/omnipackProductPackPayload.d.ts +11 -0
- package/dist/fulfillment/omnipack/omnipackProductPackPayload.js +38 -0
- package/dist/fulfillment/omnipack/omnipackProductPayload.d.ts +18 -0
- package/dist/fulfillment/omnipack/omnipackProductPayload.js +51 -0
- package/dist/fulfillment/omnipack/omnipackReconciliationPayload.d.ts +1 -0
- package/dist/fulfillment/omnipack/omnipackReconciliationPayload.js +3 -0
- package/dist/fulfillment/omnipack/omnipackStatusVocabulary.d.ts +4 -0
- package/dist/fulfillment/omnipack/omnipackStatusVocabulary.js +87 -0
- package/dist/fulfillment/omnipack/omnipackWebhookPayload.d.ts +9 -0
- package/dist/fulfillment/omnipack/omnipackWebhookPayload.js +76 -0
- package/dist/fulfillment/omnipack/productSyncMappers.d.ts +18 -0
- package/dist/fulfillment/omnipack/productSyncMappers.js +43 -0
- package/dist/fulfillment/omnipack/providerError.d.ts +25 -0
- package/dist/fulfillment/omnipack/providerError.js +47 -0
- package/dist/fulfillment/omnipack/trackingReferences.d.ts +16 -0
- package/dist/fulfillment/omnipack/trackingReferences.js +52 -0
- package/dist/fulfillment/paidFulfillmentRecovery.d.ts +67 -0
- package/dist/fulfillment/paidFulfillmentRecovery.js +154 -0
- package/dist/fulfillment/paidHandoff.d.ts +27 -0
- package/dist/fulfillment/paidHandoff.js +86 -0
- package/dist/fulfillment/paidHandoffPorts.d.ts +24 -0
- package/dist/fulfillment/paidHandoffPorts.js +3 -0
- package/dist/fulfillment/postgresDeliveryAlignment.d.ts +4 -0
- package/dist/fulfillment/postgresDeliveryAlignment.js +12 -0
- package/dist/fulfillment/postgresDispatchStore.d.ts +16 -0
- package/dist/fulfillment/postgresDispatchStore.js +44 -0
- package/dist/fulfillment/postgresEvidenceReadPort.d.ts +25 -0
- package/dist/fulfillment/postgresEvidenceReadPort.js +45 -0
- package/dist/fulfillment/postgresPaidFulfillmentRecovery.d.ts +25 -0
- package/dist/fulfillment/postgresPaidFulfillmentRecovery.js +26 -0
- package/dist/fulfillment/postgresPaidHandoffStore.d.ts +4 -0
- package/dist/fulfillment/postgresPaidHandoffStore.js +10 -0
- package/dist/fulfillment/postgresShipmentSpine.d.ts +63 -0
- package/dist/fulfillment/postgresShipmentSpine.js +232 -0
- package/dist/fulfillment/postgresStockSyncPort.d.ts +8 -0
- package/dist/fulfillment/postgresStockSyncPort.js +63 -0
- package/dist/fulfillment/productReconciliationEvidenceHandler.d.ts +36 -0
- package/dist/fulfillment/productReconciliationEvidenceHandler.js +50 -0
- package/dist/fulfillment/providerCapabilities.d.ts +9 -0
- package/dist/fulfillment/providerCapabilities.js +17 -0
- package/dist/fulfillment/providerCommandLedger.d.ts +143 -0
- package/dist/fulfillment/providerCommandLedger.js +14 -0
- package/dist/fulfillment/providerDispatchAcceptance.d.ts +26 -0
- package/dist/fulfillment/providerDispatchAcceptance.js +23 -0
- package/dist/fulfillment/providerDispatchError.d.ts +6 -0
- package/dist/fulfillment/providerDispatchError.js +5 -0
- package/dist/fulfillment/providerDispatchWorker.d.ts +31 -0
- package/dist/fulfillment/providerDispatchWorker.js +172 -0
- package/dist/fulfillment/providerProductSyncWorker.d.ts +28 -0
- package/dist/fulfillment/providerProductSyncWorker.js +122 -0
- package/dist/fulfillment/providerReconciliationError.d.ts +9 -0
- package/dist/fulfillment/providerReconciliationError.js +17 -0
- package/dist/fulfillment/providerReconciliationTerminalSkip.d.ts +2 -0
- package/dist/fulfillment/providerReconciliationTerminalSkip.js +6 -0
- package/dist/fulfillment/providerReconciliationTimestamp.d.ts +1 -0
- package/dist/fulfillment/providerReconciliationTimestamp.js +27 -0
- package/dist/fulfillment/providerReconciliationWorker.d.ts +82 -0
- package/dist/fulfillment/providerReconciliationWorker.js +151 -0
- package/dist/fulfillment/providerStockAvailability.d.ts +13 -0
- package/dist/fulfillment/providerStockAvailability.js +51 -0
- package/dist/fulfillment/providerStockSyncWorker.d.ts +37 -0
- package/dist/fulfillment/providerStockSyncWorker.js +186 -0
- package/dist/fulfillment/providerTrackingReferences.d.ts +15 -0
- package/dist/fulfillment/providerTrackingReferences.js +23 -0
- package/dist/fulfillment/replacementRefusals.d.ts +5 -0
- package/dist/fulfillment/replacementRefusals.js +38 -0
- package/dist/fulfillment/replacementShipmentHandler.d.ts +5 -0
- package/dist/fulfillment/replacementShipmentHandler.js +46 -0
- package/dist/fulfillment/schemaCapabilities.d.ts +13 -0
- package/dist/fulfillment/schemaCapabilities.js +7 -0
- package/dist/fulfillment/shipmentsOverviewHandler.d.ts +16 -0
- package/dist/fulfillment/shipmentsOverviewHandler.js +54 -0
- package/dist/fulfillment/statusMap.d.ts +2 -0
- package/dist/fulfillment/statusMap.js +1 -0
- package/dist/fulfillment/trackingProgress.d.ts +1 -0
- package/dist/fulfillment/trackingProgress.js +1 -0
- package/dist/fulfillment/verifiedIngress.d.ts +52 -0
- package/dist/fulfillment/verifiedIngress.js +66 -0
- package/dist/payments/adapterConformance.d.ts +8 -0
- package/dist/payments/adapterConformance.js +109 -0
- package/dist/payments/bindings.d.ts +3 -0
- package/dist/payments/bindings.js +17 -0
- package/dist/payments/checkoutActivePaymentActionResolver.d.ts +7 -0
- package/dist/payments/checkoutActivePaymentActionResolver.js +77 -0
- package/dist/payments/checkoutRecoveryPaymentResolver.d.ts +8 -0
- package/dist/payments/checkoutRecoveryPaymentResolver.js +87 -0
- package/dist/payments/contracts/adapter.d.ts +113 -0
- package/dist/payments/contracts/adapter.js +1 -0
- package/dist/payments/contracts/conformance.d.ts +155 -0
- package/dist/payments/contracts/conformance.js +1 -0
- package/dist/payments/contracts/evidence.d.ts +157 -0
- package/dist/payments/contracts/evidence.js +1 -0
- package/dist/payments/contracts/index.d.ts +10 -0
- package/dist/payments/contracts/index.js +1 -0
- package/dist/payments/contracts/preparedAttempt.d.ts +69 -0
- package/dist/payments/contracts/preparedAttempt.js +1 -0
- package/dist/payments/contracts/reconciliation.d.ts +303 -0
- package/dist/payments/contracts/reconciliation.js +1 -0
- package/dist/payments/contracts/repair.d.ts +92 -0
- package/dist/payments/contracts/repair.js +1 -0
- package/dist/payments/contracts/truth.d.ts +92 -0
- package/dist/payments/contracts/truth.js +1 -0
- package/dist/payments/eventConformanceRunner.d.ts +12 -0
- package/dist/payments/eventConformanceRunner.js +125 -0
- package/dist/payments/index.d.ts +16 -0
- package/dist/payments/index.js +16 -0
- package/dist/payments/paymentAttemptSilenceWindow.d.ts +10 -0
- package/dist/payments/paymentAttemptSilenceWindow.js +19 -0
- package/dist/payments/paymentMethodLifecycle.d.ts +11 -0
- package/dist/payments/paymentMethodLifecycle.js +37 -0
- package/dist/payments/paymentProviderReconciliationAbsenceReopen.d.ts +7 -0
- package/dist/payments/paymentProviderReconciliationAbsenceReopen.js +100 -0
- package/dist/payments/paymentProviderReconciliationEvidence.d.ts +36 -0
- package/dist/payments/paymentProviderReconciliationEvidence.js +91 -0
- package/dist/payments/paymentProviderReconciliationWorker.d.ts +4 -0
- package/dist/payments/paymentProviderReconciliationWorker.js +245 -0
- package/dist/payments/paymentTruth.d.ts +8 -0
- package/dist/payments/paymentTruth.js +125 -0
- package/dist/payments/paymentVerifyNowHandler.d.ts +27 -0
- package/dist/payments/paymentVerifyNowHandler.js +70 -0
- package/dist/payments/paymentVerifyNowService.d.ts +23 -0
- package/dist/payments/paymentVerifyNowService.js +93 -0
- package/dist/payments/paymentWebhookPostProcessing.d.ts +22 -0
- package/dist/payments/paymentWebhookPostProcessing.js +15 -0
- package/dist/payments/postgresPaymentTruth.d.ts +10 -0
- package/dist/payments/postgresPaymentTruth.js +126 -0
- package/dist/payments/preparedAttempt.d.ts +6 -0
- package/dist/payments/preparedAttempt.js +70 -0
- package/dist/payments/preparedAttemptFinalization.d.ts +5 -0
- package/dist/payments/preparedAttemptFinalization.js +12 -0
- package/dist/payments/schemaCapabilities.d.ts +10 -0
- package/dist/payments/schemaCapabilities.js +10 -0
- package/dist/payments/sql.d.ts +20 -0
- package/dist/payments/sql.js +6 -0
- package/dist/payments/stripe/classifyDecline.d.ts +46 -0
- package/dist/payments/stripe/classifyDecline.js +58 -0
- package/dist/payments/stripe/declineFailureHints.d.ts +46 -0
- package/dist/payments/stripe/declineFailureHints.js +87 -0
- package/dist/payments/stripe/donorContracts.d.ts +120 -0
- package/dist/payments/stripe/donorContracts.js +1 -0
- package/dist/payments/stripe/executionDecline.d.ts +53 -0
- package/dist/payments/stripe/executionDecline.js +107 -0
- package/dist/payments/stripe/index.d.ts +41 -0
- package/dist/payments/stripe/index.js +264 -0
- package/dist/payments/stripe/intentMapper.d.ts +26 -0
- package/dist/payments/stripe/intentMapper.js +51 -0
- package/dist/payments/stripe/methodFacts.d.ts +10 -0
- package/dist/payments/stripe/methodFacts.js +12 -0
- package/dist/payments/stripe/paymentFailureEvidence.d.ts +26 -0
- package/dist/payments/stripe/paymentFailureEvidence.js +33 -0
- package/dist/payments/stripe/stripeFailureEvidence.d.ts +41 -0
- package/dist/payments/stripe/stripeFailureEvidence.js +82 -0
- package/dist/payments/stripe/stripeIntentConfirmationEvidence.d.ts +28 -0
- package/dist/payments/stripe/stripeIntentConfirmationEvidence.js +32 -0
- package/dist/payments/stripe/stripePaymentReconciliationProvider.d.ts +35 -0
- package/dist/payments/stripe/stripePaymentReconciliationProvider.js +222 -0
- package/dist/payments/stripe/stripeRefundEvidence.d.ts +8 -0
- package/dist/payments/stripe/stripeRefundEvidence.js +17 -0
- package/dist/payments/stripe/stripeSandboxPaymentExecutionAdapter.d.ts +45 -0
- package/dist/payments/stripe/stripeSandboxPaymentExecutionAdapter.js +220 -0
- package/dist/payments/stripe/stripeWebhookNormalizer.d.ts +14 -0
- package/dist/payments/stripe/stripeWebhookNormalizer.js +243 -0
- package/dist/payments/stripe/webhookSignature.d.ts +14 -0
- package/dist/payments/stripe/webhookSignature.js +43 -0
- package/dist/payments/tpay/blikRecurringMapper.d.ts +37 -0
- package/dist/payments/tpay/blikRecurringMapper.js +24 -0
- package/dist/payments/tpay/capabilities.d.ts +3 -0
- package/dist/payments/tpay/capabilities.js +18 -0
- package/dist/payments/tpay/classifyDecline.d.ts +46 -0
- package/dist/payments/tpay/classifyDecline.js +58 -0
- package/dist/payments/tpay/declineFailureHints.d.ts +72 -0
- package/dist/payments/tpay/declineFailureHints.js +37 -0
- package/dist/payments/tpay/donorContracts.d.ts +140 -0
- package/dist/payments/tpay/donorContracts.js +1 -0
- package/dist/payments/tpay/index.d.ts +73 -0
- package/dist/payments/tpay/index.js +294 -0
- package/dist/payments/tpay/paymentFailureEvidence.d.ts +26 -0
- package/dist/payments/tpay/paymentFailureEvidence.js +33 -0
- package/dist/payments/tpay/preparedProviderAttempt.d.ts +22 -0
- package/dist/payments/tpay/preparedProviderAttempt.js +24 -0
- package/dist/payments/tpay/providerAttemptFailureDiagnostic.d.ts +3 -0
- package/dist/payments/tpay/providerAttemptFailureDiagnostic.js +95 -0
- package/dist/payments/tpay/tpayCreateTransactionInput.d.ts +25 -0
- package/dist/payments/tpay/tpayCreateTransactionInput.js +128 -0
- package/dist/payments/tpay/tpayDeclineEvidence.d.ts +126 -0
- package/dist/payments/tpay/tpayDeclineEvidence.js +177 -0
- package/dist/payments/tpay/tpayDispatchFailure.d.ts +60 -0
- package/dist/payments/tpay/tpayDispatchFailure.js +121 -0
- package/dist/payments/tpay/tpayFailureEvidence.d.ts +20 -0
- package/dist/payments/tpay/tpayFailureEvidence.js +61 -0
- package/dist/payments/tpay/tpayHttpClient.d.ts +52 -0
- package/dist/payments/tpay/tpayHttpClient.js +185 -0
- package/dist/payments/tpay/tpayHttpFailure.d.ts +57 -0
- package/dist/payments/tpay/tpayHttpFailure.js +143 -0
- package/dist/payments/tpay/tpayPaymentExecutionAdapter.d.ts +20 -0
- package/dist/payments/tpay/tpayPaymentExecutionAdapter.js +249 -0
- package/dist/payments/tpay/tpayPaymentReconciliationProvider.d.ts +5 -0
- package/dist/payments/tpay/tpayPaymentReconciliationProvider.js +244 -0
- package/dist/payments/tpay/tpayTransactionPayload.d.ts +120 -0
- package/dist/payments/tpay/tpayTransactionPayload.js +96 -0
- package/dist/payments/tpay/wallClockTime.d.ts +31 -0
- package/dist/payments/tpay/wallClockTime.js +57 -0
- package/dist/payments/tpay/webhookJwsVerifier.d.ts +17 -0
- package/dist/payments/tpay/webhookJwsVerifier.js +51 -0
- package/dist/payments/tpay/webhookNormalizer.d.ts +54 -0
- package/dist/payments/tpay/webhookNormalizer.js +200 -0
- package/dist/payments/verifiedEvidenceProjection.d.ts +12 -0
- package/dist/payments/verifiedEvidenceProjection.js +166 -0
- package/dist/payments/verifiedIngress.d.ts +119 -0
- package/dist/payments/verifiedIngress.js +220 -0
- package/package.json +37 -2
- package/release-gates.json +105 -3
- package/sql/20260808210000_fulfillment_shipment_rail.sql +810 -0
- package/sql/20260810180000_payment_settlement_rail.sql +574 -0
- package/sql/20260815150000_payment_truth_reconciliation.sql +479 -0
- package/sql/20260819150000_fulfillment_shipment_sequence.sql +98 -0
- package/sql/20260826100000_late_payment_shifts_the_subscription_cycle.sql +215 -0
- package/sql/20261022080000_fulfillment_command_ledger.sql +786 -0
- package/sql/20261022090000_fulfillment_delivery_alignment.sql +1106 -0
- package/src/composition/composeBundle.ts +13 -0
- package/src/composition/fulfillmentContribution.ts +28 -0
- package/src/composition/index.ts +2 -0
- package/src/fulfillment/adapterConformance.ts +195 -0
- package/src/fulfillment/bindings.ts +26 -0
- package/src/fulfillment/commerceFulfillmentHandlers.ts +263 -0
- package/src/fulfillment/commerceFulfillmentReadModel.ts +257 -0
- package/src/fulfillment/contracts/alignment.ts +13 -0
- package/src/fulfillment/contracts/bindings.ts +30 -0
- package/src/fulfillment/contracts/capabilities.ts +15 -0
- package/src/fulfillment/contracts/conformance.ts +61 -0
- package/src/fulfillment/contracts/customerAlignment.ts +4 -0
- package/src/fulfillment/contracts/dispatch.ts +58 -0
- package/src/fulfillment/contracts/index.ts +18 -0
- package/src/fulfillment/contracts/label.ts +9 -0
- package/src/fulfillment/contracts/nativeUnsupported.ts +6 -0
- package/src/fulfillment/contracts/observation.ts +18 -0
- package/src/fulfillment/contracts/operator.ts +31 -0
- package/src/fulfillment/contracts/read.ts +42 -0
- package/src/fulfillment/contracts/readback.ts +11 -0
- package/src/fulfillment/contracts/replacement.ts +15 -0
- package/src/fulfillment/contracts/store.ts +26 -0
- package/src/fulfillment/contracts/sync.ts +61 -0
- package/src/fulfillment/currentFulfillmentParcel.ts +2 -0
- package/src/fulfillment/delayReadModel.ts +60 -0
- package/src/fulfillment/deliveryAlignmentReadModel.ts +15 -0
- package/src/fulfillment/dispatchProjection.ts +20 -0
- package/src/fulfillment/fulfillmentObservationEffects.ts +86 -0
- package/src/fulfillment/handlerSchemas.ts +255 -0
- package/src/fulfillment/handlerSupport.ts +25 -0
- package/src/fulfillment/index.ts +38 -0
- package/src/fulfillment/labelLifecycle.ts +11 -0
- package/src/fulfillment/lowStockEvidenceHandler.ts +60 -0
- package/src/fulfillment/migrations.ts +8 -0
- package/src/fulfillment/nativeUnsupported.ts +23 -0
- package/src/fulfillment/normalizedFulfillmentFacts.ts +2 -0
- package/src/fulfillment/observationProjection.ts +41 -0
- package/src/fulfillment/omnipack/capabilities.ts +18 -0
- package/src/fulfillment/omnipack/client.ts +258 -0
- package/src/fulfillment/omnipack/index.ts +312 -0
- package/src/fulfillment/omnipack/mappers.ts +261 -0
- package/src/fulfillment/omnipack/observation.ts +61 -0
- package/src/fulfillment/omnipack/omnipackDispatchPayload.ts +93 -0
- package/src/fulfillment/omnipack/omnipackOutboundOrderPayload.ts +194 -0
- package/src/fulfillment/omnipack/omnipackProductPackPayload.ts +47 -0
- package/src/fulfillment/omnipack/omnipackProductPayload.ts +67 -0
- package/src/fulfillment/omnipack/omnipackReconciliationPayload.ts +7 -0
- package/src/fulfillment/omnipack/omnipackStatusVocabulary.ts +103 -0
- package/src/fulfillment/omnipack/omnipackWebhookPayload.ts +73 -0
- package/src/fulfillment/omnipack/productSyncMappers.ts +74 -0
- package/src/fulfillment/omnipack/providerError.ts +64 -0
- package/src/fulfillment/omnipack/trackingReferences.ts +77 -0
- package/src/fulfillment/paidFulfillmentRecovery.ts +252 -0
- package/src/fulfillment/paidHandoff.ts +122 -0
- package/src/fulfillment/paidHandoffPorts.ts +21 -0
- package/src/fulfillment/postgresDeliveryAlignment.ts +14 -0
- package/src/fulfillment/postgresDispatchStore.ts +55 -0
- package/src/fulfillment/postgresEvidenceReadPort.ts +59 -0
- package/src/fulfillment/postgresPaidFulfillmentRecovery.ts +29 -0
- package/src/fulfillment/postgresPaidHandoffStore.ts +11 -0
- package/src/fulfillment/postgresShipmentSpine.ts +321 -0
- package/src/fulfillment/postgresStockSyncPort.ts +65 -0
- package/src/fulfillment/productReconciliationEvidenceHandler.ts +64 -0
- package/src/fulfillment/providerCapabilities.ts +18 -0
- package/src/fulfillment/providerCommandLedger.ts +53 -0
- package/src/fulfillment/providerDispatchAcceptance.ts +31 -0
- package/src/fulfillment/providerDispatchError.ts +7 -0
- package/src/fulfillment/providerDispatchWorker.ts +131 -0
- package/src/fulfillment/providerProductSyncWorker.ts +154 -0
- package/src/fulfillment/providerReconciliationError.ts +20 -0
- package/src/fulfillment/providerReconciliationTerminalSkip.ts +8 -0
- package/src/fulfillment/providerReconciliationTimestamp.ts +34 -0
- package/src/fulfillment/providerReconciliationWorker.ts +150 -0
- package/src/fulfillment/providerStockAvailability.ts +60 -0
- package/src/fulfillment/providerStockSyncWorker.ts +222 -0
- package/src/fulfillment/providerTrackingReferences.ts +31 -0
- package/src/fulfillment/replacementRefusals.ts +43 -0
- package/src/fulfillment/replacementShipmentHandler.ts +47 -0
- package/src/fulfillment/schemaCapabilities.ts +7 -0
- package/src/fulfillment/shipmentsOverviewHandler.ts +73 -0
- package/src/fulfillment/statusMap.ts +2 -0
- package/src/fulfillment/trackingProgress.ts +1 -0
- package/src/fulfillment/verifiedIngress.ts +80 -0
- package/src/payments/adapterConformance.ts +118 -0
- package/src/payments/bindings.ts +17 -0
- package/src/payments/checkoutActivePaymentActionResolver.ts +97 -0
- package/src/payments/checkoutRecoveryPaymentResolver.ts +108 -0
- package/src/payments/contracts/adapter.ts +96 -0
- package/src/payments/contracts/conformance.ts +171 -0
- package/src/payments/contracts/evidence.ts +142 -0
- package/src/payments/contracts/index.ts +54 -0
- package/src/payments/contracts/preparedAttempt.ts +86 -0
- package/src/payments/contracts/reconciliation.ts +296 -0
- package/src/payments/contracts/repair.ts +95 -0
- package/src/payments/contracts/truth.ts +108 -0
- package/src/payments/eventConformanceRunner.ts +149 -0
- package/src/payments/index.ts +21 -0
- package/src/payments/paymentAttemptSilenceWindow.ts +26 -0
- package/src/payments/paymentMethodLifecycle.ts +50 -0
- package/src/payments/paymentProviderReconciliationAbsenceReopen.ts +130 -0
- package/src/payments/paymentProviderReconciliationEvidence.ts +117 -0
- package/src/payments/paymentProviderReconciliationWorker.ts +291 -0
- package/src/payments/paymentTruth.ts +131 -0
- package/src/payments/paymentVerifyNowHandler.ts +94 -0
- package/src/payments/paymentVerifyNowService.ts +125 -0
- package/src/payments/paymentWebhookPostProcessing.ts +36 -0
- package/src/payments/postgresPaymentTruth.ts +149 -0
- package/src/payments/preparedAttempt.ts +90 -0
- package/src/payments/preparedAttemptFinalization.ts +13 -0
- package/src/payments/schemaCapabilities.ts +10 -0
- package/src/payments/sql.ts +6 -0
- package/src/payments/stripe/classifyDecline.ts +62 -0
- package/src/payments/stripe/declineFailureHints.ts +89 -0
- package/src/payments/stripe/donorContracts.ts +87 -0
- package/src/payments/stripe/executionDecline.ts +126 -0
- package/src/payments/stripe/index.ts +265 -0
- package/src/payments/stripe/intentMapper.ts +58 -0
- package/src/payments/stripe/methodFacts.ts +21 -0
- package/src/payments/stripe/paymentFailureEvidence.ts +58 -0
- package/src/payments/stripe/stripeFailureEvidence.ts +115 -0
- package/src/payments/stripe/stripeIntentConfirmationEvidence.ts +34 -0
- package/src/payments/stripe/stripePaymentReconciliationProvider.ts +263 -0
- package/src/payments/stripe/stripeRefundEvidence.ts +19 -0
- package/src/payments/stripe/stripeSandboxPaymentExecutionAdapter.ts +283 -0
- package/src/payments/stripe/stripeWebhookNormalizer.ts +277 -0
- package/src/payments/stripe/webhookSignature.ts +55 -0
- package/src/payments/tpay/blikRecurringMapper.ts +42 -0
- package/src/payments/tpay/capabilities.ts +21 -0
- package/src/payments/tpay/classifyDecline.ts +62 -0
- package/src/payments/tpay/declineFailureHints.ts +90 -0
- package/src/payments/tpay/donorContracts.ts +104 -0
- package/src/payments/tpay/index.ts +293 -0
- package/src/payments/tpay/paymentFailureEvidence.ts +58 -0
- package/src/payments/tpay/preparedProviderAttempt.ts +38 -0
- package/src/payments/tpay/providerAttemptFailureDiagnostic.ts +99 -0
- package/src/payments/tpay/tpayCreateTransactionInput.ts +158 -0
- package/src/payments/tpay/tpayDeclineEvidence.ts +233 -0
- package/src/payments/tpay/tpayDispatchFailure.ts +186 -0
- package/src/payments/tpay/tpayFailureEvidence.ts +75 -0
- package/src/payments/tpay/tpayHttpClient.ts +290 -0
- package/src/payments/tpay/tpayHttpFailure.ts +186 -0
- package/src/payments/tpay/tpayPaymentExecutionAdapter.ts +297 -0
- package/src/payments/tpay/tpayPaymentReconciliationProvider.ts +285 -0
- package/src/payments/tpay/tpayTransactionPayload.ts +207 -0
- package/src/payments/tpay/wallClockTime.ts +57 -0
- package/src/payments/tpay/webhookJwsVerifier.ts +82 -0
- package/src/payments/tpay/webhookNormalizer.ts +279 -0
- package/src/payments/verifiedEvidenceProjection.ts +179 -0
- package/src/payments/verifiedIngress.ts +317 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { tryReopenPreparedAbsence } from "./paymentProviderReconciliationAbsenceReopen.js";
|
|
2
|
+
import {
|
|
3
|
+
applyResultKey,
|
|
4
|
+
bucket,
|
|
5
|
+
clamp,
|
|
6
|
+
classificationOf,
|
|
7
|
+
evidencePayload,
|
|
8
|
+
observedEvidenceKey,
|
|
9
|
+
preparedAttemptEvidenceKey,
|
|
10
|
+
safeMessage,
|
|
11
|
+
terminalAmountMismatch,
|
|
12
|
+
} from "./paymentProviderReconciliationEvidence.js";
|
|
13
|
+
import { silenceIsOverdue } from "./paymentAttemptSilenceWindow.js";
|
|
14
|
+
import type {
|
|
15
|
+
ClaimedPaymentAttempt,
|
|
16
|
+
PaymentProviderReconciliationPort,
|
|
17
|
+
PaymentProviderReconciliationWorkerInput,
|
|
18
|
+
PaymentProviderReconciliationWorkerResult,
|
|
19
|
+
ProviderReconciliationStatus,
|
|
20
|
+
SilenceWindowLookup,
|
|
21
|
+
} from "./contracts/reconciliation.js";
|
|
22
|
+
|
|
23
|
+
export type {
|
|
24
|
+
ClaimedPaymentAttempt,
|
|
25
|
+
PaymentProviderReconciliationPort,
|
|
26
|
+
PaymentProviderReconciliationProvider,
|
|
27
|
+
PaymentProviderReconciliationWorkerInput,
|
|
28
|
+
PaymentProviderReconciliationWorkerResult,
|
|
29
|
+
ProviderReconciliationStatus,
|
|
30
|
+
ProviderReconciliationStatusKind,
|
|
31
|
+
ReconciliationProviderKind,
|
|
32
|
+
SilenceWindowLookup,
|
|
33
|
+
} from "./contracts/reconciliation.js";
|
|
34
|
+
|
|
35
|
+
const MAX_ATTEMPTS_PER_RUN = 4;
|
|
36
|
+
const DEFAULT_BATCH_SIZE = MAX_ATTEMPTS_PER_RUN;
|
|
37
|
+
|
|
38
|
+
export const DEFAULT_STALE_AFTER_SECONDS = 15 * 60;
|
|
39
|
+
|
|
40
|
+
export async function runPaymentProviderReconciliationWorker(
|
|
41
|
+
input: PaymentProviderReconciliationWorkerInput,
|
|
42
|
+
): Promise<PaymentProviderReconciliationWorkerResult> {
|
|
43
|
+
const now = input.now ?? new Date().toISOString();
|
|
44
|
+
const staleAfterSeconds = clamp(input.staleAfterSeconds ?? DEFAULT_STALE_AFTER_SECONDS, 60, 86_400);
|
|
45
|
+
// Provider reads and optional prepared-absence probes are sequential and may
|
|
46
|
+
// be slow. Preserve the per-run ceiling even for an oversized requested batch.
|
|
47
|
+
const batchSize = clamp(input.batchSize ?? DEFAULT_BATCH_SIZE, 1, MAX_ATTEMPTS_PER_RUN);
|
|
48
|
+
const claimKey = input.claimKey ?? `payment-provider-reconciliation:${bucket(now)}`;
|
|
49
|
+
const result: PaymentProviderReconciliationWorkerResult = {
|
|
50
|
+
ok: true,
|
|
51
|
+
checked: 0,
|
|
52
|
+
corrected: 0,
|
|
53
|
+
succeeded: 0,
|
|
54
|
+
failed: 0,
|
|
55
|
+
pending: 0,
|
|
56
|
+
unknown: 0,
|
|
57
|
+
ignored: 0,
|
|
58
|
+
dunningOpened: 0,
|
|
59
|
+
replayed: 0,
|
|
60
|
+
providerCalls: 0,
|
|
61
|
+
preparedWithoutProviderAck: 0,
|
|
62
|
+
preparedAttemptsReopened: 0,
|
|
63
|
+
manualReview: 0,
|
|
64
|
+
silenceOverdue: 0,
|
|
65
|
+
amountCurrencyMismatches: 0,
|
|
66
|
+
failures: 0,
|
|
67
|
+
skipped: false,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Prepared attempts can remain operator-manual indefinitely. Bound their
|
|
71
|
+
// share so they can never consume a whole worker batch and starve provider
|
|
72
|
+
// readback for finalized attempts. A batch of one intentionally prioritizes
|
|
73
|
+
// provider readback; normal scheduled runs use the larger default batch.
|
|
74
|
+
const preparedLimit = Math.floor(batchSize / 2);
|
|
75
|
+
const preparedAttempts = preparedLimit > 0
|
|
76
|
+
? await input.port.claimPreparedAttempts({
|
|
77
|
+
now,
|
|
78
|
+
staleAfterSeconds,
|
|
79
|
+
limit: preparedLimit,
|
|
80
|
+
claimKey: `${claimKey}:prepared`,
|
|
81
|
+
})
|
|
82
|
+
: [];
|
|
83
|
+
for (const attempt of preparedAttempts) {
|
|
84
|
+
const providerStatus: ProviderReconciliationStatus = {
|
|
85
|
+
status: "unknown",
|
|
86
|
+
providerStatus: "prepared_without_provider_ack",
|
|
87
|
+
occurredAt: null,
|
|
88
|
+
failureReason: "prepared_without_provider_ack",
|
|
89
|
+
amountMinor: null,
|
|
90
|
+
currency: null,
|
|
91
|
+
rawPayload: {
|
|
92
|
+
source: "payment-provider-reconciliation.prepared-watchdog.v0",
|
|
93
|
+
provider: attempt.provider,
|
|
94
|
+
providerCallPlanned: true,
|
|
95
|
+
providerAcknowledged: false,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
const evidence = await input.port.recordEvidence({
|
|
99
|
+
idempotencyKey: preparedAttemptEvidenceKey(attempt, now),
|
|
100
|
+
provider: attempt.provider,
|
|
101
|
+
providerPaymentId: null,
|
|
102
|
+
paymentIntentId: attempt.paymentIntentId,
|
|
103
|
+
paymentAttemptId: attempt.paymentAttemptId,
|
|
104
|
+
localStatus: attempt.attemptStatus,
|
|
105
|
+
providerStatus: providerStatus.providerStatus,
|
|
106
|
+
correctionStatus: "failed",
|
|
107
|
+
checkedAt: now,
|
|
108
|
+
payload: evidencePayload(attempt, providerStatus, {
|
|
109
|
+
applied: false,
|
|
110
|
+
operatorReviewRequired: true,
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
113
|
+
if (evidence.replayed) result.replayed += 1;
|
|
114
|
+
result.preparedWithoutProviderAck += 1;
|
|
115
|
+
result.failures += 1;
|
|
116
|
+
result.ok = false;
|
|
117
|
+
result.reason = result.reason ?? "prepared_without_provider_ack";
|
|
118
|
+
|
|
119
|
+
// Auto-reopen after a GENUINE provider-side absence proof. A purely local
|
|
120
|
+
// "no ack recorded" signal cannot rule out the crash window between
|
|
121
|
+
// executionPort.execute() (provider charged) and finalizeProviderAttempt
|
|
122
|
+
// (refs persisted) — reopening in that window would mint a fresh PSP
|
|
123
|
+
// idempotency key and double-charge. Providers without an absence probe
|
|
124
|
+
|
|
125
|
+
if (input.autoReopenPreparedAbsence) {
|
|
126
|
+
const reopened = await tryReopenPreparedAbsence(input, attempt, now);
|
|
127
|
+
if (reopened) result.preparedAttemptsReopened += 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const remainingLimit = batchSize - preparedAttempts.length;
|
|
132
|
+
const attempts = remainingLimit > 0
|
|
133
|
+
? await input.port.claimStaleAttempts({
|
|
134
|
+
now,
|
|
135
|
+
staleAfterSeconds,
|
|
136
|
+
limit: remainingLimit,
|
|
137
|
+
claimKey,
|
|
138
|
+
})
|
|
139
|
+
: [];
|
|
140
|
+
result.checked = preparedAttempts.length + attempts.length;
|
|
141
|
+
|
|
142
|
+
for (const attempt of attempts) {
|
|
143
|
+
const provider = input.providers[attempt.provider];
|
|
144
|
+
if (!provider) {
|
|
145
|
+
await recordNonTerminalEvidence(input.port, attempt, now, {
|
|
146
|
+
status: "unknown",
|
|
147
|
+
providerStatus: "provider_not_configured",
|
|
148
|
+
occurredAt: null,
|
|
149
|
+
failureReason: "provider_not_configured",
|
|
150
|
+
amountMinor: null,
|
|
151
|
+
currency: null,
|
|
152
|
+
rawPayload: { provider: attempt.provider, source: "payment-provider-reconciliation.v0" },
|
|
153
|
+
}, "failed");
|
|
154
|
+
result.unknown += 1;
|
|
155
|
+
result.failures += 1;
|
|
156
|
+
result.ok = false;
|
|
157
|
+
result.reason = result.reason ?? `${attempt.provider}_provider_not_configured`;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let providerStatus: ProviderReconciliationStatus;
|
|
162
|
+
try {
|
|
163
|
+
if (!attempt.providerPaymentId) {
|
|
164
|
+
throw new Error("provider_payment_id_missing");
|
|
165
|
+
}
|
|
166
|
+
result.providerCalls += 1;
|
|
167
|
+
providerStatus = await provider.readPayment({
|
|
168
|
+
providerPaymentId: attempt.providerPaymentId,
|
|
169
|
+
attempt,
|
|
170
|
+
});
|
|
171
|
+
} catch (error) {
|
|
172
|
+
providerStatus = {
|
|
173
|
+
status: "unknown",
|
|
174
|
+
providerStatus: "provider_read_failed",
|
|
175
|
+
occurredAt: null,
|
|
176
|
+
failureReason: safeMessage(error),
|
|
177
|
+
amountMinor: null,
|
|
178
|
+
currency: null,
|
|
179
|
+
rawPayload: {
|
|
180
|
+
provider: attempt.provider,
|
|
181
|
+
providerPaymentId: attempt.providerPaymentId,
|
|
182
|
+
readError: safeMessage(error),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
await recordNonTerminalEvidence(input.port, attempt, now, providerStatus, "failed");
|
|
186
|
+
result.unknown += 1;
|
|
187
|
+
result.failures += 1;
|
|
188
|
+
result.ok = false;
|
|
189
|
+
result.reason = result.reason ?? safeMessage(error);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (providerStatus.status === "pending" || providerStatus.status === "unknown") {
|
|
194
|
+
// Polling cannot separate "still running" from "stuck" - both answer
|
|
195
|
+
// non-terminally forever - so ask the rail how long its own silence stays
|
|
196
|
+
// normal and surface the attempt past that. NOT a terminal decision:
|
|
197
|
+
// elapsed time says nothing about whether money moved.
|
|
198
|
+
const overdue = silenceIsOverdue(input.capabilities, attempt, now);
|
|
199
|
+
await recordNonTerminalEvidence(input.port, attempt, now, providerStatus, "observed", overdue);
|
|
200
|
+
if (providerStatus.status === "pending") result.pending += 1;
|
|
201
|
+
else result.unknown += 1;
|
|
202
|
+
if (overdue) {
|
|
203
|
+
result.silenceOverdue += 1;
|
|
204
|
+
result.manualReview += 1;
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const mismatch = terminalAmountMismatch(attempt, providerStatus);
|
|
210
|
+
if (mismatch) {
|
|
211
|
+
await recordNonTerminalEvidence(input.port, attempt, now, {
|
|
212
|
+
...providerStatus,
|
|
213
|
+
failureReason: mismatch,
|
|
214
|
+
rawPayload: { ...providerStatus.rawPayload, reconciliationMismatch: mismatch },
|
|
215
|
+
}, "failed");
|
|
216
|
+
result.manualReview += 1;
|
|
217
|
+
result.amountCurrencyMismatches += 1;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const occurredAt = providerStatus.occurredAt ?? now;
|
|
222
|
+
const resultStatus = providerStatus.status;
|
|
223
|
+
try {
|
|
224
|
+
const applied = await input.port.applyTerminalResult({
|
|
225
|
+
idempotencyKey: applyResultKey(attempt, resultStatus),
|
|
226
|
+
expectedOrderId: attempt.orderId,
|
|
227
|
+
expectedPaymentIntentId: attempt.paymentIntentId,
|
|
228
|
+
expectedPaymentAttemptId: attempt.paymentAttemptId,
|
|
229
|
+
expectedPaymentId: attempt.paymentId,
|
|
230
|
+
provider: attempt.provider,
|
|
231
|
+
providerPaymentId: attempt.providerPaymentId,
|
|
232
|
+
localStatus: attempt.attemptStatus,
|
|
233
|
+
providerStatus: providerStatus.providerStatus,
|
|
234
|
+
resultStatus,
|
|
235
|
+
occurredAt,
|
|
236
|
+
failureReason: resultStatus === "failed"
|
|
237
|
+
? providerStatus.failureReason ?? `provider_reconciliation_${providerStatus.providerStatus}`
|
|
238
|
+
: null,
|
|
239
|
+
...classificationOf(resultStatus, providerStatus),
|
|
240
|
+
checkedAt: now,
|
|
241
|
+
payload: evidencePayload(attempt, providerStatus, { applied: true, resultStatus }),
|
|
242
|
+
});
|
|
243
|
+
if (applied.replayed) result.replayed += 1;
|
|
244
|
+
|
|
245
|
+
if (applied.correctionStatus === "ignored") {
|
|
246
|
+
result.ignored += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
result.corrected += applied.replayed ? 0 : 1;
|
|
251
|
+
if (resultStatus === "succeeded") result.succeeded += 1;
|
|
252
|
+
else result.failed += 1;
|
|
253
|
+
if (applied.subscriptionWebhookDunning?.opened && !applied.subscriptionWebhookDunning.replayed) {
|
|
254
|
+
result.dunningOpened += 1;
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
await recordNonTerminalEvidence(input.port, attempt, now, {
|
|
258
|
+
...providerStatus,
|
|
259
|
+
failureReason: safeMessage(error),
|
|
260
|
+
rawPayload: { ...providerStatus.rawPayload, applyResultError: safeMessage(error) },
|
|
261
|
+
}, "failed");
|
|
262
|
+
result.failures += 1;
|
|
263
|
+
result.ok = false;
|
|
264
|
+
result.reason = result.reason ?? safeMessage(error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function recordNonTerminalEvidence(
|
|
272
|
+
port: PaymentProviderReconciliationPort,
|
|
273
|
+
attempt: ClaimedPaymentAttempt,
|
|
274
|
+
checkedAt: string,
|
|
275
|
+
providerStatus: ProviderReconciliationStatus,
|
|
276
|
+
correctionStatus: "observed" | "failed",
|
|
277
|
+
silenceOverdue = false,
|
|
278
|
+
): Promise<{ replayed: boolean }> {
|
|
279
|
+
return port.recordEvidence({
|
|
280
|
+
idempotencyKey: observedEvidenceKey(attempt, providerStatus.status, correctionStatus, checkedAt),
|
|
281
|
+
provider: attempt.provider,
|
|
282
|
+
providerPaymentId: attempt.providerPaymentId,
|
|
283
|
+
paymentIntentId: attempt.paymentIntentId,
|
|
284
|
+
paymentAttemptId: attempt.paymentAttemptId,
|
|
285
|
+
localStatus: attempt.attemptStatus,
|
|
286
|
+
providerStatus: providerStatus.providerStatus,
|
|
287
|
+
correctionStatus,
|
|
288
|
+
checkedAt,
|
|
289
|
+
payload: evidencePayload(attempt, providerStatus, { applied: false, silenceOverdue }),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { PaymentTruthRefusalClass, PaymentSourceEvidence, PaymentTruthEvent, PaymentReconciliationResult, CanonicalPaymentSource } from "./contracts/truth.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
const PAYMENT_TRUTH_REFUSAL_CLASSES: readonly PaymentTruthRefusalClass[] = [
|
|
5
|
+
"authorization_refused",
|
|
6
|
+
"insufficient_funds",
|
|
7
|
+
"method_unavailable",
|
|
8
|
+
"risk_refused",
|
|
9
|
+
"unknown_refusal",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function paymentReconciliationFingerprint(input: PaymentReconciliationResult): string {
|
|
13
|
+
if (!input.eventId.trim() || input.idempotencyKey.trim().length < 8 || !isInstant(input.occurredAt)) {
|
|
14
|
+
throw new Error("payment_reconciliation_input_invalid");
|
|
15
|
+
}
|
|
16
|
+
if (input.evidenceStatus === "failed" && input.outcome !== "indeterminate") {
|
|
17
|
+
throw new Error("payment_reconciliation_failure_cannot_terminalize");
|
|
18
|
+
}
|
|
19
|
+
if (input.outcome === "refused" && !input.failure) {
|
|
20
|
+
throw new Error("payment_truth_refusal_classification_required");
|
|
21
|
+
}
|
|
22
|
+
if (input.outcome !== "refused" && input.failure) {
|
|
23
|
+
throw new Error("payment_truth_failure_only_for_refusal");
|
|
24
|
+
}
|
|
25
|
+
assertPaymentFailure(input.failure);
|
|
26
|
+
assertPaymentSourceEvidence(input.evidence);
|
|
27
|
+
return digest({
|
|
28
|
+
eventId: input.eventId,
|
|
29
|
+
evidenceStatus: input.evidenceStatus,
|
|
30
|
+
outcome: input.outcome,
|
|
31
|
+
occurredAt: input.occurredAt,
|
|
32
|
+
failure: input.failure,
|
|
33
|
+
evidence: input.evidence,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Fingerprint accepted at a provider boundary before local ownership is known. */
|
|
38
|
+
export function paymentSourceFingerprint(input: CanonicalPaymentSource): string {
|
|
39
|
+
if (!input.sourceEventId.trim() || !input.eventKind.trim()
|
|
40
|
+
|| (input.occurredAt !== null && !isInstant(input.occurredAt))) {
|
|
41
|
+
throw new Error("payment_source_fingerprint_input_invalid");
|
|
42
|
+
}
|
|
43
|
+
if (!/^[0-9a-f]{64}$/.test(input.payloadFingerprint)) {
|
|
44
|
+
throw new Error("payment_truth_payload_fingerprint_invalid");
|
|
45
|
+
}
|
|
46
|
+
return digest(input);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function paymentPayloadFingerprint(payload: unknown): string {
|
|
50
|
+
return digest(payload);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Canonical identity for one normalized event; idempotency is deliberately not part of it. */
|
|
54
|
+
export function paymentTruthFingerprint(input: PaymentTruthEvent): string {
|
|
55
|
+
assertPaymentTruthEvent(input);
|
|
56
|
+
return digest({
|
|
57
|
+
sourceEventId: input.sourceEventId,
|
|
58
|
+
settlementIntentId: input.settlementIntentId,
|
|
59
|
+
outcome: input.outcome,
|
|
60
|
+
amountMinor: input.amountMinor,
|
|
61
|
+
currency: input.currency,
|
|
62
|
+
occurredAt: input.occurredAt,
|
|
63
|
+
failure: input.failure,
|
|
64
|
+
evidence: input.evidence,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function assertPaymentTruthEvent(input: PaymentTruthEvent): void {
|
|
69
|
+
if (!input.sourceEventId.trim()) throw new Error("payment_truth_source_event_id_required");
|
|
70
|
+
if (input.idempotencyKey.trim().length < 8) throw new Error("payment_truth_idempotency_key_invalid");
|
|
71
|
+
if (!input.settlementIntentId.trim()) throw new Error("payment_truth_settlement_intent_required");
|
|
72
|
+
if (!Number.isSafeInteger(input.amountMinor) || input.amountMinor <= 0) {
|
|
73
|
+
throw new Error("payment_truth_amount_invalid");
|
|
74
|
+
}
|
|
75
|
+
if (!/^[A-Z]{3}$/.test(input.currency)) throw new Error("payment_truth_currency_invalid");
|
|
76
|
+
if (!isInstant(input.occurredAt) || !isInstant(input.evidence.observedAt)) {
|
|
77
|
+
throw new Error("payment_truth_time_invalid");
|
|
78
|
+
}
|
|
79
|
+
if (!input.evidence.sourceReference.trim() || !input.evidence.observedStatus.trim()) {
|
|
80
|
+
throw new Error("payment_truth_source_evidence_invalid");
|
|
81
|
+
}
|
|
82
|
+
if (!/^[0-9a-f]{64}$/.test(input.evidence.payloadFingerprint)) {
|
|
83
|
+
throw new Error("payment_truth_payload_fingerprint_invalid");
|
|
84
|
+
}
|
|
85
|
+
if (input.outcome === "refused" && !input.failure) {
|
|
86
|
+
throw new Error("payment_truth_refusal_classification_required");
|
|
87
|
+
}
|
|
88
|
+
if (input.outcome !== "refused" && input.failure) {
|
|
89
|
+
throw new Error("payment_truth_failure_only_for_refusal");
|
|
90
|
+
}
|
|
91
|
+
assertPaymentFailure(input.failure);
|
|
92
|
+
assertPaymentSourceEvidence(input.evidence);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isInstant(value: string): boolean {
|
|
96
|
+
return value.trim().length > 0 && Number.isFinite(Date.parse(value));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function stableJson(value: unknown): string {
|
|
100
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
101
|
+
if (value && typeof value === "object") {
|
|
102
|
+
return `{${Object.entries(value as Record<string, unknown>)
|
|
103
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
104
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
|
|
105
|
+
.join(",")}}`;
|
|
106
|
+
}
|
|
107
|
+
return JSON.stringify(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function assertPaymentFailure(failure: PaymentTruthEvent["failure"]): void {
|
|
111
|
+
if (failure && !PAYMENT_TRUTH_REFUSAL_CLASSES.includes(failure.classification)) {
|
|
112
|
+
throw new Error("payment_truth_failure_class_invalid");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function assertPaymentSourceEvidence(evidence: PaymentSourceEvidence): void {
|
|
117
|
+
if (evidence.sourceKind !== "accepted_event" && evidence.sourceKind !== "reconciliation_read") {
|
|
118
|
+
throw new Error("payment_truth_source_kind_invalid");
|
|
119
|
+
}
|
|
120
|
+
if (!evidence.sourceReference.trim() || !evidence.observedStatus.trim()) {
|
|
121
|
+
throw new Error("payment_truth_source_evidence_invalid");
|
|
122
|
+
}
|
|
123
|
+
if (!isInstant(evidence.observedAt)) throw new Error("payment_truth_time_invalid");
|
|
124
|
+
if (!/^[0-9a-f]{64}$/.test(evidence.payloadFingerprint)) {
|
|
125
|
+
throw new Error("payment_truth_payload_fingerprint_invalid");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function digest(value: unknown): string {
|
|
130
|
+
return createHash("sha256").update(stableJson(value)).digest("hex");
|
|
131
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { HttpRequest, HttpResponse } from "../http/index.js";
|
|
3
|
+
import type { BffResponder } from "../bff/index.js";
|
|
4
|
+
import type {
|
|
5
|
+
PaymentProviderReconciliationProvider,
|
|
6
|
+
PaymentProviderReconciliationPort,
|
|
7
|
+
} from "./contracts/reconciliation.js";
|
|
8
|
+
import { verifyPaymentAttemptNow } from "./paymentVerifyNowService.js";
|
|
9
|
+
import type { PaymentVerifyReadPort } from "./contracts/reconciliation.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Buyer-triggered "verify now": after a CLIENT-side confirm failure the FE has
|
|
13
|
+
* the only copy of the outcome (a pre-charge provider rejection emits no
|
|
14
|
+
* webhook), so it asks the server to read the provider immediately instead of
|
|
15
|
+
* waiting for the next adopter-configured reconciliation pass. The client can never assert a result — truth is the live
|
|
16
|
+
* provider readback, and the only write path is the reconciliation apply RPC
|
|
17
|
+
* with the cron's own idempotency key, so both paths converge replay-safely.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const paymentVerifyRequestSchema = z.object({
|
|
21
|
+
orderId: z.guid(),
|
|
22
|
+
paymentIntentId: z.guid(),
|
|
23
|
+
clientId: z.guid(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export type PaymentVerifyRequest = z.infer<typeof paymentVerifyRequestSchema>;
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export function createCommercePaymentVerifyHandler(deps: {
|
|
31
|
+
readPort: PaymentVerifyReadPort;
|
|
32
|
+
applyPort: Pick<PaymentProviderReconciliationPort, "applyTerminalResult">;
|
|
33
|
+
providers: Partial<Record<string, PaymentProviderReconciliationProvider>>;
|
|
34
|
+
mutationsEnabled: () => boolean;
|
|
35
|
+
now?: () => Date;
|
|
36
|
+
responder: BffResponder;
|
|
37
|
+
}): (req: HttpRequest, res: HttpResponse) => Promise<void> {
|
|
38
|
+
const now = deps.now ?? (() => new Date());
|
|
39
|
+
const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = deps.responder;
|
|
40
|
+
return async (req, res) => {
|
|
41
|
+
if (req.method !== "POST") {
|
|
42
|
+
sendMethodNotAllowed(res, ["POST"]);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!deps.mutationsEnabled()) {
|
|
46
|
+
sendBffError(res, "UPSTREAM_UNAVAILABLE", "Provider payment verify is disabled", {
|
|
47
|
+
details: {
|
|
48
|
+
feature: "payment-verify",
|
|
49
|
+
featureFlag: "COMMERCE_PROVIDER_PAYMENTS_ENABLED",
|
|
50
|
+
reason: "feature_flag_disabled",
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const parsed = paymentVerifyRequestSchema.safeParse(req.body);
|
|
57
|
+
if (!parsed.success) {
|
|
58
|
+
sendBffError(res, "BAD_REQUEST", "Invalid payment verify request", {
|
|
59
|
+
details: parsed.error.flatten(),
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const snapshot = await deps.readPort.readVerifiableAttempt(parsed.data);
|
|
65
|
+
if (!snapshot) {
|
|
66
|
+
sendBffError(res, "NOT_FOUND", "Payment not found");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Same non-leaking ownership shape as payment-status: a non-owning triple
|
|
70
|
+
// reads as absent rather than confirming the payment exists.
|
|
71
|
+
if (snapshot.orderClientId !== parsed.data.clientId) {
|
|
72
|
+
sendBffError(res, "NOT_FOUND", "Payment not found");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = await verifyPaymentAttemptNow({
|
|
77
|
+
snapshot,
|
|
78
|
+
applyPort: deps.applyPort,
|
|
79
|
+
providers: deps.providers,
|
|
80
|
+
now,
|
|
81
|
+
// This route has exactly one kind of caller: a browser sitting in a live
|
|
82
|
+
// checkout, asking about the attempt it is currently waiting on. The
|
|
83
|
+
// reconciliation worker never comes through here — it holds the provider
|
|
84
|
+
// and calls `readPayment` itself — so the purpose is a property of the
|
|
85
|
+
// seam, not something a request may choose.
|
|
86
|
+
purpose: "active_checkout",
|
|
87
|
+
});
|
|
88
|
+
sendBffSuccess(res, {
|
|
89
|
+
status: result.status,
|
|
90
|
+
verified: result.verified,
|
|
91
|
+
applied: result.applied,
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { ProviderReadPurpose } from "./contracts/evidence.js";
|
|
2
|
+
import type { VerifiableAttemptSnapshot, PaymentVerifyReadPort, PaymentVerifyNowResult } from "./contracts/reconciliation.js";
|
|
3
|
+
import type {
|
|
4
|
+
ClaimedPaymentAttempt,
|
|
5
|
+
PaymentProviderReconciliationPort,
|
|
6
|
+
PaymentProviderReconciliationProvider,
|
|
7
|
+
ProviderReconciliationStatus,
|
|
8
|
+
} from "./contracts/reconciliation.js";
|
|
9
|
+
import { applyResultKey, evidencePayload, terminalAmountMismatch } from "./paymentProviderReconciliationEvidence.js";
|
|
10
|
+
|
|
11
|
+
const LIVE_ATTEMPT_STATUSES = new Set(["sent_to_provider", "requires_action", "processing", "created"]);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Shared buyer/cron-compatible live PSP read. It never accepts a browser
|
|
15
|
+
* outcome and terminal writes still pass through the canonical reconciliation
|
|
16
|
+
* RPC, so recovery cannot create a second payment truth path.
|
|
17
|
+
*/
|
|
18
|
+
export async function verifyPaymentAttemptNow(input: {
|
|
19
|
+
snapshot: VerifiableAttemptSnapshot;
|
|
20
|
+
applyPort: Pick<PaymentProviderReconciliationPort, "applyTerminalResult">;
|
|
21
|
+
providers: Partial<Record<string, PaymentProviderReconciliationProvider>>;
|
|
22
|
+
now: () => Date;
|
|
23
|
+
providerPaymentId?: string;
|
|
24
|
+
providerStatus?: ProviderReconciliationStatus;
|
|
25
|
+
/**
|
|
26
|
+
* Passed straight to the provider read. A caller that omits it gets the
|
|
27
|
+
* reconciliation reading, in which an intent still waiting for its first
|
|
28
|
+
* payment method is a finished, abandoned attempt.
|
|
29
|
+
*/
|
|
30
|
+
purpose?: ProviderReadPurpose;
|
|
31
|
+
}): Promise<PaymentVerifyNowResult> {
|
|
32
|
+
const { snapshot } = input;
|
|
33
|
+
if (!LIVE_ATTEMPT_STATUSES.has(snapshot.attemptStatus)) {
|
|
34
|
+
return { status: snapshot.attemptStatus, verified: false, applied: false, providerPaymentId: null, attempt: null };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const provider = input.providers[snapshot.provider];
|
|
38
|
+
const providerPaymentId = input.providerPaymentId
|
|
39
|
+
?? snapshot.providerAttemptId
|
|
40
|
+
?? snapshot.intentProviderPaymentId
|
|
41
|
+
?? snapshot.providerSessionId;
|
|
42
|
+
if (!provider || !providerPaymentId) {
|
|
43
|
+
return { status: "unavailable", verified: false, applied: false, providerPaymentId, attempt: null };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const attempt = toClaimedAttemptShape(snapshot, providerPaymentId);
|
|
47
|
+
const providerStatus = input.providerStatus
|
|
48
|
+
?? await provider.readPayment({
|
|
49
|
+
providerPaymentId,
|
|
50
|
+
attempt,
|
|
51
|
+
...(input.purpose ? { purpose: input.purpose } : {}),
|
|
52
|
+
});
|
|
53
|
+
if (providerStatus.status !== "succeeded" && providerStatus.status !== "failed") {
|
|
54
|
+
return {
|
|
55
|
+
// Preserve the established payment-verify contract: an unknown provider
|
|
56
|
+
// vocabulary is still non-terminal and therefore reported as pending.
|
|
57
|
+
status: "pending",
|
|
58
|
+
verified: true,
|
|
59
|
+
applied: false,
|
|
60
|
+
providerPaymentId,
|
|
61
|
+
attempt,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (terminalAmountMismatch(attempt, providerStatus)) {
|
|
66
|
+
return { status: "manual_review", verified: true, applied: false, providerPaymentId, attempt };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const checkedAt = input.now().toISOString();
|
|
70
|
+
const applied = await input.applyPort.applyTerminalResult({
|
|
71
|
+
idempotencyKey: applyResultKey(attempt, providerStatus.status),
|
|
72
|
+
expectedOrderId: snapshot.orderId,
|
|
73
|
+
expectedPaymentIntentId: snapshot.paymentIntentId,
|
|
74
|
+
expectedPaymentAttemptId: snapshot.paymentAttemptId,
|
|
75
|
+
expectedPaymentId: snapshot.paymentId,
|
|
76
|
+
provider: attempt.provider,
|
|
77
|
+
providerPaymentId,
|
|
78
|
+
localStatus: snapshot.attemptStatus,
|
|
79
|
+
providerStatus: providerStatus.providerStatus,
|
|
80
|
+
resultStatus: providerStatus.status,
|
|
81
|
+
occurredAt: providerStatus.occurredAt ?? checkedAt,
|
|
82
|
+
failureReason: providerStatus.status === "failed"
|
|
83
|
+
? providerStatus.failureReason ?? `provider_verify_${providerStatus.providerStatus}`
|
|
84
|
+
: null,
|
|
85
|
+
checkedAt,
|
|
86
|
+
payload: evidencePayload(attempt, providerStatus, {
|
|
87
|
+
source: "commerce-payment-verify.v0",
|
|
88
|
+
trigger: "buyer_confirm_settled",
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
status: providerStatus.status,
|
|
94
|
+
verified: true,
|
|
95
|
+
applied: applied.correctionStatus === "corrected" && !applied.replayed,
|
|
96
|
+
providerPaymentId,
|
|
97
|
+
attempt,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function toClaimedAttemptShape(
|
|
102
|
+
snapshot: VerifiableAttemptSnapshot,
|
|
103
|
+
providerPaymentId: string,
|
|
104
|
+
): ClaimedPaymentAttempt {
|
|
105
|
+
return {
|
|
106
|
+
paymentAttemptId: snapshot.paymentAttemptId,
|
|
107
|
+
paymentIntentId: snapshot.paymentIntentId,
|
|
108
|
+
paymentId: snapshot.paymentId,
|
|
109
|
+
orderId: snapshot.orderId,
|
|
110
|
+
subscriptionId: null,
|
|
111
|
+
subscriptionCycleId: null,
|
|
112
|
+
provider: snapshot.provider as ClaimedPaymentAttempt["provider"],
|
|
113
|
+
providerPaymentId,
|
|
114
|
+
providerAttemptId: snapshot.providerAttemptId,
|
|
115
|
+
providerSessionId: snapshot.providerSessionId,
|
|
116
|
+
attemptStatus: snapshot.attemptStatus,
|
|
117
|
+
intentStatus: snapshot.intentStatus,
|
|
118
|
+
amountMinor: snapshot.amountMinor,
|
|
119
|
+
currency: snapshot.currency,
|
|
120
|
+
orderMode: snapshot.orderMode as ClaimedPaymentAttempt["orderMode"],
|
|
121
|
+
cycleRetryAttempt: 0,
|
|
122
|
+
cycleNextRetryAt: null,
|
|
123
|
+
localUpdatedAt: snapshot.localUpdatedAt,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type PaymentRefusalHandoff = {
|
|
2
|
+
outcome: "refused" | "not_refused";
|
|
3
|
+
idempotencyKey: string;
|
|
4
|
+
paymentIntentId: string | null;
|
|
5
|
+
sourceEventId: string;
|
|
6
|
+
failureReason: string | null;
|
|
7
|
+
occurredAt: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface PaymentFailureDunningPort {
|
|
11
|
+
openFromExplicitRefusal(input: {
|
|
12
|
+
idempotencyKey: string;
|
|
13
|
+
paymentIntentId: string;
|
|
14
|
+
sourceEventId: string;
|
|
15
|
+
failureReason: string | null;
|
|
16
|
+
occurredAt: string;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Neutral terminal handoff. Only explicit refusal evidence may enter dunning;
|
|
22
|
+
* silence, timeout and every other non-terminal answer are deliberate no-ops.
|
|
23
|
+
*/
|
|
24
|
+
export async function handoffExplicitPaymentRefusal(
|
|
25
|
+
input: PaymentRefusalHandoff,
|
|
26
|
+
port: PaymentFailureDunningPort,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
if (input.outcome !== "refused" || !input.paymentIntentId) return;
|
|
29
|
+
await port.openFromExplicitRefusal({
|
|
30
|
+
idempotencyKey: input.idempotencyKey,
|
|
31
|
+
paymentIntentId: input.paymentIntentId,
|
|
32
|
+
sourceEventId: input.sourceEventId,
|
|
33
|
+
failureReason: input.failureReason,
|
|
34
|
+
occurredAt: input.occurredAt,
|
|
35
|
+
});
|
|
36
|
+
}
|