Wechsel: Lastschrift-Fehler nur für den echten Wechsel, Abgleich ohne Webhook, Gutschrift netto, Skript prüft Stripe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc20ce0673
commit
662f8d8144
7 files changed
+528
-76
No files matched your search
+23
-10
@@ -17,6 +17,7 @@ import {
|
||||
handleSwitchInvoicePaid,
|
||||
handleSwitchPaymentFailed,
|
||||
previewSwitch,
|
||||
reconcilePendingSwitch,
|
||||
switchToWartung,
|
||||
} from '../services/planSwitch.js'
|
||||
import { PRODUCT_KEYS, productView, termsDocFor } from '../services/products.js'
|
||||
@@ -156,16 +157,25 @@ function isPilotAllowed(customer, offer = null) {
|
||||
router.get('/', requireSession, async (req, res) => {
|
||||
try {
|
||||
const customerId = getSessionCustomerId(req)
|
||||
const [sub, customer, events] = await Promise.all([
|
||||
getCurrentSubscription(customerId),
|
||||
getDocument(config.collections.customers, customerId).catch(() => null),
|
||||
// Ohne lesbare Ereignisse (z. B. Collection noch nicht angelegt) bleibt die Seite nutzbar: die
|
||||
// Widerrufsfrist des Abschlusses zaehlt dann ab startedAt (F14), offene Aufgaben fehlen
|
||||
// Ohne lesbare Ereignisse (z. B. Collection noch nicht angelegt) bleibt die Seite nutzbar: die
|
||||
// Widerrufsfrist des Abschlusses zaehlt dann ab startedAt (F14), offene Aufgaben fehlen
|
||||
const readEvents = () =>
|
||||
listContractEvents(customerId).catch((e) => {
|
||||
console.error('[abo] Vertragsereignisse nicht lesbar:', e.message)
|
||||
return []
|
||||
}),
|
||||
})
|
||||
let [sub, customer, events] = await Promise.all([
|
||||
getCurrentSubscription(customerId),
|
||||
getDocument(config.collections.customers, customerId).catch(() => null),
|
||||
readEvents(),
|
||||
])
|
||||
// Wechsel wartet laenger als Stripe eine Aenderung zurueckhaelt (Webhook nicht angekommen): mit Stripe
|
||||
// abgleichen (Task 9, Fix 1); Fehler landen nur im Log
|
||||
const pendingSwitch = sub && events.find((e) => e.type === 'wechsel' && e.status === 'offen' && e.subscriptionId === sub.$id)
|
||||
if (pendingSwitch && (await reconcilePendingSwitch(pendingSwitch))) {
|
||||
sub = await getCurrentSubscription(customerId)
|
||||
events = await readEvents()
|
||||
}
|
||||
const active = Boolean(sub && ACTIVE_STATUSES.has(sub.status))
|
||||
const [ledger, offer] = await Promise.all([
|
||||
// Arbeitszeit gibt es nur im Wartungs-Abo
|
||||
@@ -339,7 +349,8 @@ router.post('/switch/preview', requireSession, async (req, res) => {
|
||||
})
|
||||
|
||||
/**
|
||||
* Bestellung { acceptTerms: true, earlyStart: true, termsVersion, prorationDate } ("Zahlungspflichtig bestellen").
|
||||
* Bestellung { acceptTerms: true, earlyStart: true, termsVersion, prorationDate, amountDueCents } ("Zahlungspflichtig
|
||||
* bestellen"); prorationDate und amountDueCents kommen aus der Vorschau.
|
||||
* Nachweis: Fassung, Zeitpunkt, IP, Browser und die Zustimmung zum Sofortbeginn (§ 357a Abs. 2 BGB) landen am
|
||||
* Abo und im Ereignis `wechsel`. Antwort: die Abo-Ansicht wie in GET /api/abo und paymentUrl (Karte: Zahlung
|
||||
* noch zu bestaetigen, das Portal leitet zur Stripe-Zahlungsseite weiter; sonst null).
|
||||
@@ -349,14 +360,14 @@ router.post('/switch', requireSession, async (req, res) => {
|
||||
if (req.session.viewAs) {
|
||||
return res.status(403).json({ error: 'Im "Als Kunde ansehen"-Modus kann nicht gewechselt werden.' })
|
||||
}
|
||||
const { acceptTerms, earlyStart, termsVersion, prorationDate } = req.body || {}
|
||||
const { acceptTerms, earlyStart, termsVersion, prorationDate, amountDueCents } = req.body || {}
|
||||
if (acceptTerms !== true) {
|
||||
return res.status(400).json({ error: 'Bitte stimmen Sie den Vertragsbedingungen zu, um zu wechseln.' })
|
||||
}
|
||||
if (earlyStart !== true) {
|
||||
return res.status(400).json({ error: 'Bitte bestätigen Sie, dass wir sofort mit dem Wartungs-Abo beginnen sollen.' })
|
||||
}
|
||||
if (!termsVersion || !Number.isInteger(prorationDate)) {
|
||||
if (!termsVersion || !Number.isInteger(prorationDate) || !Number.isInteger(amountDueCents)) {
|
||||
return res.status(400).json({ error: 'Die Bestellung ist unvollständig. Bitte laden Sie die Seite neu.' })
|
||||
}
|
||||
if (termsVersion !== config.abo.termsVersion) {
|
||||
@@ -367,7 +378,9 @@ router.post('/switch', requireSession, async (req, res) => {
|
||||
const { subscription, paymentUrl } = await switchToWartung(
|
||||
customerId,
|
||||
{ termsVersion, consentAt: now, earlyStartConsentAt: now, consentIp: req.ip, consentUserAgent: req.get('user-agent') || '' },
|
||||
prorationDate
|
||||
prorationDate,
|
||||
// Fix 1 (8): der in der Bestelluebersicht gezeigte Betrag; weicht die neue Vorschau ab, 409
|
||||
{ expectedAmountCents: amountDueCents }
|
||||
)
|
||||
const events = await listContractEvents(customerId).catch((e) => {
|
||||
console.error('[abo] Vertragsereignisse nicht lesbar:', e.message)
|
||||
|
||||
Reference in new issue
Block a user