Mails: Produktname statt fest Wartungs-Abo, neue Mails für Wechsel und Zusatzspeicher, UptimeRobot-Hinweis
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
0d2254ca20
commit
19f9c747a2
24 files changed
+407
-100
No files matched your search
@@ -197,6 +197,7 @@ async function stripeContext(doc, customer) {
|
||||
stripeSub,
|
||||
wartungPriceId,
|
||||
aboItemId: items.aboItemId,
|
||||
storageUnits: items.storageUnits,
|
||||
// Beginn des laufenden Abrechnungsmonats (seit API 2025-03 am Posten)
|
||||
periodStart: aboItem?.current_period_start ?? stripeSub.current_period_start ?? null,
|
||||
paymentMethod: paymentMethodTypeOf(stripeSub),
|
||||
@@ -297,7 +298,7 @@ async function sendSwitchConfirmation(contact, consent, facts, blocks) {
|
||||
name: contact.name,
|
||||
wechseltag: fmtDate(facts.switchedAt),
|
||||
betrag_heute: fmtEuro(facts.amountDueCents),
|
||||
monatspreis: fmtEuro(config.products.wartung.amountCents),
|
||||
monatspreis: fmtEuro(monthlyCentsOf(facts)),
|
||||
mindestlaufzeit_bis: fmtDate(addMonths(new Date(facts.switchedAt), 12)),
|
||||
bedingungen_version: consent.termsVersion,
|
||||
zustimmung_zeitpunkt: fmtDateTime(consent.consentAt),
|
||||
@@ -317,13 +318,19 @@ async function sendSwitchNotice(contact, subscriptionId, facts, blocks) {
|
||||
stripe_abo_id: subscriptionId,
|
||||
wechseltag: fmtDate(facts.switchedAt),
|
||||
betrag_heute: fmtEuro(facts.amountDueCents),
|
||||
monatspreis: fmtEuro(config.products.wartung.amountCents),
|
||||
monatspreis: fmtEuro(monthlyCentsOf(facts)),
|
||||
},
|
||||
blocks,
|
||||
})
|
||||
}
|
||||
|
||||
const factsOf = (event) => ({ switchedAt: event.details?.switchedAt || event.consentAt, amountDueCents: event.details?.amountDueCents })
|
||||
const factsOf = (event) => ({
|
||||
switchedAt: event.details?.switchedAt || event.consentAt,
|
||||
amountDueCents: event.details?.amountDueCents,
|
||||
monthlyCents: event.details?.monthlyCents,
|
||||
})
|
||||
// Monatsgebuehr fuer {{monatspreis}}; Ereignisse von vor Task 12 kennen nur den Katalogpreis des Wartungs-Abos
|
||||
const monthlyCentsOf = (facts) => facts.monthlyCents ?? config.products.wartung.amountCents
|
||||
|
||||
// Ein Wechsel je Abo zur selben Zeit (Doppelklick): der zweite wartet nicht, sondern bekommt 409
|
||||
const switchesInFlight = new Set()
|
||||
@@ -370,6 +377,8 @@ async function performSwitch(customerId, current, consent, prorationDate, expect
|
||||
}
|
||||
const sepa = ctx.paymentMethod === 'sepa_debit'
|
||||
const switchedAt = new Date().toISOString()
|
||||
// Spec 3.5: Monatsgebuehr des Wartungs-Abos mit gebuchtem Zusatzspeicher ({{monatspreis}} in K12 und T12)
|
||||
const monthlyCents = config.products.wartung.amountCents + ctx.storageUnits * config.mailStorage.amountCents
|
||||
// Stripe-Metadaten am Abo (max. 500 Zeichen je Wert): Produkt, neue Mindestlaufzeit, Nachweis der Zustimmung
|
||||
const metadata = {
|
||||
productKey: 'wartung',
|
||||
@@ -421,6 +430,7 @@ async function performSwitch(customerId, current, consent, prorationDate, expect
|
||||
details: {
|
||||
from: 'hosting',
|
||||
amountDueCents,
|
||||
monthlyCents,
|
||||
prorationDate,
|
||||
switchedAt,
|
||||
paymentMethod: ctx.paymentMethod,
|
||||
@@ -440,10 +450,10 @@ async function performSwitch(customerId, current, consent, prorationDate, expect
|
||||
})
|
||||
|
||||
const contact = { name: customer?.name || '', email: customer?.email || doc.email || '' }
|
||||
const facts = { switchedAt, amountDueCents }
|
||||
const facts = { switchedAt, amountDueCents, monthlyCents }
|
||||
const lastschrift = sepa && effective && !paid
|
||||
// K12 sofort; zahlung_offen nur bei zurueckgehaltener Aenderung (Karte), lastschrift bei SEPA (F37)
|
||||
await sendSwitchConfirmation(contact, consent, facts, { zahlung_offen: pending, lastschrift })
|
||||
await sendSwitchConfirmation(contact, consent, facts, { zahlung_offen: pending, lastschrift, zusatzspeicher: ctx.storageUnits > 0 })
|
||||
// T12 erst, wenn der Wechsel wirksam ist - ohne Nachweis sofort, damit das Team ihn von Hand verfolgt
|
||||
if (effective || !event) {
|
||||
await sendSwitchNotice(contact, doc.$id, facts, { wirksam: effective, lastschrift, nachweis_fehlt: !event })
|
||||
@@ -623,7 +633,7 @@ export async function handleSwitchPaymentFailed(invoice) {
|
||||
if (d.invoiceId && d.invoiceId !== invoice.id) return { skipped: 'nicht die Wechselrechnung' }
|
||||
if (d.lastschriftFailedAt) return { skipped: 'schon gemeldet' }
|
||||
await updateContractEvent(event.$id, { details: { lastschriftFailedAt: new Date().toISOString(), invoiceId: invoice.id } })
|
||||
await sendSwitchNotice(contact, subscriptionId, { switchedAt: d.switchedAt, amountDueCents: invoice.amount_due }, { lastschrift_gescheitert: true })
|
||||
await sendSwitchNotice(contact, subscriptionId, { switchedAt: d.switchedAt, amountDueCents: invoice.amount_due, monthlyCents: d.monthlyCents }, { lastschrift_gescheitert: true })
|
||||
return { notified: true, eventId: event.$id }
|
||||
})
|
||||
}
|
||||
@@ -636,7 +646,11 @@ export async function handleSwitchPaymentFailed(invoice) {
|
||||
await sendSwitchNotice(
|
||||
contact,
|
||||
subscriptionId,
|
||||
{ switchedAt: new Date(switchedAt).toISOString(), amountDueCents: invoice.amount_due },
|
||||
{
|
||||
switchedAt: new Date(switchedAt).toISOString(),
|
||||
amountDueCents: invoice.amount_due,
|
||||
monthlyCents: config.products.wartung.amountCents + describeItems(stripeSub.items?.data).storageUnits * config.mailStorage.amountCents,
|
||||
},
|
||||
{ lastschrift_gescheitert: true, nachweis_fehlt: true }
|
||||
)
|
||||
return { notified: true, eventId: null }
|
||||
|
||||
Reference in new issue
Block a user