@openlup/platform 0.16.0 → 0.17.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 +84 -0
- package/README.md +285 -5
- 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 +51 -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/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 +46 -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/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/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/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/package.json +12 -2
- package/release-gates.json +41 -8
- package/sql/20260807190000_comms_outbox_rail.sql +329 -0
- package/sql/20260810120001_transactional_delivery_receipts.sql +209 -0
- package/sql/20260813120000_communications_control_plane.sql +568 -0
- package/sql/20261019200000_communications_native_operations.sql +68 -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 +51 -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/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 +46 -0
- package/src/communications/integrationEventSignature.ts +49 -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/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/http/index.ts +1 -0
- package/src/http/webhookSignature.ts +96 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { PaymentFailureCustomerCause } from "@openlup/core/payment";
|
|
3
|
+
import type { Locale } from "./email.js";
|
|
4
|
+
import { type EmailBlock } from "./blocks.js";
|
|
5
|
+
export interface SubscriptionPaymentExpiredEmailVars {
|
|
6
|
+
firstName: string | null;
|
|
7
|
+
/** Formatted gross amount of the unpaid cycle, e.g. "129,99 zł", or null. */
|
|
8
|
+
amountLabel: string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Why the charges were refused, in the payer-facing vocabulary. This template
|
|
11
|
+
* needs it most: "after several attempts" without a reason is the one place
|
|
12
|
+
* the rail asks for trust and offers nothing to trust. Absent or `unknown`
|
|
13
|
+
* renders no cause sentence.
|
|
14
|
+
*/
|
|
15
|
+
cause?: PaymentFailureCustomerCause | null;
|
|
16
|
+
/**
|
|
17
|
+
* Recovery-link URL for the case's `resume_subscription` token. When present
|
|
18
|
+
* the template renders the resume CTA; when absent it keeps the support-led
|
|
19
|
+
* wording, because a button with no destination is worse than no button.
|
|
20
|
+
*/
|
|
21
|
+
recoveryUrl?: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface SubscriptionPaymentExpiredEmailContent {
|
|
24
|
+
subject: string;
|
|
25
|
+
preheader: string;
|
|
26
|
+
blocks: EmailBlock[];
|
|
27
|
+
}
|
|
28
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
29
|
+
export declare function createSubscriptionPaymentExpiredContentFactory(input: {
|
|
30
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "paymentExpired">;
|
|
31
|
+
subscriptionPaymentCauseSentence: (locale: Locale, cause: PaymentFailureCustomerCause) => string | null;
|
|
32
|
+
}): {
|
|
33
|
+
subscriptionPaymentExpiredEmailContent: (locale: Locale, vars: SubscriptionPaymentExpiredEmailVars, signoff: string) => SubscriptionPaymentExpiredEmailContent;
|
|
34
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { accentBox, button, heading, paragraph, } from "./blocks.js";
|
|
2
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
3
|
+
export function createSubscriptionPaymentExpiredContentFactory(input) {
|
|
4
|
+
const { subscriptionEmailContent, subscriptionPaymentCauseSentence } = input;
|
|
5
|
+
function subscriptionPaymentExpiredEmailContent(locale, vars, signoff) {
|
|
6
|
+
const copy = subscriptionEmailContent.paymentExpired[locale];
|
|
7
|
+
const recoveryUrl = vars.recoveryUrl?.trim() ? vars.recoveryUrl.trim() : null;
|
|
8
|
+
const causeSentence = subscriptionPaymentCauseSentence(locale, vars.cause ?? "unknown");
|
|
9
|
+
const detail = [
|
|
10
|
+
// Leads the box for the same reason as in the failed notice: it answers the
|
|
11
|
+
// question the paused subscription raises, before the figures.
|
|
12
|
+
...(causeSentence ? [causeSentence] : []),
|
|
13
|
+
...(recoveryUrl
|
|
14
|
+
? [
|
|
15
|
+
vars.amountLabel ? copy.detail(vars.amountLabel)[0] : copy.detail(null)[0],
|
|
16
|
+
copy.ctaDetail,
|
|
17
|
+
]
|
|
18
|
+
: copy.detail(vars.amountLabel)),
|
|
19
|
+
];
|
|
20
|
+
const blocks = [
|
|
21
|
+
heading(copy.heading),
|
|
22
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
23
|
+
paragraph(copy.intro),
|
|
24
|
+
accentBox(detail),
|
|
25
|
+
];
|
|
26
|
+
if (recoveryUrl)
|
|
27
|
+
blocks.push(button(copy.cta, recoveryUrl));
|
|
28
|
+
blocks.push(paragraph(copy.outro));
|
|
29
|
+
blocks.push(paragraph(signoff));
|
|
30
|
+
return {
|
|
31
|
+
subject: copy.subject,
|
|
32
|
+
preheader: recoveryUrl ? copy.preheaderWithCta : copy.preheader,
|
|
33
|
+
blocks,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { subscriptionPaymentExpiredEmailContent };
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { PaymentFailureCustomerCause } from "@openlup/core/payment";
|
|
3
|
+
import type { Locale } from "./email.js";
|
|
4
|
+
import { type EmailBlock } from "./blocks.js";
|
|
5
|
+
export interface SubscriptionPaymentFailedEmailVars {
|
|
6
|
+
firstName: string | null;
|
|
7
|
+
/** Formatted gross amount that failed, e.g. "129,99 zł", or null when unknown. */
|
|
8
|
+
amountLabel: string | null;
|
|
9
|
+
/** Dunning retry attempt: 1 (gentle), 2 (reminder), 3+ (final retry announced). */
|
|
10
|
+
retryAttempt: number;
|
|
11
|
+
/** Absolute recovery URL (localized); null/absent → no button. */
|
|
12
|
+
recoveryUrl?: string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Pre-formatted calendar day of the scheduled next charge, in the merchant's
|
|
15
|
+
* zone (e.g. "11.08.2026"). The dunning row that selects THIS template always
|
|
16
|
+
* carries a scheduled retry, so in production this is present; null keeps the
|
|
17
|
+
* pre-date sentence rather than printing a placeholder date.
|
|
18
|
+
*/
|
|
19
|
+
nextRetryDateLabel?: string | null;
|
|
20
|
+
/** Card scheme as stored on the method's consent snapshot, e.g. "visa". */
|
|
21
|
+
methodScheme?: string | null;
|
|
22
|
+
/** Trailing digits of the stored method, e.g. "4242". */
|
|
23
|
+
methodLastDigits?: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Why the charge was refused, in the payer-facing vocabulary. Absent or
|
|
26
|
+
* `unknown` renders no cause sentence at all — see `subscriptionPaymentCauseCopy`.
|
|
27
|
+
*/
|
|
28
|
+
cause?: PaymentFailureCustomerCause | null;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the ladder scheduled a further charge for this cycle. Absent or
|
|
31
|
+
* `true` is today's rail and today's copy. `false` states that no further
|
|
32
|
+
* attempt is coming and leaves the CTA as the only remedy; any
|
|
33
|
+
* `nextRetryDateLabel` supplied alongside it is ignored rather than printed,
|
|
34
|
+
* because a date is only ever printed as part of a retry promise.
|
|
35
|
+
*/
|
|
36
|
+
retryScheduled?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface SubscriptionPaymentFailedEmailContent {
|
|
39
|
+
subject: string;
|
|
40
|
+
preheader: string;
|
|
41
|
+
blocks: EmailBlock[];
|
|
42
|
+
}
|
|
43
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
44
|
+
export declare function createSubscriptionPaymentFailedContentFactory(input: {
|
|
45
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "paymentFailed">;
|
|
46
|
+
subscriptionPaymentCauseSentence: (locale: Locale, cause: PaymentFailureCustomerCause) => string | null;
|
|
47
|
+
}): {
|
|
48
|
+
subscriptionPaymentFailedEmailContent: (locale: Locale, vars: SubscriptionPaymentFailedEmailVars, signoff: string) => SubscriptionPaymentFailedEmailContent;
|
|
49
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { accentBox, button, heading, paragraph, } from "./blocks.js";
|
|
2
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
3
|
+
export function createSubscriptionPaymentFailedContentFactory(input) {
|
|
4
|
+
const { subscriptionEmailContent, subscriptionPaymentCauseSentence } = input;
|
|
5
|
+
function subscriptionPaymentFailedEmailContent(locale, vars, signoff) {
|
|
6
|
+
const copy = subscriptionEmailContent.paymentFailed[locale];
|
|
7
|
+
// Method facts are additive and BOTH halves must be present: "karta Visa ••••"
|
|
8
|
+
// with no digits identifies nothing, and bare digits with no scheme is worse.
|
|
9
|
+
// Absent facts drop the sentence rather than render a placeholder.
|
|
10
|
+
const scheme = vars.methodScheme?.trim() || null;
|
|
11
|
+
const lastDigits = vars.methodLastDigits?.trim() || null;
|
|
12
|
+
// The cause leads the box: it is the answer to the question the subject line
|
|
13
|
+
// just raised, and the amount means little until the reader knows why. It is
|
|
14
|
+
// also why it is PREPENDED and never appended — the retry sentence must stay
|
|
15
|
+
// last (see below).
|
|
16
|
+
const causeSentence = subscriptionPaymentCauseSentence(locale, vars.cause ?? "unknown");
|
|
17
|
+
// Only an explicit `false` turns the promises off. Absent means "no producer
|
|
18
|
+
// told us", and the only rail that exists always schedules a retry, so absent
|
|
19
|
+
// must keep rendering today's copy rather than the safer-sounding new copy:
|
|
20
|
+
// telling a customer no attempt is coming when one is would push them to
|
|
21
|
+
// cancel a subscription that was about to recover on its own.
|
|
22
|
+
const retryScheduled = vars.retryScheduled !== false;
|
|
23
|
+
const detail = [
|
|
24
|
+
...(causeSentence ? [causeSentence] : []),
|
|
25
|
+
vars.amountLabel
|
|
26
|
+
? copy.amountLine(vars.amountLabel)
|
|
27
|
+
: retryScheduled
|
|
28
|
+
? copy.noAmountLine
|
|
29
|
+
: copy.noAmountLineNoRetry,
|
|
30
|
+
...(scheme && lastDigits ? [copy.methodLine(scheme, lastDigits)] : []),
|
|
31
|
+
// Kept LAST in the box on purpose: the retry sentence is the one the
|
|
32
|
+
// characterization pins read off the end of `accentBox.lines`. Its
|
|
33
|
+
// no-retry replacement takes the same position for the same reason.
|
|
34
|
+
retryScheduled
|
|
35
|
+
? copy.reassurance(vars.retryAttempt, vars.nextRetryDateLabel?.trim() || null)
|
|
36
|
+
: copy.noRetryAction,
|
|
37
|
+
];
|
|
38
|
+
const blocks = [
|
|
39
|
+
heading(copy.heading),
|
|
40
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
41
|
+
paragraph(copy.intro),
|
|
42
|
+
accentBox(detail),
|
|
43
|
+
];
|
|
44
|
+
if (vars.recoveryUrl) {
|
|
45
|
+
blocks.push(button(copy.cta, vars.recoveryUrl));
|
|
46
|
+
}
|
|
47
|
+
blocks.push(paragraph(copy.outro));
|
|
48
|
+
blocks.push(paragraph(signoff));
|
|
49
|
+
return {
|
|
50
|
+
subject: copy.subject,
|
|
51
|
+
preheader: copy.preheader,
|
|
52
|
+
blocks,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { subscriptionPaymentFailedEmailContent };
|
|
56
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import { type EmailBlock } from "./blocks.js";
|
|
4
|
+
export interface SubscriptionPaymentRecoveredEmailVars {
|
|
5
|
+
firstName: string | null;
|
|
6
|
+
/** Formatted gross amount of the recovered cycle, e.g. "129,99 zł", or null. */
|
|
7
|
+
amountLabel: string | null;
|
|
8
|
+
}
|
|
9
|
+
export interface SubscriptionPaymentRecoveredEmailContent {
|
|
10
|
+
subject: string;
|
|
11
|
+
preheader: string;
|
|
12
|
+
blocks: EmailBlock[];
|
|
13
|
+
}
|
|
14
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
15
|
+
export declare function createSubscriptionPaymentRecoveredContentFactory(input: {
|
|
16
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "paymentRecovered">;
|
|
17
|
+
}): {
|
|
18
|
+
subscriptionPaymentRecoveredEmailContent: (locale: Locale, vars: SubscriptionPaymentRecoveredEmailVars, signoff: string) => SubscriptionPaymentRecoveredEmailContent;
|
|
19
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { accentBox, heading, paragraph, } from "./blocks.js";
|
|
2
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
3
|
+
export function createSubscriptionPaymentRecoveredContentFactory(input) {
|
|
4
|
+
const { subscriptionEmailContent } = input;
|
|
5
|
+
function subscriptionPaymentRecoveredEmailContent(locale, vars, signoff) {
|
|
6
|
+
const copy = subscriptionEmailContent.paymentRecovered[locale];
|
|
7
|
+
const blocks = [
|
|
8
|
+
heading(copy.heading),
|
|
9
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
10
|
+
paragraph(copy.intro),
|
|
11
|
+
accentBox(copy.detail(vars.amountLabel)),
|
|
12
|
+
paragraph(copy.outro),
|
|
13
|
+
paragraph(signoff),
|
|
14
|
+
];
|
|
15
|
+
return {
|
|
16
|
+
subject: copy.subject,
|
|
17
|
+
preheader: copy.preheader,
|
|
18
|
+
blocks,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return { subscriptionPaymentRecoveredEmailContent };
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { PaymentFailureCustomerCause } from "@openlup/core/payment";
|
|
3
|
+
import type { Locale } from "./email.js";
|
|
4
|
+
import { type EmailBlock } from "./blocks.js";
|
|
5
|
+
/**
|
|
6
|
+
* Which of the two unchargeable states produced this notice.
|
|
7
|
+
*
|
|
8
|
+
* ⛔ THESE TWO TOKENS ARE LEDGER IDENTITY, NOT COPY KEYS. The producer's dedupe
|
|
9
|
+
* key AND its two-send cap are one string, `<subscriptionId>:<cause>:<day>`, and
|
|
10
|
+
* the cap is read back by matching this token as a LIKE prefix. Renaming either
|
|
11
|
+
* value — including "aligning" it with the shared cause vocabulary below —
|
|
12
|
+
* silently resets every cap ever recorded, because the new prefix matches
|
|
13
|
+
* nothing. The mapping to the shared vocabulary therefore happens at RENDER
|
|
14
|
+
* time, here, and never in the producer.
|
|
15
|
+
*/
|
|
16
|
+
export type SubscriptionRenewalAtRiskCause = "mandate" | "missing";
|
|
17
|
+
export interface SubscriptionRenewalAtRiskEmailVars {
|
|
18
|
+
firstName: string | null;
|
|
19
|
+
/** Calendar day of the scheduled renewal in the merchant zone, or null. */
|
|
20
|
+
renewalDateLabel: string | null;
|
|
21
|
+
cause: SubscriptionRenewalAtRiskCause;
|
|
22
|
+
/** Account payments deep link; the same one the activation-gap email uses. */
|
|
23
|
+
ctaUrl: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SubscriptionRenewalAtRiskEmailContent {
|
|
26
|
+
subject: string;
|
|
27
|
+
preheader: string;
|
|
28
|
+
blocks: EmailBlock[];
|
|
29
|
+
}
|
|
30
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
31
|
+
export declare function createSubscriptionRenewalAtRiskContentFactory(input: {
|
|
32
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "renewalAtRisk">;
|
|
33
|
+
subscriptionPaymentCauseSentence: (locale: Locale, cause: PaymentFailureCustomerCause) => string | null;
|
|
34
|
+
}): {
|
|
35
|
+
subscriptionRenewalAtRiskEmailContent: (locale: Locale, vars: SubscriptionRenewalAtRiskEmailVars, signoff: string) => SubscriptionRenewalAtRiskEmailContent;
|
|
36
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { accentBox, button, heading, paragraph, } from "./blocks.js";
|
|
2
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
3
|
+
export function createSubscriptionRenewalAtRiskContentFactory(input) {
|
|
4
|
+
const { subscriptionEmailContent, subscriptionPaymentCauseSentence } = input;
|
|
5
|
+
/** Render-time only. See the ⛔ above before touching either side of this map. */
|
|
6
|
+
const SHARED_CAUSE = {
|
|
7
|
+
mandate: "method_cannot_recur",
|
|
8
|
+
missing: "method_missing",
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Both of this rail's causes state something about the stored method, so both
|
|
12
|
+
* have a shared sentence and neither can be `unknown`. The non-null assertion is
|
|
13
|
+
* carried by the `SHARED_CAUSE` map's exhaustive typing, not by an assumption.
|
|
14
|
+
*/
|
|
15
|
+
function causeSentence(locale, cause) {
|
|
16
|
+
return subscriptionPaymentCauseSentence(locale, SHARED_CAUSE[cause]) ?? "";
|
|
17
|
+
}
|
|
18
|
+
function subscriptionRenewalAtRiskEmailContent(locale, vars, signoff) {
|
|
19
|
+
const copy = subscriptionEmailContent.renewalAtRisk[locale];
|
|
20
|
+
const blocks = [
|
|
21
|
+
heading(copy.heading),
|
|
22
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
23
|
+
paragraph(copy.intro(vars.renewalDateLabel)),
|
|
24
|
+
accentBox([causeSentence(locale, vars.cause), copy.consequence]),
|
|
25
|
+
button(copy.cta, vars.ctaUrl),
|
|
26
|
+
paragraph(copy.outro),
|
|
27
|
+
paragraph(signoff),
|
|
28
|
+
];
|
|
29
|
+
return {
|
|
30
|
+
subject: copy.subject,
|
|
31
|
+
preheader: copy.preheader,
|
|
32
|
+
blocks,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { subscriptionRenewalAtRiskEmailContent };
|
|
36
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import { type EmailBlock } from "./blocks.js";
|
|
4
|
+
export interface SubscriptionRenewalUpcomingEmailVars {
|
|
5
|
+
firstName: string | null;
|
|
6
|
+
/** Pre-formatted renewal CHARGE date (locale-formatted by the worker). */
|
|
7
|
+
renewalDateLabel: string | null;
|
|
8
|
+
/** Pre-formatted estimated delivery window, or null when unavailable. */
|
|
9
|
+
deliveryWindowLabel?: string | null;
|
|
10
|
+
/** Localized holiday caveat; present ONLY when a holiday moved the window. */
|
|
11
|
+
holidayNote?: string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Pre-formatted composition edit deadline. Null when unknown OR when the
|
|
14
|
+
* deadline has already passed at send time — see the module header.
|
|
15
|
+
*/
|
|
16
|
+
editCutoffLabel?: string | null;
|
|
17
|
+
/** Formatted gross amount of the upcoming charge, e.g. "129,99 zł", or null. */
|
|
18
|
+
amountLabel: string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Starter-pack acquisition stage of the delivery being announced, or null for
|
|
21
|
+
* an ordinary renewal. `"graduation"` means the NEXT delivery switches to the
|
|
22
|
+
* steady package — a size and rhythm change the customer agreed to at checkout
|
|
23
|
+
* but has not seen since, so it gets its own paragraph rather than a detail
|
|
24
|
+
* line. `"delivery2"` only marks the discounted repeat; its amount already
|
|
25
|
+
* rides on `amountLabel`.
|
|
26
|
+
*/
|
|
27
|
+
starterStage?: "delivery2" | "graduation" | null;
|
|
28
|
+
/** Units per delivery from the graduation on; renders only with `"graduation"`. */
|
|
29
|
+
starterSteadyUnitCount?: number | null;
|
|
30
|
+
/** Days between deliveries from the graduation on; renders only with `"graduation"`. */
|
|
31
|
+
starterSteadyCadenceDays?: number | null;
|
|
32
|
+
/** Adopter-formatted steady package, already localized; renders only with `"graduation"`. */
|
|
33
|
+
starterSteadyDetail?: string | null;
|
|
34
|
+
/** Absolute "manage subscription" URL; null/absent → no button. */
|
|
35
|
+
ctaUrl?: string | null;
|
|
36
|
+
/** Optional consumer context label; absent/null uses the selected pack's fallback. */
|
|
37
|
+
contextName?: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface SubscriptionRenewalUpcomingEmailContent {
|
|
40
|
+
subject: string;
|
|
41
|
+
preheader: string;
|
|
42
|
+
blocks: EmailBlock[];
|
|
43
|
+
}
|
|
44
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
45
|
+
export declare function createSubscriptionRenewalUpcomingContentFactory(input: {
|
|
46
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "renewalUpcoming">;
|
|
47
|
+
}): {
|
|
48
|
+
subscriptionRenewalUpcomingEmailContent: (locale: Locale, vars: SubscriptionRenewalUpcomingEmailVars, signoff: string) => SubscriptionRenewalUpcomingEmailContent;
|
|
49
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { accentBox, button, heading, paragraph, } from "./blocks.js";
|
|
2
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
3
|
+
export function createSubscriptionRenewalUpcomingContentFactory(input) {
|
|
4
|
+
const { subscriptionEmailContent } = input;
|
|
5
|
+
function subscriptionRenewalUpcomingEmailContent(locale, vars, signoff) {
|
|
6
|
+
const copy = subscriptionEmailContent.renewalUpcoming[locale];
|
|
7
|
+
const details = [
|
|
8
|
+
vars.renewalDateLabel ? copy.dateLine(vars.renewalDateLabel) : copy.noDateLine,
|
|
9
|
+
];
|
|
10
|
+
if (vars.deliveryWindowLabel)
|
|
11
|
+
details.push(copy.deliveryLine(vars.deliveryWindowLabel));
|
|
12
|
+
if (vars.holidayNote)
|
|
13
|
+
details.push(vars.holidayNote);
|
|
14
|
+
if (vars.amountLabel)
|
|
15
|
+
details.push(copy.amountLine(vars.amountLabel));
|
|
16
|
+
// Null when already past — the caller, not this renderer, owns that decision.
|
|
17
|
+
if (vars.editCutoffLabel)
|
|
18
|
+
details.push(copy.editCutoffLine(vars.editCutoffLabel));
|
|
19
|
+
const blocks = [
|
|
20
|
+
heading(copy.heading),
|
|
21
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
22
|
+
paragraph(copy.intro(vars.contextName ?? null)),
|
|
23
|
+
accentBox(details, { label: copy.detailsLabel }),
|
|
24
|
+
];
|
|
25
|
+
// The package-size change is the one thing in this email a customer could be
|
|
26
|
+
// surprised by, so it gets its own paragraph ABOVE the generic self-service
|
|
27
|
+
// line, and it names moving the delivery first.
|
|
28
|
+
if (vars.starterStage === "graduation" && vars.starterSteadyUnitCount && vars.starterSteadyCadenceDays) {
|
|
29
|
+
blocks.push(paragraph(copy.starterGraduationLine(vars.starterSteadyUnitCount, vars.starterSteadyCadenceDays, vars.starterSteadyDetail)));
|
|
30
|
+
}
|
|
31
|
+
blocks.push(paragraph(copy.manageLine));
|
|
32
|
+
if (vars.ctaUrl) {
|
|
33
|
+
blocks.push(button(copy.cta, vars.ctaUrl));
|
|
34
|
+
}
|
|
35
|
+
blocks.push(paragraph(signoff));
|
|
36
|
+
return {
|
|
37
|
+
subject: copy.subject,
|
|
38
|
+
preheader: copy.preheader,
|
|
39
|
+
blocks,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return { subscriptionRenewalUpcomingEmailContent };
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import { type EmailBlock } from "./blocks.js";
|
|
4
|
+
export interface SubscriptionResumedEmailVars {
|
|
5
|
+
firstName: string | null;
|
|
6
|
+
/** Consumer-facing brand label injected by the app/composition root. */
|
|
7
|
+
brandName: string;
|
|
8
|
+
/** Absolute CTA URL (manage subscription); null/absent → no button. */
|
|
9
|
+
ctaUrl?: string | null;
|
|
10
|
+
/** Optional consumer context label; absent/null uses the selected pack's fallback. */
|
|
11
|
+
contextName?: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface SubscriptionResumedEmailContent {
|
|
14
|
+
subject: string;
|
|
15
|
+
preheader: string;
|
|
16
|
+
blocks: EmailBlock[];
|
|
17
|
+
}
|
|
18
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
19
|
+
export declare function createSubscriptionResumedContentFactory(input: {
|
|
20
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "resumed">;
|
|
21
|
+
}): {
|
|
22
|
+
subscriptionResumedEmailContent: (locale: Locale, vars: SubscriptionResumedEmailVars, signoff: string) => SubscriptionResumedEmailContent;
|
|
23
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { accentBox, button, heading, paragraph, } from "./blocks.js";
|
|
2
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
3
|
+
export function createSubscriptionResumedContentFactory(input) {
|
|
4
|
+
const { subscriptionEmailContent } = input;
|
|
5
|
+
function subscriptionResumedEmailContent(locale, vars, signoff) {
|
|
6
|
+
const copy = subscriptionEmailContent.resumed[locale];
|
|
7
|
+
const blocks = [
|
|
8
|
+
heading(copy.heading),
|
|
9
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
10
|
+
paragraph(copy.intro(vars.contextName ?? null)),
|
|
11
|
+
accentBox(copy.reassurance(vars.brandName)),
|
|
12
|
+
];
|
|
13
|
+
if (vars.ctaUrl) {
|
|
14
|
+
blocks.push(button(copy.cta, vars.ctaUrl));
|
|
15
|
+
}
|
|
16
|
+
blocks.push(paragraph(copy.outro));
|
|
17
|
+
blocks.push(paragraph(signoff));
|
|
18
|
+
return {
|
|
19
|
+
subject: copy.subject,
|
|
20
|
+
preheader: copy.preheader,
|
|
21
|
+
blocks,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return { subscriptionResumedEmailContent };
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import { type EmailBlock } from "./blocks.js";
|
|
4
|
+
export interface SubscriptionWelcomeEmailVars {
|
|
5
|
+
firstName: string | null;
|
|
6
|
+
cadenceDays: number | null;
|
|
7
|
+
/** Pre-formatted renewal CHARGE date (locale-formatted by the handler), or null. */
|
|
8
|
+
chargeDateLabel: string | null;
|
|
9
|
+
/** Pre-formatted estimated delivery window, e.g. "04.08.2026 – 05.08.2026", or null. */
|
|
10
|
+
deliveryWindowLabel: string | null;
|
|
11
|
+
/** Pre-formatted composition edit deadline, or null when unknown/already passed. */
|
|
12
|
+
editCutoffLabel?: string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Localized holiday caveat, supplied by the handler ONLY when a public holiday
|
|
15
|
+
* actually moved the estimated window. Null/absent is the normal case and must
|
|
16
|
+
* render nothing — a standing "we account for holidays" line is noise.
|
|
17
|
+
*/
|
|
18
|
+
holidayNote?: string | null;
|
|
19
|
+
/** Absolute "manage subscription" URL; null/absent → no button. */
|
|
20
|
+
ctaUrl?: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Starter-pack acquisition facts, present only for a subscription that carries
|
|
23
|
+
* an acquisition marker. `starterAmountLabel` is the DELIVERY-2 amount — the
|
|
24
|
+
* discounted repeat that lands on the date already stated above — and the
|
|
25
|
+
* steady pair describes what delivery 3 onwards looks like. The whole block is
|
|
26
|
+
* dropped when any of it is unknown; a half-stated plan is worse than none.
|
|
27
|
+
*/
|
|
28
|
+
starterAmountLabel?: string | null;
|
|
29
|
+
starterSteadyUnitCount?: number | null;
|
|
30
|
+
starterSteadyCadenceDays?: number | null;
|
|
31
|
+
/** Adopter-formatted steady package, already localized; the steady line's third argument. */
|
|
32
|
+
starterSteadyDetail?: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface SubscriptionWelcomeEmailContent {
|
|
35
|
+
subject: string;
|
|
36
|
+
preheader: string;
|
|
37
|
+
blocks: EmailBlock[];
|
|
38
|
+
}
|
|
39
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
40
|
+
export declare function createSubscriptionWelcomeContentFactory(input: {
|
|
41
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "welcome">;
|
|
42
|
+
}): {
|
|
43
|
+
subscriptionWelcomeEmailContent: (locale: Locale, vars: SubscriptionWelcomeEmailVars, signoff: string) => SubscriptionWelcomeEmailContent;
|
|
44
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { accentBox, button, heading, paragraph, } from "./blocks.js";
|
|
2
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
3
|
+
export function createSubscriptionWelcomeContentFactory(input) {
|
|
4
|
+
const { subscriptionEmailContent } = input;
|
|
5
|
+
function subscriptionWelcomeEmailContent(locale, vars, signoff) {
|
|
6
|
+
const copy = subscriptionEmailContent.welcome[locale];
|
|
7
|
+
const details = [];
|
|
8
|
+
if (vars.cadenceDays && vars.cadenceDays > 0)
|
|
9
|
+
details.push(copy.cadenceLine(vars.cadenceDays));
|
|
10
|
+
// Both labels are derived from the same `next_cycle_at`, so they are present or
|
|
11
|
+
// absent together; the fallback keeps the pre-existing "we'll confirm" promise.
|
|
12
|
+
details.push(vars.chargeDateLabel && vars.deliveryWindowLabel
|
|
13
|
+
? copy.deliveryLine(vars.deliveryWindowLabel, vars.chargeDateLabel)
|
|
14
|
+
: copy.fallbackLine);
|
|
15
|
+
if (vars.holidayNote)
|
|
16
|
+
details.push(vars.holidayNote);
|
|
17
|
+
// Acquisition plan: the delivery-2 amount and the steady rhythm, stated once at
|
|
18
|
+
// the only moment the customer is actually reading about their plan.
|
|
19
|
+
if (vars.starterAmountLabel)
|
|
20
|
+
details.push(copy.starterDelivery2Line(vars.starterAmountLabel));
|
|
21
|
+
if (vars.starterSteadyUnitCount && vars.starterSteadyCadenceDays) {
|
|
22
|
+
details.push(copy.starterSteadyLine(vars.starterSteadyUnitCount, vars.starterSteadyCadenceDays, vars.starterSteadyDetail));
|
|
23
|
+
}
|
|
24
|
+
if (vars.editCutoffLabel)
|
|
25
|
+
details.push(copy.editCutoffLine(vars.editCutoffLabel));
|
|
26
|
+
const blocks = [
|
|
27
|
+
heading(copy.heading),
|
|
28
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
29
|
+
paragraph(copy.intro),
|
|
30
|
+
accentBox(details, { label: copy.detailsLabel }),
|
|
31
|
+
];
|
|
32
|
+
blocks.push(paragraph(copy.manageLine));
|
|
33
|
+
if (vars.ctaUrl) {
|
|
34
|
+
blocks.push(button(copy.cta, vars.ctaUrl));
|
|
35
|
+
}
|
|
36
|
+
blocks.push(paragraph(copy.outro));
|
|
37
|
+
blocks.push(paragraph(signoff));
|
|
38
|
+
return {
|
|
39
|
+
subject: copy.subject,
|
|
40
|
+
preheader: copy.preheader,
|
|
41
|
+
blocks,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { subscriptionWelcomeEmailContent };
|
|
45
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SubscriptionEmailContent } from "./subscriptionEmailContent.js";
|
|
2
|
+
import type { Locale } from "./email.js";
|
|
3
|
+
import { type EmailBlock } from "./blocks.js";
|
|
4
|
+
export interface SubscriptionWinbackEmailVars {
|
|
5
|
+
firstName: string | null;
|
|
6
|
+
/** Consumer-facing brand label injected by the app/composition root. */
|
|
7
|
+
brandName: string;
|
|
8
|
+
/** Absolute CTA URL (the customer account / "Wznów" button); null/absent → no button. */
|
|
9
|
+
ctaUrl?: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface SubscriptionWinbackEmailContent {
|
|
12
|
+
subject: string;
|
|
13
|
+
preheader: string;
|
|
14
|
+
blocks: EmailBlock[];
|
|
15
|
+
}
|
|
16
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
17
|
+
export declare function createSubscriptionWinbackContentFactory(input: {
|
|
18
|
+
subscriptionEmailContent: Pick<SubscriptionEmailContent, "winback">;
|
|
19
|
+
}): {
|
|
20
|
+
subscriptionWinbackEmailContent: (locale: Locale, vars: SubscriptionWinbackEmailVars, signoff: string) => SubscriptionWinbackEmailContent;
|
|
21
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { accentBox, button, heading, paragraph, } from "./blocks.js";
|
|
2
|
+
/** Bind host-selected copy and pure helpers without provider or environment access. */
|
|
3
|
+
export function createSubscriptionWinbackContentFactory(input) {
|
|
4
|
+
const { subscriptionEmailContent } = input;
|
|
5
|
+
function subscriptionWinbackEmailContent(locale, vars, signoff) {
|
|
6
|
+
const copy = subscriptionEmailContent.winback[locale];
|
|
7
|
+
const blocks = [
|
|
8
|
+
heading(copy.heading),
|
|
9
|
+
paragraph(copy.greeting(vars.firstName)),
|
|
10
|
+
paragraph(copy.intro),
|
|
11
|
+
];
|
|
12
|
+
if (copy.reasonsLabel) {
|
|
13
|
+
blocks.push(paragraph(copy.reasonsLabel(vars.brandName)));
|
|
14
|
+
}
|
|
15
|
+
blocks.push(accentBox(copy.reasons));
|
|
16
|
+
if (vars.ctaUrl) {
|
|
17
|
+
blocks.push(button(copy.cta, vars.ctaUrl));
|
|
18
|
+
}
|
|
19
|
+
blocks.push(paragraph(copy.outro));
|
|
20
|
+
blocks.push(paragraph(signoff));
|
|
21
|
+
return {
|
|
22
|
+
subject: copy.subject,
|
|
23
|
+
preheader: copy.preheader,
|
|
24
|
+
blocks,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return { subscriptionWinbackEmailContent };
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface TransactionalDeliveryCommand {
|
|
2
|
+
readonly idempotencyKey: string;
|
|
3
|
+
/** Opaque recipient reference; never persisted outside the command fingerprint. */
|
|
4
|
+
readonly recipientReference: string;
|
|
5
|
+
/** Opaque template reference; never persisted outside the command fingerprint. */
|
|
6
|
+
readonly templateReference: string;
|
|
7
|
+
/** Optional bounded reference to an expiring/revocable access grant. */
|
|
8
|
+
readonly accessGrantReference?: string;
|
|
9
|
+
}
|
|
10
|
+
export type TransactionalDeliveryReceipt = {
|
|
11
|
+
readonly idempotencyKey: string;
|
|
12
|
+
readonly commandFingerprint: string;
|
|
13
|
+
readonly state: "accepted";
|
|
14
|
+
readonly deliveryReference: string;
|
|
15
|
+
readonly errorCode: null;
|
|
16
|
+
readonly attemptCount: number;
|
|
17
|
+
} | {
|
|
18
|
+
readonly idempotencyKey: string;
|
|
19
|
+
readonly commandFingerprint: string;
|
|
20
|
+
readonly state: "failed";
|
|
21
|
+
readonly deliveryReference: null;
|
|
22
|
+
readonly errorCode: string;
|
|
23
|
+
readonly attemptCount: number;
|
|
24
|
+
};
|
|
25
|
+
export interface TransactionalDeliveryReceiptStore {
|
|
26
|
+
readReceipt(idempotencyKey: string): Promise<TransactionalDeliveryReceipt | null>;
|
|
27
|
+
recordAccepted(input: {
|
|
28
|
+
idempotencyKey: string;
|
|
29
|
+
commandFingerprint: string;
|
|
30
|
+
deliveryReference: string;
|
|
31
|
+
}): Promise<TransactionalDeliveryReceipt>;
|
|
32
|
+
recordFailed(input: {
|
|
33
|
+
idempotencyKey: string;
|
|
34
|
+
commandFingerprint: string;
|
|
35
|
+
errorCode: string;
|
|
36
|
+
}): Promise<TransactionalDeliveryReceipt>;
|
|
37
|
+
}
|
|
38
|
+
export interface CapturedTransactionalDeliveryPort {
|
|
39
|
+
deliver(command: TransactionalDeliveryCommand): Promise<{
|
|
40
|
+
deliveryReference: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|