@openlup/platform 0.25.0 → 0.27.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 +156 -440
- package/README.md +230 -11
- package/dist/commerce/adapters/managed/commerceOmsControlPlane.d.ts +3 -0
- package/dist/commerce/adapters/managed/commerceOmsControlPlane.js +71 -0
- package/dist/commerce/adapters/managed/commerceOmsMutations.d.ts +10 -0
- package/dist/commerce/adapters/managed/commerceOmsMutations.js +119 -0
- package/dist/commerce/adapters/managed/commerceOmsPort.d.ts +28 -0
- package/dist/commerce/adapters/managed/commerceOmsPort.js +101 -0
- package/dist/commerce/adapters/managed/oms/accounting.d.ts +3 -0
- package/dist/commerce/adapters/managed/oms/accounting.js +13 -0
- package/dist/commerce/adapters/managed/oms/fulfillmentReads.d.ts +17 -0
- package/dist/commerce/adapters/managed/oms/fulfillmentReads.js +81 -0
- package/dist/commerce/adapters/managed/oms/inboundEvents.d.ts +9 -0
- package/dist/commerce/adapters/managed/oms/inboundEvents.js +40 -0
- package/dist/commerce/adapters/managed/oms/outboxEvents.d.ts +2 -0
- package/dist/commerce/adapters/managed/oms/outboxEvents.js +23 -0
- package/dist/commerce/adapters/managed/oms/paymentAttempts.d.ts +3 -0
- package/dist/commerce/adapters/managed/oms/paymentAttempts.js +15 -0
- package/dist/commerce/adapters/managed/oms/queueSelection.d.ts +12 -0
- package/dist/commerce/adapters/managed/oms/queueSelection.js +109 -0
- package/dist/commerce/adapters/managed/oms/readProjections.d.ts +18 -0
- package/dist/commerce/adapters/managed/oms/readProjections.js +13 -0
- package/dist/commerce/adapters/managed/oms/readQueries.d.ts +16 -0
- package/dist/commerce/adapters/managed/oms/readQueries.js +276 -0
- package/dist/commerce/adapters/managed/oms/types.d.ts +2 -0
- package/dist/commerce/adapters/managed/oms/types.js +7 -0
- package/dist/commerce/checkoutCommandSchemas.d.ts +89 -0
- package/dist/commerce/checkoutCommandSchemas.js +70 -0
- package/dist/commerce/checkoutHelpers.d.ts +21 -0
- package/dist/commerce/checkoutHelpers.js +29 -0
- package/dist/commerce/checkoutResumeSchemas.d.ts +158 -0
- package/dist/commerce/checkoutResumeSchemas.js +145 -0
- package/dist/commerce/checkoutSchemas.d.ts +158 -0
- package/dist/commerce/checkoutSchemas.js +161 -0
- package/dist/commerce/commerceReturnsPort.d.ts +97 -0
- package/dist/commerce/commerceReturnsPort.js +39 -0
- package/dist/commerce/configuratorIntentPersistenceSchemas.d.ts +20 -0
- package/dist/commerce/configuratorIntentPersistenceSchemas.js +21 -0
- package/dist/commerce/configuratorIntentSchemas.d.ts +63 -0
- package/dist/commerce/configuratorIntentSchemas.js +43 -0
- package/dist/commerce/contractPrimitives.d.ts +50 -0
- package/dist/commerce/contractPrimitives.js +25 -0
- package/dist/commerce/contracts/availability.d.ts +23 -0
- package/dist/commerce/contracts/availability.js +1 -0
- package/dist/commerce/contracts/fulfillmentRecovery.d.ts +25 -0
- package/dist/commerce/contracts/fulfillmentRecovery.js +1 -0
- package/dist/commerce/contracts/index.d.ts +13 -0
- package/dist/commerce/contracts/index.js +1 -0
- package/dist/commerce/contracts/intent.d.ts +86 -0
- package/dist/commerce/contracts/intent.js +1 -0
- package/dist/commerce/contracts/omsTypes.d.ts +1216 -0
- package/dist/commerce/contracts/omsTypes.js +1 -0
- package/dist/commerce/contracts/order.d.ts +39 -0
- package/dist/commerce/contracts/order.js +1 -0
- package/dist/commerce/contracts/orderMoney.d.ts +52 -0
- package/dist/commerce/contracts/orderMoney.js +1 -0
- package/dist/commerce/contracts/ports.d.ts +113 -0
- package/dist/commerce/contracts/ports.js +1 -0
- package/dist/commerce/contracts/recovery.d.ts +11 -0
- package/dist/commerce/contracts/recovery.js +1 -0
- package/dist/commerce/contracts/runtimePorts.d.ts +196 -0
- package/dist/commerce/contracts/runtimePorts.js +1 -0
- package/dist/commerce/contracts/state.d.ts +26 -0
- package/dist/commerce/contracts/state.js +43 -0
- package/dist/commerce/customerDefaultsSnapshotSchemas.d.ts +51 -0
- package/dist/commerce/customerDefaultsSnapshotSchemas.js +106 -0
- package/dist/commerce/errors.d.ts +18 -0
- package/dist/commerce/errors.js +36 -0
- package/dist/commerce/fulfillmentEligibility.d.ts +6 -0
- package/dist/commerce/fulfillmentEligibility.js +31 -0
- package/dist/commerce/index.d.ts +72 -0
- package/dist/commerce/index.js +67 -0
- package/dist/commerce/model/customerFulfillmentRecovery.d.ts +40 -0
- package/dist/commerce/model/customerFulfillmentRecovery.js +144 -0
- package/dist/commerce/model/fulfillmentCancellationEligibility.d.ts +7 -0
- package/dist/commerce/model/fulfillmentCancellationEligibility.js +9 -0
- package/dist/commerce/model/omsAccountingVisibility.d.ts +12 -0
- package/dist/commerce/model/omsAccountingVisibility.js +118 -0
- package/dist/commerce/model/omsBillingInvoiceBuyer.d.ts +2 -0
- package/dist/commerce/model/omsBillingInvoiceBuyer.js +15 -0
- package/dist/commerce/model/omsCurrency.d.ts +4 -0
- package/dist/commerce/model/omsCurrency.js +5 -0
- package/dist/commerce/model/omsFulfillmentDebug.d.ts +20 -0
- package/dist/commerce/model/omsFulfillmentDebug.js +122 -0
- package/dist/commerce/model/omsFulfillmentSummary.d.ts +16 -0
- package/dist/commerce/model/omsFulfillmentSummary.js +189 -0
- package/dist/commerce/model/omsListTotals.d.ts +13 -0
- package/dist/commerce/model/omsListTotals.js +32 -0
- package/dist/commerce/model/omsOperationalLadder.d.ts +51 -0
- package/dist/commerce/model/omsOperationalLadder.js +158 -0
- package/dist/commerce/model/omsOrderLineMoney.d.ts +4 -0
- package/dist/commerce/model/omsOrderLineMoney.js +25 -0
- package/dist/commerce/model/omsPaymentMethodSummary.d.ts +5 -0
- package/dist/commerce/model/omsPaymentMethodSummary.js +11 -0
- package/dist/commerce/model/omsReadModel.d.ts +43 -0
- package/dist/commerce/model/omsReadModel.js +211 -0
- package/dist/commerce/model/omsReadModelCommunication.d.ts +3 -0
- package/dist/commerce/model/omsReadModelCommunication.js +29 -0
- package/dist/commerce/model/omsReadModelHelpers.d.ts +125 -0
- package/dist/commerce/model/omsReadModelHelpers.js +212 -0
- package/dist/commerce/model/omsReadModelList.d.ts +33 -0
- package/dist/commerce/model/omsReadModelList.js +50 -0
- package/dist/commerce/model/omsReadModelListSummary.d.ts +3 -0
- package/dist/commerce/model/omsReadModelListSummary.js +13 -0
- package/dist/commerce/model/omsReadModelPricing.d.ts +5 -0
- package/dist/commerce/model/omsReadModelPricing.js +36 -0
- package/dist/commerce/model/omsReadModelProjections.d.ts +1 -0
- package/dist/commerce/model/omsReadModelProjections.js +1 -0
- package/dist/commerce/model/omsReadModelRowCore.d.ts +79 -0
- package/dist/commerce/model/omsReadModelRowCore.js +149 -0
- package/dist/commerce/model/omsReadModelRows.d.ts +1 -0
- package/dist/commerce/model/omsReadModelRows.js +6 -0
- package/dist/commerce/offerAvailability.d.ts +18 -0
- package/dist/commerce/offerAvailability.js +53 -0
- package/dist/commerce/offerAvailabilitySchemas.d.ts +43 -0
- package/dist/commerce/offerAvailabilitySchemas.js +42 -0
- package/dist/commerce/offerEvidenceSchemas.d.ts +7 -0
- package/dist/commerce/offerEvidenceSchemas.js +13 -0
- package/dist/commerce/oms/omsContractBase.d.ts +130 -0
- package/dist/commerce/oms/omsContractBase.js +86 -0
- package/dist/commerce/oms/omsContractReadAuxSchemas.d.ts +222 -0
- package/dist/commerce/oms/omsContractReadAuxSchemas.js +123 -0
- package/dist/commerce/oms/omsContractReadSchemas.d.ts +2266 -0
- package/dist/commerce/oms/omsContractReadSchemas.js +277 -0
- package/dist/commerce/oms/omsContractRequests.d.ts +196 -0
- package/dist/commerce/oms/omsContractRequests.js +118 -0
- package/dist/commerce/oms/omsContracts.d.ts +5 -0
- package/dist/commerce/oms/omsContracts.js +4 -0
- package/dist/commerce/oms/omsControlPlane.d.ts +275 -0
- package/dist/commerce/oms/omsControlPlane.js +72 -0
- package/dist/commerce/oms/omsFulfillmentContractSchemas.d.ts +203 -0
- package/dist/commerce/oms/omsFulfillmentContractSchemas.js +94 -0
- package/dist/commerce/oms/omsFulfillmentDebugSchemas.d.ts +69 -0
- package/dist/commerce/oms/omsFulfillmentDebugSchemas.js +33 -0
- package/dist/commerce/oms/omsFulfillmentHealthSchemas.d.ts +67 -0
- package/dist/commerce/oms/omsFulfillmentHealthSchemas.js +44 -0
- package/dist/commerce/oms/omsPorts.d.ts +84 -0
- package/dist/commerce/oms/omsPorts.js +148 -0
- package/dist/commerce/oms/omsPrimitives.d.ts +5 -0
- package/dist/commerce/oms/omsPrimitives.js +3 -0
- package/dist/commerce/oms/omsProviderEvidenceSchemas.d.ts +15 -0
- package/dist/commerce/oms/omsProviderEvidenceSchemas.js +12 -0
- package/dist/commerce/orderDraftErrors.d.ts +13 -0
- package/dist/commerce/orderDraftErrors.js +20 -0
- package/dist/commerce/orderEmailEligibility.d.ts +54 -0
- package/dist/commerce/orderEmailEligibility.js +123 -0
- package/dist/commerce/orderEmailLines.d.ts +12 -0
- package/dist/commerce/orderEmailLines.js +8 -0
- package/dist/commerce/orderFeedbackContracts.d.ts +41 -0
- package/dist/commerce/orderFeedbackContracts.js +33 -0
- package/dist/commerce/orderMoney.d.ts +6 -0
- package/dist/commerce/orderMoney.js +168 -0
- package/dist/commerce/orderReviewContracts.d.ts +475 -0
- package/dist/commerce/orderReviewContracts.js +94 -0
- package/dist/commerce/orderReviewPorts.d.ts +140 -0
- package/dist/commerce/orderReviewPorts.js +1 -0
- package/dist/commerce/outboxDispatchContracts.d.ts +30 -0
- package/dist/commerce/outboxDispatchContracts.js +1 -0
- package/dist/commerce/outboxEventSchemas.d.ts +162 -0
- package/dist/commerce/outboxEventSchemas.js +164 -0
- package/dist/commerce/outboxOrderDraftEmailPorts.d.ts +207 -0
- package/dist/commerce/outboxOrderDraftEmailPorts.js +12 -0
- package/dist/commerce/paymentContinuation.d.ts +4 -0
- package/dist/commerce/paymentContinuation.js +4 -0
- package/dist/commerce/ports.d.ts +8 -0
- package/dist/commerce/ports.js +1 -0
- package/dist/commerce/recoveryDestination.d.ts +3 -0
- package/dist/commerce/recoveryDestination.js +17 -0
- package/dist/commerce/runtimeErrors.d.ts +8 -0
- package/dist/commerce/runtimeErrors.js +16 -0
- package/dist/commerce/salesChannelEnvelope.d.ts +20 -0
- package/dist/commerce/salesChannelEnvelope.js +7 -0
- package/dist/commerce/schemas.d.ts +174 -0
- package/dist/commerce/schemas.js +84 -0
- package/dist/commerce/server/checkoutCompensation.d.ts +21 -0
- package/dist/commerce/server/checkoutCompensation.js +32 -0
- package/dist/commerce/server/checkoutConflictClassifiers.d.ts +17 -0
- package/dist/commerce/server/checkoutConflictClassifiers.js +38 -0
- package/dist/commerce/server/checkoutExpiredRecoveryPolicy.d.ts +38 -0
- package/dist/commerce/server/checkoutExpiredRecoveryPolicy.js +85 -0
- package/dist/commerce/server/checkoutInvoiceBuyerSnapshot.d.ts +22 -0
- package/dist/commerce/server/checkoutInvoiceBuyerSnapshot.js +30 -0
- package/dist/commerce/server/checkoutOfferPolicyPort.d.ts +33 -0
- package/dist/commerce/server/checkoutOfferPolicyPort.js +49 -0
- package/dist/commerce/server/checkoutPaymentContinuationCredential.d.ts +58 -0
- package/dist/commerce/server/checkoutPaymentContinuationCredential.js +154 -0
- package/dist/commerce/server/checkoutQuoteTypes.d.ts +19 -0
- package/dist/commerce/server/checkoutQuoteTypes.js +1 -0
- package/dist/commerce/server/checkoutRecoveryOrderPort.d.ts +42 -0
- package/dist/commerce/server/checkoutRecoveryOrderPort.js +1 -0
- package/dist/commerce/server/checkoutRecoveryToken.d.ts +40 -0
- package/dist/commerce/server/checkoutRecoveryToken.js +26 -0
- package/dist/commerce/server/checkoutRefusalContinuation.d.ts +24 -0
- package/dist/commerce/server/checkoutRefusalContinuation.js +18 -0
- package/dist/commerce/server/checkoutRuntimeTypes.d.ts +81 -0
- package/dist/commerce/server/checkoutRuntimeTypes.js +1 -0
- package/dist/commerce/server/commerceCheckoutCommandExecution.d.ts +60 -0
- package/dist/commerce/server/commerceCheckoutCommandExecution.js +136 -0
- package/dist/commerce/server/commerceCheckoutCompensation.d.ts +26 -0
- package/dist/commerce/server/commerceCheckoutCompensation.js +46 -0
- package/dist/commerce/server/commerceCheckoutContinuationTerms.d.ts +50 -0
- package/dist/commerce/server/commerceCheckoutContinuationTerms.js +66 -0
- package/dist/commerce/server/commerceCheckoutCustomerDefaults.d.ts +18 -0
- package/dist/commerce/server/commerceCheckoutCustomerDefaults.js +17 -0
- package/dist/commerce/server/commerceCheckoutFailureHandler.d.ts +51 -0
- package/dist/commerce/server/commerceCheckoutFailureHandler.js +90 -0
- package/dist/commerce/server/commerceCheckoutHandler.d.ts +213 -0
- package/dist/commerce/server/commerceCheckoutHandler.js +196 -0
- package/dist/commerce/server/commerceCheckoutHandlerAdmission.d.ts +58 -0
- package/dist/commerce/server/commerceCheckoutHandlerAdmission.js +94 -0
- package/dist/commerce/server/commerceCheckoutHandlerHelpers.d.ts +67 -0
- package/dist/commerce/server/commerceCheckoutHandlerHelpers.js +155 -0
- package/dist/commerce/server/commerceCheckoutOrchestrationError.d.ts +9 -0
- package/dist/commerce/server/commerceCheckoutOrchestrationError.js +14 -0
- package/dist/commerce/server/commerceCheckoutPromotionPriceChange.d.ts +47 -0
- package/dist/commerce/server/commerceCheckoutPromotionPriceChange.js +43 -0
- package/dist/commerce/server/commerceCheckoutProviderPayment.d.ts +34 -0
- package/dist/commerce/server/commerceCheckoutProviderPayment.js +37 -0
- package/dist/commerce/server/commerceCheckoutQuoteGuard.d.ts +62 -0
- package/dist/commerce/server/commerceCheckoutQuoteGuard.js +106 -0
- package/dist/commerce/server/commerceCheckoutResume.d.ts +29 -0
- package/dist/commerce/server/commerceCheckoutResume.js +81 -0
- package/dist/commerce/server/commerceCheckoutResumeGuard.d.ts +54 -0
- package/dist/commerce/server/commerceCheckoutResumeGuard.js +138 -0
- package/dist/commerce/server/commerceCheckoutSavedPaymentMethod.d.ts +39 -0
- package/dist/commerce/server/commerceCheckoutSavedPaymentMethod.js +44 -0
- package/dist/commerce/server/commerceCheckoutStartRuntimeFailure.d.ts +7 -0
- package/dist/commerce/server/commerceCheckoutStartRuntimeFailure.js +39 -0
- package/dist/commerce/server/commerceCheckoutTiming.d.ts +8 -0
- package/dist/commerce/server/commerceCheckoutTiming.js +31 -0
- package/dist/commerce/server/commerceDiagnostics.d.ts +6 -0
- package/dist/commerce/server/commerceDiagnostics.js +31 -0
- package/dist/commerce/server/commerceOmsCancelOrderHandler.d.ts +7 -0
- package/dist/commerce/server/commerceOmsCancelOrderHandler.js +22 -0
- package/dist/commerce/server/commerceOmsControlPlaneHandlers.d.ts +18 -0
- package/dist/commerce/server/commerceOmsControlPlaneHandlers.js +60 -0
- package/dist/commerce/server/commerceOmsHandlers.d.ts +47 -0
- package/dist/commerce/server/commerceOmsHandlers.js +176 -0
- package/dist/commerce/server/commerceOmsMarkRefundedHandler.d.ts +7 -0
- package/dist/commerce/server/commerceOmsMarkRefundedHandler.js +19 -0
- package/dist/commerce/server/commerceOrderDraft.d.ts +24 -0
- package/dist/commerce/server/commerceOrderDraft.js +92 -0
- package/dist/commerce/server/commercePaidOrderOrchestration.d.ts +131 -0
- package/dist/commerce/server/commercePaidOrderOrchestration.js +140 -0
- package/dist/commerce/server/commerceProviderAttemptFailure.d.ts +133 -0
- package/dist/commerce/server/commerceProviderAttemptFailure.js +175 -0
- package/dist/commerce/server/commerceReturnsHandlers.d.ts +41 -0
- package/dist/commerce/server/commerceReturnsHandlers.js +100 -0
- package/dist/commerce/server/commerceRuntimePort.d.ts +16 -0
- package/dist/commerce/server/commerceRuntimePort.js +57 -0
- package/dist/commerce/server/commerceRuntimeService.d.ts +98 -0
- package/dist/commerce/server/commerceRuntimeService.js +186 -0
- package/dist/commerce/server/commerceRuntimeStartResponse.d.ts +37 -0
- package/dist/commerce/server/commerceRuntimeStartResponse.js +56 -0
- package/dist/commerce/server/commerceStarterOfferGuard.d.ts +55 -0
- package/dist/commerce/server/commerceStarterOfferGuard.js +128 -0
- package/dist/commerce/server/configuratorIntentPersistence.d.ts +47 -0
- package/dist/commerce/server/configuratorIntentPersistence.js +85 -0
- package/dist/commerce/server/configuratorIntentPersistenceHandler.d.ts +14 -0
- package/dist/commerce/server/configuratorIntentPersistenceHandler.js +42 -0
- package/dist/commerce/server/emailRuntime.d.ts +47 -0
- package/dist/commerce/server/emailRuntime.js +1 -0
- package/dist/commerce/server/handlerSupport.d.ts +68 -0
- package/dist/commerce/server/handlerSupport.js +1 -0
- package/dist/commerce/server/index.d.ts +178 -0
- package/dist/commerce/server/index.js +127 -0
- package/dist/commerce/server/offerAvailability.d.ts +21 -0
- package/dist/commerce/server/offerAvailability.js +163 -0
- package/dist/commerce/server/orderDraftRpcPayload.d.ts +20 -0
- package/dist/commerce/server/orderDraftRpcPayload.js +21 -0
- package/dist/commerce/server/orderFeedbackSubmit.d.ts +20 -0
- package/dist/commerce/server/orderFeedbackSubmit.js +16 -0
- package/dist/commerce/server/orderFeedbackTokenCheck.d.ts +17 -0
- package/dist/commerce/server/orderFeedbackTokenCheck.js +14 -0
- package/dist/commerce/server/orderReviewAdminHandlers.d.ts +47 -0
- package/dist/commerce/server/orderReviewAdminHandlers.js +41 -0
- package/dist/commerce/server/orderReviewHandlers.d.ts +114 -0
- package/dist/commerce/server/orderReviewHandlers.js +42 -0
- package/dist/commerce/server/orderReviewLifecycle.d.ts +10 -0
- package/dist/commerce/server/orderReviewLifecycle.js +121 -0
- package/dist/commerce/server/orderReviewMedia.d.ts +11 -0
- package/dist/commerce/server/orderReviewMedia.js +172 -0
- package/dist/commerce/server/orderReviewRawUploadHandler.d.ts +40 -0
- package/dist/commerce/server/orderReviewRawUploadHandler.js +316 -0
- package/dist/commerce/server/outboxCheckoutExpiredEmailHandler.d.ts +14 -0
- package/dist/commerce/server/outboxCheckoutExpiredEmailHandler.js +57 -0
- package/dist/commerce/server/outboxCheckoutRecoveryEmailHandler.d.ts +13 -0
- package/dist/commerce/server/outboxCheckoutRecoveryEmailHandler.js +150 -0
- package/dist/commerce/server/outboxDispatchRegistry.d.ts +46 -0
- package/dist/commerce/server/outboxDispatchRegistry.js +184 -0
- package/dist/commerce/server/outboxDispatchRegistryComposition.d.ts +6 -0
- package/dist/commerce/server/outboxDispatchRegistryComposition.js +23 -0
- package/dist/commerce/server/outboxOrderDraftEmailHandler.d.ts +14 -0
- package/dist/commerce/server/outboxOrderDraftEmailHandler.js +130 -0
- package/dist/commerce/server/outboxOrderPaidEmailHandler.d.ts +21 -0
- package/dist/commerce/server/outboxOrderPaidEmailHandler.js +93 -0
- package/dist/commerce/server/outboxOrderPaidFulfillmentHandler.d.ts +3 -0
- package/dist/commerce/server/outboxOrderPaidFulfillmentHandler.js +1 -0
- package/dist/commerce/server/outboxOrderPaidFulfillmentPorts.d.ts +3 -0
- package/dist/commerce/server/outboxOrderPaidFulfillmentPorts.js +1 -0
- package/dist/commerce/server/outboxOrderRefundedEmailHandler.d.ts +13 -0
- package/dist/commerce/server/outboxOrderRefundedEmailHandler.js +42 -0
- package/dist/commerce/server/outboxPaymentFailedEmailHandler.d.ts +14 -0
- package/dist/commerce/server/outboxPaymentFailedEmailHandler.js +65 -0
- package/dist/commerce/server/outboxReturnEmailHandlers.d.ts +26 -0
- package/dist/commerce/server/outboxReturnEmailHandlers.js +53 -0
- package/dist/commerce/server/outboxShipmentDeliveredEmailHandler.d.ts +17 -0
- package/dist/commerce/server/outboxShipmentDeliveredEmailHandler.js +52 -0
- package/dist/commerce/server/outboxShipmentDispatchedEmailHandler.d.ts +13 -0
- package/dist/commerce/server/outboxShipmentDispatchedEmailHandler.js +87 -0
- package/dist/commerce/server/outboxShipmentExceptionEmailHandler.d.ts +13 -0
- package/dist/commerce/server/outboxShipmentExceptionEmailHandler.js +46 -0
- package/dist/commerce/server/referenceCheckoutHandler.d.ts +48 -0
- package/dist/commerce/server/referenceCheckoutHandler.js +68 -0
- package/dist/commerce/server/reservationSweepPort.d.ts +31 -0
- package/dist/commerce/server/reservationSweepPort.js +45 -0
- package/dist/commerce/server/starterTermsPort.d.ts +22 -0
- package/dist/commerce/server/starterTermsPort.js +1 -0
- package/dist/commerce/server/supabaseCommerceReturnsPort.d.ts +13 -0
- package/dist/commerce/server/supabaseCommerceReturnsPort.js +56 -0
- package/dist/communications/consentPurposeRegistry.d.ts +1 -1
- package/dist/communications/newsletterIntegrationContracts.d.ts +5 -5
- package/dist/composition/commerceContribution.d.ts +21 -0
- package/dist/composition/commerceContribution.js +27 -0
- package/dist/composition/index.d.ts +1 -0
- package/dist/composition/index.js +1 -0
- package/dist/fulfillment/handlerSchemas.d.ts +41 -41
- package/dist/fulfillment/index.d.ts +1 -1
- package/dist/fulfillment/index.js +1 -1
- package/dist/fulfillment/paidHandoff.d.ts +12 -1
- package/dist/fulfillment/paidHandoff.js +2 -2
- package/dist/fulfillment/providerDispatchWorker.d.ts +4 -4
- package/dist/identity/customerContracts.d.ts +2 -2
- package/dist/inventory/bindings.d.ts +3 -0
- package/dist/inventory/bindings.js +10 -4
- package/dist/inventory/index.d.ts +1 -0
- package/dist/inventory/index.js +1 -0
- package/dist/inventory/managedStockAvailability.d.ts +15 -0
- package/dist/inventory/managedStockAvailability.js +16 -0
- package/dist/inventory/postgresStockAvailability.js +2 -25
- package/dist/inventory/schemas.d.ts +1 -1
- package/dist/inventory/stockReadResponse.d.ts +4 -0
- package/dist/inventory/stockReadResponse.js +27 -0
- package/dist/pricing/browser/index.d.ts +6 -0
- package/dist/pricing/browser/index.js +6 -0
- package/dist/promotions/adminPromotionsContracts.d.ts +2 -2
- package/dist/subscriptions/contracts/renewal.d.ts +2 -0
- package/dist/subscriptions/index.d.ts +3 -2
- package/dist/subscriptions/index.js +3 -2
- package/dist/subscriptions/renewalCycleFailureMechanism.js +3 -2
- package/package.json +22 -2
- package/release-gates.json +63 -8
- package/src/commerce/adapters/managed/commerceOmsControlPlane.ts +80 -0
- package/src/commerce/adapters/managed/commerceOmsMutations.ts +145 -0
- package/src/commerce/adapters/managed/commerceOmsPort.ts +158 -0
- package/src/commerce/adapters/managed/oms/accounting.ts +18 -0
- package/src/commerce/adapters/managed/oms/fulfillmentReads.ts +82 -0
- package/src/commerce/adapters/managed/oms/inboundEvents.ts +45 -0
- package/src/commerce/adapters/managed/oms/outboxEvents.ts +30 -0
- package/src/commerce/adapters/managed/oms/paymentAttempts.ts +19 -0
- package/src/commerce/adapters/managed/oms/queueSelection.ts +132 -0
- package/src/commerce/adapters/managed/oms/readProjections.ts +22 -0
- package/src/commerce/adapters/managed/oms/readQueries.ts +292 -0
- package/src/commerce/adapters/managed/oms/types.ts +9 -0
- package/src/commerce/checkoutCommandSchemas.ts +93 -0
- package/src/commerce/checkoutHelpers.ts +47 -0
- package/src/commerce/checkoutResumeSchemas.ts +164 -0
- package/src/commerce/checkoutSchemas.ts +182 -0
- package/src/commerce/commerceReturnsPort.ts +67 -0
- package/src/commerce/configuratorIntentPersistenceSchemas.ts +26 -0
- package/src/commerce/configuratorIntentSchemas.ts +47 -0
- package/src/commerce/contractPrimitives.ts +30 -0
- package/src/commerce/contracts/availability.ts +21 -0
- package/src/commerce/contracts/fulfillmentRecovery.ts +26 -0
- package/src/commerce/contracts/index.ts +17 -0
- package/src/commerce/contracts/intent.ts +58 -0
- package/src/commerce/contracts/omsTypes.ts +1217 -0
- package/src/commerce/contracts/order.ts +44 -0
- package/src/commerce/contracts/orderMoney.ts +75 -0
- package/src/commerce/contracts/ports.ts +124 -0
- package/src/commerce/contracts/recovery.ts +24 -0
- package/src/commerce/contracts/runtimePorts.ts +201 -0
- package/src/commerce/contracts/state.ts +64 -0
- package/src/commerce/customerDefaultsSnapshotSchemas.ts +117 -0
- package/src/commerce/errors.ts +53 -0
- package/src/commerce/fulfillmentEligibility.ts +32 -0
- package/src/commerce/index.ts +82 -0
- package/src/commerce/model/customerFulfillmentRecovery.ts +185 -0
- package/src/commerce/model/fulfillmentCancellationEligibility.ts +18 -0
- package/src/commerce/model/omsAccountingVisibility.ts +135 -0
- package/src/commerce/model/omsBillingInvoiceBuyer.ts +20 -0
- package/src/commerce/model/omsCurrency.ts +8 -0
- package/src/commerce/model/omsFulfillmentDebug.ts +157 -0
- package/src/commerce/model/omsFulfillmentSummary.ts +225 -0
- package/src/commerce/model/omsListTotals.ts +38 -0
- package/src/commerce/model/omsOperationalLadder.ts +181 -0
- package/src/commerce/model/omsOrderLineMoney.ts +34 -0
- package/src/commerce/model/omsPaymentMethodSummary.ts +19 -0
- package/src/commerce/model/omsReadModel.ts +302 -0
- package/src/commerce/model/omsReadModelCommunication.ts +32 -0
- package/src/commerce/model/omsReadModelHelpers.ts +279 -0
- package/src/commerce/model/omsReadModelList.ts +90 -0
- package/src/commerce/model/omsReadModelListSummary.ts +19 -0
- package/src/commerce/model/omsReadModelPricing.ts +57 -0
- package/src/commerce/model/omsReadModelProjections.ts +1 -0
- package/src/commerce/model/omsReadModelRowCore.ts +193 -0
- package/src/commerce/model/omsReadModelRows.ts +7 -0
- package/src/commerce/offerAvailability.ts +75 -0
- package/src/commerce/offerAvailabilitySchemas.ts +56 -0
- package/src/commerce/offerEvidenceSchemas.ts +17 -0
- package/src/commerce/oms/omsContractBase.ts +92 -0
- package/src/commerce/oms/omsContractReadAuxSchemas.ts +130 -0
- package/src/commerce/oms/omsContractReadSchemas.ts +303 -0
- package/src/commerce/oms/omsContractRequests.ts +143 -0
- package/src/commerce/oms/omsContracts.ts +6 -0
- package/src/commerce/oms/omsControlPlane.ts +85 -0
- package/src/commerce/oms/omsFulfillmentContractSchemas.ts +113 -0
- package/src/commerce/oms/omsFulfillmentDebugSchemas.ts +38 -0
- package/src/commerce/oms/omsFulfillmentHealthSchemas.ts +46 -0
- package/src/commerce/oms/omsPorts.ts +165 -0
- package/src/commerce/oms/omsPrimitives.ts +3 -0
- package/src/commerce/oms/omsProviderEvidenceSchemas.ts +13 -0
- package/src/commerce/orderDraftErrors.ts +28 -0
- package/src/commerce/orderEmailEligibility.ts +131 -0
- package/src/commerce/orderEmailLines.ts +22 -0
- package/src/commerce/orderFeedbackContracts.ts +47 -0
- package/src/commerce/orderMoney.ts +207 -0
- package/src/commerce/orderReviewContracts.ts +124 -0
- package/src/commerce/orderReviewPorts.ts +94 -0
- package/src/commerce/outboxDispatchContracts.ts +30 -0
- package/src/commerce/outboxEventSchemas.ts +230 -0
- package/src/commerce/outboxOrderDraftEmailPorts.ts +241 -0
- package/src/commerce/paymentContinuation.ts +5 -0
- package/src/commerce/ports.ts +7 -0
- package/src/commerce/recoveryDestination.ts +23 -0
- package/src/commerce/runtimeErrors.ts +19 -0
- package/src/commerce/salesChannelEnvelope.ts +28 -0
- package/src/commerce/schemas.ts +104 -0
- package/src/commerce/server/checkoutCompensation.ts +49 -0
- package/src/commerce/server/checkoutConflictClassifiers.ts +53 -0
- package/src/commerce/server/checkoutExpiredRecoveryPolicy.ts +107 -0
- package/src/commerce/server/checkoutInvoiceBuyerSnapshot.ts +36 -0
- package/src/commerce/server/checkoutOfferPolicyPort.ts +60 -0
- package/src/commerce/server/checkoutPaymentContinuationCredential.ts +222 -0
- package/src/commerce/server/checkoutQuoteTypes.ts +9 -0
- package/src/commerce/server/checkoutRecoveryOrderPort.ts +40 -0
- package/src/commerce/server/checkoutRecoveryToken.ts +63 -0
- package/src/commerce/server/checkoutRefusalContinuation.ts +29 -0
- package/src/commerce/server/checkoutRuntimeTypes.ts +26 -0
- package/src/commerce/server/commerceCheckoutCommandExecution.ts +188 -0
- package/src/commerce/server/commerceCheckoutCompensation.ts +66 -0
- package/src/commerce/server/commerceCheckoutContinuationTerms.ts +87 -0
- package/src/commerce/server/commerceCheckoutCustomerDefaults.ts +24 -0
- package/src/commerce/server/commerceCheckoutFailureHandler.ts +144 -0
- package/src/commerce/server/commerceCheckoutHandler.ts +269 -0
- package/src/commerce/server/commerceCheckoutHandlerAdmission.ts +157 -0
- package/src/commerce/server/commerceCheckoutHandlerHelpers.ts +223 -0
- package/src/commerce/server/commerceCheckoutOrchestrationError.ts +15 -0
- package/src/commerce/server/commerceCheckoutPromotionPriceChange.ts +83 -0
- package/src/commerce/server/commerceCheckoutProviderPayment.ts +45 -0
- package/src/commerce/server/commerceCheckoutQuoteGuard.ts +172 -0
- package/src/commerce/server/commerceCheckoutResume.ts +132 -0
- package/src/commerce/server/commerceCheckoutResumeGuard.ts +195 -0
- package/src/commerce/server/commerceCheckoutSavedPaymentMethod.ts +85 -0
- package/src/commerce/server/commerceCheckoutStartRuntimeFailure.ts +59 -0
- package/src/commerce/server/commerceCheckoutTiming.ts +53 -0
- package/src/commerce/server/commerceDiagnostics.ts +37 -0
- package/src/commerce/server/commerceOmsCancelOrderHandler.ts +43 -0
- package/src/commerce/server/commerceOmsControlPlaneHandlers.ts +71 -0
- package/src/commerce/server/commerceOmsHandlers.ts +275 -0
- package/src/commerce/server/commerceOmsMarkRefundedHandler.ts +39 -0
- package/src/commerce/server/commerceOrderDraft.ts +131 -0
- package/src/commerce/server/commercePaidOrderOrchestration.ts +275 -0
- package/src/commerce/server/commerceProviderAttemptFailure.ts +257 -0
- package/src/commerce/server/commerceReturnsHandlers.ts +143 -0
- package/src/commerce/server/commerceRuntimePort.ts +75 -0
- package/src/commerce/server/commerceRuntimeService.ts +229 -0
- package/src/commerce/server/commerceRuntimeStartResponse.ts +76 -0
- package/src/commerce/server/commerceStarterOfferGuard.ts +200 -0
- package/src/commerce/server/configuratorIntentPersistence.ts +161 -0
- package/src/commerce/server/configuratorIntentPersistenceHandler.ts +54 -0
- package/src/commerce/server/emailRuntime.ts +30 -0
- package/src/commerce/server/handlerSupport.ts +22 -0
- package/src/commerce/server/index.ts +181 -0
- package/src/commerce/server/offerAvailability.ts +275 -0
- package/src/commerce/server/orderDraftRpcPayload.ts +39 -0
- package/src/commerce/server/orderFeedbackSubmit.ts +40 -0
- package/src/commerce/server/orderFeedbackTokenCheck.ts +28 -0
- package/src/commerce/server/orderReviewAdminHandlers.ts +43 -0
- package/src/commerce/server/orderReviewHandlers.ts +57 -0
- package/src/commerce/server/orderReviewLifecycle.ts +129 -0
- package/src/commerce/server/orderReviewMedia.ts +170 -0
- package/src/commerce/server/orderReviewRawUploadHandler.ts +300 -0
- package/src/commerce/server/outboxCheckoutExpiredEmailHandler.ts +86 -0
- package/src/commerce/server/outboxCheckoutRecoveryEmailHandler.ts +188 -0
- package/src/commerce/server/outboxDispatchRegistry.ts +269 -0
- package/src/commerce/server/outboxDispatchRegistryComposition.ts +31 -0
- package/src/commerce/server/outboxOrderDraftEmailHandler.ts +162 -0
- package/src/commerce/server/outboxOrderPaidEmailHandler.ts +147 -0
- package/src/commerce/server/outboxOrderPaidFulfillmentHandler.ts +4 -0
- package/src/commerce/server/outboxOrderPaidFulfillmentPorts.ts +3 -0
- package/src/commerce/server/outboxOrderRefundedEmailHandler.ts +58 -0
- package/src/commerce/server/outboxPaymentFailedEmailHandler.ts +99 -0
- package/src/commerce/server/outboxReturnEmailHandlers.ts +92 -0
- package/src/commerce/server/outboxShipmentDeliveredEmailHandler.ts +94 -0
- package/src/commerce/server/outboxShipmentDispatchedEmailHandler.ts +112 -0
- package/src/commerce/server/outboxShipmentExceptionEmailHandler.ts +73 -0
- package/src/commerce/server/referenceCheckoutHandler.ts +87 -0
- package/src/commerce/server/reservationSweepPort.ts +67 -0
- package/src/commerce/server/starterTermsPort.ts +22 -0
- package/src/commerce/server/supabaseCommerceReturnsPort.ts +81 -0
- package/src/composition/commerceContribution.ts +35 -0
- package/src/composition/index.ts +1 -0
- package/src/fulfillment/index.ts +1 -1
- package/src/fulfillment/paidHandoff.ts +2 -2
- package/src/inventory/bindings.ts +12 -4
- package/src/inventory/index.ts +1 -0
- package/src/inventory/managedStockAvailability.ts +23 -0
- package/src/inventory/postgresStockAvailability.ts +2 -21
- package/src/inventory/stockReadResponse.ts +25 -0
- package/src/pricing/browser/index.ts +6 -0
- package/src/subscriptions/contracts/renewal.ts +2 -1
- package/src/subscriptions/index.ts +3 -2
- package/src/subscriptions/renewalCycleFailureMechanism.ts +3 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { CommerceEmailRuntime } from "./emailRuntime.js";
|
|
2
|
+
// Checkout-recovery email handler (W3, transactional). Consumes the
|
|
3
|
+
// commerce.checkout_recovery outbox events produced by the W2 enqueue cron and
|
|
4
|
+
// sends a "finish your payment" email with a signed deep-link to complete payment
|
|
5
|
+
// for the SAME unpaid order. Transactional: NO marketing-consent gate. Promotes
|
|
6
|
+
// the W2 producer from dormant to runtime-gated.
|
|
7
|
+
//
|
|
8
|
+
// Three producers feed this one handler: the W2 cron, the operator payment-link
|
|
9
|
+
// command (`operatorIssued: true`), and the buyer's own escape hatch off a payment
|
|
10
|
+
// step that will not finish (`buyerRequested: true`). They differ in one place
|
|
11
|
+
// only — which eligibility question is asked below — and share the template, the
|
|
12
|
+
// dedupe ledger and the deep-link build.
|
|
13
|
+
//
|
|
14
|
+
// handle() flow: parse the minimal payload -> resolve recipient by order ->
|
|
15
|
+
// dedupe on the email_sends ledger -> build the localized deep-link from the raw
|
|
16
|
+
// recovery token -> send -> map the provider outcome to a worker outcome.
|
|
17
|
+
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { COMMERCE_CHECKOUT_RECOVERY_EVENT_TYPE } from "../outboxEventSchemas.js";
|
|
20
|
+
import {
|
|
21
|
+
OUTBOX_CHECKOUT_RECOVERY_TEMPLATE_SLUG,
|
|
22
|
+
type OrderPaymentLifecyclePort,
|
|
23
|
+
type TransactionalEmailPort,
|
|
24
|
+
type OrderRecipientPort,
|
|
25
|
+
} from "../outboxOrderDraftEmailPorts.js";
|
|
26
|
+
import {
|
|
27
|
+
PAYMENT_SESSION_ACTIVE,
|
|
28
|
+
shouldSkipCheckoutRecovery,
|
|
29
|
+
shouldSkipOperatorCheckoutRecovery,
|
|
30
|
+
} from "../orderEmailEligibility.js";
|
|
31
|
+
import type {
|
|
32
|
+
OutboxEventRow,
|
|
33
|
+
OutboxHandler,
|
|
34
|
+
OutboxHandlerOutcome,
|
|
35
|
+
} from "../outboxDispatchContracts.js";
|
|
36
|
+
|
|
37
|
+
const HANDLER_TIMEOUT_MS = 10_000;
|
|
38
|
+
|
|
39
|
+
export const outboxCheckoutRecoveryEmailDescriptor = Object.freeze({
|
|
40
|
+
eventType: COMMERCE_CHECKOUT_RECOVERY_EVENT_TYPE, timeoutMs: HANDLER_TIMEOUT_MS,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Deferrals allowed before a nudge blocked by a live payment session retires.
|
|
44
|
+
// The dispatcher's default backoff is 60 s doubling to a 3600 s cap, so five
|
|
45
|
+
// attempts span ~31 min — past the 15 min activity window that produced the
|
|
46
|
+
// skip, and well inside the 120 min subscription sweep that would cancel the
|
|
47
|
+
// order anyway. It also sits under the default `maxAttempts` of 8: exhausting
|
|
48
|
+
// retries routes a row to the terminal DLQ as `discarded`, which records a
|
|
49
|
+
// dispatch failure for a reminder that was merely untimely. Retiring as
|
|
50
|
+
// `processed` keeps it an honest deliberate skip. A deployment that configures
|
|
51
|
+
// `maxAttempts` below this ceiling has chosen fewer retries globally, and gets
|
|
52
|
+
// that choice here too.
|
|
53
|
+
const PAYMENT_SESSION_RETRY_CEILING = 5;
|
|
54
|
+
|
|
55
|
+
// Frozen minimal parse: orderId (recipient + linkage) and recoveryToken (the
|
|
56
|
+
// deep-link) are load-bearing; mode/reminderHours degrade to safe defaults so a
|
|
57
|
+
// producer tweak never retro-DLQs an in-flight reminder.
|
|
58
|
+
//
|
|
59
|
+
// `operatorIssued` says a human minted this link on the operator surface rather
|
|
60
|
+
// than the cron finding an abandoned order. `buyerRequested` says the BUYER asked
|
|
61
|
+
// for it, from inside the payment step they are stuck on. Both optional, and
|
|
62
|
+
// absent means the cron: every row already in flight when either shipped keeps
|
|
63
|
+
// its exact behaviour.
|
|
64
|
+
const payloadSubsetSchema = z
|
|
65
|
+
.object({
|
|
66
|
+
orderId: z.guid(),
|
|
67
|
+
recoveryToken: z.string().min(1),
|
|
68
|
+
mode: z.string().optional(),
|
|
69
|
+
reminderHours: z.number().int().positive().optional(),
|
|
70
|
+
operatorIssued: z.boolean().optional(),
|
|
71
|
+
buyerRequested: z.boolean().optional(),
|
|
72
|
+
})
|
|
73
|
+
.passthrough();
|
|
74
|
+
|
|
75
|
+
export function createOutboxCheckoutRecoveryEmailHandler<Locale extends string>(deps: {
|
|
76
|
+
runtime: Pick<CommerceEmailRuntime<Locale>, "mapSendOutcome" | "parseOutboxPayload" | "resolveLocale">;
|
|
77
|
+
emailPort: TransactionalEmailPort<Locale>;
|
|
78
|
+
recipientPort: OrderRecipientPort;
|
|
79
|
+
lifecyclePort: OrderPaymentLifecyclePort;
|
|
80
|
+
}): OutboxHandler {
|
|
81
|
+
return {
|
|
82
|
+
...outboxCheckoutRecoveryEmailDescriptor,
|
|
83
|
+
async handle(row: OutboxEventRow, signal: AbortSignal): Promise<OutboxHandlerOutcome> {
|
|
84
|
+
const parsed = deps.runtime.parseOutboxPayload(payloadSubsetSchema, row.payload);
|
|
85
|
+
if (parsed.ok === false) {
|
|
86
|
+
return parsed.outcome;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const lifecycle = await deps.lifecyclePort.read(parsed.data.orderId, signal);
|
|
90
|
+
// Three producers, three questions. The cron asks "is this order still
|
|
91
|
+
// sitting unpaid", and going quiet once it is not is right. An
|
|
92
|
+
// operator-issued link is a decision a human already made about an order
|
|
93
|
+
// that has usually left `pending_payment` — expired is the ordinary case —
|
|
94
|
+
// and the redeem rail recreates it when the customer opens the link.
|
|
95
|
+
// Judging that by the cron's rule would skip it as
|
|
96
|
+
// `order_not_pending_payment` and no-send in silence.
|
|
97
|
+
const skipReason = parsed.data.buyerRequested
|
|
98
|
+
? skipReasonForBuyerRequestedRecovery(lifecycle)
|
|
99
|
+
: parsed.data.operatorIssued
|
|
100
|
+
? shouldSkipOperatorCheckoutRecovery(lifecycle)
|
|
101
|
+
: shouldSkipCheckoutRecovery(lifecycle);
|
|
102
|
+
if (skipReason === PAYMENT_SESSION_ACTIVE) {
|
|
103
|
+
// A live payment session is temporary. The producer key is unique per
|
|
104
|
+
// event, so settling it as processed would consume the only reminder
|
|
105
|
+
// without sending. Defer until the session settles, subject to the
|
|
106
|
+
// retry ceiling below.
|
|
107
|
+
//
|
|
108
|
+
// Read `attempts` defensively: `undefined < n` is false, which would
|
|
109
|
+
// retire the nudge on its FIRST deferral — the precise opposite of the
|
|
110
|
+
// intent here, and silent.
|
|
111
|
+
const attempts = Number.isFinite(row.attempts) ? row.attempts : 0;
|
|
112
|
+
if (attempts < PAYMENT_SESSION_RETRY_CEILING) {
|
|
113
|
+
return { kind: "retry", reason: PAYMENT_SESSION_ACTIVE };
|
|
114
|
+
}
|
|
115
|
+
// Past the ceiling, retire deliberately. Letting retries run out routes
|
|
116
|
+
// the row to the terminal DLQ as `discarded`, which reads as a dispatch
|
|
117
|
+
// failure for a reminder that was only untimely.
|
|
118
|
+
return { kind: "processed", detail: { skipped: "payment_session_exhausted" } };
|
|
119
|
+
}
|
|
120
|
+
if (skipReason) {
|
|
121
|
+
return { kind: "processed", detail: { skipped: skipReason } };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const recipient = await deps.recipientPort.resolve(parsed.data.orderId, signal);
|
|
125
|
+
if (recipient === null) {
|
|
126
|
+
// No client on the order (or unreadable) — nothing to send, never retry.
|
|
127
|
+
return { kind: "processed", detail: { skipped: "recipient_unresolved" } };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const alreadySent = await deps.emailPort.findExistingSend(
|
|
131
|
+
OUTBOX_CHECKOUT_RECOVERY_TEMPLATE_SLUG,
|
|
132
|
+
row.id,
|
|
133
|
+
);
|
|
134
|
+
if (alreadySent) {
|
|
135
|
+
return { kind: "processed", detail: { dedupe: "email_already_sent" } };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const locale = deps.runtime.resolveLocale(recipient.country ?? null);
|
|
139
|
+
|
|
140
|
+
const outcome = await deps.emailPort.sendCheckoutRecovery({
|
|
141
|
+
to: recipient.email,
|
|
142
|
+
firstName: recipient.firstName,
|
|
143
|
+
contextName: recipient.contextName ?? null,
|
|
144
|
+
orderId: parsed.data.orderId,
|
|
145
|
+
recoveryToken: parsed.data.recoveryToken,
|
|
146
|
+
mode: parsed.data.mode ?? "one_time",
|
|
147
|
+
reminderHours: parsed.data.reminderHours ?? 1,
|
|
148
|
+
outboxEventId: row.id,
|
|
149
|
+
// Marks the deep-link as the one the buyer mailed themselves off a stuck
|
|
150
|
+
// payment step, so the landing page can tell that arrival apart from a
|
|
151
|
+
// cron nudge. Absent for every other producer, which keeps their URLs
|
|
152
|
+
// byte-identical to what they were.
|
|
153
|
+
linkSource: parsed.data.buyerRequested ? "buyer_hatch" : undefined,
|
|
154
|
+
locale,
|
|
155
|
+
signal,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return deps.runtime.mapSendOutcome(outcome);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Eligibility for a link the BUYER asked for, from inside the payment step.
|
|
165
|
+
*
|
|
166
|
+
* Everything the operator rule suppresses is suppressed here too — the order is
|
|
167
|
+
* gone, or the money is already in — because those make the email actively wrong
|
|
168
|
+
* rather than merely late. Exactly ONE rule is lifted:
|
|
169
|
+
* `payment_session_active`.
|
|
170
|
+
*
|
|
171
|
+
* That rule is a politeness rule: do not remind someone to pay while they are
|
|
172
|
+
* paying. For this producer it is not merely unhelpful, it is inverted. A buyer
|
|
173
|
+
* who taps this control is BY CONSTRUCTION inside a live payment session — that
|
|
174
|
+
* session is the thing that will not finish, and asking for a way out of it is
|
|
175
|
+
* the whole request. Deferring on it would defer every buyer row through the
|
|
176
|
+
* retry ceiling and then retire it as `payment_session_exhausted`, so the hatch
|
|
177
|
+
* would send nothing, ever, and nothing would report a failure.
|
|
178
|
+
*
|
|
179
|
+
* Written here rather than as a fourth predicate in `orderEmailEligibility`: it
|
|
180
|
+
* is not a different question from the operator's, it is the operator's question
|
|
181
|
+
* with its one transient answer already decided by who is asking.
|
|
182
|
+
*/
|
|
183
|
+
function skipReasonForBuyerRequestedRecovery(
|
|
184
|
+
lifecycle: Parameters<typeof shouldSkipOperatorCheckoutRecovery>[0],
|
|
185
|
+
): string | null {
|
|
186
|
+
const reason = shouldSkipOperatorCheckoutRecovery(lifecycle);
|
|
187
|
+
return reason === PAYMENT_SESSION_ACTIVE ? null : reason;
|
|
188
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { CommerceEmailRuntime } from "./emailRuntime.js";
|
|
2
|
+
// Registry = the single source of truth for which event types this wave
|
|
3
|
+
// claims. The claim allowlist is derived from its keys, so the PSP-blocked
|
|
4
|
+
// event types stay structurally unclaimable until their handlers register.
|
|
5
|
+
import type { OutboxHandlerDescriptor } from "@openlup/core/outbox";
|
|
6
|
+
import { composeOutboxHandlerDescriptors, assertOutboxRegistryDescriptors } from "./outboxDispatchRegistryComposition.js";
|
|
7
|
+
import type { OutboxHandler, OutboxHandlerRegistry } from "../outboxDispatchContracts.js";
|
|
8
|
+
import type {
|
|
9
|
+
OrderPaymentLifecyclePort,
|
|
10
|
+
OrderPaidLinesPort,
|
|
11
|
+
OrderRecipientPort,
|
|
12
|
+
TransactionalEmailPort,
|
|
13
|
+
} from "../outboxOrderDraftEmailPorts.js";
|
|
14
|
+
import { createOutboxOrderDraftEmailHandler, outboxOrderDraftEmailDescriptor } from "./outboxOrderDraftEmailHandler.js";
|
|
15
|
+
import { createOutboxCheckoutRecoveryEmailHandler, outboxCheckoutRecoveryEmailDescriptor } from "./outboxCheckoutRecoveryEmailHandler.js";
|
|
16
|
+
import {
|
|
17
|
+
createOutboxOrderPaidEmailHandler, outboxOrderPaidEmailDescriptor,
|
|
18
|
+
type OrderPaidParcelNoteReader,
|
|
19
|
+
} from "./outboxOrderPaidEmailHandler.js";
|
|
20
|
+
import { createOutboxPaymentFailedEmailHandler, outboxPaymentFailedEmailDescriptor } from "./outboxPaymentFailedEmailHandler.js";
|
|
21
|
+
import { createOutboxCheckoutExpiredEmailHandler, outboxCheckoutExpiredEmailDescriptor } from "./outboxCheckoutExpiredEmailHandler.js";
|
|
22
|
+
import { createOutboxOrderRefundedEmailHandler, outboxOrderRefundedEmailDescriptor } from "./outboxOrderRefundedEmailHandler.js";
|
|
23
|
+
import { createOutboxShipmentDispatchedEmailHandler, outboxShipmentDispatchedEmailDescriptor } from "./outboxShipmentDispatchedEmailHandler.js";
|
|
24
|
+
import {
|
|
25
|
+
createOutboxShipmentDeliveredEmailHandler, outboxShipmentDeliveredEmailDescriptor,
|
|
26
|
+
type DeliveredEmailProviderKindReader,
|
|
27
|
+
} from "./outboxShipmentDeliveredEmailHandler.js";
|
|
28
|
+
import { createOutboxShipmentExceptionEmailHandler, outboxShipmentExceptionEmailDescriptor } from "./outboxShipmentExceptionEmailHandler.js";
|
|
29
|
+
import {
|
|
30
|
+
createOutboxReturnApprovedEmailHandler, outboxReturnApprovedEmailDescriptor,
|
|
31
|
+
createOutboxReturnRejectedEmailHandler, outboxReturnRejectedEmailDescriptor,
|
|
32
|
+
} from "./outboxReturnEmailHandlers.js";
|
|
33
|
+
import type { OrderPaidFulfillmentPort } from "./outboxOrderPaidFulfillmentPorts.js";
|
|
34
|
+
import {
|
|
35
|
+
createOutboxOrderPaidFulfillmentHandler, outboxOrderPaidFulfillmentDescriptor,
|
|
36
|
+
type OrderPaidRiskAssessmentPort,
|
|
37
|
+
} from "./outboxOrderPaidFulfillmentHandler.js";
|
|
38
|
+
import {
|
|
39
|
+
platformOwnsBuyerEmail,
|
|
40
|
+
type OrderBuyerCommsPolicy,
|
|
41
|
+
} from "../salesChannelEnvelope.js";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Reads the sales-source buyer-comms policy for one order. `null` is
|
|
45
|
+
* the answer for an order that names no channel — a storefront order, or one
|
|
46
|
+
* that predates the source axis.
|
|
47
|
+
*
|
|
48
|
+
* Read on every event so changes to `sales_channels.buyer_comms_owner` take
|
|
49
|
+
* effect on the next dispatch. A cached policy could keep sending after
|
|
50
|
+
* ownership has changed.
|
|
51
|
+
*/
|
|
52
|
+
export interface OrderBuyerCommsPolicyReader {
|
|
53
|
+
readOrderBuyerCommsPolicy(orderUuid: string): Promise<OrderBuyerCommsPolicy | null>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type TransactionalEmailRegistryDeps<Locale extends string = string> = {
|
|
57
|
+
runtime: CommerceEmailRuntime<Locale>;
|
|
58
|
+
emailPort: TransactionalEmailPort<Locale>;
|
|
59
|
+
recipientPort: OrderRecipientPort;
|
|
60
|
+
lifecyclePort: OrderPaymentLifecyclePort;
|
|
61
|
+
orderPaidLinesPort: OrderPaidLinesPort;
|
|
62
|
+
// Optional adopter note for the paid receipt; absent keeps today's receipt.
|
|
63
|
+
orderPaidParcelNote?: OrderPaidParcelNoteReader<Locale>;
|
|
64
|
+
// The delivered-email handler suppresses OUR delivered email for providers whose
|
|
65
|
+
// capability profile owns the notice (none today). Absent only in
|
|
66
|
+
// tests/non-transactional composition.
|
|
67
|
+
deliveredProviderKindReader?: DeliveredEmailProviderKindReader;
|
|
68
|
+
// Suppresses OUR buyer-facing email for channel-owned orders. Absent only in
|
|
69
|
+
// tests/non-transactional composition, where it degrades to today's behaviour.
|
|
70
|
+
buyerCommsPolicyReader?: OrderBuyerCommsPolicyReader;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function readOrderUuid(payload: Record<string, unknown>): string | null {
|
|
74
|
+
const value = payload.orderUuid;
|
|
75
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Wraps ONE buyer-facing email handler so a channel that owns buyer
|
|
80
|
+
* communication does not also get ours.
|
|
81
|
+
*
|
|
82
|
+
* WHY THE WRAPPER IS AT THE HANDLER AND NOT AT THE TRIGGER. The checkout email
|
|
83
|
+
* reconciler (`commerce_reconcile_checkout_email_outbox`) back-fills a `commerce.order.paid.email`
|
|
84
|
+
* row for EVERY order that reads `paid` and has none, keyed only on the order
|
|
85
|
+
* id — it does not look at `source_kind`, and outboxDispatchRuntime runs it
|
|
86
|
+
* inline immediately before the dispatch worker in the same invocation. A
|
|
87
|
+
* trigger-level or emitter-level suppression would therefore be undone on the
|
|
88
|
+
* very next reconcile pass, silently, and the buyer would get the email anyway.
|
|
89
|
+
* The dispatcher is the last place that can still decide.
|
|
90
|
+
*
|
|
91
|
+
* Recorded as `processed` rather than `discard` so the event settles once and
|
|
92
|
+
* never retries. `skipped` is one of the DELIVERY_LIFECYCLE_METADATA_KEYS below,
|
|
93
|
+
* so the email-delivery lifecycle trigger reads the suppression as a skip with a
|
|
94
|
+
* reason instead of leaving a delivery hanging — the same shape the
|
|
95
|
+
* carrier-owned delivered notice already uses.
|
|
96
|
+
*/
|
|
97
|
+
function suppressForChannel(
|
|
98
|
+
handler: OutboxHandler,
|
|
99
|
+
reader: OrderBuyerCommsPolicyReader | undefined,
|
|
100
|
+
): OutboxHandler {
|
|
101
|
+
if (!reader) return handler;
|
|
102
|
+
return {
|
|
103
|
+
eventType: handler.eventType,
|
|
104
|
+
timeoutMs: handler.timeoutMs,
|
|
105
|
+
async handle(row, signal, execution) {
|
|
106
|
+
const orderUuid = readOrderUuid(row.payload);
|
|
107
|
+
// A payload with no usable order id is not a policy question. Hand it to
|
|
108
|
+
// the handler, whose own contract parse produces the right discard rather
|
|
109
|
+
// than this wrapper inventing a second failure mode for the same row.
|
|
110
|
+
if (orderUuid !== null) {
|
|
111
|
+
const policy = await reader.readOrderBuyerCommsPolicy(orderUuid);
|
|
112
|
+
if (!platformOwnsBuyerEmail(policy)) {
|
|
113
|
+
return { kind: "processed", detail: { skipped: "channel_owns_buyer_comms" } };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return handler.handle(row, signal, execution);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function describeCoreOutboxHandlers(selection: {
|
|
122
|
+
transactionalEmail: boolean; fulfillmentEnabled: boolean;
|
|
123
|
+
}): OutboxHandlerDescriptor[] {
|
|
124
|
+
return [
|
|
125
|
+
...(selection.transactionalEmail ? [
|
|
126
|
+
outboxOrderDraftEmailDescriptor, outboxCheckoutRecoveryEmailDescriptor,
|
|
127
|
+
outboxOrderPaidEmailDescriptor, outboxPaymentFailedEmailDescriptor,
|
|
128
|
+
outboxCheckoutExpiredEmailDescriptor, outboxOrderRefundedEmailDescriptor,
|
|
129
|
+
outboxShipmentDispatchedEmailDescriptor, outboxShipmentDeliveredEmailDescriptor,
|
|
130
|
+
outboxShipmentExceptionEmailDescriptor, outboxReturnApprovedEmailDescriptor,
|
|
131
|
+
outboxReturnRejectedEmailDescriptor,
|
|
132
|
+
] : []),
|
|
133
|
+
...(selection.fulfillmentEnabled ? [outboxOrderPaidFulfillmentDescriptor] : []),
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function createOutboxDispatchRegistry<Locale extends string>(deps: {
|
|
138
|
+
/** Bind the existing outbox rail's composition; commerce owns no second rail. */
|
|
139
|
+
uniqueRegistry(handlers: readonly OutboxHandler[]): OutboxHandlerRegistry;
|
|
140
|
+
transactionalEmail?: TransactionalEmailRegistryDeps<Locale>;
|
|
141
|
+
fulfillmentPort?: OrderPaidFulfillmentPort;
|
|
142
|
+
// Optional risk seam, evaluated per-handle via the thunk so the flag-OFF
|
|
143
|
+
// posture can ack without side effects. When absent/disabled, fulfillment runs
|
|
144
|
+
// exactly as before.
|
|
145
|
+
riskPort?: OrderPaidRiskAssessmentPort;
|
|
146
|
+
riskEnabled?: boolean;
|
|
147
|
+
// Read once at job construction. When false, the fulfillment event type is not
|
|
148
|
+
// registered, so commerce.order.paid rows stay unclaimed for later activation.
|
|
149
|
+
fulfillmentEnabled: boolean;
|
|
150
|
+
// Handlers from OTHER domains (subscription, accounting, …), composed by the
|
|
151
|
+
// job so this commerce file never imports cross-domain code. Each must claim a
|
|
152
|
+
// distinct event type. Appended after the commerce handlers.
|
|
153
|
+
extraHandlers?: readonly OutboxHandler[];
|
|
154
|
+
expectedDescriptors?: () => readonly OutboxHandlerDescriptor[];
|
|
155
|
+
}): OutboxHandlerRegistry {
|
|
156
|
+
// Applied to the buyer-facing set below. The four email handlers it is NOT
|
|
157
|
+
// applied to are structurally unreachable for a channel order on the ingest
|
|
158
|
+
// path this repository ships, and each is asserted rather than assumed:
|
|
159
|
+
// - order-draft-created: emitted INSIDE `commerce_create_order_draft`.
|
|
160
|
+
// `commerce_create_channel_order` writes the order header directly and
|
|
161
|
+
// emits `channel.order.ingested`, so no draft event ever exists.
|
|
162
|
+
// - checkout-recovery: minted from an expired-checkout recovery token, which
|
|
163
|
+
// only the checkout-expiry path can create.
|
|
164
|
+
// - checkout-expired and payment-failed: both key off a one-time order
|
|
165
|
+
// reaching status `expired`/`failed`, and the ONLY writer of those statuses
|
|
166
|
+
// for a one-time order is `commerce_payment_apply_result`. The ingest saga
|
|
167
|
+
// calls `applySucceeded` and nothing else — the marketplace collected the
|
|
168
|
+
// money before the order existed here, so there is no decline or session to
|
|
169
|
+
// expire. Nothing sweeps a stale `draft` order to `expired` either.
|
|
170
|
+
// This is an argument about the CURRENT ingest path. A later wave that gives a
|
|
171
|
+
// channel order a decline or expiry rail must revisit this list, not assume it.
|
|
172
|
+
//
|
|
173
|
+
// `commerce.order.canceled` is absent from this enumeration because its email
|
|
174
|
+
// handler (outboxOrderCanceledEmailHandler.ts) is registered by NOTHING — the
|
|
175
|
+
// only claimant of that event type is the accounting reversal handler, which
|
|
176
|
+
// arrives through `extraHandlers` and must keep running for a channel order.
|
|
177
|
+
// Wrapping a handler this registry does not compose would be theatre; if that
|
|
178
|
+
// email is ever wired up, it belongs in the wrapped set.
|
|
179
|
+
const suppress = (handler: OutboxHandler): OutboxHandler =>
|
|
180
|
+
suppressForChannel(handler, deps.transactionalEmail?.buyerCommsPolicyReader);
|
|
181
|
+
const handlers: OutboxHandler[] = [
|
|
182
|
+
...(deps.transactionalEmail
|
|
183
|
+
? [
|
|
184
|
+
createOutboxOrderDraftEmailHandler({
|
|
185
|
+
runtime: deps.transactionalEmail.runtime,
|
|
186
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
187
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
188
|
+
lifecyclePort: deps.transactionalEmail.lifecyclePort,
|
|
189
|
+
}),
|
|
190
|
+
createOutboxCheckoutRecoveryEmailHandler({
|
|
191
|
+
runtime: deps.transactionalEmail.runtime,
|
|
192
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
193
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
194
|
+
lifecyclePort: deps.transactionalEmail.lifecyclePort,
|
|
195
|
+
}),
|
|
196
|
+
suppress(createOutboxOrderPaidEmailHandler({
|
|
197
|
+
runtime: deps.transactionalEmail.runtime,
|
|
198
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
199
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
200
|
+
linesPort: deps.transactionalEmail.orderPaidLinesPort,
|
|
201
|
+
describeParcel: deps.transactionalEmail.orderPaidParcelNote,
|
|
202
|
+
})),
|
|
203
|
+
createOutboxPaymentFailedEmailHandler({
|
|
204
|
+
runtime: deps.transactionalEmail.runtime,
|
|
205
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
206
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
207
|
+
lifecyclePort: deps.transactionalEmail.lifecyclePort,
|
|
208
|
+
}),
|
|
209
|
+
createOutboxCheckoutExpiredEmailHandler({
|
|
210
|
+
runtime: deps.transactionalEmail.runtime,
|
|
211
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
212
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
213
|
+
lifecyclePort: deps.transactionalEmail.lifecyclePort,
|
|
214
|
+
}),
|
|
215
|
+
suppress(createOutboxOrderRefundedEmailHandler({
|
|
216
|
+
runtime: deps.transactionalEmail.runtime,
|
|
217
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
218
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
219
|
+
})),
|
|
220
|
+
suppress(createOutboxShipmentDispatchedEmailHandler({
|
|
221
|
+
runtime: deps.transactionalEmail.runtime,
|
|
222
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
223
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
224
|
+
})),
|
|
225
|
+
suppress(createOutboxShipmentDeliveredEmailHandler({
|
|
226
|
+
runtime: deps.transactionalEmail.runtime,
|
|
227
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
228
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
229
|
+
providerKindReader: deps.transactionalEmail.deliveredProviderKindReader,
|
|
230
|
+
})),
|
|
231
|
+
suppress(createOutboxShipmentExceptionEmailHandler({
|
|
232
|
+
runtime: deps.transactionalEmail.runtime,
|
|
233
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
234
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
235
|
+
})),
|
|
236
|
+
suppress(createOutboxReturnApprovedEmailHandler({
|
|
237
|
+
runtime: deps.transactionalEmail.runtime,
|
|
238
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
239
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
240
|
+
})),
|
|
241
|
+
suppress(createOutboxReturnRejectedEmailHandler({
|
|
242
|
+
runtime: deps.transactionalEmail.runtime,
|
|
243
|
+
emailPort: deps.transactionalEmail.emailPort,
|
|
244
|
+
recipientPort: deps.transactionalEmail.recipientPort,
|
|
245
|
+
})),
|
|
246
|
+
]
|
|
247
|
+
: []),
|
|
248
|
+
...(deps.fulfillmentEnabled && deps.fulfillmentPort
|
|
249
|
+
? [
|
|
250
|
+
createOutboxOrderPaidFulfillmentHandler({
|
|
251
|
+
fulfillmentPort: deps.fulfillmentPort,
|
|
252
|
+
riskPort: deps.riskPort,
|
|
253
|
+
isRiskEnabled: () => deps.riskEnabled === true,
|
|
254
|
+
}),
|
|
255
|
+
]
|
|
256
|
+
: []),
|
|
257
|
+
...(deps.extraHandlers ?? []),
|
|
258
|
+
];
|
|
259
|
+
const registry = deps.uniqueRegistry(handlers);
|
|
260
|
+
const expected = deps.expectedDescriptors?.() ?? composeOutboxHandlerDescriptors([
|
|
261
|
+
...describeCoreOutboxHandlers({
|
|
262
|
+
transactionalEmail: Boolean(deps.transactionalEmail),
|
|
263
|
+
fulfillmentEnabled: deps.fulfillmentEnabled && Boolean(deps.fulfillmentPort),
|
|
264
|
+
}),
|
|
265
|
+
...(deps.extraHandlers ?? []).map(({ eventType, timeoutMs }) => ({ eventType, timeoutMs })),
|
|
266
|
+
]);
|
|
267
|
+
assertOutboxRegistryDescriptors(registry, expected);
|
|
268
|
+
return registry;
|
|
269
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { OutboxHandlerDescriptor } from "@openlup/core/outbox";
|
|
2
|
+
import type { OutboxHandlerRegistry } from "../outboxDispatchContracts.js";
|
|
3
|
+
|
|
4
|
+
/** Preserve native registration order and the timeout sum of same-event handlers. */
|
|
5
|
+
export function composeOutboxHandlerDescriptors(
|
|
6
|
+
handlers: readonly OutboxHandlerDescriptor[],
|
|
7
|
+
): OutboxHandlerDescriptor[] {
|
|
8
|
+
const descriptors = new Map<string, OutboxHandlerDescriptor>();
|
|
9
|
+
for (const handler of handlers) {
|
|
10
|
+
const existing = descriptors.get(handler.eventType);
|
|
11
|
+
descriptors.set(handler.eventType, {
|
|
12
|
+
eventType: handler.eventType,
|
|
13
|
+
timeoutMs: (existing?.timeoutMs ?? 0) + handler.timeoutMs,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return [...descriptors.values()];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Refuse a different claim surface before a worker can call claimBatch. */
|
|
20
|
+
export function assertOutboxRegistryDescriptors(
|
|
21
|
+
registry: OutboxHandlerRegistry,
|
|
22
|
+
expected: readonly OutboxHandlerDescriptor[],
|
|
23
|
+
): void {
|
|
24
|
+
const actual = [...registry.entries()];
|
|
25
|
+
if (actual.length !== expected.length || actual.some(([key, handler], index) => {
|
|
26
|
+
const descriptor = expected[index];
|
|
27
|
+
return key !== descriptor.eventType || handler.eventType !== key
|
|
28
|
+
|| handler.timeoutMs !== descriptor.timeoutMs
|
|
29
|
+
|| !Number.isFinite(handler.timeoutMs) || handler.timeoutMs <= 0;
|
|
30
|
+
})) throw new Error("outbox_registry_descriptor_mismatch");
|
|
31
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { CommerceEmailRuntime } from "./emailRuntime.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { COMMERCE_ORDER_DRAFT_CREATED_EVENT_TYPE } from "../outboxEventSchemas.js";
|
|
4
|
+
import type {
|
|
5
|
+
OutboxEventRow,
|
|
6
|
+
OutboxHandler,
|
|
7
|
+
OutboxHandlerOutcome,
|
|
8
|
+
} from "../outboxDispatchContracts.js";
|
|
9
|
+
import {
|
|
10
|
+
OUTBOX_ORDER_CONFIRMATION_TEMPLATE_SLUG,
|
|
11
|
+
type OrderEmailTotals,
|
|
12
|
+
type OrderPaymentLifecyclePort,
|
|
13
|
+
type OrderRecipientPort,
|
|
14
|
+
type TransactionalEmailPort,
|
|
15
|
+
} from "../outboxOrderDraftEmailPorts.js";
|
|
16
|
+
import { buildItems, type RawLine } from "../orderEmailLines.js";
|
|
17
|
+
import { shouldSkipOrderDraftConfirmation } from "../orderEmailEligibility.js";
|
|
18
|
+
|
|
19
|
+
export { OUTBOX_ORDER_CONFIRMATION_TEMPLATE_SLUG };
|
|
20
|
+
|
|
21
|
+
const HANDLER_TIMEOUT_MS = 10_000;
|
|
22
|
+
|
|
23
|
+
export const outboxOrderDraftEmailDescriptor = Object.freeze({
|
|
24
|
+
eventType: COMMERCE_ORDER_DRAFT_CREATED_EVENT_TYPE, timeoutMs: HANDLER_TIMEOUT_MS,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Frozen minimal parse (challenge R-m5): validate ONLY the fields this handler
|
|
28
|
+
// consumes, with schemas owned HERE — never the live quote/order-draft zod
|
|
29
|
+
// schemas. A producer-side schema deploy must not retro-DLQ in-flight events.
|
|
30
|
+
// Everything except orderUuid/orderId is optional-tolerant: malformed lines or
|
|
31
|
+
// totals degrade the email content, they never fail the event.
|
|
32
|
+
const payloadSubsetSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
orderUuid: z.guid(),
|
|
35
|
+
orderId: z.string().min(1),
|
|
36
|
+
orderDraftSnapshot: z.unknown().optional(),
|
|
37
|
+
})
|
|
38
|
+
.passthrough();
|
|
39
|
+
|
|
40
|
+
const moneySubsetSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
amountMinor: z.number().int().nonnegative(),
|
|
43
|
+
currency: z.string().trim().min(1),
|
|
44
|
+
})
|
|
45
|
+
.passthrough();
|
|
46
|
+
|
|
47
|
+
const lineSubsetSchema = z
|
|
48
|
+
.object({
|
|
49
|
+
quantity: z.number().int().positive(),
|
|
50
|
+
productSlug: z.string().trim().min(1).optional(),
|
|
51
|
+
sku: z.string().trim().min(1).optional(),
|
|
52
|
+
lineSubtotalGross: moneySubsetSchema.optional(),
|
|
53
|
+
})
|
|
54
|
+
.passthrough();
|
|
55
|
+
|
|
56
|
+
const snapshotSubsetSchema = z
|
|
57
|
+
.object({
|
|
58
|
+
lines: z.array(z.unknown()).optional(),
|
|
59
|
+
totals: z
|
|
60
|
+
.object({
|
|
61
|
+
subtotalGross: z.unknown().optional(),
|
|
62
|
+
discountTotalGross: z.unknown().optional(),
|
|
63
|
+
totalGross: z.unknown().optional(),
|
|
64
|
+
})
|
|
65
|
+
.passthrough()
|
|
66
|
+
.optional(),
|
|
67
|
+
})
|
|
68
|
+
.passthrough();
|
|
69
|
+
|
|
70
|
+
function extractLines(snapshot: unknown): RawLine[] {
|
|
71
|
+
const parsed = snapshotSubsetSchema.safeParse(snapshot);
|
|
72
|
+
if (!parsed.success || parsed.data.lines === undefined) return [];
|
|
73
|
+
const lines: RawLine[] = [];
|
|
74
|
+
for (const candidate of parsed.data.lines) {
|
|
75
|
+
const line = lineSubsetSchema.safeParse(candidate);
|
|
76
|
+
if (!line.success) continue;
|
|
77
|
+
if (line.data.productSlug === undefined && line.data.sku === undefined) continue;
|
|
78
|
+
lines.push({
|
|
79
|
+
label: null,
|
|
80
|
+
quantity: line.data.quantity,
|
|
81
|
+
lineSubtotalMinor: line.data.lineSubtotalGross?.amountMinor ?? null,
|
|
82
|
+
currency: line.data.lineSubtotalGross?.currency ?? null,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return lines;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildTotals<Locale extends string>(snapshot: unknown, runtime: Pick<CommerceEmailRuntime<Locale>, "formatMoney" | "genericItemLabel" | "mapSendOutcome" | "parseOutboxPayload" | "resolveLocale">): OrderEmailTotals | null {
|
|
89
|
+
const parsed = snapshotSubsetSchema.safeParse(snapshot);
|
|
90
|
+
if (!parsed.success || parsed.data.totals === undefined) return null;
|
|
91
|
+
const total = moneySubsetSchema.safeParse(parsed.data.totals.totalGross);
|
|
92
|
+
if (!total.success) return null;
|
|
93
|
+
const subtotal = moneySubsetSchema.safeParse(parsed.data.totals.subtotalGross);
|
|
94
|
+
const discount = moneySubsetSchema.safeParse(parsed.data.totals.discountTotalGross);
|
|
95
|
+
const currency = total.data.currency;
|
|
96
|
+
return {
|
|
97
|
+
// Omit the subtotal row when it cannot be parsed — do NOT fall back to the
|
|
98
|
+
// grand total, which would render "Subtotal = Total" alongside a separate
|
|
99
|
+
// discount line that no longer reconciles.
|
|
100
|
+
subtotalLabel: subtotal.success
|
|
101
|
+
? runtime.formatMoney(subtotal.data.amountMinor, subtotal.data.currency)
|
|
102
|
+
: null,
|
|
103
|
+
discountLabel:
|
|
104
|
+
discount.success && discount.data.amountMinor > 0
|
|
105
|
+
? `-${runtime.formatMoney(discount.data.amountMinor, discount.data.currency)}`
|
|
106
|
+
: null,
|
|
107
|
+
totalLabel: runtime.formatMoney(total.data.amountMinor, currency),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createOutboxOrderDraftEmailHandler<Locale extends string>(deps: {
|
|
112
|
+
runtime: Pick<CommerceEmailRuntime<Locale>, "formatMoney" | "genericItemLabel" | "mapSendOutcome" | "parseOutboxPayload" | "resolveLocale">;
|
|
113
|
+
emailPort: TransactionalEmailPort<Locale>;
|
|
114
|
+
recipientPort: OrderRecipientPort;
|
|
115
|
+
lifecyclePort: OrderPaymentLifecyclePort;
|
|
116
|
+
}): OutboxHandler {
|
|
117
|
+
return {
|
|
118
|
+
...outboxOrderDraftEmailDescriptor,
|
|
119
|
+
async handle(row: OutboxEventRow, signal: AbortSignal): Promise<OutboxHandlerOutcome> {
|
|
120
|
+
const parsed = deps.runtime.parseOutboxPayload(payloadSubsetSchema, row.payload);
|
|
121
|
+
if (parsed.ok === false) {
|
|
122
|
+
return parsed.outcome;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const lifecycle = await deps.lifecyclePort.read(parsed.data.orderUuid, signal);
|
|
126
|
+
const skipReason = shouldSkipOrderDraftConfirmation(lifecycle);
|
|
127
|
+
if (skipReason) {
|
|
128
|
+
return { kind: "processed", detail: { skipped: skipReason } };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const recipient = await deps.recipientPort.resolve(parsed.data.orderUuid, signal);
|
|
132
|
+
if (recipient === null) {
|
|
133
|
+
return { kind: "processed", detail: { skipped: "recipient_unresolved" } };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const alreadySent = await deps.emailPort.findExistingSend(
|
|
137
|
+
OUTBOX_ORDER_CONFIRMATION_TEMPLATE_SLUG,
|
|
138
|
+
row.id,
|
|
139
|
+
);
|
|
140
|
+
if (alreadySent) {
|
|
141
|
+
return { kind: "processed", detail: { dedupe: "email_already_sent" } };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const lines = extractLines(parsed.data.orderDraftSnapshot);
|
|
145
|
+
const locale = deps.runtime.resolveLocale(recipient.country ?? null);
|
|
146
|
+
|
|
147
|
+
const outcome = await deps.emailPort.sendOrderConfirmation({
|
|
148
|
+
to: recipient.email,
|
|
149
|
+
firstName: recipient.firstName,
|
|
150
|
+
contextName: recipient.contextName ?? null,
|
|
151
|
+
orderId: parsed.data.orderId,
|
|
152
|
+
outboxEventId: row.id,
|
|
153
|
+
items: buildItems(lines, locale, deps.runtime),
|
|
154
|
+
totals: buildTotals(parsed.data.orderDraftSnapshot, deps.runtime),
|
|
155
|
+
locale,
|
|
156
|
+
signal,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return deps.runtime.mapSendOutcome(outcome);
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|