@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,263 @@
|
|
|
1
|
+
import { failureEvidencePayload } from "./paymentFailureEvidence.js";
|
|
2
|
+
import type {
|
|
3
|
+
ProviderReconciliationStatus,
|
|
4
|
+
} from "./donorContracts.js";
|
|
5
|
+
import type {
|
|
6
|
+
PaymentProviderRecoveryProvider,
|
|
7
|
+
ProviderRecoveryAction,
|
|
8
|
+
} from "./donorContracts.js";
|
|
9
|
+
import { classifyDecline } from "./classifyDecline.js";
|
|
10
|
+
import { declineCodeHints } from "./declineFailureHints.js";
|
|
11
|
+
import { noIssuerWasAsked } from "./stripeIntentConfirmationEvidence.js";
|
|
12
|
+
import type { StripeIntentLike } from "./stripeSandboxPaymentExecutionAdapter.js";
|
|
13
|
+
|
|
14
|
+
export interface StripePaymentIntentReader {
|
|
15
|
+
retrievePaymentIntent(paymentIntentId: string): Promise<StripeIntentLike>;
|
|
16
|
+
cancelPaymentIntent?(paymentIntentId: string): Promise<StripeIntentLike>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StripePaymentIntentSearcher {
|
|
20
|
+
/**
|
|
21
|
+
* Search Stripe PaymentIntents by a metadata key/value pair (GET
|
|
22
|
+
* /v1/payment_intents/search). Every intent the payment-control adapter
|
|
23
|
+
* creates carries metadata.paymentIntentId = <local intent uuid>
|
|
24
|
+
* (stripeSandboxPaymentExecutionAdapter metadataFor), so this is the
|
|
25
|
+
* intent-correlated absence probe for prepared attempts that never recorded
|
|
26
|
+
* a provider payment id.
|
|
27
|
+
*/
|
|
28
|
+
searchPaymentIntentsByMetadata(input: {
|
|
29
|
+
key: string;
|
|
30
|
+
value: string;
|
|
31
|
+
limit?: number;
|
|
32
|
+
}): Promise<{ ids: string[] }>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createStripePaymentReconciliationProvider(
|
|
36
|
+
client: StripePaymentIntentReader & Partial<StripePaymentIntentSearcher>,
|
|
37
|
+
): PaymentProviderRecoveryProvider {
|
|
38
|
+
const search = client.searchPaymentIntentsByMetadata?.bind(client);
|
|
39
|
+
const cancel = client.cancelPaymentIntent?.bind(client);
|
|
40
|
+
return {
|
|
41
|
+
resolvePaymentReference(input) {
|
|
42
|
+
return input.providerAttemptId ?? input.intentProviderPaymentId ?? input.providerSessionId;
|
|
43
|
+
},
|
|
44
|
+
async readRecoveryPayment(input) {
|
|
45
|
+
const intent = await client.retrievePaymentIntent(input.providerPaymentId);
|
|
46
|
+
let status = normalizeStripeIntent(intent);
|
|
47
|
+
let clientAction: ProviderRecoveryAction | null = null;
|
|
48
|
+
if (intent.status === "processing" || intent.status === "requires_capture") {
|
|
49
|
+
clientAction = null;
|
|
50
|
+
} else if (input.purpose === "active_checkout" && noIssuerWasAsked(intent)) {
|
|
51
|
+
clientAction = embeddedAction(intent.client_secret);
|
|
52
|
+
// The status is lowered whether or not a form can be handed back. Our
|
|
53
|
+
// inability to reopen the card panel is a fact about US; it is not an
|
|
54
|
+
// issuer's refusal, and it must not be recorded as one.
|
|
55
|
+
status = { ...status, status: "pending" };
|
|
56
|
+
} else if (intent.status === "requires_action" || intent.status === "requires_confirmation") {
|
|
57
|
+
clientAction = embeddedAction(intent.client_secret);
|
|
58
|
+
// Global reconciliation treats requires_action as terminal for
|
|
59
|
+
// off-session dunning. Buyer recovery is different: without a current
|
|
60
|
+
// client secret it is uncertain, not permission to open a new intent.
|
|
61
|
+
if (!clientAction || input.purpose === "active_checkout") {
|
|
62
|
+
status = { ...status, status: "pending" };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
status,
|
|
67
|
+
identityMatches: intent.id === input.providerPaymentId,
|
|
68
|
+
configuredMoneyMatches: stripeConfiguredMoneyMatches(intent, input.attempt),
|
|
69
|
+
manualReviewRequired: false,
|
|
70
|
+
clientAction,
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
async readPayment(input) {
|
|
74
|
+
const intent = await client.retrievePaymentIntent(input.providerPaymentId);
|
|
75
|
+
const status = normalizeStripeIntent(intent);
|
|
76
|
+
// Same rule as the recovery read above, on the rail the buyer's own
|
|
77
|
+
// "check it now" travels: an intent no issuer has been asked about is
|
|
78
|
+
// unsettled, not refused. Only `active_checkout` gets this reading, so
|
|
79
|
+
// the reconciliation worker — which names no purpose — goes on
|
|
80
|
+
// terminalizing abandonment exactly as before.
|
|
81
|
+
return input.purpose === "active_checkout" && noIssuerWasAsked(intent)
|
|
82
|
+
? { ...status, status: "pending" }
|
|
83
|
+
: status;
|
|
84
|
+
},
|
|
85
|
+
...(cancel
|
|
86
|
+
? {
|
|
87
|
+
async closePayment(input): Promise<ProviderReconciliationStatus> {
|
|
88
|
+
return normalizeStripeIntent(await cancel(input.providerPaymentId));
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
: {}),
|
|
92
|
+
...(search
|
|
93
|
+
? {
|
|
94
|
+
async findPaymentByLocalIntent(input): Promise<"found" | "absent"> {
|
|
95
|
+
const result = await search({
|
|
96
|
+
key: "paymentIntentId",
|
|
97
|
+
value: input.attempt.paymentIntentId,
|
|
98
|
+
limit: 1,
|
|
99
|
+
});
|
|
100
|
+
return result.ids.length > 0 ? "found" : "absent";
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
: {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function normalizeStripeIntent(intent: StripeIntentLike): ProviderReconciliationStatus {
|
|
108
|
+
const status = normalizeStripeStatus(intent.status);
|
|
109
|
+
return {
|
|
110
|
+
status,
|
|
111
|
+
providerStatus: intent.status,
|
|
112
|
+
occurredAt: null,
|
|
113
|
+
failureReason: failureReason(intent),
|
|
114
|
+
amountMinor: readAmountMinor(intent),
|
|
115
|
+
currency: typeof intent.currency === "string" ? intent.currency.toUpperCase() : null,
|
|
116
|
+
rawPayload: {
|
|
117
|
+
provider: "stripe",
|
|
118
|
+
paymentIntentId: intent.id,
|
|
119
|
+
status: intent.status,
|
|
120
|
+
latestChargePresent: Boolean(intent.latest_charge),
|
|
121
|
+
nextActionType: intent.next_action?.type ?? null,
|
|
122
|
+
lastPaymentErrorCode: intent.last_payment_error?.code ?? null,
|
|
123
|
+
lastPaymentErrorType: intent.last_payment_error?.type ?? null,
|
|
124
|
+
...failureCapture(intent),
|
|
125
|
+
...failureEvidencePayload(intent.diagnosticFailureEvidence),
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The disposition of a failed intent nobody actually refused.
|
|
132
|
+
*
|
|
133
|
+
* Deliberately NOT a taxonomy class. The kernel classifies REFUSALS — it answers
|
|
134
|
+
* "may this instrument be charged again, and by whom" — and an absence of
|
|
135
|
+
* attempt is not a refusal, so an "abandonment" class would be a category error
|
|
136
|
+
* inside a vocabulary whose every other member describes an issuer's verdict.
|
|
137
|
+
*/
|
|
138
|
+
export const ABANDONED_BEFORE_CONFIRMATION = "abandoned_before_confirmation";
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `requires_payment_method` on an intent that was never confirmed: no
|
|
142
|
+
* `last_payment_error`, no charge. The buyer closed the tab; no issuer was ever
|
|
143
|
+
* asked. Six of the fifteen production failure rows in the historical corpus are
|
|
144
|
+
* this state.
|
|
145
|
+
*
|
|
146
|
+
* Both absences are required before the claim is made. Presence of either is
|
|
147
|
+
* evidence an attempt happened, and mislabelling a real decline as abandonment
|
|
148
|
+
* would silently drop it out of dunning — the more expensive of the two errors.
|
|
149
|
+
*/
|
|
150
|
+
function abandonedBeforeConfirmation(intent: StripeIntentLike): boolean {
|
|
151
|
+
return intent.status === "requires_payment_method"
|
|
152
|
+
&& !intent.last_payment_error
|
|
153
|
+
&& !intent.latest_charge;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function embeddedAction(clientSecret: unknown): ProviderRecoveryAction | null {
|
|
157
|
+
return typeof clientSecret === "string" && clientSecret.length > 0
|
|
158
|
+
? { kind: "provider_embedded", provider: "stripe", clientSecret }
|
|
159
|
+
: null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The refusal evidence this rail can state, and the class it produces.
|
|
164
|
+
*
|
|
165
|
+
* This rail births most production failure rows, and it used to keep only
|
|
166
|
+
* `last_payment_error.code` — the coarse bucket ("card_declined") — while
|
|
167
|
+
* dropping `decline_code`, the fine code the adapter's hint table is written
|
|
168
|
+
* against, and `advice_code`, the issuer's own retry verdict. With both gone the
|
|
169
|
+
* taxonomy had nothing to read here, so the programme's classification telemetry
|
|
170
|
+
* stayed empty no matter how many refusals arrived.
|
|
171
|
+
*
|
|
172
|
+
* Classification goes through the shared `classifyDecline` seam, the same one
|
|
173
|
+
* the synchronous decline path uses, so there is exactly one place a refusal
|
|
174
|
+
* becomes a class. Only neutral evidence crosses: this provider's own code
|
|
175
|
+
* vocabulary is translated here, by this adapter's table, and travels no further.
|
|
176
|
+
*
|
|
177
|
+
* The class does not stop here. Since wave 3i-b it reaches the attempt and the
|
|
178
|
+
* dunning case through the terminal write's two classification parameters, and
|
|
179
|
+
* from there it becomes the cause sentence the payer is shown. It remains no
|
|
180
|
+
* part of any retry cadence.
|
|
181
|
+
*/
|
|
182
|
+
function failureCapture(intent: StripeIntentLike): Record<string, unknown> {
|
|
183
|
+
if (normalizeStripeStatus(intent.status) !== "failed") return {};
|
|
184
|
+
if (abandonedBeforeConfirmation(intent)) {
|
|
185
|
+
return { nonDeclineDisposition: ABANDONED_BEFORE_CONFIRMATION };
|
|
186
|
+
}
|
|
187
|
+
const declineCode = readCode(intent.last_payment_error?.decline_code);
|
|
188
|
+
const adviceCode = readCode(intent.last_payment_error?.advice_code);
|
|
189
|
+
const neutralReasonHints = declineCodeHints(declineCode);
|
|
190
|
+
const classification = classifyDecline({
|
|
191
|
+
// Correlation handle only — the kernel never reads it. The intent's own
|
|
192
|
+
// status is the provider-native fact when the refusal named no code.
|
|
193
|
+
code: readCode(intent.last_payment_error?.code) ?? intent.status,
|
|
194
|
+
// A read of an existing intent says nothing about whether the payer's bank
|
|
195
|
+
// can hold a reusable mandate, so this rail never claims it.
|
|
196
|
+
mandateUnsupported: false,
|
|
197
|
+
...(declineCode ? { declineCode } : {}),
|
|
198
|
+
...(adviceCode ? { adviceCode } : {}),
|
|
199
|
+
...(neutralReasonHints.length > 0 ? { neutralReasonHints } : {}),
|
|
200
|
+
}, { failureReasonKey: failureReason(intent) ?? undefined });
|
|
201
|
+
return {
|
|
202
|
+
declineCode: declineCode ?? null,
|
|
203
|
+
adviceCode: adviceCode ?? null,
|
|
204
|
+
neutralReasonHints,
|
|
205
|
+
failureClass: classification.failureClass,
|
|
206
|
+
failureClassDecidedBy: classification.decidedBy,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function readCode(value: string | null | undefined): string | undefined {
|
|
211
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function stripeConfiguredMoneyMatches(
|
|
215
|
+
intent: StripeIntentLike,
|
|
216
|
+
attempt: { amountMinor: number; currency: string },
|
|
217
|
+
): boolean {
|
|
218
|
+
return typeof intent.amount === "number"
|
|
219
|
+
&& Number.isFinite(intent.amount)
|
|
220
|
+
&& intent.amount === attempt.amountMinor
|
|
221
|
+
&& typeof intent.currency === "string"
|
|
222
|
+
&& intent.currency.toUpperCase() === attempt.currency.toUpperCase();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function normalizeStripeStatus(status: string): ProviderReconciliationStatus["status"] {
|
|
226
|
+
if (status === "succeeded") return "succeeded";
|
|
227
|
+
if (
|
|
228
|
+
status === "requires_action" ||
|
|
229
|
+
status === "requires_confirmation" ||
|
|
230
|
+
status === "requires_payment_method" ||
|
|
231
|
+
status === "canceled"
|
|
232
|
+
) {
|
|
233
|
+
return "failed";
|
|
234
|
+
}
|
|
235
|
+
if (status === "processing" || status === "requires_capture") return "pending";
|
|
236
|
+
return "unknown";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function failureReason(intent: StripeIntentLike): string | null {
|
|
240
|
+
const status = normalizeStripeStatus(intent.status);
|
|
241
|
+
if (status !== "failed") return null;
|
|
242
|
+
return intent.last_payment_error?.code
|
|
243
|
+
? `stripe_${intent.last_payment_error.code}`
|
|
244
|
+
: `stripe_${intent.status}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function readAmountMinor(intent: StripeIntentLike): number | null {
|
|
248
|
+
// amount_received is settlement truth only after success. For an incomplete,
|
|
249
|
+
// requires-payment-method, cancelled, or requires-capture intent Stripe
|
|
250
|
+
// legitimately returns 0 while `amount` still holds the configured amount.
|
|
251
|
+
// Comparing that zero to the local order created a false mismatch and made
|
|
252
|
+
// reconciliation 502-loop on an ordinary failed payment.
|
|
253
|
+
if (intent.status === "succeeded" && typeof intent.amount_received === "number" && Number.isFinite(intent.amount_received)) {
|
|
254
|
+
return intent.amount_received;
|
|
255
|
+
}
|
|
256
|
+
if (typeof intent.amount === "number" && Number.isFinite(intent.amount)) {
|
|
257
|
+
return intent.amount;
|
|
258
|
+
}
|
|
259
|
+
if (typeof intent.amount_received === "number" && Number.isFinite(intent.amount_received)) {
|
|
260
|
+
return intent.amount_received;
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A Charge observation is cumulative; its amount never replaces payment amount. */
|
|
2
|
+
export function stripeRefundEvidence(eventType: string, charge: Record<string, unknown>): {
|
|
3
|
+
refundKind: "full" | "partial" | null;
|
|
4
|
+
snapshot: { chargeId: string; amountRefundedMinor: number } | null;
|
|
5
|
+
} {
|
|
6
|
+
if (eventType !== "charge.refunded") return { refundKind: null, snapshot: null };
|
|
7
|
+
const amount = charge.amount;
|
|
8
|
+
const refunded = charge.amount_refunded;
|
|
9
|
+
const chargeId = charge.id;
|
|
10
|
+
if (typeof chargeId !== "string" || !chargeId.trim()
|
|
11
|
+
|| typeof amount !== "number" || !Number.isSafeInteger(amount) || amount <= 0
|
|
12
|
+
|| typeof refunded !== "number" || !Number.isSafeInteger(refunded) || refunded <= 0 || refunded > amount) {
|
|
13
|
+
return { refundKind: null, snapshot: null };
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
refundKind: refunded === amount ? "full" : "partial",
|
|
17
|
+
snapshot: { chargeId, amountRefundedMinor: refunded },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { failureEvidencePayload } from "./paymentFailureEvidence.js";
|
|
2
|
+
import { declineFromError, declinedExecutionFacts } from "./executionDecline.js";
|
|
3
|
+
import { noIssuerWasAsked } from "./stripeIntentConfirmationEvidence.js";
|
|
4
|
+
import type { PaymentExecutionPort } from "./donorContracts.js";
|
|
5
|
+
import type {
|
|
6
|
+
CanonicalPaymentEvent,
|
|
7
|
+
PaymentExecutionInput,
|
|
8
|
+
PaymentExecutionResult,
|
|
9
|
+
PaymentNextActionKind,
|
|
10
|
+
PaymentProviderFlow,
|
|
11
|
+
} from "./donorContracts.js";
|
|
12
|
+
|
|
13
|
+
/** The one place this adapter spells its own provider identity. */
|
|
14
|
+
export const PROVIDER_KIND = "stripe" as const;
|
|
15
|
+
|
|
16
|
+
import type { StripeIntentLike } from "./stripeFailureEvidence.js";
|
|
17
|
+
export type { StripeIntentLike } from "./stripeFailureEvidence.js";
|
|
18
|
+
|
|
19
|
+
export interface StripeSandboxClient {
|
|
20
|
+
createPaymentIntent(input: {
|
|
21
|
+
amount: number;
|
|
22
|
+
currency: string;
|
|
23
|
+
customer?: string;
|
|
24
|
+
paymentMethod?: string;
|
|
25
|
+
offSession?: boolean;
|
|
26
|
+
setupFutureUsage?: "off_session";
|
|
27
|
+
metadata: Record<string, string>;
|
|
28
|
+
}, options: { idempotencyKey: string }): Promise<StripeIntentLike>;
|
|
29
|
+
createSetupIntent(input: {
|
|
30
|
+
customer?: string;
|
|
31
|
+
paymentMethod?: string;
|
|
32
|
+
usage: "off_session";
|
|
33
|
+
metadata: Record<string, string>;
|
|
34
|
+
}, options: { idempotencyKey: string }): Promise<StripeIntentLike>;
|
|
35
|
+
/**
|
|
36
|
+
* Ensure-or-create a provider Customer for the client. Required before a
|
|
37
|
+
* `setup_future_usage='off_session'` PaymentIntent (the provider rejects it
|
|
38
|
+
* without a customer). Idempotent via the idempotency key.
|
|
39
|
+
*/
|
|
40
|
+
ensureCustomer?(input: {
|
|
41
|
+
clientId: string;
|
|
42
|
+
metadata: Record<string, string>;
|
|
43
|
+
}, options: { idempotencyKey: string }): Promise<string>;
|
|
44
|
+
retrievePaymentIntent?(paymentIntentId: string): Promise<StripeIntentLike>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StripeSandboxAdapterOptions {
|
|
48
|
+
client: StripeSandboxClient;
|
|
49
|
+
defaultFlow?: Extract<
|
|
50
|
+
PaymentProviderFlow,
|
|
51
|
+
"one_time_payment" | "setup_reusable_method" | "off_session_payment"
|
|
52
|
+
>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createStripeSandboxPaymentExecutionAdapter({
|
|
56
|
+
client,
|
|
57
|
+
defaultFlow = "one_time_payment",
|
|
58
|
+
}: StripeSandboxAdapterOptions): PaymentExecutionPort {
|
|
59
|
+
return {
|
|
60
|
+
async execute(input: PaymentExecutionInput): Promise<PaymentExecutionResult> {
|
|
61
|
+
const flow = stripeFlow(input.providerFlow ?? defaultFlow);
|
|
62
|
+
// A reusable-mandate PaymentIntent (setup_future_usage) requires a Stripe
|
|
63
|
+
// Customer. Ensure-or-create one for the client when none was passed.
|
|
64
|
+
let customerRef = input.customerRef;
|
|
65
|
+
if (!customerRef && input.saveForFutureUse && input.clientId && client.ensureCustomer) {
|
|
66
|
+
customerRef = await client.ensureCustomer(
|
|
67
|
+
{ clientId: input.clientId, metadata: { clientId: input.clientId } },
|
|
68
|
+
// A provider attempt is per click; a Stripe Customer is per OpenLup
|
|
69
|
+
// client. A stable key avoids orphan Customers when checkout recovery
|
|
70
|
+
// is reloaded before the first method webhook stores the reference.
|
|
71
|
+
{ idempotencyKey: `openlup:stripe:customer:${input.clientId}` },
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const created = flow === "setup_reusable_method"
|
|
75
|
+
? await client.createSetupIntent({
|
|
76
|
+
customer: customerRef,
|
|
77
|
+
paymentMethod: input.paymentMethodRef,
|
|
78
|
+
usage: "off_session",
|
|
79
|
+
metadata: metadataFor(input, flow),
|
|
80
|
+
}, { idempotencyKey: input.providerIdempotencyKey })
|
|
81
|
+
: await client.createPaymentIntent({
|
|
82
|
+
amount: input.amountMinor,
|
|
83
|
+
currency: input.currency.toLowerCase(),
|
|
84
|
+
customer: customerRef,
|
|
85
|
+
paymentMethod: input.paymentMethodRef,
|
|
86
|
+
offSession: flow === "off_session_payment" ? true : undefined,
|
|
87
|
+
setupFutureUsage: input.saveForFutureUse ? "off_session" : undefined,
|
|
88
|
+
metadata: metadataFor(input, flow),
|
|
89
|
+
}, { idempotencyKey: input.providerIdempotencyKey }).catch((error: unknown) => {
|
|
90
|
+
// An off-session charge is confirmed inside this call, so the payer's
|
|
91
|
+
// issuer can refuse it here and nowhere else. That refusal is a FACT,
|
|
92
|
+
// not an ambiguous call: rethrowing it makes the renewal orchestrator
|
|
93
|
+
// record an indeterminate outcome, which applies no result, opens no
|
|
94
|
+
// dunning case and tells the customer nothing. Every other error class
|
|
95
|
+
// — network, auth, config, rate limit — keeps throwing, because after
|
|
96
|
+
// those the charge may still have been accepted and only the
|
|
97
|
+
// fail-closed path is safe.
|
|
98
|
+
const decline = flow === "off_session_payment" ? declineFromError(error) : null;
|
|
99
|
+
if (!decline) throw error;
|
|
100
|
+
return decline;
|
|
101
|
+
});
|
|
102
|
+
if ("decline" in created) return { ...executionResultBase(input, flow), ...declinedExecutionFacts(created) };
|
|
103
|
+
const intent = created;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
...executionResultBase(input, flow),
|
|
107
|
+
providerAttemptId: intent.id,
|
|
108
|
+
providerSessionId: intent.id,
|
|
109
|
+
attemptStatus: attemptStatusFromStripe(intent),
|
|
110
|
+
nextActionKind: nextActionKindFromStripe(intent),
|
|
111
|
+
// `clientSecret` is the single canonical exit for the FE to confirm with
|
|
112
|
+
// the provider's browser SDK. It MUST NOT be persisted or duplicated
|
|
113
|
+
// into `responsePayload` (the latter is logged); the consumer reads it
|
|
114
|
+
// once from the top-level result and discards.
|
|
115
|
+
clientSecret: intent.client_secret ?? null,
|
|
116
|
+
customerProviderRef: intent.customer ?? null,
|
|
117
|
+
paymentMethodRef: intent.payment_method ?? null,
|
|
118
|
+
webhookExpected: true,
|
|
119
|
+
recoveryRequired: intent.status === "requires_action" && flow === "off_session_payment",
|
|
120
|
+
responsePayload: {
|
|
121
|
+
providerStatus: intent.status,
|
|
122
|
+
providerAttemptId: intent.id,
|
|
123
|
+
providerSessionIdPresent: Boolean(intent.client_secret),
|
|
124
|
+
latestChargePresent: Boolean(intent.latest_charge),
|
|
125
|
+
customerRefPresent: Boolean(intent.customer),
|
|
126
|
+
paymentMethodRefPresent: Boolean(intent.payment_method),
|
|
127
|
+
...failureEvidencePayload(intent.diagnosticFailureEvidence),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function parseStripePaymentWebhookEvent(rawPayload: Record<string, unknown>): CanonicalPaymentEvent {
|
|
135
|
+
const eventId = readString(rawPayload, "id");
|
|
136
|
+
const eventType = readString(rawPayload, "type");
|
|
137
|
+
const data = readObject(rawPayload, "data");
|
|
138
|
+
const object = readObject(data, "object");
|
|
139
|
+
const providerPaymentId = readString(object, "id");
|
|
140
|
+
const amount = readOptionalNumber(object, "amount_received") ?? readOptionalNumber(object, "amount");
|
|
141
|
+
const currency = readOptionalString(object, "currency")?.toUpperCase();
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
provider_event_id: eventId,
|
|
145
|
+
event_type: canonicalStripeEventType(eventType, object),
|
|
146
|
+
payment_provider_id: providerPaymentId,
|
|
147
|
+
amount_minor: amount,
|
|
148
|
+
next_action_kind: nextActionKindFromStripe({
|
|
149
|
+
status: readOptionalString(object, "status") ?? "",
|
|
150
|
+
next_action: readOptionalObject(object, "next_action"),
|
|
151
|
+
}) ?? undefined,
|
|
152
|
+
raw_payload: {
|
|
153
|
+
provider: PROVIDER_KIND,
|
|
154
|
+
eventId,
|
|
155
|
+
eventType,
|
|
156
|
+
providerPaymentId,
|
|
157
|
+
amountMinor: amount,
|
|
158
|
+
currency,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The facts every result of this adapter carries, whatever the provider answered. */
|
|
164
|
+
function executionResultBase(
|
|
165
|
+
input: PaymentExecutionInput,
|
|
166
|
+
flow: string,
|
|
167
|
+
): Pick<PaymentExecutionResult, "provider" | "requestPayload"> {
|
|
168
|
+
return {
|
|
169
|
+
provider: PROVIDER_KIND,
|
|
170
|
+
requestPayload: {
|
|
171
|
+
providerIdempotencyKey: input.providerIdempotencyKey,
|
|
172
|
+
providerRequestFingerprint: input.providerRequestFingerprint,
|
|
173
|
+
providerFlow: flow,
|
|
174
|
+
amountMinor: input.amountMinor,
|
|
175
|
+
currency: input.currency,
|
|
176
|
+
orderRef: input.orderRef,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function stripeFlow(flow: PaymentProviderFlow): "one_time_payment" | "setup_reusable_method" | "off_session_payment" {
|
|
182
|
+
if (flow === "one_time_payment" || flow === "setup_reusable_method" || flow === "off_session_payment") {
|
|
183
|
+
return flow;
|
|
184
|
+
}
|
|
185
|
+
throw new Error(`Unsupported Stripe provider flow: ${flow}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function metadataFor(input: PaymentExecutionInput, flow: string): Record<string, string> {
|
|
189
|
+
const metadata: Record<string, string> = {
|
|
190
|
+
source: "openlup.payment-control",
|
|
191
|
+
paymentIntentId: input.paymentIntentId,
|
|
192
|
+
orderRef: input.orderRef,
|
|
193
|
+
providerRequestFingerprint: input.providerRequestFingerprint,
|
|
194
|
+
providerFlow: flow,
|
|
195
|
+
};
|
|
196
|
+
if (input.clientId) metadata.clientId = input.clientId;
|
|
197
|
+
if (input.mode === "subscription_cycle") metadata.mode = input.mode;
|
|
198
|
+
return metadata;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* What the durable attempt row is allowed to claim about this execution.
|
|
203
|
+
*
|
|
204
|
+
* Takes the intent, not just its status, because the two states sharing
|
|
205
|
+
* `requires_payment_method` differ only in what else is on the object: a fresh
|
|
206
|
+
* intent the payer never confirmed, and one an issuer has just refused (which
|
|
207
|
+
* carries a payment error and usually a charge).
|
|
208
|
+
*
|
|
209
|
+
* `processing` claims money is moving, and a catch-all used to reach it — so a
|
|
210
|
+
* never-confirmed intent was recorded as a charge in flight, and a payer was
|
|
211
|
+
* told their payment was already processing when no issuer had been asked.
|
|
212
|
+
* `sent_to_provider` is the truth: the request reached the provider, no outcome
|
|
213
|
+
* is known. It belongs to every live-attempt set the platform keeps, so the
|
|
214
|
+
* correction costs no watchdog coverage, and it is fail-closed at the
|
|
215
|
+
* provider-attempt admission gate exactly as `processing` is — admission does
|
|
216
|
+
* not widen by one state.
|
|
217
|
+
*
|
|
218
|
+
* The catch-all stays `processing` on purpose: an unrecognised provider status is
|
|
219
|
+
* not evidence that nothing happened.
|
|
220
|
+
*/
|
|
221
|
+
function attemptStatusFromStripe(
|
|
222
|
+
intent: StripeIntentLike,
|
|
223
|
+
): NonNullable<PaymentExecutionResult["attemptStatus"]> {
|
|
224
|
+
if (intent.status === "requires_action" || intent.status === "requires_confirmation") {
|
|
225
|
+
return "requires_action";
|
|
226
|
+
}
|
|
227
|
+
if (noIssuerWasAsked(intent)) return "sent_to_provider";
|
|
228
|
+
return "processing";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function nextActionKindFromStripe(intent: Pick<StripeIntentLike, "status" | "next_action">): PaymentNextActionKind | null {
|
|
232
|
+
if (intent.status !== "requires_action" && intent.status !== "requires_confirmation") return null;
|
|
233
|
+
if (intent.next_action?.type === "redirect_to_url") return "redirect";
|
|
234
|
+
if (intent.next_action?.type === "use_stripe_sdk") return "3ds_challenge";
|
|
235
|
+
return "sca_required";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function canonicalStripeEventType(
|
|
239
|
+
stripeEventType: string,
|
|
240
|
+
object: Record<string, unknown>,
|
|
241
|
+
): CanonicalPaymentEvent["event_type"] {
|
|
242
|
+
if (stripeEventType === "payment_intent.succeeded") return "payment.succeeded";
|
|
243
|
+
if (stripeEventType === "payment_intent.payment_failed" || stripeEventType === "payment_intent.canceled") {
|
|
244
|
+
return "payment.failed";
|
|
245
|
+
}
|
|
246
|
+
if (stripeEventType === "payment_intent.requires_action") return "payment.requires_action";
|
|
247
|
+
if (stripeEventType === "charge.refunded") return "payment.refunded";
|
|
248
|
+
if (stripeEventType.startsWith("charge.dispute.")) return "payment.disputed";
|
|
249
|
+
const status = readOptionalString(object, "status");
|
|
250
|
+
if (status === "requires_action") return "payment.requires_action";
|
|
251
|
+
throw new Error(`Unsupported Stripe webhook event: ${stripeEventType}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function readObject(value: Record<string, unknown>, key: string): Record<string, unknown> {
|
|
255
|
+
const raw = value[key];
|
|
256
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
257
|
+
throw new Error(`Stripe payload missing object ${key}`);
|
|
258
|
+
}
|
|
259
|
+
return raw as Record<string, unknown>;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function readOptionalObject(value: Record<string, unknown>, key: string): Record<string, unknown> | null {
|
|
263
|
+
const raw = value[key];
|
|
264
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function readString(value: Record<string, unknown>, key: string): string {
|
|
268
|
+
const raw = value[key];
|
|
269
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
270
|
+
throw new Error(`Stripe payload missing string ${key}`);
|
|
271
|
+
}
|
|
272
|
+
return raw;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function readOptionalString(value: Record<string, unknown>, key: string): string | undefined {
|
|
276
|
+
const raw = value[key];
|
|
277
|
+
return typeof raw === "string" && raw.length > 0 ? raw : undefined;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function readOptionalNumber(value: Record<string, unknown>, key: string): number | undefined {
|
|
281
|
+
const raw = value[key];
|
|
282
|
+
return typeof raw === "number" ? raw : undefined;
|
|
283
|
+
}
|