Case study

Uitbetalingen die nooit twee keer betalen

Een creator-marketplace betaalt zijn creators uit via Stripe Connect en PayPal. Code rond geld gaat anders stuk dan gewone code: bij een time-out weet je niet of het geld is overgemaakt. Zo heb ik het gebouwd, zodat geen retry, dubbele webhook of race iemand twee keer kan uitbetalen.

Het project

Een multi-tenant marketplace voor een Amerikaans creatief bureau. Merken plaatsen betaalde opdrachten, creators leveren content, en een goedgekeurde inzending wordt een uitbetaling. Elk merk heeft een eigen database, en de creator kiest zelf hoe hij betaald wordt: op een bankrekening via Stripe Connect, of via PayPal. Ik heb het hele platform ontworpen en gebouwd als enige developer.

2
betaalproviders achter één uitbetaalproces
104
geautomatiseerde tests op uitbetalingen en hun webhooks
10 min
tussen de controles die onze administratie vergelijken met de providers
1
plek in de code die de status van een uitbetaling mag wijzigen

Wat er mis kan gaan, en wat het tegenhoudt

Elk van deze situaties komt in de praktijk voor. Voor elk ervan is er een regel in de code en een test die het bewijst.

1

Iemand klikt twee keer op Betalen

Of twee beheerders klikken op hetzelfde moment. Voordat de provider überhaupt wordt aangeroepen, wordt de uitbetaling vergrendeld, gecontroleerd en opgeslagen als in behandeling. De tweede klik vindt een uitbetaling die niet meer betaalbaar is en wordt geweigerd. De aanroep naar Stripe of PayPal gebeurt pas nadat dat veilig is vastgelegd.

2

De aanvraag krijgt een time-out: is het geld overgemaakt?

Dit is de klassieke manier om dubbel te betalen: de verbinding valt weg, de code gaat uit van een mislukking, en iemand probeert het opnieuw. Hier wordt een onbeantwoorde aanvraag nooit als mislukt behandeld. De uitbetaling blijft in behandeling, want het geld kan al onderweg zijn.

Een nieuwe poging stuurt precies dezelfde referentie mee als de eerste, zodat de provider haar zelf herkent en weigert opnieuw te betalen. Die referentie schuift pas door als de provider duidelijk nee heeft gezegd.

3

Een bevestiging komt twee keer, te vroeg, of van een oude poging

Providers bevestigen betalingen met webhooks, en ze beloven minstens één aflevering, niet precies één.

  • Elke webhook wordt gecontroleerd op de handtekening van de provider voordat er iets gebeurt
  • Dezelfde bevestiging twee keer verwerken verandert de tweede keer niets
  • Een bevestiging kan eerder binnenkomen dan het antwoord van de provider op de aanvraag zelf; daarom wordt de uitbetaling eerst als in behandeling opgeslagen
  • Elke bevestiging draagt de poging waar ze bij hoort. Nieuws over een oudere poging wordt genegeerd, zodat een late mislukking een nieuwere betaling niet ongedaan kan maken
4

Een bevestiging komt nooit aan

Webhooks raken zoek. Zonder vangnet zou zo'n uitbetaling eeuwig blijven hangen, en zou iemand haar uiteindelijk met de hand betalen, voor de tweede keer. Daarom vraagt een geplande controle elke tien minuten rechtstreeks bij Stripe en PayPal na hoe het staat met elke uitbetaling die nog wacht, en handelt die af met het antwoord van de provider.

Dat geldt ook voor merken die gepauzeerd of gearchiveerd zijn, want geld dat onderweg is moet nog steeds aankomen. Eén merk met een probleem blokkeert de andere nooit.

5

De kleine lettertjes van de provider

De gevaarlijke gevallen zitten in de details van elke provider:

  • Stripe vergeet de referentie van een aanvraag na 24 uur. Een heel late nieuwe poging zoekt eerst op of de overboeking al bestaat, en stuurt niets als dat zo is
  • PayPal beantwoordt een herhaalde aanvraag met een foutmelding. Dat is geen weigering: de eerdere betaling bestaat. De code zoekt die op en gaat ermee verder, waar het als mislukt behandelen tot een tweede betaling zou leiden
  • PayPal-geld dat naar een adres zonder account is gestuurd, blijft onopgeëist staan. Het wordt geannuleerd, zodat het geld direct terugkomt en een nieuwe poging niet dubbel kan betalen als de creator zich later aanmeldt
  • Een overboeking die is teruggedraaid, of die niet overeenkomt met het bedrag, wordt nooit als betaald gemarkeerd
6

Het saldo van het platform is leeg

Dat is het probleem van het platform, niet van de creator. De uitbetaling gaat terug naar wachtend, met een duidelijke melding voor de beheerder om het saldo aan te vullen. De creator ziet geen verontrustend "mislukt" voor iets waar hij niets mee te maken heeft.

7

Iemand wijzigt waar het geld naartoe gaat

Bij elke wijziging van de uitbetaalbestemming krijgt de creator een e-mail, als struikeldraad tegen overname van een account. Links voor de bankgegevens zijn ondertekend en eenmalig, en worden nooit als kale link per e-mail verstuurd.

De regels die ik hanteer voor alle code die geld verplaatst

Niet alleen in dit project. Alles wat geld, tegoed, voorraad of een status wijzigt, gaat langs deze lijst.

Eén transactie

Wat bij elkaar hoort, slaagt of mislukt samen. Geen "aanmaken, en weer verwijderen als stap twee mislukt".

Vergrendelen wat je leest

Hangt de nieuwe waarde af van de huidige, dan wordt de rij vergrendeld en opnieuw gelezen voordat er iets besloten wordt.

De database blokkeert dubbelen

"Is dit al verwerkt?" is geen controle in de code, maar een unieke sleutel in de database.

Elke retry is veilig

Van elke webhook, taak en aanvraag ga ik ervan uit dat die twee keer, te laat of in de verkeerde volgorde binnenkomt.

Statuswijzigingen zijn stappen

Een uitbetaling kan alleen langs toegestane stappen bewegen, via één stuk code. Nooit een losse toewijzing.

Een vangnet erachter

Een geplande controle vergelijkt onze administratie met die van de provider en handelt af wat erdoorheen is geglipt.

Een kleine eerste stap

Code review voor een vaste prijs van €950

Weet je niet goed hoe je systeem ervoor staat, of wat een upgrade zou vragen? Ik lees de code en vertel het je, op papier, voordat je aan iets groters begint.

  • Ik lees de code en de dependencies van één applicatie
  • Een rapport in gewone taal: wat verouderd is, wat risico geeft en wat in orde is
  • Beveiligingsbevindingen, op volgorde van urgentie
  • Een stappenplan voor de upgrade of reparatie, met een vaste offerte
  • Een gesprek waarin ik het met je doorloop
€950
excl. btw, één applicatie

Laat je het werk uit het rapport door mij uitvoeren, dan gaat de €950 van die factuur af.

Is je systeem erg groot? Dan zeg ik dat voordat ik begin, niet achteraf.

Vraag een code review aan