- Docs
- Integrations
- Payments
Payments
Create, refund, and fetch payments and verify provider webhooks with a stub, Stripe, or Paddle adapter.
payment.Payment creates, refunds, and fetches charges and decodes provider webhooks. Customers and subscriptions are Billing. The flow is identical across stub, stripe, and paddle.
Quick start
Section titled “Quick start”stub (the default) moves no money or network traffic. auto_approve: true marks payments succeeded immediately, testing the success path without a webhook.
PAYMENT_ADAPTER=stubPAYMENT_AUTO_APPROVE=truePAYMENT_MAX_WEBHOOK_BYTES=1048576Provider keys come from shared/providersopt: secret_key (Stripe), api_key (Paddle), endpoint, sandbox. Payment adds webhook_secret (never logged, keep in env), auto_approve, and max_webhook_bytes (default 1MiB, negative rejected). Env vars mirror the keys. See the payment config reference.
package main
import ( "context" "fmt"
paymentstub "github.com/zenta-dev/zever/adapters/payment/stub" "github.com/zenta-dev/zever/config" "github.com/zenta-dev/zever/container" "github.com/zenta-dev/zever/core/payment")
func main() { paymentstub.Register() // container wires nothing cfg := config.Default() cfg.Payment.Options.AutoApprove = true c := container.New(cfg) ctx := context.Background() defer c.Close(ctx)
p, err := c.Payment() if err != nil { panic(err) } res, _ := p.CreatePayment(ctx, payment.Request{Amount: 2500, Currency: "usd"}) fmt.Println("payment:", res.ID, string(res.Status))
got, _ := p.GetPayment(ctx, res.ID) fmt.Println("fetched:", got.ID, string(got.Status)) _ = p.Close()}Expected output with stub and auto_approve: true:
payment: stub_1 succeededfetched: stub_1 succeededWith auto_approve: false the same program prints pending; the webhook path flips it later.
Amount semantics
Section titled “Amount semantics”Every amount in payment.Request, payment.Result, and payment.Event is an int64 in minor units (cents), regardless of adapter.
stripereports native minor-unit integers, so values pass through untouched.paddlereports decimal strings in major units (e.g."10.00"). The adapter normalizes them to minor units:"10"and"10.00"both become1000,"10.99"becomes1099.- Sub-cent precision (more than two decimal places) rounds half away from zero, symmetrically for positive and negative totals:
"10.005"becomes1001,"-10.005"becomes-1001. - Totals that no longer fit
int64once scaled fail withErrAmountOverflowinstead of wrapping. - The reverse direction agrees: partial-refund adjustment amounts are formatted back to major-unit decimals (
1000is sent as"10.00").
Verify webhooks
Section titled “Verify webhooks”Decoding checks the provider signature against webhook_secret before touching the payload, under the max_webhook_bytes bound; a failure or oversized payload never reaches your handler as a trusted Event. Pass the raw body unmodified, plus the signature header value, to WebhookEvent(ctx, raw, signature). stub performs no verification and holds no funds, so it is test-only.
Choose an adapter
Section titled “Choose an adapter”| Adapter | Package | Notes |
|---|---|---|
stub |
adapters/payment/stub |
Default. No network, no real money. |
stripe |
adapters/payment/stripe |
Needs secret_key. |
paddle |
adapters/payment/paddle |
Needs api_key. |
Production checklist
Section titled “Production checklist”paymentnever stores card data (provider PCI scope) and is not a ledger; it initiates and reacts.SecretKey,APIKey, andWebhookSecretnever appear in logs or errors. Hold them in Secrets.Refund(ctx, id, amount, key)takes an idempotency key so a retry cannot double-refund.
Troubleshooting
Section titled “Troubleshooting”Stub payments stay pending in tests
Cause: auto_approve is false. Fix: set auto_approve: true (or PAYMENT_AUTO_APPROVE).
The webhook handler never fires
Cause: the signature check runs first. Fix: confirm PAYMENT_WEBHOOK_SECRET matches the provider and the raw body reaches WebhookEvent unmodified.
max_webhook_bytes rejects large events
Cause: a payload over the bound. Fix: raise the bound deliberately after checking the payload is legitimate.