Finanzen: Stripe-Rechnungen und Auszahlungen lesen (deutsch gemappt, 60-s-Cache)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-01 15:42:06 +00:00
1 parent 6af9834293
commit db243f50bb
4 files changed
+441

No files matched your search

+24
View File
@@ -14,6 +14,9 @@ import http from 'node:http'
* 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).
* Listen (Finanzen): GET /v1/invoices (expand data.customer ersetzt customer durch das Objekt aus st.customers,
* sofern vorhanden) und GET /v1/payouts (st.payouts) blaettern wie Stripe mit limit (hoechstens 100) und
* starting_after samt has_more; die Reihenfolge ist die Einfuegereihenfolge der Map.
*/
// Parameter, die Stripe bei ausstehenden Aktualisierungen annimmt (docs.stripe.com/billing/subscriptions/
@@ -32,6 +35,7 @@ export async function startFakeStripe() {
const coupons = new Map()
const billingPortalConfigurations = new Map()
const customers = new Map()
const payouts = new Map()
const checkoutSessions = new Map()
// Abo-Aenderungen, die Stripe bis zur Zahlung zurueckhaelt (pending_update), je Abo-ID
const pendingUpdates = new Map()
@@ -65,6 +69,15 @@ export async function startFakeStripe() {
const invoiceLinesOf = (invoiceId) =>
(invoices.get(invoiceId)?.lines?.data || []).map((line, i) => ({ id: `li_${invoiceId}_${i + 1}`, object: 'line_item', ...line }))
const EMBEDDED_LINES = 10
// Listenantwort wie Stripe: limit (Standard 10, hoechstens 100) und starting_after (ID des letzten Eintrags der Vorseite)
const pageOf = (items, query, url) => {
const limit = Math.min(Number(query.get('limit')) || 10, 100)
const after = query.get('starting_after')
const afterIndex = after ? items.findIndex((item) => item.id === after) : -1
if (after && afterIndex < 0) return invalid('starting_after', `No such object: '${after}'`)
const start = afterIndex + 1
return [200, { object: 'list', data: items.slice(start, start + limit), has_more: start + limit < items.length, url }]
}
const readBody = (req) =>
new Promise((resolve) => {
@@ -203,6 +216,16 @@ export async function startFakeStripe() {
prices.set(obj.id, obj)
return [200, obj]
}
if (method === 'GET' && path === '/v1/invoices') {
const expandCustomer = [...query.entries()].some(([k, v]) => k.startsWith('expand') && v === 'data.customer')
const all = [...invoices.values()].map((inv) =>
expandCustomer && typeof inv.customer === 'string' && customers.has(inv.customer) ? { ...inv, customer: customers.get(inv.customer) } : inv
)
return pageOf(all, query, '/v1/invoices')
}
if (method === 'GET' && path === '/v1/payouts') {
return pageOf([...payouts.values()], query, '/v1/payouts')
}
if (method === 'GET' && (m = path.match(/^\/v1\/invoices\/([^/]+)$/))) {
if (!invoices.has(m[1])) return noSuch('invoice', m[1])
// Wie Stripe: hoechstens 10 Zeilen eingebettet, weitere ueber GET /v1/invoices/:id/lines (has_more)
@@ -388,6 +411,7 @@ export async function startFakeStripe() {
coupons,
billingPortalConfigurations,
customers,
payouts,
checkoutSessions,
/**
* Einmalig in die naechste Antwort von POST /v1/subscriptions/:id gemischt (z. B. latest_invoice). Mit