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:
ClaudeandClaude Opus 5.5 committed 2026-09-29 09:24:02 +00:00
1 parent bc20ce0673
commit 662f8d8144
7 files changed
+528 -76

No files matched your search

+19 -9
View File
@@ -8,10 +8,11 @@ import http from 'node:http'
* Abo-Änderungen (F17): POST /v1/subscriptions/:id mischt items[i][id|price|quantity|deleted] und
* metadata (leerer Wert löscht) in das gespeicherte Abo; st.nextSubscriptionUpdate wird einmalig nur
* in die nächste Antwort gemischt (z. B. latest_invoice). Enthält er pending_update, bleibt das Abo wie bei
* Stripe unverändert (ausstehende Aktualisierung, Task 9), bis st.applyPendingUpdate(id) die Änderung
* übernimmt. Wie Stripe: mit payment_behavior=pending_if_incomplete nur die dafür erlaubten Parameter,
* Metadaten-Schlüssel höchstens 40 und Werte höchstens 500 Zeichen. Rechnungen: GET /v1/invoices/:id
* bettet wie Stripe höchstens 10 Zeilen ein, GET /v1/invoices/:id/lines blättert (limit, starting_after);
* Stripe unverändert und trägt pending_update (ausstehende Aktualisierung, Task 9), bis st.applyPendingUpdate(id)
* die Änderung übernimmt oder st.expirePendingUpdate(id) sie verwirft. Wie Stripe: mit
* payment_behavior=pending_if_incomplete nur die dafür erlaubten Parameter, Metadaten-Schlüssel höchstens 40
* und Werte höchstens 500 Zeichen. Rechnungen: GET /v1/invoices/:id bettet wie Stripe höchstens 10 Zeilen ein,
* GET /v1/invoices/:id/lines blättert (limit, starting_after);
* POST /v1/invoices/create_preview liefert st.invoicePreview (Objekt, oder Funktion des Aufruf-Bodys).
*/
@@ -157,8 +158,10 @@ export async function startFakeStripe() {
nextSubscriptionUpdate = null
if (extra?.pending_update) {
// Zahlung nicht erfolgt (Task 9): Stripe haelt die Aenderung in pending_update zurueck, das Abo bleibt
// unveraendert, bis die Rechnung bezahlt ist (im Test: applyPendingUpdate)
// unveraendert und traegt pending_update, bis die Rechnung bezahlt ist (applyPendingUpdate) oder die
// Aenderung verfaellt (expirePendingUpdate)
pendingUpdates.set(m[1], updated)
subscriptions.set(m[1], { ...existing, pending_update: extra.pending_update })
return [200, { ...existing, ...extra }]
}
subscriptions.set(m[1], updated)
@@ -372,8 +375,8 @@ export async function startFakeStripe() {
checkoutSessions,
/**
* Einmalig in die naechste Antwort von POST /v1/subscriptions/:id gemischt (z. B. latest_invoice). Mit
* pending_update bleibt das Abo wie bei Stripe unveraendert, bis applyPendingUpdate(id) die Aenderung
* uebernimmt.
* pending_update bleibt das Abo wie bei Stripe unveraendert (und traegt pending_update), bis
* applyPendingUpdate(id) die Aenderung uebernimmt oder expirePendingUpdate(id) sie verwirft.
*/
get nextSubscriptionUpdate() {
return nextSubscriptionUpdate
@@ -386,8 +389,15 @@ export async function startFakeStripe() {
const updated = pendingUpdates.get(id)
if (!updated) throw new Error(`Stripe-Nachbau: keine ausstehende Aktualisierung fuer ${id}`)
pendingUpdates.delete(id)
subscriptions.set(id, updated)
return updated
const applied = { ...updated, pending_update: null }
subscriptions.set(id, applied)
return applied
},
/** Zurueckgehaltene Aenderung verfaellt (Stripe storniert die Rechnung und verwirft pending_update). */
expirePendingUpdate(id) {
if (!pendingUpdates.has(id)) throw new Error(`Stripe-Nachbau: keine ausstehende Aktualisierung fuer ${id}`)
pendingUpdates.delete(id)
subscriptions.set(id, { ...subscriptions.get(id), pending_update: null })
},
/** Antwort von POST /v1/invoices/create_preview: Objekt oder Funktion (body) => Objekt; null = leere Vorschau. */
get invoicePreview() {