Finanzen: Zeitgrenzen fuer Stripe-Lesen, Schutz vor doppelt angelegten Rechnungen

I2: Leseanfragen an Stripe mit timeout 15 s und einer Wiederholung (auch Folgeseiten), die Uebersicht
wartet hoechstens 20 s auf Stripe und rechnet sonst ohne Stripe weiter.
I3: idempotencyKey vom Aufrufer an stripe.invoices.create, Schutz im Prozess gegen gleichzeitiges und
30 s spaeteres Doppelanlegen (409), 502 mit invoiceId/Nummer wenn die fertige Rechnung nicht lesbar ist.
M9: Warnung, wenn die Rechnungsliste bei 1000 abgeschnitten wird.
M11: Ursache eines 502 an einer bestehenden Rechnung (Art, Status, Code) im Protokoll.

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 ef2ce49669
commit daa451094a
8 files changed
+476 -48

No files matched your search

+7 -2
View File
@@ -46,7 +46,8 @@ const BANK_UNAVAILABLE_MESSAGE = 'Die Bank hat die Freigabe nicht bestätigt. Bi
const publicDoc = (doc) => Object.fromEntries(Object.entries(doc).filter(([key]) => key === '$id' || !key.startsWith('$')))
/** Fehlerart fuers Protokoll: nie die Meldung (sie koennte Kennungen der Bank oder von Appwrite enthalten). */
const logFailure = (what, err) => console.error(`[finanzen] ${what} fehlgeschlagen:`, err?.name || 'Error', err?.status ?? '', err?.code ?? '')
const logFailure = (what, err) =>
console.error(`[finanzen] ${what} fehlgeschlagen:`, err?.type || err?.name || 'Error', err?.status ?? err?.statusCode ?? '', err?.code ?? '')
router.get('/uebersicht', async (_req, res) => {
try {
@@ -148,6 +149,8 @@ const isStripeError = (err) => typeof err?.type === 'string' && err.type.startsW
*/
function sendFinanceError(res, what, err) {
if (err instanceof AssignError || err instanceof InvoiceError) {
// Ein 502 an einer bestehenden Rechnung (nicht gesendet, nicht lesbar) hat seine Ursache bei Stripe: nur Art, Status und Code ins Protokoll
if (err.status === 502 && err.cause) logFailure(what, err.cause)
const body = { error: err.message }
if (err.invoiceId) body.invoiceId = err.invoiceId
if (err.invoiceNumber !== undefined) body.invoiceNumber = err.invoiceNumber
@@ -205,7 +208,7 @@ router.get('/rechnungen', async (req, res) => {
})
router.post('/rechnungen', async (req, res) => {
const { customerId, items, daysUntilDue, memo, senden } = req.body || {}
const { customerId, items, daysUntilDue, memo, senden, idempotencyKey } = req.body || {}
try {
const rechnung = await createProjectInvoice({
customerId,
@@ -214,6 +217,8 @@ router.post('/rechnungen', async (req, res) => {
memo,
// Nur ein echtes Ja sendet sofort: alles andere bleibt ein Entwurf
senden: senden === true || senden === 'true',
// Optional: ein Schluessel je beabsichtigter Rechnung schuetzt vor doppeltem Anlegen (siehe createProjectInvoice)
idempotencyKey,
createdBy: req.appwriteUser?.email,
})
return res.json({ rechnung })