Přeskočit na obsah

Příliš široká skip-podmínka ve webhooku spolkne reálnou platbu (faktura se nevystaví)

import { Aside } from ‘@astrojs/starlight/components’;

Zákazníkovi se strhne předplatné z karty (banka i brána potvrzují platbu, subscription = aktivní), ale v systému se nevygeneruje faktura. Hromadně postihne účty, které platí mimo „čistý” scénář (vypršelý trial, opožděná aktivace) — desítky plateb bez dokladu, účetní/právní problém.

Handler na invoice.paid měl přeskakovat fakturu za start trialu (0 Kč, jen účetní událost). Skip ale použil i typ události:

const amountPaid = invoice.amount_paid || 0;
if (amountPaid === 0 || billingReason === 'subscription_create') { // ← příliš široké
break; // nevygeneruje fakturu
}

Háček: u zákazníka, jehož trial už vypršel, brána při založení subscription strhne plnou částku hned — a ta platba je na faktuře s billing_reason='subscription_create' (amount_paid > 0). Plošný skip podle subscription_create tedy spolkl i tyhle reálné platby. Čistý scénář (nový trial → 0 Kč subscription_create → skip → za 14 dní subscription_cycle s platbou) fungoval, takže bug zůstal dlouho skrytý.

Skipovat výhradně podle částky:

if (amountPaid === 0) { // jen skutečné 0 Kč trial-start události
break;
}
// jinak vygeneruj fakturu (subscription_create s platbou i subscription_cycle)

Náprava existujících: dohledat v lozích/bráně všechny přeskočené subscription_create faktury, které byly reálně zaplaceny (amount_paid > 0), a pro ty bez dokladu fakturu zpětně vygenerovat (idempotentně přes unikátní stripe_invoice_id). Re-trigger lze udělat podepsaným re-POSTem původní webhook události na endpoint.

Jak se tomu vyvarovat v jiných systémech

Sekce “Jak se tomu vyvarovat v jiných systémech”
  • Detection: grep webhook logů na „skip”/„ignored” u platebních událostí; pro každou přeskočenou ověř v bráně amount_paid. Cross-check: počet zaplacených subscription faktur v bráně vs. počet vystavených dokladů v systému — rozdíl = spolknuté platby.
  • Anti-pattern: skip platebního webhooku podle billing_reason / event.type / názvu plánu místo podle reálné částky; předpoklad „subscription_create = vždy trial = 0 Kč”.
  • Lepší přístup: rozhodnutí „vystavit doklad?” odvozuj jen od amount_paid > 0. Přidej smírčí kontrolu (reconciliation) — pravidelně porovnej zaplacené faktury v bráně s vystavenými doklady a alertuj na rozdíl.

Sister bugs / související

Sekce “Sister bugs / související”

Patří k rodině „webhook tiše spolkne kritický krok” — viz [[silent-constraint-rejection-error-as-value]]. Vždy: u peněz nikdy nespoléhej na typ události, ale na potvrzenou částku, a měj reconciliation.

Přidal aiarchitekt.cz · 15. 6. 2026 2:00
Provozuje aiarchitekt.cz