Buchungskalender: Abschluss – S1 keine doppelte Erstattung nach Heilung

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5.5 committed 2026-10-07 15:20:23 +00:00
1 parent 2df56eccc5
commit 435dd0b746
3 files changed
+64 -11

No files matched your search

+30 -1
View File
@@ -261,7 +261,7 @@ test('Erstattung echt: Stripe-Aufruf mit Metadaten unterkunft und Idempotency-Ke
st.calls.length = 0
const r = await ab.storniere(ctx, id, { grund: 'Krank', erstattungCent: 12000, now: NOW })
assert.equal(r.anfrage.erstattetCent, 12000)
const [call] = st.calls.filter((c) => c.path === '/v1/refunds')
const [call] = st.calls.filter((c) => c.path === '/v1/refunds' && c.method === 'POST')
assert.equal(call.account, ACCOUNT)
assert.equal(call.body.payment_intent, 'pi_6')
assert.equal(call.body.amount, '12000')
@@ -280,6 +280,35 @@ test('Erstattung: Stripe scheitert -> 502, Storno bleibt, erstattetCent unveraen
assert.equal(a.erstattetCent ?? 0, 0)
})
test('Erstattung: Stripe ging durch, Speichern scheiterte, Webhook heilt -> Wiederholung erstattet nicht zweimal', async () => {
const { id, ctx } = await bezahlt('pi_h1')
st.setPaymentIntent(ACCOUNT, 'pi_h1', { amount: 20000 })
await ab.storniere(ctx, id, { now: NOW })
aw.failNext({ collection: 'ukAnfragen', method: 'PATCH', status: 500 })
await assert.rejects(ab.erstatteBetrag(ctx, id, { betragCent: 20000, now: NOW }))
assert.equal(refunds().length, 1)
assert.equal((await sp.ladeAnfrage(id)).erstattetCent ?? 0, 0)
await W.verarbeiteUkEreignis({ type: 'charge.refunded', account: ACCOUNT, data: { object: { id: 'ch_h1', payment_intent: 'pi_h1', amount_refunded: 20000, metadata: { wkUk: '1', webklar: 'unterkunft', anfrageId: id } } } }, NOW)
const r = await ab.storniere(ctx, id, { erstattungCent: 20000, now: NOW })
assert.equal(refunds().length, 1)
assert.equal(r.anfrage.erstattetCent, 20000)
})
test('Erstattung: Stripe ging durch, Speichern scheiterte, KEIN Webhook -> Wiederholung zieht nach, kein zweiter Refund; weiterer Betrag nutzt den Live-Stand', async () => {
const { id, ctx } = await bezahlt('pi_h2')
st.setPaymentIntent(ACCOUNT, 'pi_h2', { amount: 20000 })
await ab.storniere(ctx, id, { now: NOW })
aw.failNext({ collection: 'ukAnfragen', method: 'PATCH', status: 500 })
await assert.rejects(ab.erstatteBetrag(ctx, id, { betragCent: 5000, now: NOW }))
const r = await ab.storniere(ctx, id, { erstattungCent: 5000, now: NOW })
assert.equal(refunds().length, 1)
assert.equal(r.anfrage.erstattetCent, 5000)
const a = await ab.erstatteBetrag(ctx, id, { betragCent: 15000, now: NOW })
assert.equal(a.erstattetCent, 20000)
assert.equal(refunds().at(-1).idempotencyKey, `uk-erstattung-${id}-20000`)
assert.equal(erstattungen('pi_h2').reduce((s, x) => s + x.amount, 0), 20000)
})
test('Webhook charge.refunded: erstattetCent = Maximum, nie zurueck', async () => {
const { id, ctx } = await bezahlt('pi_8')
await ab.storniere(ctx, id, { now: NOW })