Stripe-tapahtuman käsittely
Yhteenveto
- Laukaisin:
POST /api/v1/billing/stripe/webhook(deployment/template.yaml:810) → oma lambdaStripeWebhookFunction, handlerstripe_webhook/handler.handler - Lopputulos: tenantti on provisioitu tai sen tilaustila ja tuoteoikeudet ovat päivittyneet. Epäonnistuessa kuorma on DLQ-jonossa ja Stripe uusii.
- Idempotentti: kyllä, mutta vain valmistuneille tapahtumille. Tapahtumalukko torjuu uusinnat vasta, kun ensimmäinen käsittely on merkitty valmiiksi — ks. Stripe-tapahtumalukko.
Tämä on ainoa moduulin sisääntulopiste, joka ei tule VeraFramen omasta
käyttöliittymästä. Käsittelijä on tarkoituksella ohut: se tunnistaa tenantin ja
delegoi säännöt saas_onboarding.py:lle, jotta sama logiikka toimii myös
skripteistä ja kirjautumisesta.
Sekvenssi
sequenceDiagram
participant Stripe
participant WH as stripe_webhook.handler
participant Lock as StripeWebhookEventTable
participant Onb as saas_onboarding
participant Store as CONFIG_BUCKET
participant DLQ as SQS DLQ
Stripe->>WH: POST /api/v1/billing/stripe/webhook
WH->>WH: verify_stripe_signature (HMAC, v1)
WH->>Lock: _try_claim_event (ehdollinen put_item)
Lock-->>WH: claimed | processed
alt jo käsitelty
WH-->>Stripe: 200 { duplicate: true }
else
WH->>WH: tapahtumatyypin haarautus
WH->>Onb: provision_saas_tenant / update_saas_subscription
Onb->>Store: config.json (koko dokumentti)
WH->>Lock: _mark_event_processed
WH-->>Stripe: 200 { received: true }
end
Note over WH,DLQ: poikkeus → _release_event_claim + _send_to_dlq → 500
Vaiheet
Vaihe 1 — Allekirjoituksen tarkistus
- Mitä:
Stripe-Signature-otsikko jäsennetään (t=aikaleima,v1=allekirjoitukset) ja verrataan HMAC-SHA256-arvoon salaisuudellaSTRIPE_WEBHOOK_SECRET. Vertailu on vakioaikainen (hmac.compare_digest), ja mikä tahansav1-arvo riittää. - Koodi:
stripe_webhook/handler.py:45, tarkistinsaas_onboarding.py:905(verify_stripe_signature) - Data sisään: raaka
body-merkkijono (jäsentämätön, koska allekirjoitus lasketaan siitä sellaisenaan) →400tai jäsennetty JSON - Huom: aikaleimaa
tei verrata nykyhetkeen. Replay-suojaa ei siis ole allekirjoitustasolla — sen hoitaa tapahtumalukko (vaihe 2), mutta vain 30 vrk TTL:n ajan.
Vaihe 2 — Idempotenssivaraus
- Mitä:
stripe#{event_id}-rivi varataan ehdollisella kirjoituksella. Jos rivi on jo tilassaprocessed, palautetaan heti200 {duplicate: true}käsittelemättä mitään. - Koodi:
stripe_webhook/handler.py:57,stripe_webhook/handler.py:190(_try_claim_event) - Data: Stripe-tapahtumalukko
- Huom: jos
idpuuttuu kuormasta, koko lukko ohitetaan (if event_id:). Sama koskee tilannetta, jossa taulun nimi puuttuu ympäristöstä.
Vaihe 3 — Tenantin tunnistus
- Mitä: ensisijainen lähde on tapahtuman metadatan
customer_id. Jos se puuttuu, tenantti etsitään Stripencustomer-tunnuksella selaamalla koko konfiguraatiosäiliö läpi ja vertaamallasaas.stripe_customer_id-kenttää. - Koodi:
stripe_webhook/handler.py:137(_resolve_customer_id),saas_onboarding.py:676(find_customer_config_by_stripe_customer_id) - Data: Tenantin saas-lohko (ei näytteessä)
- Miksi varapolku: Stripe-portaalissa tehdyt muutokset ja uudelleenaktivoinnit eivät aina kanna alkuperäistä metadataa.
- Huom: varapolku on
list_objects(bucket, "")eli täysi listaus jaget_objectjokaiselle*/config.json-avaimelle. Kustannus kasvaa lineaarisesti tenanttien määrässä ja se maksetaan joka kerta, kun metadata puuttuu.
Vaihe 4 — Tapahtumatyypin haarautus
- Mitä: viisi tunnettua tapahtumaa; tuntematon tyyppi kuitataan
200:lla ilman toimenpiteitä. - Koodi:
stripe_webhook/handler.py:63–124 - Data ulos: Tenantin saas-lohko (ei näytteessä) + Tuoteominaisuudet ja compliance (ei näytteessä)
| Tapahtuma | Toimenpide | Tila johon kirjoitetaan | Metadatan lähde |
|---|---|---|---|
checkout.session.completed |
Tenantin provisiointi (ei näytteessä) | active |
session metadata |
customer.subscription.updated |
Tilauksen tilan päivitys (ei näytteessä) | Stripen tila sellaisenaan, oletus active |
tilauksen metadata |
customer.subscription.deleted |
Tilan päivitys | canceled → armonaika käynnistyy |
tilauksen metadata |
invoice.payment_failed |
Tilan päivitys | past_due → kaikki tuotteet kiinni |
subscription_details.metadata tai metadata |
invoice.paid |
Tilan päivitys | active |
subscription_details.metadata tai metadata |
Kaksi yksityiskohtaa, jotka kannattaa tietää:
checkout.session.completedei tarkista tenanttitunnuksen olemassaoloa. Jos metadatasta puuttuucustomer_idjacompany_name,suggest_customer_idnormalisoi tyhjän merkkijonon muotoontenant(saas_onboarding.py:55) ja provisioi tenantin nimeltätenant. Muissa haaroissa tyhjä tunnus johtaa toimettomuuteen (if customer_id:), mutta tässä haarassa ei ole vastaavaa vartijaa.invoice.payment_failedsulkee hallintapaneelin.past_dueosuuupdate_saas_subscriptionin kolmanteen haaraan, joka nollaa myösadmin_dashboard- jaexport_reports-liput. Maksun epäonnistuminen estää siis asiakkaalta myös laskutusportaaliin pääsyn, koska/saas/billing-portalon admin-reitti (handler_api.py:427,handler_api.py:447).
Vaihe 5 — Valmistuminen tai DLQ
- Mitä: onnistuessa rivi merkitään
processed-tilaan ja palautetaan200 {received, type}. Poikkeus vapauttaa varauksen, kirjoittaa kuorman DLQ-jonoon ja palauttaa500sanitoidulla virheviestillä (Stripen kuorman sisältöä ei kaiuteta ulos). - Koodi:
stripe_webhook/handler.py:125,stripe_webhook/handler.py:220(_mark_event_processed),stripe_webhook/handler.py:236(_release_event_claim),stripe_webhook/handler.py:246(_send_to_dlq) - Data: DLQ-viesti
{event_type, payload, error[:1000], captured_at}jonoonveraframe-stripe-webhook-dlq-{Environment}(deployment/template.yaml:265, viestin säilytys 14 vrk) - Valvonta: kaksi CloudWatch-hälytystä — lambdan virheet
(
deployment/template.yaml:814) ja DLQ:n viestimäärä (deployment/template.yaml:831). Molemmat ilmoittavat SNS-sähköpostiin, josAlertEmailon annettu.
Virhetilanteet
| Tilanne | Käytös | Kutsujalle | Koodi |
|---|---|---|---|
STRIPE_WEBHOOK_SECRET puuttuu |
ei käsittelyä | 503 Stripe webhook ei ole konfiguroitu |
stripe_webhook/handler.py:42 |
| Allekirjoitus ei täsmää tai puuttuu | ei käsittelyä | 400 Virheellinen Stripe signature |
stripe_webhook/handler.py:45 |
| Kuorma ei ole JSONia | ei käsittelyä | 400 Virheellinen JSON |
stripe_webhook/handler.py:48 |
| Tapahtuma jo käsitelty | ei käsittelyä | 200 {duplicate: true} |
stripe_webhook/handler.py:59 |
| Tunnistamaton tapahtumatyyppi | ei käsittelyä, merkitään käsitellyksi | 200 |
stripe_webhook/handler.py:125 |
| Tenanttia ei löydy metadatasta eikä Stripe-tunnuksella | ei käsittelyä, merkitään käsitellyksi | 200 |
stripe_webhook/handler.py:79, stripe_webhook/handler.py:93, stripe_webhook/handler.py:106, stripe_webhook/handler.py:117 |
| Käsittely heittää | varaus vapautetaan, kuorma DLQ:hun | 500 Sisainen palveluvirhe |
stripe_webhook/handler.py:127–stripe_webhook/handler.py:132 |
| DLQ-kirjoitus heittää | vain loki | (ei muuta vastausta) | stripe_webhook/handler.py:265 |
Hiljainen kuittaus on tässä tarkoituksellinen valinta: tuntematon tyyppi ja
löytymätön tenantti saavat 200:n, jotta Stripe ei uusi niitä ikuisesti. Se
tarkoittaa myös, ettei tenantin tunnistuksen epäonnistumisesta jää jälkeä
mihinkään — ei DLQ:hun eikä hälytykseen.
Liittyvät
- Datavirta: Stripe-tapahtumasta tuoteoikeuksiksi
- Prosessit: Tenantin provisiointi (ei näytteessä) · Tilauksen tilan päivitys (ei näytteessä) · Tilauksen täsmäytys Stripestä
- Liiketoimintaprosessi: Cloud-tilauksen elinkaari