Billig + Swedbank-pay
Vår/sommer 2020 ble Billig skrevet opp til å bruke Swedbank Pay (rebranding av PayEx etter det ble kjøpt opp av Swedbank) sitt nye API, ettersom det gamle APIet skulle bli lagt ned. Flyten er stort sett lik som til Billig + PayEx; den største forskjellen er at det nye APIet bruker JSON istedenfor SOAP XML.
Oversikt
Kjøpsflyten er omtrent slik:
- Brukeren konstruerer en handlekurv e.l. på frontenden til han/hun er fornøyd.
- I en kjøpsform på frontenden fylles epostadresse eller kortnummer inn, sammen med kortinformasjon. Denne POSTes til betalingsbackenden (som ligger på en egen maskin med svært begrenset tilgang), med mål /pay.
- /pay gjør all nødvendig initialisering mot Swedbank Pay-APIet, med et «create payment»-kall etterfulgt av et authorize-kall (som inneholder kortdataen til kunden). Dette lager en reservasjon i kundens bankkonto, men pengene blir ikke faktisk trukket før i neste steg (såkalt to-fase-kjøp). Kunden blir deretter videresendt til frontendens OK-side som viser billettene (eller error-side med feilmelding), eventuelt via en 3D-secure-roundtrip.
- process-purchases.pl fullfører reservasjonen (gjør “capture” med et nytt API-kall for å gjøre den om til et ekte trekk), sender epost med billetter og merker billettene som aktive i databasen.
Dersom det på noe tidspunkt oppstår en feil i prosessen (alt fra ugyldig epostadresse til manglende penger på konto) blir brukeren sendt til frontendens feilside, med et session-parameter. Session-parameteret peker til en spesiell tabell i Billig-databasen hvor feilmelding ligger, samt nok informasjon til å rekonstruere handlekurven (slik at brukeren ikke trenger å gjøre det selv, men heller kan korrigere kun det som er feil).
Det kan når som helst skje at denne prosessen stopper, enten fordi brukeren går lei, fordi strømmen eller nettet går, eller andre årsaker man ikke har tenkt på. Det går derfor jevnlig et timeout-script (process-purchases) som tar ansvar for å rydde opp gamle ordre, og kansellere dem. Mer om dette under.
Detaljert informasjon om APIet fra frontendens side finnes i Billig frontend-API. Dokumentasjonen på Swedbank Pay-APIet finnes her: https://developer.swedbankpay.com/payments/card/direct
Intern implementasjon
Tilstandsmaskin
Kjernen i betalingsimplementasjonen er at alle ordre ikke lenger bare har et «paid»-tidspunkt (som også fungerer til å se om ordren er betalt eller ikke), men to ekstra felter: Tilstandsnummer, og kanselleringsflagg. Det eksisterer constraints i databasen for å sikre at kun gyldige kombinasjoner av disse blir brukt.
Kanselleringsflagget blir satt dersom brukeren begynner på en ny ordre før den gamle er ferdig; dette eksisterer for å unngå dobbeltklikk-problematikk (brukeren klikket to ganger på «kjøp» raskt etter hverandre), og for å raskere få uferdige ordre gjennom systemet. Kanselleringsflagget betyr i praksis at ingen prosesser skal gå lenger i å fullføre ordren, men avbryte den så fort som mulig. For hver bruker kan kun én ikke-kansellert ordre eksistere samtidig (det er igjen constraints for dette).
Tilstandene en ordre kan gå gjennom er disse:
- 1. Ordren finnes i Billig.
- 2. Har initialisert i Swedbank Pay, venter på authorize (3D-Secure/bankklarering).
- 3. Penger er reservert men ikke trukket ennå (authorize er utført, men ikke capture).
- 4. Penger er trukket (capture er utført).
- 5. Epost med billetter i er sendt, billetten er aktivert.
- -1. Kjøpet ble avbrutt, ingen penger er trukket.
Ordre starter alltid i tilstand 1, og beveger seg så gjennom tilstandene én for én til de enten havner i tilstand 5 (hvor også «paid» er satt), eller blir kansellert og flyttet til tilstand -1 (hvor også kanselleringsflagget fjernes). Det gir ikke mening at en ordre i tilstand -1 eller 5 har kanselleringsflagget satt.
Merk at en ordre som er i tilstand 2 egentlig er i ukjent tilstand hos Swedbank Pay; den i prinsippet være i enten 2, 3, 4 eller -1 avhengig av hva brukeren har gjort i ettertid. For sikkerhets skyld kan også ordre i sjeldne tilfeller endre seg i PayEx etter at vi har flyttet den til -1 (som regel at det er blitt trukket penger etter at vi tror at en ordre har timet ut). Den eneste måten vi kan finne ut av det på, er ved at PayEx kaller en spesiell callback-URL hos oss; dersom vi for en kansellert ordre (tilstand -1) har fått callback etter siste complete, flyttes den tilbake til tilstand 2, med «cancel»-flagget satt. Tidspunktene for callback og complete måles med en SQL-sekvens, for å unngå hjørnetilfeller når systemklokken beveger seg bakover o.a.. Dessuten er det tatt spesielt hensyn til om vi skulle få callback mens vi kjører complete på samme ordre: Tidspunktet som er lagret hentes ut før complete-kallet (men committes først etter at kallet har gått gjennom og vi har gjort alle endringer vi skal gjøre basert på det). Slik sørger vi for å avgjøre races i den konservative retningen (et ekstra complete-kall er ufarlig).
Det er ytterst sjelden at en ordre er i tilstand 3 og kansellert samtidig; duplikatlogikken vil, dersom den ser en annen ordre i tilstand 3, kansellere den nye ordren i stedet for den gamle. Det eneste tilfellet hvor denne kombinasjonen kan eksistere i praksis, er i et hjørnetilfelle i timeout-scriptet (se under), eller når Swedbank Pay informerer oss lenge after-the-fact (via en callback) om at en ordre vi trodde var kansellert, i virkeligheten er betalt (hvor den da blir i tilstand 2 med cancel-flagget satt, som kan flytte den til tilstand 3 med cancel-flagget satt). En ordre er aldri i tilstand 4 samtidig som kanselleringsflagget er satt.
Korrekthet
Flere prosesser (f.eks. webgrensesnittet og en eller flere instanser av timeout-scriptet) kan ønske å endre på tilstanden eller kanselleringsflagget. Det er derfor innført et låsesystem (ved hjelp av SELECT FOR UPDATE) likt det man ofte finner i «vanlig» låsbasert (ofte objektorientert) programmering. Vanlige regler burde være kjent for de som skal endre på dette systemet, men en grei oppsummering er:
- Aldri endre på tilstanden til en ordre (herunder kanselleringsflagget) uten først å ha tatt en lås.
- Hold låser så kort tid som mulig; husk at du kan blokkere andre lesere.
- Aldri anta at tilstanden ikke har endret seg med mindre du holder låsen.
- Følge av de to forrige punktene: Aldri hold en lås under soving. Spesifikt betyr dette at om du skal gjøre et web service-kall, må du slippe opp låsen først, gjøre kallet, ta låsen igjen, og sjekke hva tilstanden nå er før du gjør noe mer.
Timeout
Timeoutscriptet (process-purchases.pl) går jevnlig fra cron på okkupasjon, og tar ansvar for ordre som enten er gått ut eller markert som kansellert, i tillegg til at det gjør capture på ordre og sender ut eposter med billetter. Logikken er relativt enkel:
- Tilstand 1: Flytt til -1.
- Tilstand 2: Gjør get-kall mot /payments i Swedbank Pay for å finne ut om ordren egentlig er i 3, 4 eller -1.
- Tilstand 3, cancel-flagget satt: Kanseller reservasjonen, flytt til -1. (Merk: Denne kombinasjonen kan bare genereres av timeout.pl selv.)
- Tilstand 3, cancel-flagget ikke satt: Gjør capture. Flytt til 4.
- Tilstand 4, cancel-flagget ikke satt: Send epost med billetter i, flytt til 5.
Ved flytting til -1 (uansett fra-tilstand): Om cancel-flagget ikke er satt, send epost til brukeren for å informere om at ordren ble avbrutt. På den måten slipper brukeren å lure på om ordren gikk gjennom eller ikke, og får dessuten en link (lignende error-linkene i webgrensesnittet) hvor han/hun kan ta opp igjen kjøpet om ønskelig. (Eposten er tenkt informativ og ikke reklamerende, men vil nok likevel ha en viss salgseffekt.)
Lenker: Start, billig, billig generelt, billig stripe, historie
Epost: itk@samfundet.no | Telefon: 992 15 925 | Sist endret: 2020-09-04 13:54 | Revisjon: 8 (historie, blame) | Totalt: 1905 kB | Rediger