@openlup/platform 0.16.0 → 0.18.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 +180 -0
- package/README.md +564 -6
- package/dist/catalog/activeSkuSelection.d.ts +2 -0
- package/dist/catalog/activeSkuSelection.js +20 -0
- package/dist/catalog/adminBindings.d.ts +72 -0
- package/dist/catalog/adminBindings.js +1 -0
- package/dist/catalog/adminBundleHandler.d.ts +26 -0
- package/dist/catalog/adminBundleHandler.js +111 -0
- package/dist/catalog/adminBundleReadHandler.d.ts +20 -0
- package/dist/catalog/adminBundleReadHandler.js +185 -0
- package/dist/catalog/adminBundleWritePort.d.ts +85 -0
- package/dist/catalog/adminBundleWritePort.js +1 -0
- package/dist/catalog/bundleAvailability.d.ts +63 -0
- package/dist/catalog/bundleAvailability.js +78 -0
- package/dist/catalog/bundleAvailabilityService.d.ts +65 -0
- package/dist/catalog/bundleAvailabilityService.js +89 -0
- package/dist/catalog/bundleBindings.d.ts +33 -0
- package/dist/catalog/bundleBindings.js +1 -0
- package/dist/catalog/bundleCatalogQueries.d.ts +28 -0
- package/dist/catalog/bundleCatalogQueries.js +84 -0
- package/dist/catalog/bundleCatalogReadPort.d.ts +166 -0
- package/dist/catalog/bundleCatalogReadPort.js +36 -0
- package/dist/catalog/bundleCatalogStore.d.ts +3 -0
- package/dist/catalog/bundleCatalogStore.js +152 -0
- package/dist/catalog/bundleRuleCodes.d.ts +41 -0
- package/dist/catalog/bundleRuleCodes.js +110 -0
- package/dist/catalog/bundleRules.d.ts +69 -0
- package/dist/catalog/bundleRules.js +102 -0
- package/dist/catalog/catalogDraftStore.d.ts +4 -0
- package/dist/catalog/catalogDraftStore.js +63 -0
- package/dist/catalog/catalogSkuEnvelope.d.ts +7 -0
- package/dist/catalog/catalogSkuEnvelope.js +142 -0
- package/dist/catalog/catalogSkuEnvelopeMapper.d.ts +38 -0
- package/dist/catalog/catalogSkuEnvelopeMapper.js +88 -0
- package/dist/catalog/contracts/bundleAdmin.d.ts +143 -0
- package/dist/catalog/contracts/bundleAdmin.js +1 -0
- package/dist/catalog/contracts/bundleAvailability.d.ts +61 -0
- package/dist/catalog/contracts/bundleAvailability.js +1 -0
- package/dist/catalog/contracts/bundleRead.d.ts +223 -0
- package/dist/catalog/contracts/bundleRead.js +1 -0
- package/dist/catalog/contracts/draft.d.ts +79 -0
- package/dist/catalog/contracts/draft.js +47 -0
- package/dist/catalog/contracts/foundation.d.ts +78 -0
- package/dist/catalog/contracts/foundation.js +53 -0
- package/dist/catalog/contracts/index.d.ts +14 -0
- package/dist/catalog/contracts/index.js +6 -0
- package/dist/catalog/contracts/lifecycle.d.ts +77 -0
- package/dist/catalog/contracts/lifecycle.js +1 -0
- package/dist/catalog/contracts/productMediaContracts.d.ts +77 -0
- package/dist/catalog/contracts/productMediaContracts.js +149 -0
- package/dist/catalog/contracts/productTypes.d.ts +54 -0
- package/dist/catalog/contracts/productTypes.js +16 -0
- package/dist/catalog/contracts/resolveProductMedia.d.ts +3 -0
- package/dist/catalog/contracts/resolveProductMedia.js +78 -0
- package/dist/catalog/contracts/sellable.d.ts +23 -0
- package/dist/catalog/contracts/sellable.js +1 -0
- package/dist/catalog/draftHandler.d.ts +25 -0
- package/dist/catalog/draftHandler.js +118 -0
- package/dist/catalog/draftPorts.d.ts +18 -0
- package/dist/catalog/draftPorts.js +8 -0
- package/dist/catalog/draftSchemas.d.ts +423 -0
- package/dist/catalog/draftSchemas.js +89 -0
- package/dist/catalog/draftValidation.d.ts +21 -0
- package/dist/catalog/draftValidation.js +125 -0
- package/dist/catalog/foundationSchemas.d.ts +98 -0
- package/dist/catalog/foundationSchemas.js +110 -0
- package/dist/catalog/index.d.ts +43 -0
- package/dist/catalog/index.js +25 -0
- package/dist/catalog/mediaSourcePack.d.ts +51 -0
- package/dist/catalog/mediaSourcePack.js +247 -0
- package/dist/catalog/productTypeSchemas.d.ts +46 -0
- package/dist/catalog/productTypeSchemas.js +72 -0
- package/dist/catalog/publicationPorts.d.ts +42 -0
- package/dist/catalog/publicationPorts.js +1 -0
- package/dist/catalog/readPorts.d.ts +51 -0
- package/dist/catalog/readPorts.js +1 -0
- package/dist/catalog/referenceCatalogHandler.d.ts +7 -0
- package/dist/catalog/referenceCatalogHandler.js +27 -0
- package/dist/catalog/resolvedSkuProjection.d.ts +88 -0
- package/dist/catalog/resolvedSkuProjection.js +60 -0
- package/dist/catalog/sellableBundleHandler.d.ts +46 -0
- package/dist/catalog/sellableBundleHandler.js +84 -0
- package/dist/catalog/sellableSchemas.d.ts +46 -0
- package/dist/catalog/sellableSchemas.js +27 -0
- package/dist/catalog/sql.d.ts +46 -0
- package/dist/catalog/sql.js +13 -0
- package/dist/catalog/validation.d.ts +2 -0
- package/dist/catalog/validation.js +3 -0
- package/dist/communications/accountingDocumentDelivery.d.ts +35 -0
- package/dist/communications/accountingDocumentDelivery.js +33 -0
- package/dist/communications/authEmailError.d.ts +5 -0
- package/dist/communications/authEmailError.js +8 -0
- package/dist/communications/authEmailPorts.d.ts +2 -0
- package/dist/communications/authEmailPorts.js +1 -0
- package/dist/communications/authSendEmailHook.d.ts +35 -0
- package/dist/communications/authSendEmailHook.js +250 -0
- package/dist/communications/capturedTransactionalDelivery.d.ts +13 -0
- package/dist/communications/capturedTransactionalDelivery.js +14 -0
- package/dist/communications/communicationSync.d.ts +11 -0
- package/dist/communications/communicationSync.js +79 -0
- package/dist/communications/communicationSyncWorker.d.ts +68 -0
- package/dist/communications/communicationSyncWorker.js +218 -0
- package/dist/communications/consentPolicy.d.ts +19 -0
- package/dist/communications/consentPolicy.js +43 -0
- package/dist/communications/consentPurposeRegistry.d.ts +4 -0
- package/dist/communications/consentPurposeRegistry.js +24 -0
- package/dist/communications/contracts/abandonedCart.d.ts +27 -0
- package/dist/communications/contracts/abandonedCart.js +25 -0
- package/dist/communications/contracts/authEmail.d.ts +62 -0
- package/dist/communications/contracts/authEmail.js +1 -0
- package/dist/communications/contracts/backInStock.d.ts +28 -0
- package/dist/communications/contracts/backInStock.js +28 -0
- package/dist/communications/contracts/blocks.d.ts +24 -0
- package/dist/communications/contracts/blocks.js +23 -0
- package/dist/communications/contracts/checkoutExpired.d.ts +22 -0
- package/dist/communications/contracts/checkoutExpired.js +25 -0
- package/dist/communications/contracts/checkoutRecovery.d.ts +26 -0
- package/dist/communications/contracts/checkoutRecovery.js +37 -0
- package/dist/communications/contracts/commerceEmailContent.d.ts +176 -0
- package/dist/communications/contracts/commerceEmailContent.js +1 -0
- package/dist/communications/contracts/commerceEngagementEmailContent.d.ts +95 -0
- package/dist/communications/contracts/commerceEngagementEmailContent.js +1 -0
- package/dist/communications/contracts/consent.d.ts +12 -0
- package/dist/communications/contracts/consent.js +1 -0
- package/dist/communications/contracts/email.d.ts +182 -0
- package/dist/communications/contracts/email.js +1 -0
- package/dist/communications/contracts/guideCard.d.ts +3 -0
- package/dist/communications/contracts/guideCard.js +26 -0
- package/dist/communications/contracts/index.d.ts +52 -0
- package/dist/communications/contracts/index.js +42 -0
- package/dist/communications/contracts/inquiryContent.d.ts +32 -0
- package/dist/communications/contracts/inquiryContent.js +33 -0
- package/dist/communications/contracts/invoiceDocumentContent.d.ts +26 -0
- package/dist/communications/contracts/invoiceDocumentContent.js +36 -0
- package/dist/communications/contracts/marketingAutomation.d.ts +52 -0
- package/dist/communications/contracts/marketingAutomation.js +1 -0
- package/dist/communications/contracts/marketingSync.d.ts +194 -0
- package/dist/communications/contracts/marketingSync.js +1 -0
- package/dist/communications/contracts/newsletter.d.ts +63 -0
- package/dist/communications/contracts/newsletter.js +1 -0
- package/dist/communications/contracts/newsletterWebhook.d.ts +26 -0
- package/dist/communications/contracts/newsletterWebhook.js +1 -0
- package/dist/communications/contracts/optOutContent.d.ts +11 -0
- package/dist/communications/contracts/optOutContent.js +17 -0
- package/dist/communications/contracts/orderCanceled.d.ts +25 -0
- package/dist/communications/contracts/orderCanceled.js +27 -0
- package/dist/communications/contracts/orderDraft.d.ts +46 -0
- package/dist/communications/contracts/orderDraft.js +46 -0
- package/dist/communications/contracts/orderPaid.d.ts +53 -0
- package/dist/communications/contracts/orderPaid.js +71 -0
- package/dist/communications/contracts/orderRefunded.d.ts +24 -0
- package/dist/communications/contracts/orderRefunded.js +27 -0
- package/dist/communications/contracts/paymentFailed.d.ts +24 -0
- package/dist/communications/contracts/paymentFailed.js +24 -0
- package/dist/communications/contracts/publicIntake.d.ts +49 -0
- package/dist/communications/contracts/publicIntake.js +1 -0
- package/dist/communications/contracts/render.d.ts +2 -0
- package/dist/communications/contracts/render.js +229 -0
- package/dist/communications/contracts/reorderReminder.d.ts +24 -0
- package/dist/communications/contracts/reorderReminder.js +24 -0
- package/dist/communications/contracts/returnEmails.d.ts +25 -0
- package/dist/communications/contracts/returnEmails.js +62 -0
- package/dist/communications/contracts/reviewEffects.d.ts +26 -0
- package/dist/communications/contracts/reviewEffects.js +25 -0
- package/dist/communications/contracts/reviewRequest.d.ts +26 -0
- package/dist/communications/contracts/reviewRequest.js +24 -0
- package/dist/communications/contracts/roleNotificationContent.d.ts +25 -0
- package/dist/communications/contracts/roleNotificationContent.js +46 -0
- package/dist/communications/contracts/shipmentDelivered.d.ts +23 -0
- package/dist/communications/contracts/shipmentDelivered.js +24 -0
- package/dist/communications/contracts/shipmentDispatched.d.ts +27 -0
- package/dist/communications/contracts/shipmentDispatched.js +35 -0
- package/dist/communications/contracts/shipmentException.d.ts +21 -0
- package/dist/communications/contracts/shipmentException.js +26 -0
- package/dist/communications/contracts/subscriptionActivationActionRequired.d.ts +17 -0
- package/dist/communications/contracts/subscriptionActivationActionRequired.js +22 -0
- package/dist/communications/contracts/subscriptionAddressChanged.d.ts +19 -0
- package/dist/communications/contracts/subscriptionAddressChanged.js +25 -0
- package/dist/communications/contracts/subscriptionCancelled.d.ts +19 -0
- package/dist/communications/contracts/subscriptionCancelled.js +25 -0
- package/dist/communications/contracts/subscriptionCycleSkipped.d.ts +25 -0
- package/dist/communications/contracts/subscriptionCycleSkipped.js +40 -0
- package/dist/communications/contracts/subscriptionDeliveryRescheduled.d.ts +25 -0
- package/dist/communications/contracts/subscriptionDeliveryRescheduled.js +39 -0
- package/dist/communications/contracts/subscriptionEmailContent.d.ts +145 -0
- package/dist/communications/contracts/subscriptionEmailContent.js +1 -0
- package/dist/communications/contracts/subscriptionPackageChanged.d.ts +20 -0
- package/dist/communications/contracts/subscriptionPackageChanged.js +26 -0
- package/dist/communications/contracts/subscriptionPauseReminder.d.ts +23 -0
- package/dist/communications/contracts/subscriptionPauseReminder.js +29 -0
- package/dist/communications/contracts/subscriptionPaused.d.ts +21 -0
- package/dist/communications/contracts/subscriptionPaused.js +25 -0
- package/dist/communications/contracts/subscriptionPaymentExpired.d.ts +34 -0
- package/dist/communications/contracts/subscriptionPaymentExpired.js +37 -0
- package/dist/communications/contracts/subscriptionPaymentFailed.d.ts +49 -0
- package/dist/communications/contracts/subscriptionPaymentFailed.js +56 -0
- package/dist/communications/contracts/subscriptionPaymentRecovered.d.ts +19 -0
- package/dist/communications/contracts/subscriptionPaymentRecovered.js +22 -0
- package/dist/communications/contracts/subscriptionRenewalAtRisk.d.ts +36 -0
- package/dist/communications/contracts/subscriptionRenewalAtRisk.js +36 -0
- package/dist/communications/contracts/subscriptionRenewalUpcoming.d.ts +49 -0
- package/dist/communications/contracts/subscriptionRenewalUpcoming.js +43 -0
- package/dist/communications/contracts/subscriptionResumed.d.ts +23 -0
- package/dist/communications/contracts/subscriptionResumed.js +25 -0
- package/dist/communications/contracts/subscriptionWelcome.d.ts +44 -0
- package/dist/communications/contracts/subscriptionWelcome.js +45 -0
- package/dist/communications/contracts/subscriptionWinback.d.ts +21 -0
- package/dist/communications/contracts/subscriptionWinback.js +28 -0
- package/dist/communications/contracts/transactionalDelivery.d.ts +42 -0
- package/dist/communications/contracts/transactionalDelivery.js +1 -0
- package/dist/communications/contracts/types.d.ts +46 -0
- package/dist/communications/contracts/types.js +1 -0
- package/dist/communications/contribution.d.ts +10 -0
- package/dist/communications/contribution.js +24 -0
- package/dist/communications/deliveryReconciliation.d.ts +64 -0
- package/dist/communications/deliveryReconciliation.js +60 -0
- package/dist/communications/emailEgress.d.ts +38 -0
- package/dist/communications/emailEgress.js +100 -0
- package/dist/communications/emailHealthAssessment.d.ts +39 -0
- package/dist/communications/emailHealthAssessment.js +59 -0
- package/dist/communications/emailHealthMetricsPort.d.ts +17 -0
- package/dist/communications/emailHealthMetricsPort.js +1 -0
- package/dist/communications/emailNotificationControl.d.ts +27 -0
- package/dist/communications/emailNotificationControl.js +10 -0
- package/dist/communications/emailObservation.d.ts +28 -0
- package/dist/communications/emailObservation.js +23 -0
- package/dist/communications/emailTransport.d.ts +82 -0
- package/dist/communications/emailTransport.js +95 -0
- package/dist/communications/errors.d.ts +15 -0
- package/dist/communications/errors.js +30 -0
- package/dist/communications/index.d.ts +47 -0
- package/dist/communications/index.js +38 -0
- package/dist/communications/integrationEventSignature.d.ts +15 -0
- package/dist/communications/integrationEventSignature.js +37 -0
- package/dist/communications/klaviyo/consentPush.d.ts +51 -0
- package/dist/communications/klaviyo/consentPush.js +19 -0
- package/dist/communications/klaviyo/index.d.ts +110 -0
- package/dist/communications/klaviyo/index.js +860 -0
- package/dist/communications/managedTransactionalDeliveryStore.d.ts +12 -0
- package/dist/communications/managedTransactionalDeliveryStore.js +49 -0
- package/dist/communications/nativeOperations.d.ts +79 -0
- package/dist/communications/nativeOperations.js +3 -0
- package/dist/communications/newsletterIntegrationContracts.d.ts +130 -0
- package/dist/communications/newsletterIntegrationContracts.js +128 -0
- package/dist/communications/newsletterWebhookPort.d.ts +11 -0
- package/dist/communications/newsletterWebhookPort.js +84 -0
- package/dist/communications/newsletterWebhookProcessing.d.ts +17 -0
- package/dist/communications/newsletterWebhookProcessing.js +71 -0
- package/dist/communications/originPolicy.d.ts +40 -0
- package/dist/communications/originPolicy.js +100 -0
- package/dist/communications/postgresCommunicationSync.d.ts +79 -0
- package/dist/communications/postgresCommunicationSync.js +111 -0
- package/dist/communications/postgresConsentEvaluator.d.ts +19 -0
- package/dist/communications/postgresConsentEvaluator.js +43 -0
- package/dist/communications/postgresDeliveryReconciliation.d.ts +3 -0
- package/dist/communications/postgresDeliveryReconciliation.js +113 -0
- package/dist/communications/postgresNativeOperations.d.ts +13 -0
- package/dist/communications/postgresNativeOperations.js +63 -0
- package/dist/communications/postgresNotificationRecipients.d.ts +37 -0
- package/dist/communications/postgresNotificationRecipients.js +60 -0
- package/dist/communications/postgresTransactionalDeliveryStore.d.ts +7 -0
- package/dist/communications/postgresTransactionalDeliveryStore.js +55 -0
- package/dist/communications/preferencesToken.d.ts +10 -0
- package/dist/communications/preferencesToken.js +40 -0
- package/dist/communications/publicPreferenceToken.d.ts +24 -0
- package/dist/communications/publicPreferenceToken.js +53 -0
- package/dist/communications/resend/index.d.ts +40 -0
- package/dist/communications/resend/index.js +310 -0
- package/dist/communications/shipmentNotifications.d.ts +34 -0
- package/dist/communications/shipmentNotifications.js +61 -0
- package/dist/communications/sql.d.ts +26 -0
- package/dist/communications/sql.js +7 -0
- package/dist/communications/standardWebhooks.d.ts +14 -0
- package/dist/communications/standardWebhooks.js +73 -0
- package/dist/communications/status.d.ts +11 -0
- package/dist/communications/status.js +36 -0
- package/dist/communications/transactionalDelivery.d.ts +19 -0
- package/dist/communications/transactionalDelivery.js +96 -0
- package/dist/communications/transactionalDeliveryIdentifier.d.ts +3 -0
- package/dist/communications/transactionalDeliveryIdentifier.js +6 -0
- package/dist/communications/unsubscribeToken.d.ts +77 -0
- package/dist/communications/unsubscribeToken.js +150 -0
- package/dist/communications/validation.d.ts +2 -0
- package/dist/communications/validation.js +3 -0
- package/dist/composition/catalogContribution.d.ts +18 -0
- package/dist/composition/catalogContribution.js +21 -0
- package/dist/composition/index.d.ts +1 -0
- package/dist/composition/index.js +1 -0
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/webhookSignature.d.ts +42 -0
- package/dist/http/webhookSignature.js +86 -0
- package/dist/identity/customerContracts.d.ts +2 -2
- package/dist/inventory/adminInventory.d.ts +13 -0
- package/dist/inventory/adminInventory.js +214 -0
- package/dist/inventory/contracts/forecast.d.ts +86 -0
- package/dist/inventory/contracts/forecast.js +1 -0
- package/dist/inventory/contracts/index.d.ts +4 -0
- package/dist/inventory/contracts/index.js +1 -0
- package/dist/inventory/contracts/inventory.d.ts +157 -0
- package/dist/inventory/contracts/inventory.js +1 -0
- package/dist/inventory/contracts/newPurchaseStock.d.ts +20 -0
- package/dist/inventory/contracts/newPurchaseStock.js +1 -0
- package/dist/inventory/contracts/reservation.d.ts +18 -0
- package/dist/inventory/contracts/reservation.js +1 -0
- package/dist/inventory/forecast.d.ts +24 -0
- package/dist/inventory/forecast.js +200 -0
- package/dist/inventory/index.d.ts +13 -0
- package/dist/inventory/index.js +10 -0
- package/dist/inventory/inventoryHandlers.d.ts +53 -0
- package/dist/inventory/inventoryHandlers.js +151 -0
- package/dist/inventory/newPurchaseStockAvailability.d.ts +47 -0
- package/dist/inventory/newPurchaseStockAvailability.js +75 -0
- package/dist/inventory/ports.d.ts +21 -0
- package/dist/inventory/ports.js +16 -0
- package/dist/inventory/renewalReservation.d.ts +27 -0
- package/dist/inventory/renewalReservation.js +61 -0
- package/dist/inventory/reservationPolicy.d.ts +6 -0
- package/dist/inventory/reservationPolicy.js +19 -0
- package/dist/inventory/schemas.d.ts +137 -0
- package/dist/inventory/schemas.js +112 -0
- package/dist/inventory/sql.d.ts +14 -0
- package/dist/inventory/sql.js +5 -0
- package/dist/inventory/subscriptionForecastCalculation.d.ts +4 -0
- package/dist/inventory/subscriptionForecastCalculation.js +133 -0
- package/dist/pricing/acquisitionFinancialPolicy.d.ts +26 -0
- package/dist/pricing/acquisitionFinancialPolicy.js +1 -0
- package/dist/pricing/acquisitionOfferPricing.d.ts +27 -0
- package/dist/pricing/acquisitionOfferPricing.js +25 -0
- package/dist/pricing/acquisitionOfferPricingHandler.d.ts +20 -0
- package/dist/pricing/acquisitionOfferPricingHandler.js +105 -0
- package/dist/pricing/acquisitionOfferPricingSchemas.d.ts +31 -0
- package/dist/pricing/acquisitionOfferPricingSchemas.js +33 -0
- package/dist/pricing/acquisitionQuoteBinding.d.ts +12 -0
- package/dist/pricing/acquisitionQuoteBinding.js +12 -0
- package/dist/pricing/acquisitionReadiness.d.ts +31 -0
- package/dist/pricing/acquisitionReadiness.js +117 -0
- package/dist/pricing/acquisitionTerms.d.ts +19 -0
- package/dist/pricing/acquisitionTerms.js +126 -0
- package/dist/pricing/acquisitionTermsSchemas.d.ts +59 -0
- package/dist/pricing/acquisitionTermsSchemas.js +42 -0
- package/dist/pricing/catalogFactsProvenance.d.ts +33 -0
- package/dist/pricing/catalogFactsProvenance.js +64 -0
- package/dist/pricing/catalogFactsSchemas.d.ts +45 -0
- package/dist/pricing/catalogFactsSchemas.js +58 -0
- package/dist/pricing/commerceOfferPricing.d.ts +25 -0
- package/dist/pricing/commerceOfferPricing.js +139 -0
- package/dist/pricing/commerceOfferPricingCache.d.ts +10 -0
- package/dist/pricing/commerceOfferPricingCache.js +44 -0
- package/dist/pricing/commerceOfferPricingHandler.d.ts +21 -0
- package/dist/pricing/commerceOfferPricingHandler.js +78 -0
- package/dist/pricing/commercePriceAuthority.d.ts +26 -0
- package/dist/pricing/commercePriceAuthority.js +79 -0
- package/dist/pricing/commercePricingPolicyErrors.d.ts +40 -0
- package/dist/pricing/commercePricingPolicyErrors.js +67 -0
- package/dist/pricing/commerceQuoteBatchHandler.d.ts +37 -0
- package/dist/pricing/commerceQuoteBatchHandler.js +83 -0
- package/dist/pricing/commerceQuoteCatalogReadPort.d.ts +27 -0
- package/dist/pricing/commerceQuoteCatalogReadPort.js +8 -0
- package/dist/pricing/commerceQuoteHandler.d.ts +31 -0
- package/dist/pricing/commerceQuoteHandler.js +110 -0
- package/dist/pricing/commerceQuoteSnapshotExpect.d.ts +9 -0
- package/dist/pricing/commerceQuoteSnapshotExpect.js +22 -0
- package/dist/pricing/contracts/acquisitionTerms.d.ts +32 -0
- package/dist/pricing/contracts/acquisitionTerms.js +2 -0
- package/dist/pricing/contracts/authority.d.ts +27 -0
- package/dist/pricing/contracts/authority.js +1 -0
- package/dist/pricing/contracts/evidence.d.ts +33 -0
- package/dist/pricing/contracts/evidence.js +1 -0
- package/dist/pricing/contracts/index.d.ts +17 -0
- package/dist/pricing/contracts/index.js +7 -0
- package/dist/pricing/contracts/money.d.ts +27 -0
- package/dist/pricing/contracts/money.js +1 -0
- package/dist/pricing/contracts/offer.d.ts +52 -0
- package/dist/pricing/contracts/offer.js +4 -0
- package/dist/pricing/contracts/offerPolicy.d.ts +16 -0
- package/dist/pricing/contracts/offerPolicy.js +5 -0
- package/dist/pricing/contracts/policy.d.ts +22 -0
- package/dist/pricing/contracts/policy.js +3 -0
- package/dist/pricing/contracts/provenance.d.ts +24 -0
- package/dist/pricing/contracts/provenance.js +2 -0
- package/dist/pricing/contracts/quote.d.ts +94 -0
- package/dist/pricing/contracts/quote.js +1 -0
- package/dist/pricing/dbBackedCommerceQuoteHelpers.d.ts +78 -0
- package/dist/pricing/dbBackedCommerceQuoteHelpers.js +146 -0
- package/dist/pricing/dbBackedCommerceQuotePort.d.ts +66 -0
- package/dist/pricing/dbBackedCommerceQuotePort.js +206 -0
- package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
- package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
- package/dist/pricing/index.d.ts +51 -0
- package/dist/pricing/index.js +36 -0
- package/dist/pricing/offerPolicyReadiness.d.ts +21 -0
- package/dist/pricing/offerPolicyReadiness.js +79 -0
- package/dist/pricing/offerPolicySchemas.d.ts +25 -0
- package/dist/pricing/offerPolicySchemas.js +15 -0
- package/dist/pricing/offerPricingSchemas.d.ts +315 -0
- package/dist/pricing/offerPricingSchemas.js +65 -0
- package/dist/pricing/ports.d.ts +13 -0
- package/dist/pricing/ports.js +9 -0
- package/dist/pricing/pricingPolicyToken.d.ts +39 -0
- package/dist/pricing/pricingPolicyToken.js +84 -0
- package/dist/pricing/quoteAcquisitionTerms.d.ts +2 -0
- package/dist/pricing/quoteAcquisitionTerms.js +4 -0
- package/dist/pricing/quoteCodeRejectionDetails.d.ts +20 -0
- package/dist/pricing/quoteCodeRejectionDetails.js +22 -0
- package/dist/pricing/quotePorts.d.ts +30 -0
- package/dist/pricing/quotePorts.js +20 -0
- package/dist/pricing/quotePrimitives.d.ts +13 -0
- package/dist/pricing/quotePrimitives.js +18 -0
- package/dist/pricing/quotePromotionEvidence.d.ts +39 -0
- package/dist/pricing/quotePromotionEvidence.js +73 -0
- package/dist/pricing/quoteSchemas.d.ts +1176 -0
- package/dist/pricing/quoteSchemas.js +196 -0
- package/dist/pricing/subscriptionPricePolicy.d.ts +52 -0
- package/dist/pricing/subscriptionPricePolicy.js +96 -0
- package/dist/pricing/taxProfileGuard.d.ts +2 -0
- package/dist/pricing/taxProfileGuard.js +24 -0
- package/dist/pricing/validation.d.ts +2 -0
- package/dist/pricing/validation.js +3 -0
- package/dist/promotions/adminBindings.d.ts +72 -0
- package/dist/promotions/adminBindings.js +1 -0
- package/dist/promotions/adminPromotionCodesContracts.d.ts +498 -0
- package/dist/promotions/adminPromotionCodesContracts.js +211 -0
- package/dist/promotions/adminPromotionCodesDataPort.d.ts +47 -0
- package/dist/promotions/adminPromotionCodesDataPort.js +1 -0
- package/dist/promotions/adminPromotionCodesHandler.d.ts +14 -0
- package/dist/promotions/adminPromotionCodesHandler.js +194 -0
- package/dist/promotions/adminPromotionsContracts.d.ts +235 -0
- package/dist/promotions/adminPromotionsContracts.js +99 -0
- package/dist/promotions/adminPromotionsDataPort.d.ts +42 -0
- package/dist/promotions/adminPromotionsDataPort.js +1 -0
- package/dist/promotions/adminPromotionsHandler.d.ts +16 -0
- package/dist/promotions/adminPromotionsHandler.js +47 -0
- package/dist/promotions/contracts/index.d.ts +387 -0
- package/dist/promotions/contracts/index.js +1 -0
- package/dist/promotions/index.d.ts +22 -0
- package/dist/promotions/index.js +21 -0
- package/dist/promotions/promoDataPort.d.ts +47 -0
- package/dist/promotions/promoDataPort.js +14 -0
- package/dist/promotions/promotionAcceptanceTelemetry.d.ts +29 -0
- package/dist/promotions/promotionAcceptanceTelemetry.js +45 -0
- package/dist/promotions/promotionBenefitDefinition.d.ts +37 -0
- package/dist/promotions/promotionBenefitDefinition.js +33 -0
- package/dist/promotions/promotionClaimSweepPort.d.ts +14 -0
- package/dist/promotions/promotionClaimSweepPort.js +8 -0
- package/dist/promotions/promotionClaims.d.ts +5 -0
- package/dist/promotions/promotionClaims.js +57 -0
- package/dist/promotions/promotionCodePreview.d.ts +11 -0
- package/dist/promotions/promotionCodePreview.js +154 -0
- package/dist/promotions/promotionCodeQuotePort.d.ts +25 -0
- package/dist/promotions/promotionCodeQuotePort.js +1 -0
- package/dist/promotions/promotionQuoteAcceptance.d.ts +65 -0
- package/dist/promotions/promotionQuoteAcceptance.js +202 -0
- package/dist/promotions/promotionQuoteBinding.d.ts +71 -0
- package/dist/promotions/promotionQuoteBinding.js +85 -0
- package/dist/promotions/quoteAutomaticPromotions.d.ts +24 -0
- package/dist/promotions/quoteAutomaticPromotions.js +36 -0
- package/dist/promotions/quotePromoDiscounts.d.ts +32 -0
- package/dist/promotions/quotePromoDiscounts.js +140 -0
- package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
- package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
- package/dist/promotions/quotePromotionV2.d.ts +29 -0
- package/dist/promotions/quotePromotionV2.js +174 -0
- package/dist/promotions/sql.d.ts +14 -0
- package/dist/promotions/sql.js +5 -0
- package/dist/promotions/validation.d.ts +2 -0
- package/dist/promotions/validation.js +3 -0
- package/dist/shipping/addressCanonLookupHandler.d.ts +7 -0
- package/dist/shipping/addressCanonLookupHandler.js +105 -0
- package/dist/shipping/addressCanonical.d.ts +2 -0
- package/dist/shipping/addressCanonical.js +26 -0
- package/dist/shipping/addressPorts.d.ts +39 -0
- package/dist/shipping/addressPorts.js +1 -0
- package/dist/shipping/addressSchemas.d.ts +30 -0
- package/dist/shipping/addressSchemas.js +1 -0
- package/dist/shipping/adminShippingRateHandler.d.ts +4 -0
- package/dist/shipping/adminShippingRateHandler.js +64 -0
- package/dist/shipping/contracts/address.d.ts +144 -0
- package/dist/shipping/contracts/address.js +1 -0
- package/dist/shipping/contracts/delivery.d.ts +110 -0
- package/dist/shipping/contracts/delivery.js +1 -0
- package/dist/shipping/contracts/index.d.ts +3 -0
- package/dist/shipping/contracts/index.js +1 -0
- package/dist/shipping/contracts/shippingRate.d.ts +11 -0
- package/dist/shipping/contracts/shippingRate.js +1 -0
- package/dist/shipping/customerAddressesHandler.d.ts +5 -0
- package/dist/shipping/customerAddressesHandler.js +80 -0
- package/dist/shipping/customerSelfServiceAddressModels.d.ts +27 -0
- package/dist/shipping/customerSelfServiceAddressModels.js +101 -0
- package/dist/shipping/deliverySelectionHandler.d.ts +10 -0
- package/dist/shipping/deliverySelectionHandler.js +77 -0
- package/dist/shipping/deliverySelectionPort.d.ts +8 -0
- package/dist/shipping/deliverySelectionPort.js +1 -0
- package/dist/shipping/deliverySelectionSummary.d.ts +2 -0
- package/dist/shipping/deliverySelectionSummary.js +53 -0
- package/dist/shipping/handlerSchemas.d.ts +21 -0
- package/dist/shipping/handlerSchemas.js +1 -0
- package/dist/shipping/index.d.ts +17 -0
- package/dist/shipping/index.js +8 -0
- package/dist/shipping/pickupPointSearchPort.d.ts +4 -0
- package/dist/shipping/pickupPointSearchPort.js +1 -0
- package/dist/shipping/readDeliverySelectionEvidence.d.ts +2 -0
- package/dist/shipping/readDeliverySelectionEvidence.js +44 -0
- package/dist/shipping/shippingRatePorts.d.ts +52 -0
- package/dist/shipping/shippingRatePorts.js +1 -0
- package/package.json +72 -2
- package/release-gates.json +249 -8
- package/sql/20260807140000_read_parity_catalog.sql +94 -0
- package/sql/20260807190000_comms_outbox_rail.sql +329 -0
- package/sql/20260810120000_storefront_catalog_attributes.sql +51 -0
- package/sql/20260810120001_transactional_delivery_receipts.sql +209 -0
- package/sql/20260812140000_sellable_bundle_catalog.sql +124 -0
- package/sql/20260812210000_bundle_operator_write_boundary.sql +567 -0
- package/sql/20260813120000_communications_control_plane.sql +568 -0
- package/sql/20260814000000_fulfillment_stock_evidence.sql +239 -0
- package/sql/20260815120000_automatic_subscription_renewal.sql +1083 -0
- package/sql/20260817180000_promotion_claim_lifecycle.sql +591 -0
- package/sql/20260817180100_promotion_claim_lifecycle_validation.sql +23 -0
- package/sql/20260827220000_catalog_document_revision_foundation.sql +226 -0
- package/sql/20260827220001_catalog_document_revision_foundation_validate.sql +10 -0
- package/sql/20260908100000_catalog_draft_foundation.sql +217 -0
- package/sql/20261019200000_communications_native_operations.sql +68 -0
- package/src/catalog/activeSkuSelection.ts +20 -0
- package/src/catalog/adminBindings.ts +59 -0
- package/src/catalog/adminBundleHandler.ts +165 -0
- package/src/catalog/adminBundleReadHandler.ts +261 -0
- package/src/catalog/adminBundleWritePort.ts +102 -0
- package/src/catalog/bundleAvailability.ts +129 -0
- package/src/catalog/bundleAvailabilityService.ts +171 -0
- package/src/catalog/bundleBindings.ts +29 -0
- package/src/catalog/bundleCatalogQueries.ts +98 -0
- package/src/catalog/bundleCatalogReadPort.ts +177 -0
- package/src/catalog/bundleCatalogStore.ts +269 -0
- package/src/catalog/bundleRuleCodes.ts +134 -0
- package/src/catalog/bundleRules.ts +153 -0
- package/src/catalog/catalogDraftStore.ts +71 -0
- package/src/catalog/catalogSkuEnvelope.ts +214 -0
- package/src/catalog/catalogSkuEnvelopeMapper.ts +160 -0
- package/src/catalog/contracts/bundleAdmin.ts +20 -0
- package/src/catalog/contracts/bundleAvailability.ts +5 -0
- package/src/catalog/contracts/bundleRead.ts +13 -0
- package/src/catalog/contracts/draft.ts +69 -0
- package/src/catalog/contracts/foundation.ts +104 -0
- package/src/catalog/contracts/index.ts +14 -0
- package/src/catalog/contracts/lifecycle.ts +74 -0
- package/src/catalog/contracts/productMediaContracts.ts +170 -0
- package/src/catalog/contracts/productTypes.ts +35 -0
- package/src/catalog/contracts/resolveProductMedia.ts +85 -0
- package/src/catalog/contracts/sellable.ts +12 -0
- package/src/catalog/draftHandler.ts +105 -0
- package/src/catalog/draftPorts.ts +18 -0
- package/src/catalog/draftSchemas.ts +88 -0
- package/src/catalog/draftValidation.ts +100 -0
- package/src/catalog/foundationSchemas.ts +123 -0
- package/src/catalog/index.ts +43 -0
- package/src/catalog/mediaSourcePack.ts +219 -0
- package/src/catalog/productTypeSchemas.ts +70 -0
- package/src/catalog/publicationPorts.ts +44 -0
- package/src/catalog/readPorts.ts +61 -0
- package/src/catalog/referenceCatalogHandler.ts +39 -0
- package/src/catalog/resolvedSkuProjection.ts +76 -0
- package/src/catalog/sellableBundleHandler.ts +133 -0
- package/src/catalog/sellableSchemas.ts +33 -0
- package/src/catalog/sql.ts +14 -0
- package/src/catalog/validation.ts +3 -0
- package/src/communications/accountingDocumentDelivery.ts +63 -0
- package/src/communications/authEmailError.ts +10 -0
- package/src/communications/authEmailPorts.ts +2 -0
- package/src/communications/authSendEmailHook.ts +310 -0
- package/src/communications/capturedTransactionalDelivery.ts +29 -0
- package/src/communications/communicationSync.ts +96 -0
- package/src/communications/communicationSyncWorker.ts +302 -0
- package/src/communications/consentPolicy.ts +82 -0
- package/src/communications/consentPurposeRegistry.ts +31 -0
- package/src/communications/contracts/abandonedCart.ts +64 -0
- package/src/communications/contracts/authEmail.ts +73 -0
- package/src/communications/contracts/backInStock.ts +69 -0
- package/src/communications/contracts/blocks.ts +47 -0
- package/src/communications/contracts/checkoutExpired.ts +62 -0
- package/src/communications/contracts/checkoutRecovery.ts +68 -0
- package/src/communications/contracts/commerceEmailContent.ts +189 -0
- package/src/communications/contracts/commerceEngagementEmailContent.ts +106 -0
- package/src/communications/contracts/consent.ts +13 -0
- package/src/communications/contracts/email.ts +210 -0
- package/src/communications/contracts/guideCard.ts +34 -0
- package/src/communications/contracts/index.ts +52 -0
- package/src/communications/contracts/inquiryContent.ts +53 -0
- package/src/communications/contracts/invoiceDocumentContent.ts +57 -0
- package/src/communications/contracts/marketingAutomation.ts +45 -0
- package/src/communications/contracts/marketingSync.ts +109 -0
- package/src/communications/contracts/newsletter.ts +78 -0
- package/src/communications/contracts/newsletterWebhook.ts +23 -0
- package/src/communications/contracts/optOutContent.ts +23 -0
- package/src/communications/contracts/orderCanceled.ts +64 -0
- package/src/communications/contracts/orderDraft.ts +112 -0
- package/src/communications/contracts/orderPaid.ts +147 -0
- package/src/communications/contracts/orderRefunded.ts +63 -0
- package/src/communications/contracts/paymentFailed.ts +59 -0
- package/src/communications/contracts/publicIntake.ts +53 -0
- package/src/communications/contracts/render.ts +271 -0
- package/src/communications/contracts/reorderReminder.ts +59 -0
- package/src/communications/contracts/returnEmails.ts +113 -0
- package/src/communications/contracts/reviewEffects.ts +60 -0
- package/src/communications/contracts/reviewRequest.ts +59 -0
- package/src/communications/contracts/roleNotificationContent.ts +65 -0
- package/src/communications/contracts/shipmentDelivered.ts +59 -0
- package/src/communications/contracts/shipmentDispatched.ts +77 -0
- package/src/communications/contracts/shipmentException.ts +62 -0
- package/src/communications/contracts/subscriptionActivationActionRequired.ts +37 -0
- package/src/communications/contracts/subscriptionAddressChanged.ts +56 -0
- package/src/communications/contracts/subscriptionCancelled.ts +57 -0
- package/src/communications/contracts/subscriptionCycleSkipped.ts +80 -0
- package/src/communications/contracts/subscriptionDeliveryRescheduled.ts +79 -0
- package/src/communications/contracts/subscriptionEmailContent.ts +163 -0
- package/src/communications/contracts/subscriptionPackageChanged.ts +59 -0
- package/src/communications/contracts/subscriptionPauseReminder.ts +63 -0
- package/src/communications/contracts/subscriptionPaused.ts +58 -0
- package/src/communications/contracts/subscriptionPaymentExpired.ts +83 -0
- package/src/communications/contracts/subscriptionPaymentFailed.ts +117 -0
- package/src/communications/contracts/subscriptionPaymentRecovered.ts +50 -0
- package/src/communications/contracts/subscriptionRenewalAtRisk.ts +85 -0
- package/src/communications/contracts/subscriptionRenewalUpcoming.ts +103 -0
- package/src/communications/contracts/subscriptionResumed.ts +60 -0
- package/src/communications/contracts/subscriptionWelcome.ts +102 -0
- package/src/communications/contracts/subscriptionWinback.ts +62 -0
- package/src/communications/contracts/transactionalDelivery.ts +43 -0
- package/src/communications/contracts/types.ts +58 -0
- package/src/communications/contribution.ts +33 -0
- package/src/communications/deliveryReconciliation.ts +108 -0
- package/src/communications/emailEgress.ts +111 -0
- package/src/communications/emailHealthAssessment.ts +106 -0
- package/src/communications/emailHealthMetricsPort.ts +17 -0
- package/src/communications/emailNotificationControl.ts +28 -0
- package/src/communications/emailObservation.ts +43 -0
- package/src/communications/emailTransport.ts +167 -0
- package/src/communications/errors.ts +34 -0
- package/src/communications/index.ts +47 -0
- package/src/communications/integrationEventSignature.ts +49 -0
- package/src/communications/klaviyo/consentPush.ts +19 -0
- package/src/communications/klaviyo/index.ts +649 -0
- package/src/communications/managedTransactionalDeliveryStore.ts +59 -0
- package/src/communications/nativeOperations.ts +40 -0
- package/src/communications/newsletterIntegrationContracts.ts +154 -0
- package/src/communications/newsletterWebhookPort.ts +104 -0
- package/src/communications/newsletterWebhookProcessing.ts +102 -0
- package/src/communications/originPolicy.ts +151 -0
- package/src/communications/postgresCommunicationSync.ts +127 -0
- package/src/communications/postgresConsentEvaluator.ts +49 -0
- package/src/communications/postgresDeliveryReconciliation.ts +101 -0
- package/src/communications/postgresNativeOperations.ts +55 -0
- package/src/communications/postgresNotificationRecipients.ts +57 -0
- package/src/communications/postgresTransactionalDeliveryStore.ts +79 -0
- package/src/communications/preferencesToken.ts +62 -0
- package/src/communications/publicPreferenceToken.ts +68 -0
- package/src/communications/resend/index.ts +350 -0
- package/src/communications/shipmentNotifications.ts +78 -0
- package/src/communications/sql.ts +7 -0
- package/src/communications/standardWebhooks.ts +84 -0
- package/src/communications/status.ts +51 -0
- package/src/communications/transactionalDelivery.ts +114 -0
- package/src/communications/transactionalDeliveryIdentifier.ts +9 -0
- package/src/communications/unsubscribeToken.ts +186 -0
- package/src/communications/validation.ts +3 -0
- package/src/composition/catalogContribution.ts +32 -0
- package/src/composition/index.ts +1 -0
- package/src/http/index.ts +1 -0
- package/src/http/webhookSignature.ts +96 -0
- package/src/inventory/adminInventory.ts +251 -0
- package/src/inventory/contracts/forecast.ts +96 -0
- package/src/inventory/contracts/index.ts +18 -0
- package/src/inventory/contracts/inventory.ts +14 -0
- package/src/inventory/contracts/newPurchaseStock.ts +23 -0
- package/src/inventory/contracts/reservation.ts +20 -0
- package/src/inventory/forecast.ts +239 -0
- package/src/inventory/index.ts +21 -0
- package/src/inventory/inventoryHandlers.ts +215 -0
- package/src/inventory/newPurchaseStockAvailability.ts +120 -0
- package/src/inventory/ports.ts +46 -0
- package/src/inventory/renewalReservation.ts +88 -0
- package/src/inventory/reservationPolicy.ts +27 -0
- package/src/inventory/schemas.ts +128 -0
- package/src/inventory/sql.ts +5 -0
- package/src/inventory/subscriptionForecastCalculation.ts +157 -0
- package/src/pricing/acquisitionFinancialPolicy.ts +26 -0
- package/src/pricing/acquisitionOfferPricing.ts +38 -0
- package/src/pricing/acquisitionOfferPricingHandler.ts +157 -0
- package/src/pricing/acquisitionOfferPricingSchemas.ts +35 -0
- package/src/pricing/acquisitionQuoteBinding.ts +12 -0
- package/src/pricing/acquisitionReadiness.ts +148 -0
- package/src/pricing/acquisitionTerms.ts +125 -0
- package/src/pricing/acquisitionTermsSchemas.ts +52 -0
- package/src/pricing/catalogFactsProvenance.ts +102 -0
- package/src/pricing/catalogFactsSchemas.ts +69 -0
- package/src/pricing/commerceOfferPricing.ts +224 -0
- package/src/pricing/commerceOfferPricingCache.ts +53 -0
- package/src/pricing/commerceOfferPricingHandler.ts +137 -0
- package/src/pricing/commercePriceAuthority.ts +104 -0
- package/src/pricing/commercePricingPolicyErrors.ts +88 -0
- package/src/pricing/commerceQuoteBatchHandler.ts +129 -0
- package/src/pricing/commerceQuoteCatalogReadPort.ts +42 -0
- package/src/pricing/commerceQuoteHandler.ts +184 -0
- package/src/pricing/commerceQuoteSnapshotExpect.ts +48 -0
- package/src/pricing/contracts/acquisitionTerms.ts +26 -0
- package/src/pricing/contracts/authority.ts +44 -0
- package/src/pricing/contracts/evidence.ts +25 -0
- package/src/pricing/contracts/index.ts +17 -0
- package/src/pricing/contracts/money.ts +32 -0
- package/src/pricing/contracts/offer.ts +42 -0
- package/src/pricing/contracts/offerPolicy.ts +17 -0
- package/src/pricing/contracts/policy.ts +26 -0
- package/src/pricing/contracts/provenance.ts +19 -0
- package/src/pricing/contracts/quote.ts +106 -0
- package/src/pricing/dbBackedCommerceQuoteHelpers.ts +256 -0
- package/src/pricing/dbBackedCommerceQuotePort.ts +260 -0
- package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
- package/src/pricing/index.ts +52 -0
- package/src/pricing/offerPolicyReadiness.ts +110 -0
- package/src/pricing/offerPolicySchemas.ts +23 -0
- package/src/pricing/offerPricingSchemas.ts +92 -0
- package/src/pricing/ports.ts +17 -0
- package/src/pricing/pricingPolicyToken.ts +119 -0
- package/src/pricing/quoteAcquisitionTerms.ts +10 -0
- package/src/pricing/quoteCodeRejectionDetails.ts +23 -0
- package/src/pricing/quotePorts.ts +58 -0
- package/src/pricing/quotePrimitives.ts +21 -0
- package/src/pricing/quotePromotionEvidence.ts +82 -0
- package/src/pricing/quoteSchemas.ts +230 -0
- package/src/pricing/subscriptionPricePolicy.ts +113 -0
- package/src/pricing/taxProfileGuard.ts +33 -0
- package/src/pricing/validation.ts +3 -0
- package/src/promotions/adminBindings.ts +53 -0
- package/src/promotions/adminPromotionCodesContracts.ts +244 -0
- package/src/promotions/adminPromotionCodesDataPort.ts +56 -0
- package/src/promotions/adminPromotionCodesHandler.ts +254 -0
- package/src/promotions/adminPromotionsContracts.ts +121 -0
- package/src/promotions/adminPromotionsDataPort.ts +48 -0
- package/src/promotions/adminPromotionsHandler.ts +72 -0
- package/src/promotions/contracts/index.ts +387 -0
- package/src/promotions/index.ts +22 -0
- package/src/promotions/promoDataPort.ts +65 -0
- package/src/promotions/promotionAcceptanceTelemetry.ts +73 -0
- package/src/promotions/promotionBenefitDefinition.ts +48 -0
- package/src/promotions/promotionClaimSweepPort.ts +23 -0
- package/src/promotions/promotionClaims.ts +70 -0
- package/src/promotions/promotionCodePreview.ts +183 -0
- package/src/promotions/promotionCodeQuotePort.ts +35 -0
- package/src/promotions/promotionQuoteAcceptance.ts +284 -0
- package/src/promotions/promotionQuoteBinding.ts +158 -0
- package/src/promotions/quoteAutomaticPromotions.ts +58 -0
- package/src/promotions/quotePromoDiscounts.ts +185 -0
- package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
- package/src/promotions/quotePromotionV2.ts +223 -0
- package/src/promotions/sql.ts +5 -0
- package/src/promotions/validation.ts +3 -0
- package/src/shipping/addressCanonLookupHandler.ts +114 -0
- package/src/shipping/addressCanonical.ts +31 -0
- package/src/shipping/addressPorts.ts +48 -0
- package/src/shipping/addressSchemas.ts +28 -0
- package/src/shipping/adminShippingRateHandler.ts +72 -0
- package/src/shipping/contracts/address.ts +71 -0
- package/src/shipping/contracts/delivery.ts +114 -0
- package/src/shipping/contracts/index.ts +18 -0
- package/src/shipping/contracts/shippingRate.ts +7 -0
- package/src/shipping/customerAddressesHandler.ts +100 -0
- package/src/shipping/customerSelfServiceAddressModels.ts +124 -0
- package/src/shipping/deliverySelectionHandler.ts +94 -0
- package/src/shipping/deliverySelectionPort.ts +10 -0
- package/src/shipping/deliverySelectionSummary.ts +56 -0
- package/src/shipping/handlerSchemas.ts +19 -0
- package/src/shipping/index.ts +23 -0
- package/src/shipping/pickupPointSearchPort.ts +8 -0
- package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
- package/src/shipping/shippingRatePorts.ts +36 -0
|
@@ -0,0 +1,1083 @@
|
|
|
1
|
+
-- Public automatic-subscription-renewal rail.
|
|
2
|
+
--
|
|
3
|
+
-- CHANGE. This forward adds the neutral, durable operation spine required to move one due
|
|
4
|
+
-- subscription through a fenced worker claim, stock admission and reservation, cycle/order/
|
|
5
|
+
-- settlement preparation, and a terminal acknowledgement. One immutable operation fingerprint
|
|
6
|
+
-- is copied onto every business relation the operation owns. The same `(subscription,
|
|
7
|
+
-- scheduled_at)` can therefore never become two cycles, orders, reservations or settlement
|
|
8
|
+
-- intents, even when leases expire or workers race.
|
|
9
|
+
--
|
|
10
|
+
-- WHY. The public catalogue already owns subscriptions, observed stock, orders and settlement
|
|
11
|
+
-- truth, but no runtime could compose them into an unattended renewal. In particular, observed
|
|
12
|
+
-- stock was explicitly not ATP, and the settlement rail deliberately had no reservation. This
|
|
13
|
+
-- forward supplies those missing semantics without recreating a managed-provider schema.
|
|
14
|
+
--
|
|
15
|
+
-- SAFETY AND DEPARTURES. The canonical operation key is
|
|
16
|
+
-- `subscription:<uuid>:cycle:<ISO instant>`. A SHA-256 fingerprint over the neutral immutable
|
|
17
|
+
-- snapshot is supplied by the application, shape-checked here, persisted unchanged and compared
|
|
18
|
+
-- on every replay. PostgreSQL does not pretend it contacted a payment provider: a successful
|
|
19
|
+
-- terminal acknowledgement requires a non-empty opaque external reference and delegates money
|
|
20
|
+
-- truth and the single subscription-schedule advance to `commerce_record_settlement`; this
|
|
21
|
+
-- forward never advances `next_cycle_at` a second time. An indeterminate provider call remains
|
|
22
|
+
-- `attempt_prepared`; it does not release stock, open dunning or authorize another attempt.
|
|
23
|
+
-- Inventory refusal and pre-payment expiry are durable neutral refusals and never customer
|
|
24
|
+
-- dunning. There are no provider enums, gateway payloads, webhook tables, retry ladders, roles,
|
|
25
|
+
-- RLS policies, grants or SECURITY DEFINER routines below. All functions are invoker-rights with
|
|
26
|
+
-- fixed search paths; the host application remains the security boundary.
|
|
27
|
+
--
|
|
28
|
+
-- ROLLBACK. Disable the direct renewal adopter first. The new relations/functions and nullable
|
|
29
|
+
-- linkage columns are capability-local. Existing lifecycle, stock and settlement rows remain
|
|
30
|
+
-- valid after those links are removed; no historical row is rewritten by this migration.
|
|
31
|
+
|
|
32
|
+
CREATE TABLE public.subscription_renewal_terms (
|
|
33
|
+
subscription_id uuid PRIMARY KEY REFERENCES public.subscriptions(id) ON DELETE CASCADE,
|
|
34
|
+
next_cycle_number integer NOT NULL DEFAULT 1,
|
|
35
|
+
currency_code text NOT NULL,
|
|
36
|
+
stock_source_key text NOT NULL,
|
|
37
|
+
settlement_channel_key text NOT NULL,
|
|
38
|
+
unattended_charge_authorized_at timestamptz NOT NULL,
|
|
39
|
+
authorization_ref text NOT NULL,
|
|
40
|
+
payer_account_ref text,
|
|
41
|
+
delivery_admission text NOT NULL DEFAULT 'allowed',
|
|
42
|
+
delivery_block_reason text,
|
|
43
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
44
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
45
|
+
CONSTRAINT subscription_renewal_terms_cycle_number_check CHECK (next_cycle_number > 0),
|
|
46
|
+
CONSTRAINT subscription_renewal_terms_currency_check CHECK (currency_code ~ '^[A-Z]{3}$'),
|
|
47
|
+
CONSTRAINT subscription_renewal_terms_stock_source_check
|
|
48
|
+
CHECK (stock_source_key ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),
|
|
49
|
+
CONSTRAINT subscription_renewal_terms_channel_check
|
|
50
|
+
CHECK (btrim(settlement_channel_key) <> '' AND char_length(settlement_channel_key) <= 64),
|
|
51
|
+
CONSTRAINT subscription_renewal_terms_authorization_ref_check
|
|
52
|
+
CHECK (btrim(authorization_ref) <> '' AND char_length(authorization_ref) <= 180),
|
|
53
|
+
CONSTRAINT subscription_renewal_terms_payer_account_ref_check
|
|
54
|
+
CHECK (payer_account_ref IS NULL OR (
|
|
55
|
+
btrim(payer_account_ref) <> '' AND char_length(payer_account_ref) <= 180)),
|
|
56
|
+
CONSTRAINT subscription_renewal_terms_delivery_admission_check
|
|
57
|
+
CHECK (delivery_admission IN ('allowed', 'blocked')),
|
|
58
|
+
CONSTRAINT subscription_renewal_terms_delivery_reason_check CHECK (
|
|
59
|
+
(delivery_admission = 'allowed' AND delivery_block_reason IS NULL)
|
|
60
|
+
OR (delivery_admission = 'blocked' AND btrim(COALESCE(delivery_block_reason, '')) <> '')
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
COMMENT ON COLUMN public.subscription_renewal_terms.authorization_ref IS
|
|
65
|
+
'Opaque host evidence that unattended charging was authorized; stored and compared, never parsed.';
|
|
66
|
+
COMMENT ON COLUMN public.subscription_renewal_terms.settlement_channel_key IS
|
|
67
|
+
'Opaque settlement-channel selection. This rail contains no provider vocabulary.';
|
|
68
|
+
|
|
69
|
+
CREATE TABLE public.subscription_renewal_term_lines (
|
|
70
|
+
subscription_id uuid NOT NULL REFERENCES public.subscription_renewal_terms(subscription_id)
|
|
71
|
+
ON DELETE CASCADE,
|
|
72
|
+
line_ordinal integer NOT NULL,
|
|
73
|
+
sku text NOT NULL,
|
|
74
|
+
quantity integer NOT NULL,
|
|
75
|
+
unit_amount_minor bigint NOT NULL,
|
|
76
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
77
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
78
|
+
PRIMARY KEY (subscription_id, line_ordinal),
|
|
79
|
+
CONSTRAINT subscription_renewal_term_lines_sku_key UNIQUE (subscription_id, sku),
|
|
80
|
+
CONSTRAINT subscription_renewal_term_lines_ordinal_check CHECK (line_ordinal > 0),
|
|
81
|
+
CONSTRAINT subscription_renewal_term_lines_sku_check CHECK (btrim(sku) <> ''),
|
|
82
|
+
CONSTRAINT subscription_renewal_term_lines_quantity_check CHECK (quantity > 0),
|
|
83
|
+
CONSTRAINT subscription_renewal_term_lines_amount_check CHECK (unit_amount_minor >= 0)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
CREATE TABLE public.subscription_renewal_operations (
|
|
87
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
88
|
+
subscription_id uuid NOT NULL REFERENCES public.subscriptions(id) ON DELETE RESTRICT,
|
|
89
|
+
scheduled_at timestamptz NOT NULL,
|
|
90
|
+
cycle_number integer NOT NULL,
|
|
91
|
+
identity_key text NOT NULL,
|
|
92
|
+
operation_fingerprint text NOT NULL,
|
|
93
|
+
cycle_snapshot jsonb NOT NULL,
|
|
94
|
+
state text NOT NULL DEFAULT 'claimed',
|
|
95
|
+
claim_token uuid NOT NULL,
|
|
96
|
+
claim_generation bigint NOT NULL DEFAULT 1,
|
|
97
|
+
claimed_by text NOT NULL,
|
|
98
|
+
claimed_at timestamptz NOT NULL,
|
|
99
|
+
lease_expires_at timestamptz NOT NULL,
|
|
100
|
+
attempt_prepared_at timestamptz,
|
|
101
|
+
external_attempt_ref text,
|
|
102
|
+
external_attempt_acknowledged_at timestamptz,
|
|
103
|
+
terminal_outcome text,
|
|
104
|
+
terminal_reason text,
|
|
105
|
+
terminal_at timestamptz,
|
|
106
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
107
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
108
|
+
CONSTRAINT subscription_renewal_operations_identity_key_key UNIQUE (identity_key),
|
|
109
|
+
CONSTRAINT subscription_renewal_operations_cycle_key UNIQUE (subscription_id, scheduled_at),
|
|
110
|
+
CONSTRAINT subscription_renewal_operations_cycle_number_check CHECK (cycle_number > 0),
|
|
111
|
+
CONSTRAINT subscription_renewal_operations_identity_check
|
|
112
|
+
CHECK (char_length(btrim(identity_key)) BETWEEN 48 AND 180),
|
|
113
|
+
CONSTRAINT subscription_renewal_operations_fingerprint_check
|
|
114
|
+
CHECK (operation_fingerprint ~ '^[0-9a-f]{64}$'),
|
|
115
|
+
CONSTRAINT subscription_renewal_operations_snapshot_check
|
|
116
|
+
CHECK (jsonb_typeof(cycle_snapshot) = 'object'),
|
|
117
|
+
CONSTRAINT subscription_renewal_operations_state_check
|
|
118
|
+
CHECK (state IN ('claimed', 'reserved', 'attempt_prepared', 'succeeded', 'refused')),
|
|
119
|
+
CONSTRAINT subscription_renewal_operations_claim_generation_check CHECK (claim_generation > 0),
|
|
120
|
+
CONSTRAINT subscription_renewal_operations_claimed_by_check CHECK (btrim(claimed_by) <> ''),
|
|
121
|
+
CONSTRAINT subscription_renewal_operations_lease_check CHECK (lease_expires_at > claimed_at),
|
|
122
|
+
CONSTRAINT subscription_renewal_operations_external_attempt_check CHECK (
|
|
123
|
+
(external_attempt_ref IS NULL AND external_attempt_acknowledged_at IS NULL)
|
|
124
|
+
OR (btrim(COALESCE(external_attempt_ref, '')) <> ''
|
|
125
|
+
AND char_length(external_attempt_ref) <= 240
|
|
126
|
+
AND external_attempt_acknowledged_at IS NOT NULL)
|
|
127
|
+
),
|
|
128
|
+
CONSTRAINT subscription_renewal_operations_terminal_check CHECK (
|
|
129
|
+
(state IN ('succeeded', 'refused')
|
|
130
|
+
AND terminal_outcome = CASE state WHEN 'succeeded' THEN 'succeeded' ELSE 'refused' END
|
|
131
|
+
AND terminal_at IS NOT NULL)
|
|
132
|
+
OR (state NOT IN ('succeeded', 'refused')
|
|
133
|
+
AND terminal_outcome IS NULL AND terminal_reason IS NULL AND terminal_at IS NULL)
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
-- squawk-ignore require-concurrent-index-creation
|
|
138
|
+
CREATE INDEX subscription_renewal_operations_due_recovery_idx
|
|
139
|
+
ON public.subscription_renewal_operations (state, lease_expires_at, scheduled_at);
|
|
140
|
+
|
|
141
|
+
CREATE TABLE public.subscription_renewal_outcomes (
|
|
142
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
143
|
+
renewal_operation_id uuid NOT NULL REFERENCES public.subscription_renewal_operations(id)
|
|
144
|
+
ON DELETE CASCADE,
|
|
145
|
+
operation_fingerprint text NOT NULL,
|
|
146
|
+
event_key text NOT NULL,
|
|
147
|
+
outcome text NOT NULL,
|
|
148
|
+
reason text,
|
|
149
|
+
external_ref text,
|
|
150
|
+
occurred_at timestamptz NOT NULL,
|
|
151
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
152
|
+
CONSTRAINT subscription_renewal_outcomes_event_key_key UNIQUE (renewal_operation_id, event_key),
|
|
153
|
+
CONSTRAINT subscription_renewal_outcomes_fingerprint_check
|
|
154
|
+
CHECK (operation_fingerprint ~ '^[0-9a-f]{64}$'),
|
|
155
|
+
CONSTRAINT subscription_renewal_outcomes_event_key_check CHECK (btrim(event_key) <> ''),
|
|
156
|
+
CONSTRAINT subscription_renewal_outcomes_outcome_check CHECK (outcome IN ('succeeded', 'refused')),
|
|
157
|
+
CONSTRAINT subscription_renewal_outcomes_external_ref_check CHECK (
|
|
158
|
+
external_ref IS NULL OR (btrim(external_ref) <> '' AND char_length(external_ref) <= 240)
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
CREATE TABLE public.fulfillment_inventory_reservations (
|
|
163
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
164
|
+
renewal_operation_id uuid NOT NULL REFERENCES public.subscription_renewal_operations(id)
|
|
165
|
+
ON DELETE RESTRICT,
|
|
166
|
+
operation_fingerprint text NOT NULL,
|
|
167
|
+
source_key text NOT NULL,
|
|
168
|
+
status text NOT NULL DEFAULT 'held',
|
|
169
|
+
expires_at timestamptz,
|
|
170
|
+
terminal_reason text,
|
|
171
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
172
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
173
|
+
CONSTRAINT fulfillment_inventory_reservations_operation_key UNIQUE (renewal_operation_id),
|
|
174
|
+
CONSTRAINT fulfillment_inventory_reservations_fingerprint_check
|
|
175
|
+
CHECK (operation_fingerprint ~ '^[0-9a-f]{64}$'),
|
|
176
|
+
CONSTRAINT fulfillment_inventory_reservations_source_check
|
|
177
|
+
CHECK (source_key ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$'),
|
|
178
|
+
CONSTRAINT fulfillment_inventory_reservations_status_check
|
|
179
|
+
CHECK (status IN ('held', 'committed', 'consumed', 'released', 'expired')),
|
|
180
|
+
CONSTRAINT fulfillment_inventory_reservations_expiry_check CHECK (
|
|
181
|
+
(status = 'held' AND expires_at IS NOT NULL AND expires_at > created_at)
|
|
182
|
+
OR (status <> 'held')
|
|
183
|
+
),
|
|
184
|
+
CONSTRAINT fulfillment_inventory_reservations_terminal_reason_check CHECK (
|
|
185
|
+
(status IN ('released', 'expired') AND btrim(COALESCE(terminal_reason, '')) <> '')
|
|
186
|
+
OR (status NOT IN ('released', 'expired') AND terminal_reason IS NULL)
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
CREATE TABLE public.fulfillment_inventory_reservation_lines (
|
|
191
|
+
reservation_id uuid NOT NULL REFERENCES public.fulfillment_inventory_reservations(id)
|
|
192
|
+
ON DELETE CASCADE,
|
|
193
|
+
operation_fingerprint text NOT NULL,
|
|
194
|
+
sku text NOT NULL,
|
|
195
|
+
quantity integer NOT NULL,
|
|
196
|
+
PRIMARY KEY (reservation_id, sku),
|
|
197
|
+
CONSTRAINT fulfillment_inventory_reservation_lines_fingerprint_check
|
|
198
|
+
CHECK (operation_fingerprint ~ '^[0-9a-f]{64}$'),
|
|
199
|
+
CONSTRAINT fulfillment_inventory_reservation_lines_sku_check CHECK (btrim(sku) <> ''),
|
|
200
|
+
CONSTRAINT fulfillment_inventory_reservation_lines_quantity_check CHECK (quantity > 0)
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
-- Nullable links preserve every row created by earlier forwards. New renewal rows always populate
|
|
204
|
+
-- both columns, and the functions below compare the fingerprint to the root before mutating.
|
|
205
|
+
ALTER TABLE public.subscription_cycles
|
|
206
|
+
ADD COLUMN renewal_operation_id uuid,
|
|
207
|
+
ADD COLUMN renewal_operation_fingerprint text;
|
|
208
|
+
ALTER TABLE public.subscription_cycles
|
|
209
|
+
ADD CONSTRAINT subscription_cycles_renewal_operation_fkey
|
|
210
|
+
FOREIGN KEY (renewal_operation_id) REFERENCES public.subscription_renewal_operations(id)
|
|
211
|
+
ON DELETE RESTRICT NOT VALID,
|
|
212
|
+
ADD CONSTRAINT subscription_cycles_renewal_fingerprint_check CHECK (
|
|
213
|
+
(renewal_operation_id IS NULL AND renewal_operation_fingerprint IS NULL)
|
|
214
|
+
OR (renewal_operation_id IS NOT NULL
|
|
215
|
+
AND renewal_operation_fingerprint ~ '^[0-9a-f]{64}$')
|
|
216
|
+
) NOT VALID;
|
|
217
|
+
|
|
218
|
+
-- openlup:allow-unique-index: this partial index covers only renewal-owned rows. The column is
|
|
219
|
+
-- introduced immediately above, so no pre-existing row can enter the index; non-concurrent form
|
|
220
|
+
-- keeps the manifest forward transaction-safe while avoiding an index entry for every legacy row.
|
|
221
|
+
-- squawk-ignore require-concurrent-index-creation
|
|
222
|
+
CREATE UNIQUE INDEX subscription_cycles_renewal_operation_key
|
|
223
|
+
ON public.subscription_cycles (renewal_operation_id)
|
|
224
|
+
WHERE renewal_operation_id IS NOT NULL;
|
|
225
|
+
|
|
226
|
+
ALTER TABLE public.commerce_orders
|
|
227
|
+
ADD COLUMN renewal_operation_id uuid,
|
|
228
|
+
ADD COLUMN renewal_operation_fingerprint text;
|
|
229
|
+
ALTER TABLE public.commerce_orders
|
|
230
|
+
ADD CONSTRAINT commerce_orders_renewal_operation_fkey
|
|
231
|
+
FOREIGN KEY (renewal_operation_id) REFERENCES public.subscription_renewal_operations(id)
|
|
232
|
+
ON DELETE RESTRICT NOT VALID,
|
|
233
|
+
ADD CONSTRAINT commerce_orders_renewal_fingerprint_check CHECK (
|
|
234
|
+
(renewal_operation_id IS NULL AND renewal_operation_fingerprint IS NULL)
|
|
235
|
+
OR (renewal_operation_id IS NOT NULL
|
|
236
|
+
AND renewal_operation_fingerprint ~ '^[0-9a-f]{64}$')
|
|
237
|
+
) NOT VALID;
|
|
238
|
+
|
|
239
|
+
-- openlup:allow-unique-index: the new nullable link has no pre-existing indexed rows; the partial
|
|
240
|
+
-- predicate preserves exact-one renewal ownership without indexing legacy orders.
|
|
241
|
+
-- squawk-ignore require-concurrent-index-creation
|
|
242
|
+
CREATE UNIQUE INDEX commerce_orders_renewal_operation_key
|
|
243
|
+
ON public.commerce_orders (renewal_operation_id)
|
|
244
|
+
WHERE renewal_operation_id IS NOT NULL;
|
|
245
|
+
|
|
246
|
+
ALTER TABLE public.commerce_settlement_intents
|
|
247
|
+
ADD COLUMN renewal_operation_id uuid,
|
|
248
|
+
ADD COLUMN renewal_operation_fingerprint text;
|
|
249
|
+
ALTER TABLE public.commerce_settlement_intents
|
|
250
|
+
ADD CONSTRAINT commerce_settlement_intents_renewal_operation_fkey
|
|
251
|
+
FOREIGN KEY (renewal_operation_id) REFERENCES public.subscription_renewal_operations(id)
|
|
252
|
+
ON DELETE RESTRICT NOT VALID,
|
|
253
|
+
ADD CONSTRAINT commerce_settlement_intents_renewal_fingerprint_check CHECK (
|
|
254
|
+
(renewal_operation_id IS NULL AND renewal_operation_fingerprint IS NULL)
|
|
255
|
+
OR (renewal_operation_id IS NOT NULL
|
|
256
|
+
AND renewal_operation_fingerprint ~ '^[0-9a-f]{64}$')
|
|
257
|
+
) NOT VALID;
|
|
258
|
+
|
|
259
|
+
-- openlup:allow-unique-index: the new nullable link has no pre-existing indexed rows; the partial
|
|
260
|
+
-- predicate preserves exact-one renewal ownership without indexing legacy settlement intents.
|
|
261
|
+
-- squawk-ignore require-concurrent-index-creation
|
|
262
|
+
CREATE UNIQUE INDEX commerce_settlement_intents_renewal_operation_key
|
|
263
|
+
ON public.commerce_settlement_intents (renewal_operation_id)
|
|
264
|
+
WHERE renewal_operation_id IS NOT NULL;
|
|
265
|
+
|
|
266
|
+
CREATE FUNCTION public.subscription_renewal_identity(
|
|
267
|
+
p_subscription_id uuid,
|
|
268
|
+
p_scheduled_at timestamptz
|
|
269
|
+
)
|
|
270
|
+
RETURNS text
|
|
271
|
+
LANGUAGE sql
|
|
272
|
+
IMMUTABLE
|
|
273
|
+
SET search_path = pg_catalog
|
|
274
|
+
AS $$
|
|
275
|
+
SELECT 'subscription:' || p_subscription_id::text || ':cycle:'
|
|
276
|
+
|| to_char(p_scheduled_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"');
|
|
277
|
+
$$;
|
|
278
|
+
|
|
279
|
+
CREATE FUNCTION public.subscription_renewal_operation_response(
|
|
280
|
+
p_operation public.subscription_renewal_operations,
|
|
281
|
+
p_acquired boolean,
|
|
282
|
+
p_replayed boolean,
|
|
283
|
+
p_reason text
|
|
284
|
+
)
|
|
285
|
+
RETURNS jsonb
|
|
286
|
+
LANGUAGE sql
|
|
287
|
+
STABLE
|
|
288
|
+
SET search_path = pg_catalog
|
|
289
|
+
AS $$
|
|
290
|
+
SELECT jsonb_build_object(
|
|
291
|
+
'contractVersion', 'platform.subscription.renewal.v1',
|
|
292
|
+
'acquired', p_acquired,
|
|
293
|
+
'replayed', p_replayed,
|
|
294
|
+
'reason', p_reason,
|
|
295
|
+
'operationId', p_operation.id,
|
|
296
|
+
'identityKey', p_operation.identity_key,
|
|
297
|
+
'operationFingerprint', p_operation.operation_fingerprint,
|
|
298
|
+
'subscriptionId', p_operation.subscription_id,
|
|
299
|
+
'scheduledAt', p_operation.scheduled_at,
|
|
300
|
+
'cycleNumber', p_operation.cycle_number,
|
|
301
|
+
'state', p_operation.state,
|
|
302
|
+
'claimToken', p_operation.claim_token,
|
|
303
|
+
'claimGeneration', p_operation.claim_generation,
|
|
304
|
+
'leaseExpiresAt', p_operation.lease_expires_at,
|
|
305
|
+
'cycleSnapshot', p_operation.cycle_snapshot,
|
|
306
|
+
'externalAttemptRef', p_operation.external_attempt_ref,
|
|
307
|
+
'externalAttemptAcknowledgedAt', p_operation.external_attempt_acknowledged_at,
|
|
308
|
+
'terminalOutcome', p_operation.terminal_outcome,
|
|
309
|
+
'terminalReason', p_operation.terminal_reason,
|
|
310
|
+
'terminalAt', p_operation.terminal_at,
|
|
311
|
+
'reservationId', (
|
|
312
|
+
SELECT reservation.id FROM public.fulfillment_inventory_reservations reservation
|
|
313
|
+
WHERE reservation.renewal_operation_id = p_operation.id),
|
|
314
|
+
'cycleId', (
|
|
315
|
+
SELECT cycle.id FROM public.subscription_cycles cycle
|
|
316
|
+
WHERE cycle.renewal_operation_id = p_operation.id),
|
|
317
|
+
'orderId', (
|
|
318
|
+
SELECT order_row.id FROM public.commerce_orders order_row
|
|
319
|
+
WHERE order_row.renewal_operation_id = p_operation.id),
|
|
320
|
+
'settlementIntentId', (
|
|
321
|
+
SELECT intent.id FROM public.commerce_settlement_intents intent
|
|
322
|
+
WHERE intent.renewal_operation_id = p_operation.id));
|
|
323
|
+
$$;
|
|
324
|
+
|
|
325
|
+
CREATE FUNCTION public.subscription_list_due_renewals(
|
|
326
|
+
p_as_of timestamptz DEFAULT now(),
|
|
327
|
+
p_limit integer DEFAULT 50
|
|
328
|
+
)
|
|
329
|
+
RETURNS TABLE (
|
|
330
|
+
subscription_id uuid,
|
|
331
|
+
client_id uuid,
|
|
332
|
+
scheduled_at timestamptz,
|
|
333
|
+
cadence_days integer,
|
|
334
|
+
cycle_number integer,
|
|
335
|
+
currency_code text,
|
|
336
|
+
stock_source_key text,
|
|
337
|
+
settlement_channel_key text,
|
|
338
|
+
authorization_ref text,
|
|
339
|
+
payer_account_ref text,
|
|
340
|
+
unattended_charge_authorized_at timestamptz,
|
|
341
|
+
delivery_admission text,
|
|
342
|
+
delivery_block_reason text
|
|
343
|
+
)
|
|
344
|
+
LANGUAGE sql
|
|
345
|
+
STABLE
|
|
346
|
+
SET search_path = pg_catalog
|
|
347
|
+
AS $$
|
|
348
|
+
SELECT subscription.id, subscription.client_id, subscription.next_cycle_at,
|
|
349
|
+
subscription.cadence_days, terms.next_cycle_number, terms.currency_code,
|
|
350
|
+
terms.stock_source_key, terms.settlement_channel_key, terms.authorization_ref,
|
|
351
|
+
terms.payer_account_ref,
|
|
352
|
+
terms.unattended_charge_authorized_at, terms.delivery_admission,
|
|
353
|
+
terms.delivery_block_reason
|
|
354
|
+
FROM public.subscriptions subscription
|
|
355
|
+
JOIN public.subscription_renewal_terms terms ON terms.subscription_id = subscription.id
|
|
356
|
+
WHERE subscription.status = 'active'
|
|
357
|
+
AND subscription.next_cycle_at IS NOT NULL
|
|
358
|
+
AND subscription.next_cycle_at <= COALESCE(p_as_of, now())
|
|
359
|
+
AND NOT EXISTS (
|
|
360
|
+
SELECT 1 FROM public.subscription_renewal_operations operation
|
|
361
|
+
WHERE operation.subscription_id = subscription.id
|
|
362
|
+
AND operation.scheduled_at = subscription.next_cycle_at
|
|
363
|
+
AND (
|
|
364
|
+
operation.state IN ('succeeded', 'refused')
|
|
365
|
+
OR operation.lease_expires_at > COALESCE(p_as_of, now())
|
|
366
|
+
))
|
|
367
|
+
ORDER BY subscription.next_cycle_at, subscription.id
|
|
368
|
+
LIMIT LEAST(GREATEST(COALESCE(p_limit, 50), 1), 500);
|
|
369
|
+
$$;
|
|
370
|
+
|
|
371
|
+
CREATE FUNCTION public.subscription_claim_due_renewal(
|
|
372
|
+
p_identity_key text,
|
|
373
|
+
p_operation_fingerprint text,
|
|
374
|
+
p_cycle_snapshot jsonb,
|
|
375
|
+
p_worker_id text,
|
|
376
|
+
p_lease_seconds integer DEFAULT 300,
|
|
377
|
+
p_now timestamptz DEFAULT now()
|
|
378
|
+
)
|
|
379
|
+
RETURNS jsonb
|
|
380
|
+
LANGUAGE plpgsql
|
|
381
|
+
SET search_path = pg_catalog
|
|
382
|
+
AS $$
|
|
383
|
+
DECLARE
|
|
384
|
+
v_subscription_id uuid;
|
|
385
|
+
v_scheduled_at timestamptz;
|
|
386
|
+
v_cycle_number integer;
|
|
387
|
+
v_subscription public.subscriptions%ROWTYPE;
|
|
388
|
+
v_operation public.subscription_renewal_operations%ROWTYPE;
|
|
389
|
+
BEGIN
|
|
390
|
+
IF p_identity_key IS NULL OR p_operation_fingerprint !~ '^[0-9a-f]{64}$'
|
|
391
|
+
OR p_cycle_snapshot IS NULL OR jsonb_typeof(p_cycle_snapshot) <> 'object'
|
|
392
|
+
OR p_cycle_snapshot->>'contractVersion' <> 'platform.subscription.renewal.v1'
|
|
393
|
+
OR btrim(COALESCE(p_worker_id, '')) = ''
|
|
394
|
+
OR p_lease_seconds IS NULL OR p_lease_seconds NOT BETWEEN 30 AND 3600
|
|
395
|
+
THEN
|
|
396
|
+
RAISE EXCEPTION 'subscription_renewal_claim_invalid_input' USING ERRCODE = '22023';
|
|
397
|
+
END IF;
|
|
398
|
+
|
|
399
|
+
BEGIN
|
|
400
|
+
v_subscription_id := (p_cycle_snapshot->>'subscriptionId')::uuid;
|
|
401
|
+
v_scheduled_at := (p_cycle_snapshot->>'scheduledAt')::timestamptz;
|
|
402
|
+
v_cycle_number := (p_cycle_snapshot->>'cycleNumber')::integer;
|
|
403
|
+
EXCEPTION WHEN invalid_text_representation OR numeric_value_out_of_range THEN
|
|
404
|
+
RAISE EXCEPTION 'subscription_renewal_claim_invalid_snapshot' USING ERRCODE = '22023';
|
|
405
|
+
END;
|
|
406
|
+
|
|
407
|
+
IF v_cycle_number < 1
|
|
408
|
+
OR p_identity_key <> public.subscription_renewal_identity(v_subscription_id, v_scheduled_at)
|
|
409
|
+
OR jsonb_typeof(p_cycle_snapshot->'lines') <> 'array'
|
|
410
|
+
OR jsonb_array_length(p_cycle_snapshot->'lines') = 0
|
|
411
|
+
OR p_cycle_snapshot->>'currency' !~ '^[A-Z]{3}$'
|
|
412
|
+
OR (p_cycle_snapshot->>'totalAmountMinor')::bigint < 1
|
|
413
|
+
THEN
|
|
414
|
+
RAISE EXCEPTION 'subscription_renewal_claim_invalid_snapshot' USING ERRCODE = '22023';
|
|
415
|
+
END IF;
|
|
416
|
+
|
|
417
|
+
SELECT * INTO v_subscription
|
|
418
|
+
FROM public.subscriptions
|
|
419
|
+
WHERE id = v_subscription_id
|
|
420
|
+
FOR UPDATE;
|
|
421
|
+
IF NOT FOUND OR v_subscription.status <> 'active'
|
|
422
|
+
OR v_subscription.next_cycle_at IS DISTINCT FROM v_scheduled_at
|
|
423
|
+
OR v_subscription.next_cycle_at > COALESCE(p_now, now())
|
|
424
|
+
THEN
|
|
425
|
+
RAISE EXCEPTION 'subscription_renewal_claim_not_due' USING ERRCODE = '22023';
|
|
426
|
+
END IF;
|
|
427
|
+
|
|
428
|
+
SELECT * INTO v_operation
|
|
429
|
+
FROM public.subscription_renewal_operations
|
|
430
|
+
WHERE subscription_id = v_subscription_id AND scheduled_at = v_scheduled_at
|
|
431
|
+
FOR UPDATE;
|
|
432
|
+
|
|
433
|
+
IF FOUND THEN
|
|
434
|
+
IF v_operation.identity_key <> p_identity_key
|
|
435
|
+
OR v_operation.operation_fingerprint <> p_operation_fingerprint
|
|
436
|
+
OR v_operation.cycle_snapshot <> p_cycle_snapshot
|
|
437
|
+
THEN
|
|
438
|
+
RAISE EXCEPTION 'subscription_renewal_claim_idempotency_conflict' USING ERRCODE = '23505';
|
|
439
|
+
END IF;
|
|
440
|
+
IF v_operation.state IN ('succeeded', 'refused') THEN
|
|
441
|
+
RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'terminal');
|
|
442
|
+
END IF;
|
|
443
|
+
IF v_operation.lease_expires_at > COALESCE(p_now, now()) THEN
|
|
444
|
+
RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'lease_active');
|
|
445
|
+
END IF;
|
|
446
|
+
UPDATE public.subscription_renewal_operations
|
|
447
|
+
SET claim_token = gen_random_uuid(),
|
|
448
|
+
claim_generation = claim_generation + 1,
|
|
449
|
+
claimed_by = p_worker_id,
|
|
450
|
+
claimed_at = COALESCE(p_now, now()),
|
|
451
|
+
lease_expires_at = COALESCE(p_now, now()) + make_interval(secs => p_lease_seconds),
|
|
452
|
+
updated_at = COALESCE(p_now, now())
|
|
453
|
+
WHERE id = v_operation.id
|
|
454
|
+
RETURNING * INTO v_operation;
|
|
455
|
+
RETURN public.subscription_renewal_operation_response(v_operation, true, true, 'lease_taken_over');
|
|
456
|
+
END IF;
|
|
457
|
+
|
|
458
|
+
INSERT INTO public.subscription_renewal_operations (
|
|
459
|
+
subscription_id, scheduled_at, cycle_number, identity_key, operation_fingerprint,
|
|
460
|
+
cycle_snapshot, claim_token, claimed_by, claimed_at, lease_expires_at, updated_at
|
|
461
|
+
) VALUES (
|
|
462
|
+
v_subscription_id, v_scheduled_at, v_cycle_number, p_identity_key,
|
|
463
|
+
p_operation_fingerprint, p_cycle_snapshot, gen_random_uuid(), p_worker_id,
|
|
464
|
+
COALESCE(p_now, now()), COALESCE(p_now, now()) + make_interval(secs => p_lease_seconds),
|
|
465
|
+
COALESCE(p_now, now())
|
|
466
|
+
) RETURNING * INTO v_operation;
|
|
467
|
+
RETURN public.subscription_renewal_operation_response(v_operation, true, false, 'claimed');
|
|
468
|
+
END;
|
|
469
|
+
$$;
|
|
470
|
+
|
|
471
|
+
CREATE FUNCTION public.subscription_read_renewal(p_identity_key text)
|
|
472
|
+
RETURNS jsonb
|
|
473
|
+
LANGUAGE plpgsql
|
|
474
|
+
STABLE
|
|
475
|
+
SET search_path = pg_catalog
|
|
476
|
+
AS $$
|
|
477
|
+
DECLARE v_operation public.subscription_renewal_operations%ROWTYPE;
|
|
478
|
+
BEGIN
|
|
479
|
+
SELECT * INTO v_operation FROM public.subscription_renewal_operations
|
|
480
|
+
WHERE identity_key = p_identity_key;
|
|
481
|
+
IF NOT FOUND THEN RETURN NULL; END IF;
|
|
482
|
+
RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'readback');
|
|
483
|
+
END;
|
|
484
|
+
$$;
|
|
485
|
+
|
|
486
|
+
CREATE FUNCTION public.subscription_refuse_renewal_preflight(
|
|
487
|
+
p_operation_id uuid,
|
|
488
|
+
p_claim_token uuid,
|
|
489
|
+
p_reason text,
|
|
490
|
+
p_occurred_at timestamptz DEFAULT now()
|
|
491
|
+
)
|
|
492
|
+
RETURNS jsonb
|
|
493
|
+
LANGUAGE plpgsql
|
|
494
|
+
SET search_path = pg_catalog
|
|
495
|
+
AS $$
|
|
496
|
+
DECLARE v_operation public.subscription_renewal_operations%ROWTYPE;
|
|
497
|
+
BEGIN
|
|
498
|
+
IF p_operation_id IS NULL OR p_claim_token IS NULL
|
|
499
|
+
OR btrim(COALESCE(p_reason, '')) = '' OR char_length(p_reason) > 180
|
|
500
|
+
THEN
|
|
501
|
+
RAISE EXCEPTION 'subscription_renewal_preflight_refusal_invalid_input' USING ERRCODE = '22023';
|
|
502
|
+
END IF;
|
|
503
|
+
SELECT * INTO v_operation FROM public.subscription_renewal_operations
|
|
504
|
+
WHERE id = p_operation_id FOR UPDATE;
|
|
505
|
+
IF NOT FOUND THEN
|
|
506
|
+
RAISE EXCEPTION 'subscription_renewal_preflight_refusal_not_found' USING ERRCODE = '22023';
|
|
507
|
+
END IF;
|
|
508
|
+
IF v_operation.operation_fingerprint IS NULL THEN
|
|
509
|
+
RAISE EXCEPTION 'subscription_renewal_preflight_refusal_fingerprint_missing' USING ERRCODE = '23505';
|
|
510
|
+
END IF;
|
|
511
|
+
IF v_operation.state = 'refused' THEN
|
|
512
|
+
RETURN public.subscription_renewal_operation_response(
|
|
513
|
+
v_operation, false, true, 'preflight_refusal_replay');
|
|
514
|
+
END IF;
|
|
515
|
+
IF v_operation.claim_token <> p_claim_token
|
|
516
|
+
OR v_operation.lease_expires_at <= COALESCE(p_occurred_at, now())
|
|
517
|
+
OR v_operation.state NOT IN ('claimed', 'reserved')
|
|
518
|
+
THEN
|
|
519
|
+
RAISE EXCEPTION 'subscription_renewal_preflight_refusal_stale_claim' USING ERRCODE = '40001';
|
|
520
|
+
END IF;
|
|
521
|
+
UPDATE public.fulfillment_inventory_reservations
|
|
522
|
+
SET status = 'released', terminal_reason = p_reason,
|
|
523
|
+
expires_at = NULL, updated_at = COALESCE(p_occurred_at, now())
|
|
524
|
+
WHERE renewal_operation_id = p_operation_id AND status = 'held';
|
|
525
|
+
INSERT INTO public.subscription_renewal_outcomes (
|
|
526
|
+
renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
|
|
527
|
+
) VALUES (
|
|
528
|
+
v_operation.id, v_operation.operation_fingerprint,
|
|
529
|
+
v_operation.identity_key || ':preflight-refusal', 'refused', p_reason,
|
|
530
|
+
COALESCE(p_occurred_at, now()));
|
|
531
|
+
UPDATE public.subscription_renewal_operations
|
|
532
|
+
SET state = 'refused', terminal_outcome = 'refused', terminal_reason = p_reason,
|
|
533
|
+
terminal_at = COALESCE(p_occurred_at, now()), updated_at = COALESCE(p_occurred_at, now())
|
|
534
|
+
WHERE id = p_operation_id RETURNING * INTO v_operation;
|
|
535
|
+
RETURN public.subscription_renewal_operation_response(
|
|
536
|
+
v_operation, false, false, 'preflight_refused');
|
|
537
|
+
END;
|
|
538
|
+
$$;
|
|
539
|
+
|
|
540
|
+
CREATE FUNCTION public.fulfillment_reserve_subscription_renewal(
|
|
541
|
+
p_operation_id uuid,
|
|
542
|
+
p_claim_token uuid,
|
|
543
|
+
p_hold_seconds integer DEFAULT 900,
|
|
544
|
+
p_now timestamptz DEFAULT now()
|
|
545
|
+
)
|
|
546
|
+
RETURNS jsonb
|
|
547
|
+
LANGUAGE plpgsql
|
|
548
|
+
SET search_path = pg_catalog
|
|
549
|
+
AS $$
|
|
550
|
+
DECLARE
|
|
551
|
+
v_operation public.subscription_renewal_operations%ROWTYPE;
|
|
552
|
+
v_reservation public.fulfillment_inventory_reservations%ROWTYPE;
|
|
553
|
+
v_line jsonb;
|
|
554
|
+
v_source text;
|
|
555
|
+
v_sku text;
|
|
556
|
+
v_quantity integer;
|
|
557
|
+
v_available integer;
|
|
558
|
+
v_reserved integer;
|
|
559
|
+
BEGIN
|
|
560
|
+
IF p_operation_id IS NULL OR p_claim_token IS NULL
|
|
561
|
+
OR p_hold_seconds IS NULL OR p_hold_seconds NOT BETWEEN 60 AND 86400
|
|
562
|
+
THEN
|
|
563
|
+
RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_input' USING ERRCODE = '22023';
|
|
564
|
+
END IF;
|
|
565
|
+
|
|
566
|
+
SELECT * INTO v_operation FROM public.subscription_renewal_operations
|
|
567
|
+
WHERE id = p_operation_id FOR UPDATE;
|
|
568
|
+
IF NOT FOUND THEN
|
|
569
|
+
RAISE EXCEPTION 'fulfillment_renewal_reservation_operation_not_found' USING ERRCODE = '22023';
|
|
570
|
+
END IF;
|
|
571
|
+
IF v_operation.claim_token <> p_claim_token
|
|
572
|
+
OR v_operation.lease_expires_at <= COALESCE(p_now, now())
|
|
573
|
+
THEN
|
|
574
|
+
RAISE EXCEPTION 'fulfillment_renewal_reservation_stale_claim' USING ERRCODE = '40001';
|
|
575
|
+
END IF;
|
|
576
|
+
|
|
577
|
+
SELECT * INTO v_reservation FROM public.fulfillment_inventory_reservations
|
|
578
|
+
WHERE renewal_operation_id = p_operation_id FOR UPDATE;
|
|
579
|
+
IF FOUND THEN
|
|
580
|
+
IF v_reservation.operation_fingerprint <> v_operation.operation_fingerprint THEN
|
|
581
|
+
RAISE EXCEPTION 'fulfillment_renewal_reservation_fingerprint_conflict' USING ERRCODE = '23505';
|
|
582
|
+
END IF;
|
|
583
|
+
IF v_reservation.status = 'held'
|
|
584
|
+
AND v_reservation.expires_at <= COALESCE(p_now, now())
|
|
585
|
+
AND v_operation.state = 'reserved'
|
|
586
|
+
THEN
|
|
587
|
+
UPDATE public.fulfillment_inventory_reservations
|
|
588
|
+
SET status = 'expired', expires_at = NULL,
|
|
589
|
+
terminal_reason = 'reservation_expired', updated_at = COALESCE(p_now, now())
|
|
590
|
+
WHERE id = v_reservation.id
|
|
591
|
+
RETURNING * INTO v_reservation;
|
|
592
|
+
UPDATE public.subscription_renewal_operations
|
|
593
|
+
SET state = 'refused', terminal_outcome = 'refused',
|
|
594
|
+
terminal_reason = 'reservation_expired', terminal_at = COALESCE(p_now, now()),
|
|
595
|
+
updated_at = COALESCE(p_now, now())
|
|
596
|
+
WHERE id = p_operation_id RETURNING * INTO v_operation;
|
|
597
|
+
INSERT INTO public.subscription_renewal_outcomes (
|
|
598
|
+
renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
|
|
599
|
+
) VALUES (
|
|
600
|
+
p_operation_id, v_operation.operation_fingerprint,
|
|
601
|
+
v_operation.identity_key || ':reservation-expired', 'refused',
|
|
602
|
+
'reservation_expired', COALESCE(p_now, now()));
|
|
603
|
+
RETURN jsonb_build_object(
|
|
604
|
+
'reserved', false, 'replayed', true, 'reservationId', v_reservation.id,
|
|
605
|
+
'status', 'expired', 'reason', 'reservation_expired');
|
|
606
|
+
END IF;
|
|
607
|
+
RETURN jsonb_build_object(
|
|
608
|
+
'reserved', v_reservation.status IN ('held', 'committed', 'consumed'),
|
|
609
|
+
'replayed', true, 'reservationId', v_reservation.id,
|
|
610
|
+
'status', v_reservation.status, 'reason', v_reservation.terminal_reason);
|
|
611
|
+
END IF;
|
|
612
|
+
|
|
613
|
+
IF v_operation.state <> 'claimed' THEN
|
|
614
|
+
RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_state' USING ERRCODE = '22023';
|
|
615
|
+
END IF;
|
|
616
|
+
v_source := v_operation.cycle_snapshot->>'stockSourceKey';
|
|
617
|
+
IF v_source IS NULL OR v_source !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$' THEN
|
|
618
|
+
RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_snapshot' USING ERRCODE = '22023';
|
|
619
|
+
END IF;
|
|
620
|
+
|
|
621
|
+
-- Lock all stock rows in a deterministic order before computing ATP, so two different
|
|
622
|
+
-- operations containing the same SKUs cannot both spend the same observed availability.
|
|
623
|
+
PERFORM stock.sku
|
|
624
|
+
FROM public.fulfillment_stock_current stock
|
|
625
|
+
JOIN jsonb_array_elements(v_operation.cycle_snapshot->'lines') line
|
|
626
|
+
ON stock.source_key = v_source AND stock.sku = line->>'sku'
|
|
627
|
+
ORDER BY stock.sku
|
|
628
|
+
FOR UPDATE OF stock;
|
|
629
|
+
|
|
630
|
+
FOR v_line IN SELECT value FROM jsonb_array_elements(v_operation.cycle_snapshot->'lines')
|
|
631
|
+
LOOP
|
|
632
|
+
v_sku := v_line->>'sku';
|
|
633
|
+
BEGIN v_quantity := (v_line->>'quantity')::integer;
|
|
634
|
+
EXCEPTION WHEN invalid_text_representation OR numeric_value_out_of_range THEN
|
|
635
|
+
RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_snapshot' USING ERRCODE = '22023';
|
|
636
|
+
END;
|
|
637
|
+
IF btrim(COALESCE(v_sku, '')) = '' OR v_quantity < 1 THEN
|
|
638
|
+
RAISE EXCEPTION 'fulfillment_renewal_reservation_invalid_snapshot' USING ERRCODE = '22023';
|
|
639
|
+
END IF;
|
|
640
|
+
|
|
641
|
+
SELECT stock.for_sale_quantity INTO v_available
|
|
642
|
+
FROM public.fulfillment_stock_current stock
|
|
643
|
+
WHERE stock.source_key = v_source AND stock.sku = v_sku
|
|
644
|
+
AND stock.inventory_class = 'sellable'
|
|
645
|
+
AND stock.stale_after > COALESCE(p_now, now());
|
|
646
|
+
IF NOT FOUND THEN v_available := 0; END IF;
|
|
647
|
+
|
|
648
|
+
SELECT COALESCE(sum(reservation_line.quantity), 0)::integer INTO v_reserved
|
|
649
|
+
FROM public.fulfillment_inventory_reservation_lines reservation_line
|
|
650
|
+
JOIN public.fulfillment_inventory_reservations reservation
|
|
651
|
+
ON reservation.id = reservation_line.reservation_id
|
|
652
|
+
WHERE reservation.source_key = v_source AND reservation_line.sku = v_sku
|
|
653
|
+
AND reservation.status IN ('held', 'committed')
|
|
654
|
+
AND (reservation.status = 'committed' OR reservation.expires_at > COALESCE(p_now, now()));
|
|
655
|
+
IF v_available - v_reserved < v_quantity THEN
|
|
656
|
+
UPDATE public.subscription_renewal_operations
|
|
657
|
+
SET state = 'refused', terminal_outcome = 'refused',
|
|
658
|
+
terminal_reason = 'inventory_unavailable', terminal_at = COALESCE(p_now, now()),
|
|
659
|
+
updated_at = COALESCE(p_now, now())
|
|
660
|
+
WHERE id = p_operation_id RETURNING * INTO v_operation;
|
|
661
|
+
INSERT INTO public.subscription_renewal_outcomes (
|
|
662
|
+
renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
|
|
663
|
+
) VALUES (
|
|
664
|
+
p_operation_id, v_operation.operation_fingerprint,
|
|
665
|
+
v_operation.identity_key || ':inventory', 'refused', 'inventory_unavailable',
|
|
666
|
+
COALESCE(p_now, now()));
|
|
667
|
+
RETURN jsonb_build_object(
|
|
668
|
+
'reserved', false, 'replayed', false, 'reservationId', NULL,
|
|
669
|
+
'status', 'refused', 'reason', 'inventory_unavailable');
|
|
670
|
+
END IF;
|
|
671
|
+
END LOOP;
|
|
672
|
+
|
|
673
|
+
INSERT INTO public.fulfillment_inventory_reservations (
|
|
674
|
+
renewal_operation_id, operation_fingerprint, source_key, expires_at, created_at, updated_at
|
|
675
|
+
) VALUES (
|
|
676
|
+
p_operation_id, v_operation.operation_fingerprint, v_source,
|
|
677
|
+
COALESCE(p_now, now()) + make_interval(secs => p_hold_seconds),
|
|
678
|
+
COALESCE(p_now, now()), COALESCE(p_now, now())
|
|
679
|
+
) RETURNING * INTO v_reservation;
|
|
680
|
+
|
|
681
|
+
FOR v_line IN SELECT value FROM jsonb_array_elements(v_operation.cycle_snapshot->'lines')
|
|
682
|
+
LOOP
|
|
683
|
+
INSERT INTO public.fulfillment_inventory_reservation_lines (
|
|
684
|
+
reservation_id, operation_fingerprint, sku, quantity
|
|
685
|
+
) VALUES (
|
|
686
|
+
v_reservation.id, v_operation.operation_fingerprint,
|
|
687
|
+
v_line->>'sku', (v_line->>'quantity')::integer);
|
|
688
|
+
END LOOP;
|
|
689
|
+
|
|
690
|
+
UPDATE public.subscription_renewal_operations
|
|
691
|
+
SET state = 'reserved', updated_at = COALESCE(p_now, now())
|
|
692
|
+
WHERE id = p_operation_id;
|
|
693
|
+
RETURN jsonb_build_object(
|
|
694
|
+
'reserved', true, 'replayed', false, 'reservationId', v_reservation.id,
|
|
695
|
+
'status', 'held', 'reason', NULL);
|
|
696
|
+
END;
|
|
697
|
+
$$;
|
|
698
|
+
|
|
699
|
+
CREATE FUNCTION public.fulfillment_release_subscription_renewal(
|
|
700
|
+
p_operation_id uuid,
|
|
701
|
+
p_claim_token uuid,
|
|
702
|
+
p_reason text,
|
|
703
|
+
p_now timestamptz DEFAULT now()
|
|
704
|
+
)
|
|
705
|
+
RETURNS jsonb
|
|
706
|
+
LANGUAGE plpgsql
|
|
707
|
+
SET search_path = pg_catalog
|
|
708
|
+
AS $$
|
|
709
|
+
DECLARE
|
|
710
|
+
v_operation public.subscription_renewal_operations%ROWTYPE;
|
|
711
|
+
v_reservation public.fulfillment_inventory_reservations%ROWTYPE;
|
|
712
|
+
BEGIN
|
|
713
|
+
IF btrim(COALESCE(p_reason, '')) = '' THEN
|
|
714
|
+
RAISE EXCEPTION 'fulfillment_renewal_release_reason_required' USING ERRCODE = '22023';
|
|
715
|
+
END IF;
|
|
716
|
+
SELECT * INTO v_operation FROM public.subscription_renewal_operations
|
|
717
|
+
WHERE id = p_operation_id FOR UPDATE;
|
|
718
|
+
IF NOT FOUND OR v_operation.claim_token <> p_claim_token
|
|
719
|
+
OR v_operation.lease_expires_at <= COALESCE(p_now, now())
|
|
720
|
+
THEN
|
|
721
|
+
RAISE EXCEPTION 'fulfillment_renewal_release_stale_claim' USING ERRCODE = '40001';
|
|
722
|
+
END IF;
|
|
723
|
+
IF v_operation.state = 'attempt_prepared' THEN
|
|
724
|
+
RAISE EXCEPTION 'fulfillment_renewal_release_attempt_indeterminate' USING ERRCODE = '22023';
|
|
725
|
+
END IF;
|
|
726
|
+
IF v_operation.state IN ('succeeded', 'refused') THEN
|
|
727
|
+
RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'terminal');
|
|
728
|
+
END IF;
|
|
729
|
+
|
|
730
|
+
SELECT * INTO v_reservation FROM public.fulfillment_inventory_reservations
|
|
731
|
+
WHERE renewal_operation_id = p_operation_id FOR UPDATE;
|
|
732
|
+
IF FOUND AND v_reservation.status = 'held' THEN
|
|
733
|
+
UPDATE public.fulfillment_inventory_reservations
|
|
734
|
+
SET status = 'released', expires_at = NULL, terminal_reason = p_reason,
|
|
735
|
+
updated_at = COALESCE(p_now, now())
|
|
736
|
+
WHERE id = v_reservation.id;
|
|
737
|
+
END IF;
|
|
738
|
+
UPDATE public.subscription_renewal_operations
|
|
739
|
+
SET state = 'refused', terminal_outcome = 'refused', terminal_reason = p_reason,
|
|
740
|
+
terminal_at = COALESCE(p_now, now()), updated_at = COALESCE(p_now, now())
|
|
741
|
+
WHERE id = p_operation_id RETURNING * INTO v_operation;
|
|
742
|
+
INSERT INTO public.subscription_renewal_outcomes (
|
|
743
|
+
renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
|
|
744
|
+
) VALUES (
|
|
745
|
+
p_operation_id, v_operation.operation_fingerprint,
|
|
746
|
+
v_operation.identity_key || ':release', 'refused', p_reason, COALESCE(p_now, now()));
|
|
747
|
+
RETURN public.subscription_renewal_operation_response(v_operation, false, false, 'released');
|
|
748
|
+
END;
|
|
749
|
+
$$;
|
|
750
|
+
|
|
751
|
+
CREATE FUNCTION public.fulfillment_expire_subscription_renewals(
|
|
752
|
+
p_now timestamptz DEFAULT now(),
|
|
753
|
+
p_limit integer DEFAULT 100
|
|
754
|
+
)
|
|
755
|
+
RETURNS integer
|
|
756
|
+
LANGUAGE plpgsql
|
|
757
|
+
SET search_path = pg_catalog
|
|
758
|
+
AS $$
|
|
759
|
+
DECLARE v_row record; v_count integer := 0;
|
|
760
|
+
BEGIN
|
|
761
|
+
FOR v_row IN
|
|
762
|
+
SELECT reservation.id, reservation.renewal_operation_id
|
|
763
|
+
FROM public.fulfillment_inventory_reservations reservation
|
|
764
|
+
JOIN public.subscription_renewal_operations operation
|
|
765
|
+
ON operation.id = reservation.renewal_operation_id
|
|
766
|
+
WHERE reservation.status = 'held'
|
|
767
|
+
AND reservation.expires_at <= COALESCE(p_now, now())
|
|
768
|
+
AND operation.state = 'reserved'
|
|
769
|
+
ORDER BY reservation.expires_at, reservation.id
|
|
770
|
+
FOR UPDATE OF reservation, operation SKIP LOCKED
|
|
771
|
+
LIMIT LEAST(GREATEST(COALESCE(p_limit, 100), 1), 1000)
|
|
772
|
+
LOOP
|
|
773
|
+
UPDATE public.fulfillment_inventory_reservations
|
|
774
|
+
SET status = 'expired', expires_at = NULL, terminal_reason = 'reservation_expired',
|
|
775
|
+
updated_at = COALESCE(p_now, now())
|
|
776
|
+
WHERE id = v_row.id;
|
|
777
|
+
UPDATE public.subscription_renewal_operations
|
|
778
|
+
SET state = 'refused', terminal_outcome = 'refused',
|
|
779
|
+
terminal_reason = 'reservation_expired', terminal_at = COALESCE(p_now, now()),
|
|
780
|
+
updated_at = COALESCE(p_now, now())
|
|
781
|
+
WHERE id = v_row.renewal_operation_id;
|
|
782
|
+
INSERT INTO public.subscription_renewal_outcomes (
|
|
783
|
+
renewal_operation_id, operation_fingerprint, event_key, outcome, reason, occurred_at
|
|
784
|
+
) SELECT operation.id, operation.operation_fingerprint,
|
|
785
|
+
operation.identity_key || ':reservation-expired', 'refused',
|
|
786
|
+
'reservation_expired', COALESCE(p_now, now())
|
|
787
|
+
FROM public.subscription_renewal_operations operation
|
|
788
|
+
WHERE operation.id = v_row.renewal_operation_id;
|
|
789
|
+
v_count := v_count + 1;
|
|
790
|
+
END LOOP;
|
|
791
|
+
RETURN v_count;
|
|
792
|
+
END;
|
|
793
|
+
$$;
|
|
794
|
+
|
|
795
|
+
CREATE FUNCTION public.commerce_prepare_subscription_renewal(
|
|
796
|
+
p_operation_id uuid,
|
|
797
|
+
p_claim_token uuid,
|
|
798
|
+
p_now timestamptz DEFAULT now()
|
|
799
|
+
)
|
|
800
|
+
RETURNS jsonb
|
|
801
|
+
LANGUAGE plpgsql
|
|
802
|
+
SET search_path = pg_catalog
|
|
803
|
+
AS $$
|
|
804
|
+
DECLARE
|
|
805
|
+
v_operation public.subscription_renewal_operations%ROWTYPE;
|
|
806
|
+
v_reservation public.fulfillment_inventory_reservations%ROWTYPE;
|
|
807
|
+
v_cycle_id uuid;
|
|
808
|
+
v_order_id uuid;
|
|
809
|
+
v_intent_id uuid;
|
|
810
|
+
v_line jsonb;
|
|
811
|
+
v_line_ordinal integer := 0;
|
|
812
|
+
v_sku_id uuid;
|
|
813
|
+
v_quantity integer;
|
|
814
|
+
v_unit_amount bigint;
|
|
815
|
+
v_total bigint;
|
|
816
|
+
v_currency text;
|
|
817
|
+
v_channel text;
|
|
818
|
+
BEGIN
|
|
819
|
+
SELECT * INTO v_operation FROM public.subscription_renewal_operations
|
|
820
|
+
WHERE id = p_operation_id FOR UPDATE;
|
|
821
|
+
IF NOT FOUND THEN
|
|
822
|
+
RAISE EXCEPTION 'commerce_renewal_operation_not_found' USING ERRCODE = '22023';
|
|
823
|
+
END IF;
|
|
824
|
+
IF v_operation.claim_token <> p_claim_token
|
|
825
|
+
OR v_operation.lease_expires_at <= COALESCE(p_now, now())
|
|
826
|
+
THEN
|
|
827
|
+
RAISE EXCEPTION 'commerce_renewal_stale_claim' USING ERRCODE = '40001';
|
|
828
|
+
END IF;
|
|
829
|
+
|
|
830
|
+
SELECT cycle.id, order_row.id, intent.id
|
|
831
|
+
INTO v_cycle_id, v_order_id, v_intent_id
|
|
832
|
+
FROM public.subscription_cycles cycle
|
|
833
|
+
JOIN public.commerce_orders order_row ON order_row.subscription_cycle_id = cycle.id
|
|
834
|
+
JOIN public.commerce_settlement_intents intent ON intent.order_id = order_row.id
|
|
835
|
+
WHERE cycle.renewal_operation_id = p_operation_id;
|
|
836
|
+
IF FOUND THEN
|
|
837
|
+
IF v_operation.state <> 'attempt_prepared' THEN
|
|
838
|
+
RAISE EXCEPTION 'commerce_renewal_artifact_state_conflict' USING ERRCODE = '23505';
|
|
839
|
+
END IF;
|
|
840
|
+
RETURN jsonb_build_object(
|
|
841
|
+
'contractVersion', 'platform.subscription.renewal.v1', 'replayed', true,
|
|
842
|
+
'operationId', p_operation_id, 'operationFingerprint', v_operation.operation_fingerprint,
|
|
843
|
+
'cycleId', v_cycle_id, 'orderId', v_order_id, 'settlementIntentId', v_intent_id,
|
|
844
|
+
'state', v_operation.state);
|
|
845
|
+
END IF;
|
|
846
|
+
IF v_operation.state <> 'reserved' THEN
|
|
847
|
+
RAISE EXCEPTION 'commerce_renewal_reservation_required' USING ERRCODE = '22023';
|
|
848
|
+
END IF;
|
|
849
|
+
|
|
850
|
+
SELECT * INTO v_reservation FROM public.fulfillment_inventory_reservations
|
|
851
|
+
WHERE renewal_operation_id = p_operation_id FOR UPDATE;
|
|
852
|
+
IF NOT FOUND OR v_reservation.status <> 'held'
|
|
853
|
+
OR v_reservation.expires_at <= COALESCE(p_now, now())
|
|
854
|
+
OR v_reservation.operation_fingerprint <> v_operation.operation_fingerprint
|
|
855
|
+
THEN
|
|
856
|
+
RAISE EXCEPTION 'commerce_renewal_active_reservation_required' USING ERRCODE = '22023';
|
|
857
|
+
END IF;
|
|
858
|
+
|
|
859
|
+
v_currency := v_operation.cycle_snapshot->>'currency';
|
|
860
|
+
v_channel := v_operation.cycle_snapshot->>'settlementChannelKey';
|
|
861
|
+
v_total := (v_operation.cycle_snapshot->>'totalAmountMinor')::bigint;
|
|
862
|
+
IF v_currency !~ '^[A-Z]{3}$' OR btrim(COALESCE(v_channel, '')) = '' OR v_total < 1 THEN
|
|
863
|
+
RAISE EXCEPTION 'commerce_renewal_invalid_snapshot' USING ERRCODE = '22023';
|
|
864
|
+
END IF;
|
|
865
|
+
|
|
866
|
+
INSERT INTO public.subscription_cycles (
|
|
867
|
+
subscription_id, scheduled_at, status, renewal_operation_id,
|
|
868
|
+
renewal_operation_fingerprint, updated_at
|
|
869
|
+
) VALUES (
|
|
870
|
+
v_operation.subscription_id, v_operation.scheduled_at, 'payment_pending', p_operation_id,
|
|
871
|
+
v_operation.operation_fingerprint, COALESCE(p_now, now())
|
|
872
|
+
) RETURNING id INTO v_cycle_id;
|
|
873
|
+
|
|
874
|
+
INSERT INTO public.commerce_orders (
|
|
875
|
+
client_id, subscription_cycle_id, status, currency_code, total_amount_minor,
|
|
876
|
+
source_kind, metadata, renewal_operation_id, renewal_operation_fingerprint, updated_at
|
|
877
|
+
) SELECT subscription.client_id, v_cycle_id, 'pending_payment', v_currency, v_total,
|
|
878
|
+
'storefront', jsonb_build_object(
|
|
879
|
+
'source', 'subscription.renewal',
|
|
880
|
+
'renewalIdentityKey', v_operation.identity_key,
|
|
881
|
+
'renewalOperationFingerprint', v_operation.operation_fingerprint,
|
|
882
|
+
'cycleSnapshot', v_operation.cycle_snapshot),
|
|
883
|
+
p_operation_id, v_operation.operation_fingerprint, COALESCE(p_now, now())
|
|
884
|
+
FROM public.subscriptions subscription WHERE subscription.id = v_operation.subscription_id
|
|
885
|
+
RETURNING id INTO v_order_id;
|
|
886
|
+
|
|
887
|
+
FOR v_line IN SELECT value FROM jsonb_array_elements(v_operation.cycle_snapshot->'lines')
|
|
888
|
+
LOOP
|
|
889
|
+
v_line_ordinal := v_line_ordinal + 1;
|
|
890
|
+
v_quantity := (v_line->>'quantity')::integer;
|
|
891
|
+
v_unit_amount := (v_line->>'unitAmountMinor')::bigint;
|
|
892
|
+
SELECT id INTO v_sku_id FROM public.catalog_skus WHERE sku = v_line->>'sku';
|
|
893
|
+
IF NOT FOUND OR v_quantity < 1 OR v_unit_amount < 0 THEN
|
|
894
|
+
RAISE EXCEPTION 'commerce_renewal_invalid_line' USING ERRCODE = '22023';
|
|
895
|
+
END IF;
|
|
896
|
+
INSERT INTO public.commerce_order_items (
|
|
897
|
+
order_id, sku_id, line_ordinal, quantity, unit_amount_minor, line_amount_minor
|
|
898
|
+
) VALUES (
|
|
899
|
+
v_order_id, v_sku_id, v_line_ordinal, v_quantity, v_unit_amount,
|
|
900
|
+
v_quantity * v_unit_amount);
|
|
901
|
+
END LOOP;
|
|
902
|
+
|
|
903
|
+
PERFORM public.commerce_open_settlement_intent(
|
|
904
|
+
v_operation.identity_key || ':settlement', v_order_id, v_channel, v_total, v_currency,
|
|
905
|
+
jsonb_build_object(
|
|
906
|
+
'renewalOperationId', p_operation_id,
|
|
907
|
+
'renewalOperationFingerprint', v_operation.operation_fingerprint),
|
|
908
|
+
COALESCE(p_now, now()));
|
|
909
|
+
SELECT id INTO v_intent_id FROM public.commerce_settlement_intents WHERE order_id = v_order_id;
|
|
910
|
+
UPDATE public.commerce_settlement_intents
|
|
911
|
+
SET renewal_operation_id = p_operation_id,
|
|
912
|
+
renewal_operation_fingerprint = v_operation.operation_fingerprint,
|
|
913
|
+
updated_at = COALESCE(p_now, now())
|
|
914
|
+
WHERE id = v_intent_id;
|
|
915
|
+
UPDATE public.fulfillment_inventory_reservations
|
|
916
|
+
SET status = 'committed', expires_at = NULL, updated_at = COALESCE(p_now, now())
|
|
917
|
+
WHERE id = v_reservation.id;
|
|
918
|
+
UPDATE public.subscription_renewal_operations
|
|
919
|
+
SET state = 'attempt_prepared', attempt_prepared_at = COALESCE(p_now, now()),
|
|
920
|
+
updated_at = COALESCE(p_now, now())
|
|
921
|
+
WHERE id = p_operation_id RETURNING * INTO v_operation;
|
|
922
|
+
|
|
923
|
+
RETURN jsonb_build_object(
|
|
924
|
+
'contractVersion', 'platform.subscription.renewal.v1', 'replayed', false,
|
|
925
|
+
'operationId', p_operation_id, 'operationFingerprint', v_operation.operation_fingerprint,
|
|
926
|
+
'cycleId', v_cycle_id, 'orderId', v_order_id, 'settlementIntentId', v_intent_id,
|
|
927
|
+
'state', v_operation.state);
|
|
928
|
+
END;
|
|
929
|
+
$$;
|
|
930
|
+
|
|
931
|
+
CREATE FUNCTION public.commerce_acknowledge_subscription_renewal_attempt(
|
|
932
|
+
p_operation_id uuid,
|
|
933
|
+
p_operation_fingerprint text,
|
|
934
|
+
p_external_attempt_ref text,
|
|
935
|
+
p_acknowledged_at timestamptz DEFAULT now()
|
|
936
|
+
)
|
|
937
|
+
RETURNS jsonb
|
|
938
|
+
LANGUAGE plpgsql
|
|
939
|
+
SET search_path = pg_catalog
|
|
940
|
+
AS $$
|
|
941
|
+
DECLARE v_operation public.subscription_renewal_operations%ROWTYPE;
|
|
942
|
+
BEGIN
|
|
943
|
+
IF p_operation_id IS NULL
|
|
944
|
+
OR p_operation_fingerprint !~ '^[0-9a-f]{64}$'
|
|
945
|
+
OR btrim(COALESCE(p_external_attempt_ref, '')) = ''
|
|
946
|
+
OR char_length(p_external_attempt_ref) > 240
|
|
947
|
+
THEN
|
|
948
|
+
RAISE EXCEPTION 'commerce_renewal_attempt_ack_invalid_input' USING ERRCODE = '22023';
|
|
949
|
+
END IF;
|
|
950
|
+
SELECT * INTO v_operation FROM public.subscription_renewal_operations
|
|
951
|
+
WHERE id = p_operation_id FOR UPDATE;
|
|
952
|
+
IF NOT FOUND OR v_operation.state <> 'attempt_prepared' THEN
|
|
953
|
+
RAISE EXCEPTION 'commerce_renewal_attempt_ack_not_prepared' USING ERRCODE = '22023';
|
|
954
|
+
END IF;
|
|
955
|
+
IF v_operation.operation_fingerprint <> p_operation_fingerprint THEN
|
|
956
|
+
RAISE EXCEPTION 'commerce_renewal_attempt_ack_fingerprint_conflict' USING ERRCODE = '23505';
|
|
957
|
+
END IF;
|
|
958
|
+
IF v_operation.external_attempt_ref IS NOT NULL THEN
|
|
959
|
+
IF v_operation.external_attempt_ref <> p_external_attempt_ref THEN
|
|
960
|
+
RAISE EXCEPTION 'commerce_renewal_attempt_ack_idempotency_conflict' USING ERRCODE = '23505';
|
|
961
|
+
END IF;
|
|
962
|
+
RETURN public.subscription_renewal_operation_response(
|
|
963
|
+
v_operation, false, true, 'external_attempt_ack_replay');
|
|
964
|
+
END IF;
|
|
965
|
+
UPDATE public.subscription_renewal_operations
|
|
966
|
+
SET external_attempt_ref = p_external_attempt_ref,
|
|
967
|
+
external_attempt_acknowledged_at = COALESCE(p_acknowledged_at, now()),
|
|
968
|
+
updated_at = COALESCE(p_acknowledged_at, now())
|
|
969
|
+
WHERE id = p_operation_id RETURNING * INTO v_operation;
|
|
970
|
+
RETURN public.subscription_renewal_operation_response(
|
|
971
|
+
v_operation, false, false, 'external_attempt_ack_recorded');
|
|
972
|
+
END;
|
|
973
|
+
$$;
|
|
974
|
+
|
|
975
|
+
CREATE FUNCTION public.commerce_record_subscription_renewal_outcome(
|
|
976
|
+
p_operation_id uuid,
|
|
977
|
+
p_event_key text,
|
|
978
|
+
p_operation_fingerprint text,
|
|
979
|
+
p_outcome text,
|
|
980
|
+
p_external_ref text DEFAULT NULL,
|
|
981
|
+
p_reason text DEFAULT NULL,
|
|
982
|
+
p_occurred_at timestamptz DEFAULT now()
|
|
983
|
+
)
|
|
984
|
+
RETURNS jsonb
|
|
985
|
+
LANGUAGE plpgsql
|
|
986
|
+
SET search_path = pg_catalog
|
|
987
|
+
AS $$
|
|
988
|
+
DECLARE
|
|
989
|
+
v_operation public.subscription_renewal_operations%ROWTYPE;
|
|
990
|
+
v_existing public.subscription_renewal_outcomes%ROWTYPE;
|
|
991
|
+
v_intent public.commerce_settlement_intents%ROWTYPE;
|
|
992
|
+
BEGIN
|
|
993
|
+
IF btrim(COALESCE(p_event_key, '')) = ''
|
|
994
|
+
OR p_operation_fingerprint !~ '^[0-9a-f]{64}$'
|
|
995
|
+
OR p_outcome NOT IN ('succeeded', 'refused')
|
|
996
|
+
OR (p_outcome = 'succeeded' AND btrim(COALESCE(p_external_ref, '')) = '')
|
|
997
|
+
THEN
|
|
998
|
+
RAISE EXCEPTION 'commerce_renewal_outcome_invalid_input' USING ERRCODE = '22023';
|
|
999
|
+
END IF;
|
|
1000
|
+
|
|
1001
|
+
SELECT * INTO v_operation FROM public.subscription_renewal_operations
|
|
1002
|
+
WHERE id = p_operation_id FOR UPDATE;
|
|
1003
|
+
IF NOT FOUND THEN
|
|
1004
|
+
RAISE EXCEPTION 'commerce_renewal_outcome_operation_not_found' USING ERRCODE = '22023';
|
|
1005
|
+
END IF;
|
|
1006
|
+
IF v_operation.operation_fingerprint <> p_operation_fingerprint THEN
|
|
1007
|
+
RAISE EXCEPTION 'commerce_renewal_outcome_fingerprint_conflict' USING ERRCODE = '23505';
|
|
1008
|
+
END IF;
|
|
1009
|
+
IF p_external_ref IS NOT NULL
|
|
1010
|
+
AND v_operation.external_attempt_ref IS DISTINCT FROM p_external_ref
|
|
1011
|
+
THEN
|
|
1012
|
+
RAISE EXCEPTION 'commerce_renewal_outcome_external_ack_mismatch' USING ERRCODE = '23505';
|
|
1013
|
+
END IF;
|
|
1014
|
+
|
|
1015
|
+
SELECT * INTO v_existing FROM public.subscription_renewal_outcomes
|
|
1016
|
+
WHERE renewal_operation_id = p_operation_id AND event_key = p_event_key;
|
|
1017
|
+
IF FOUND THEN
|
|
1018
|
+
IF v_existing.operation_fingerprint <> p_operation_fingerprint
|
|
1019
|
+
OR v_existing.outcome <> p_outcome
|
|
1020
|
+
OR v_existing.external_ref IS DISTINCT FROM p_external_ref
|
|
1021
|
+
THEN
|
|
1022
|
+
RAISE EXCEPTION 'commerce_renewal_outcome_idempotency_conflict' USING ERRCODE = '23505';
|
|
1023
|
+
END IF;
|
|
1024
|
+
RETURN public.subscription_renewal_operation_response(v_operation, false, true, 'terminal_replay');
|
|
1025
|
+
END IF;
|
|
1026
|
+
IF v_operation.state IN ('succeeded', 'refused') THEN
|
|
1027
|
+
RAISE EXCEPTION 'commerce_renewal_outcome_already_terminal' USING ERRCODE = '22023';
|
|
1028
|
+
END IF;
|
|
1029
|
+
IF v_operation.state <> 'attempt_prepared' THEN
|
|
1030
|
+
RAISE EXCEPTION 'commerce_renewal_outcome_attempt_not_prepared' USING ERRCODE = '22023';
|
|
1031
|
+
END IF;
|
|
1032
|
+
|
|
1033
|
+
SELECT * INTO v_intent FROM public.commerce_settlement_intents
|
|
1034
|
+
WHERE renewal_operation_id = p_operation_id;
|
|
1035
|
+
IF NOT FOUND OR v_intent.renewal_operation_fingerprint <> p_operation_fingerprint THEN
|
|
1036
|
+
RAISE EXCEPTION 'commerce_renewal_outcome_intent_not_found' USING ERRCODE = '22023';
|
|
1037
|
+
END IF;
|
|
1038
|
+
|
|
1039
|
+
PERFORM public.commerce_record_settlement(
|
|
1040
|
+
p_event_key || ':settlement', v_intent.id,
|
|
1041
|
+
CASE p_outcome WHEN 'succeeded' THEN 'succeeded' ELSE 'failed' END,
|
|
1042
|
+
p_external_ref,
|
|
1043
|
+
jsonb_build_object(
|
|
1044
|
+
'reason', COALESCE(NULLIF(btrim(COALESCE(p_reason, '')), ''),
|
|
1045
|
+
CASE p_outcome WHEN 'refused' THEN 'declined' ELSE NULL END),
|
|
1046
|
+
'renewalOperationId', p_operation_id,
|
|
1047
|
+
'renewalOperationFingerprint', p_operation_fingerprint),
|
|
1048
|
+
COALESCE(p_occurred_at, now()));
|
|
1049
|
+
|
|
1050
|
+
INSERT INTO public.subscription_renewal_outcomes (
|
|
1051
|
+
renewal_operation_id, operation_fingerprint, event_key, outcome, reason,
|
|
1052
|
+
external_ref, occurred_at
|
|
1053
|
+
) VALUES (
|
|
1054
|
+
p_operation_id, p_operation_fingerprint, p_event_key, p_outcome,
|
|
1055
|
+
NULLIF(btrim(COALESCE(p_reason, '')), ''), p_external_ref, COALESCE(p_occurred_at, now()));
|
|
1056
|
+
|
|
1057
|
+
IF p_outcome = 'succeeded' THEN
|
|
1058
|
+
UPDATE public.fulfillment_inventory_reservations
|
|
1059
|
+
SET status = 'consumed', updated_at = COALESCE(p_occurred_at, now())
|
|
1060
|
+
WHERE renewal_operation_id = p_operation_id AND status = 'committed';
|
|
1061
|
+
UPDATE public.subscription_renewal_terms
|
|
1062
|
+
SET next_cycle_number = GREATEST(next_cycle_number, v_operation.cycle_number + 1),
|
|
1063
|
+
updated_at = COALESCE(p_occurred_at, now())
|
|
1064
|
+
WHERE subscription_id = v_operation.subscription_id;
|
|
1065
|
+
ELSE
|
|
1066
|
+
UPDATE public.fulfillment_inventory_reservations
|
|
1067
|
+
SET status = 'released', terminal_reason = COALESCE(
|
|
1068
|
+
NULLIF(btrim(COALESCE(p_reason, '')), ''), 'settlement_refused'),
|
|
1069
|
+
updated_at = COALESCE(p_occurred_at, now())
|
|
1070
|
+
WHERE renewal_operation_id = p_operation_id AND status = 'committed';
|
|
1071
|
+
END IF;
|
|
1072
|
+
|
|
1073
|
+
UPDATE public.subscription_renewal_operations
|
|
1074
|
+
SET state = p_outcome,
|
|
1075
|
+
terminal_outcome = p_outcome,
|
|
1076
|
+
terminal_reason = CASE WHEN p_outcome = 'refused' THEN COALESCE(
|
|
1077
|
+
NULLIF(btrim(COALESCE(p_reason, '')), ''), 'settlement_refused') ELSE NULL END,
|
|
1078
|
+
terminal_at = COALESCE(p_occurred_at, now()),
|
|
1079
|
+
updated_at = COALESCE(p_occurred_at, now())
|
|
1080
|
+
WHERE id = p_operation_id RETURNING * INTO v_operation;
|
|
1081
|
+
RETURN public.subscription_renewal_operation_response(v_operation, false, false, 'terminal_recorded');
|
|
1082
|
+
END;
|
|
1083
|
+
$$;
|