Stripe-betalningar
På Zkrinet: lägg till blocket under Byggblock i panelen — ett klick, din egen kod rörs inte och en ögonblicksbild tas först. Installationsstegen nedan gäller om du kör PocketBase själv, till exempel efter en export.
Engångsköp och prenumerationer via Stripe Checkout, Stripes kundportal och en webhook som håller betalningar och prenumerationer uppdaterade i din databas. Kortuppgifter rör aldrig din server: kunden betalar på Stripes egen sida.
Testat mot PocketBase 0.40.4.
#Installation
- Kopiera
pb_hooks/ochpb_migrations/till din PocketBase och starta om. - Nycklar — välj ett av två sätt (miljön vinner om båda finns):
- I adminpanelen: skapa en rad i
zk_stripe_installningarmedhemlig_nyckel(sk_test_…/sk_live_…eller en begränsadrk_…) ochwebhook_hemlighet(whsec_…). Fälten är dolda och kan bara läsas av superusers. - Som miljövariabler:
ZK_STRIPE_HEMLIG_NYCKELochZK_STRIPE_WEBHOOK_HEMLIGHET.
- I adminpanelen: skapa en rad i
- Webhook i Stripe (Developers → Webhooks → Add endpoint):
- Adress:
https://din-app.se/api/zk/stripe/webhook - Händelser:
checkout.session.completed,checkout.session.async_payment_succeeded,checkout.session.async_payment_failed,checkout.session.expired,customer.subscription.created,customer.subscription.updated,customer.subscription.deleted - Kopiera Signing secret (
whsec_…) till steg 2.
- Adress:
- Priser: skapa produkter och priser i Stripe, lägg sedan in varje pris du vill sälja i
zk_stripe_priser(pris_id=price_…,namn,lage=engangellerprenumeration,aktiv). Bara priser i den listan kan köpas. - Ligger din webbapp på en annan adress än PocketBase? Lägg dess adresser i
tillatna_adresser(kommaseparerat, t.ex.https://min-app.se, https://www.min-app.se). Annars godtas bara appens egen adress (Inställningar → Application URL).
#Användning från appen
// Starta köp (användaren måste vara inloggad)
const { url } = await pb.send("/api/zk/stripe/kassa", { method: "POST", body: {
pris: "price_123",
lyckad_url: "https://min-app.se/tack?session={CHECKOUT_SESSION_ID}",
avbruten_url: "https://min-app.se/priser",
}})
location.href = url
// Har användaren en aktiv prenumeration? (användaren ser bara sina egna rader)
const aktiv = await pb.collection("zk_stripe_prenumerationer")
.getFirstListItem('status = "active" || status = "trialing"').catch(() => null)
// Kundportal: byta kort, säga upp, se kvitton
const { url: portal } = await pb.send("/api/zk/stripe/portal", { method: "POST", body: { retur_url: "https://min-app.se/konto" } })
Prislistan kan läsas utan inloggning: pb.collection("zk_stripe_priser").getFullList() (bara aktiva priser syns).
Lita på webhooken, inte på lyckad_url. Att kunden landar på tack-sidan bevisar ingenting — en betalning är betald först när raden i zk_stripe_betalningar har status = "betald".
#Swish & Klarna
Kassan är Stripe Checkout, så Swish och Klarna fungerar så fort de är påslagna hos Stripe. Inställningen betalmetoder i zk_stripe_installningar styr vad kassan skickar:
betalmetoder | Vad som händer |
|---|---|
| tomt (rekommenderat) | Kassan skickar ingen lista. Stripe visar de metoder du slagit på i Dashboard och som passar köpet (valuta, belopp, engång/prenumeration, kundens land). Du kan slå av och på metoder i Stripe utan att röra appen. |
card,swish,klarna | Bara de här metoderna erbjuds (skickas som payment_method_types). Använd det bara om en viss kassa måste vara begränsad. |
Varför tomt rekommenderas: Stripe rekommenderar själva dynamiska betalmetoder och döljer då automatiskt det som inte passar köpet (Stripe: integration options). Med en fast lista kan Dashboard inte lägga till något, och en metod som inte passar (t.ex. Swish på ett pris i euro) kan få Stripe att vägra hela kassan, i stället för att bara dölja metoden. Det sista har vi inte kunnat mäta mot riktiga Stripe, se Begränsningar.
Det här gäller enligt Stripes dokumentation (läst 7/11 2026):
| Swish | Klarna | |
|---|---|---|
| Valuta | Bara SEK, kunder i Sverige. 3–150 000 kr. | SEK, EUR, NOK, DKK, GBP, USD med flera. Beloppsgränser beror på land och betalsätt. |
| Prenumeration | Nej. "Checkout doesn't support Swish in subscription mode." | Ja (i Sverige: betala direkt och betala senare). |
| Besked om betalningen | Direkt ("immediate notification") | Stripe skriver bara "customer-initiated". Om beskedet kan dröja står inte på sidan, så pluginet räknar med att det kan göra det. Du får hela beloppet, och Klarna tar risken om kunden inte betalar. |
| Tvister | Finns inte. Kunden måste kontakta dig. | Ja |
| Källa | docs.stripe.com/payments/swish | docs.stripe.com/payments/klarna |
Swish i en prenumeration: finns swish i listan och priset är en prenumeration filtrerar kassan bort Swish innan Stripe anropas, svarar med borttagna_betalmetoder: ["swish"] och skriver en varning i PocketBase logg (Logs i adminpanelen). Blir listan tom (t.ex. bara swish) svarar kassan 400 med ett fel som förklarar varför, och Stripe anropas aldrig. Med tomt fält sköter Stripe själv det här.
Det här måste du göra i Stripe:
- Settings → Payments → Payment methods: slå på Klarna och Swish.
- Swish kräver ett godkännande från Stripe. Dokumentationen har ett formulär för att begära tillgång, och vissa branscher är spärrade (t.ex. alkohol, pantbanker, juridiska tjänster, påfyllning av digitala plånböcker). Klarna har egna regler: inga B2B-köp (Klarna rules).
- Priserna måste vara i SEK om du vill att Swish ska synas.
- Webhooken måste ta emot
checkout.session.async_payment_succeededochcheckout.session.async_payment_failed(de finns redan i listan under Installation).
Asynkrona betalningar: vissa metoder ger checkout.session.completed med payment_status = unpaid och utfallet kommer först senare. Raden i zk_stripe_betalningar går då vantar → betald (kvittot skickas först då) eller vantar → misslyckad. Ett utfall backar aldrig, inte heller om Stripe skickar händelserna i omvänd ordning. Enligt Stripe ger Swish direkt besked, så för Swish lär vantar vara ovanligt. För Klarna anger dokumentationen inte om beskedet kan dröja. Pluginet hanterar båda fallen, oavsett metod.
#Samlingar
| Samling | Vad | Vem läser |
|---|---|---|
zk_stripe_installningar | Nycklar (dolda), tillåtna adresser, kvittomejl på/av, betalmetoder | Bara superusers |
zk_stripe_priser | Vad som får säljas | Alla (bara aktiva) |
zk_stripe_betalningar | En rad per kassa: skapad → betald / vantar / misslyckad / utgangen | Användaren sina egna |
zk_stripe_prenumerationer | Stripes status (active, past_due, canceled …), periodslut, om den avslutas vid periodslut | Användaren sina egna |
zk_stripe_handelser | Varje mottagen händelse — skyddar mot dubbelhantering | Bara superusers |
#Kvittomejl (valfritt)
Är Mejl-pluginet installerat och kvittomejl påslaget i inställningarna skickas mallen kvitto när en betalning blir betald (med namn, produkt, belopp, datum och referens). Stripe kan också skicka egna kvitton — slå inte på båda om du inte vill att kunden får två.
#Säkerhet
- Webhookens signatur kontrolleras mot den råa kroppen (HMAC-SHA256), och händelser äldre än 5 minuter avvisas.
- Samma händelse hanteras bara en gång även om Stripe skickar den flera gånger. Går hanteringen fel svarar vi 500 så att Stripe försöker igen.
- Stripe garanterar inte ordningen på händelser. En betald betalning blir aldrig obetald av en sen händelse.
- Återhoppsadresserna (
lyckad_url,avbruten_url,retur_url) måste ligga på appens adress eller i vitlistan, så att din kassa inte kan användas för att skicka folk till en falsk sida. - Användare kan inte skapa eller ändra betalningsrader själva — bara webhooken och kassan skriver.
- Använd gärna en begränsad nyckel (
rk_…) med rättigheter bara för Checkout Sessions och Customer portal.
#Begränsningar
- Kassan kräver inloggad användare i samlingen
users(gästköp stöds inte i den här versionen). - Ett pris per köp (antal 1–100). Varukorg med flera olika priser stöds inte än.
- Återbetalningar och tvister speglas inte i databasen — hanteras i Stripes dashboard.
- Stripe-API-version låst till
2024-06-20. Där heter listanpayment_method_types. Från version2026-09-30.endivetar Stripe inte längre emot den parametern (den heter dåallowed_payment_method_types), så höjs versionen måste kassan ändras på samma gång. - Testerna kör mot en låtsas-Stripe. Hur riktiga Stripe svarar på en fast lista som inte passar köpet (t.ex. Swish och EUR) har vi inte mätt. Stripes fel skickas vidare som
502med Stripes egen text. - Kassan vet inte vilken valuta priset har (den sparar bara
price_…), och kan därför inte själv sortera bort Swish från priser som inte är i SEK. Stripe gör det med tomt fält. - Swish-referens (
payment_method_options[swish][reference]) går inte att sätta än.
#Testa
./bank.py stripe
24 tester mot en låtsas-Stripe (inget når internet): kassa, vitlistor, signatur (fel nyckel, gammal tid, saknat huvud), dubbletter, omkastad ordning, prenumerationens livscykel, behörighet, kundportal, kvittomejl, betalmetoder (dynamiska, fast lista, Swish bortfiltrerad i prenumeration med logg, ogiltiga värden) och asynkrona betalningar (vantar → betald/misslyckad, även omkastat). Säkerhetstesterna är verifierade med mutanter — varje skydd har brutits med flit och fångats.