# Payments

> Use when integrating payments (Stripe, Adyen, PayPal, Square, regional PSPs) — checkout, subscriptions, webhooks, refunds, disputes, reconciliation, PCI/SCA, iDEAL/SEPA/PIX.

- Skill: `muxammadmamajonov/payments` (Agent Skill)
- Install (CLI): `npx skillmds@latest add muxammadmamajonov/payments`
- Raw SKILL.md: https://api.skillmd.com/api/skills/muxammadmamajonov/payments/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: muxammadmamajonov (https://skillmd.com/u/muxammadmamajonov)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/muxammadmamajonov/payments

---


# Payments & Billing

## When to use
- Selecting or integrating a payment provider (Stripe, Adyen, PayPal, Square, Braintree, Razorpay, Mercado Pago, regional PSPs).
- Building checkout: one-time charges, saved cards, guest vs. authenticated, 3DS/SCA, buy-now-pay-later.
- Implementing subscription billing: plans, trials, metered usage, proration, dunning, grace periods, reactivation.
- Implementing invoicing, tax calculation (Stripe Tax, Avalara, TaxJar), coupons, promotions.
- Wiring payment webhooks with idempotency and retry safety.
- Implementing refunds, disputes/chargebacks, and partial captures.
- Reconciling provider payouts with internal ledger; handling failed payouts.
- Reducing PCI DSS scope; meeting SCA/3DS2 requirements for EU/UK.
- Supporting regional/alternative payment methods (iDEAL, SEPA Direct Debit, ACH, PIX, UPI, Alipay, WeChat Pay, BNPL).

Applies to web, mobile (native + webview), backend APIs, platforms (SaaS, marketplace, e-commerce), and internal billing tools.

---

## Workflow

### Phase 1 — Scope & Compliance First

**1. Classify the payment use case.**
Determine: one-time vs. recurring, platform (collect on behalf of others?) vs. direct, B2C vs. B2B, geographies and currencies, required alternative payment methods. A marketplace collecting on behalf of sellers is fundamentally different from a SaaS billing its own customers — provider features and contract structures differ.

**2. Assess PCI scope and SCA obligations.**
- Use a provider-hosted payment element (Stripe Elements / PaymentElement, Adyen Drop-in, Braintree Hosted Fields) to keep card data out of your servers and reach **PCI DSS SAQ A** (the lowest scope). Never accept raw card numbers server-side unless you have a full PCI ROC.
- For EU/UK transactions, 3DS2/SCA is mandatory for most consumer card transactions. Use a provider that handles SCA authentication automatically (Stripe's PaymentIntents API, Adyen's 3DS2 server, etc.).
- Classify card data sensitivity: PANs must never appear in logs, analytics, error messages, or client-side JS outside the secure iframe.

**3. Choose the provider and integration model.**
Consult `.claude/stack-matrix/payments.md`. Key decision axes: global coverage, split-pay/connect for marketplaces, SCA support, local payment methods, contract/pricing model, and SDK maturity for your platform. Document the choice via `.claude/templates/decision-record.md`.

---

### Phase 2 — Design the Payment Model

**4. Design the data model before writing code.**
Minimum entities for any payment integration:

```
Customer          → maps your user to provider customer ID (idempotent creation)
PaymentMethod     → stored card/bank/wallet token (never raw PAN)
Order / Cart      → what is being purchased, amounts, tax, currency
PaymentIntent     → one payment attempt lifecycle (created → processing → succeeded / failed)
Subscription      → recurring plan: plan_id, status, current_period, trial, cancel_at
Invoice           → line items, amounts, tax, due date, status
Refund            → linked to PaymentIntent, amount, reason, status
WebhookEvent      → idempotency_key, provider_event_id, type, payload, processed_at, status
```

Record in `docs/specs/payment-data-model.md` using `.claude/templates/data-model.md`. Include the currency precision rule: store all amounts as integers in the smallest currency unit (cents, pence, øre). Never float.

**5. Map the happy path and every failure path.**
Draw out: checkout initiation → payment intent creation → 3DS challenge (if required) → webhook confirmation → order fulfillment. Then map: declined card, 3DS failure, network timeout, duplicate submission, refund requested, dispute opened, payout failed. Each failure path needs a defined user-facing message, a retry policy, and an idempotency strategy.

**6. Design webhook processing as a queue, not a synchronous handler.**
Webhooks must be: (a) received and immediately ACKed with HTTP 200, (b) stored in a webhook_events table with the raw payload and idempotency key, (c) processed asynchronously by a worker, (d) idempotent — running the same event twice must be safe. See Standards.

---

### Phase 3 — Implement

**7. Implement server-side PaymentIntent / charge creation.**
- Always create the PaymentIntent server-side, never client-side. The client only confirms.
- Pass `idempotency_key` on every mutating API call (use order ID + attempt number, or a UUID stored with the order). Stripe, Adyen, and Braintree all support this header.
- Set `capture_method: manual` if you need to authorize now and capture after fulfillment (physical goods, reservations).
- For subscriptions, use the provider's subscription API (Stripe Subscriptions + Invoices, Adyen Recurring) rather than rolling your own. Provider handles dunning, retry logic, and SCA re-authentication.

**8. Implement the client-side payment UI.**
- Embed the provider's hosted element (Stripe PaymentElement, Adyen Drop-in) inside your checkout UI. This element handles card input, 3DS flow, and APM rendering without your page ever touching raw card data.
- On confirmation, the client calls `stripe.confirmPayment()` (or equivalent), which handles the 3DS redirect and returns a result. Your client then polls your backend for order status — never trust client-side confirmation alone; always confirm via webhook.

**9. Implement webhook handler.**
```
POST /webhooks/stripe  (or /webhooks/adyen etc.)
  1. Verify signature (Stripe-Signature header / HMAC)
  2. Insert into webhook_events (event_id, type, payload) ON CONFLICT DO NOTHING
  3. Return 200 immediately
  (worker) Process event by type:
    payment_intent.succeeded → fulfill order
    payment_intent.payment_failed → update order, notify user
    invoice.payment_failed → dunning flow
    charge.dispute.created → alert ops, freeze fulfillment if needed
    customer.subscription.deleted → revoke access
```

**10. Implement refunds and disputes.**
- Refunds are always initiated server-side via the provider API. Partial refunds require tracking refunded amount on the order.
- Store dispute metadata; chargebacks can arrive months after payment. Do not delete order/payment records based on data-retention policies that would prevent dispute evidence submission.
- Wire `charge.dispute.created` webhook to alert ops within minutes.

**11. Implement reconciliation.**
- Daily/hourly: fetch provider payout reports (Stripe Sigma, Adyen Settlement Detail Report) and reconcile against your internal order/payment records.
- Check: amount transferred = sum(succeeded charges) - fees - refunds - disputes for that payout window. Any gap is a bug or fraud signal.
- Store provider fee per transaction to enable margin analysis.

---

### Phase 4 — Harden & Audit

**12. Run the payment security checklist (cross-ref `.claude/checklists/security.md`).**
- Webhook signature verification in place and tested.
- No card data in logs, analytics, Sentry, or error messages.
- Idempotency key on every mutating provider API call.
- Provider API key in secret manager, not in code or `.env` committed to VCS.
- Test mode vs. live mode keys are different; only live keys are in production secrets.
- Stripe Radar / Adyen fraud rules configured.
- Amount mismatch validation: confirm server-side that the amount on the PaymentIntent matches your order total before fulfilling.

**13. Test in the provider's sandbox using test card suites.**
Test: success, generic decline, insufficient funds, 3DS required, 3DS failed, network error, duplicate idempotency key, refund, partial refund, dispute. For subscriptions: trial end, invoice payment, failed renewal, dunning retry, cancellation, reactivation.

---

## Standards

**Do:**
- Store amounts as integer cents/smallest-unit. Convert to display format only in the UI layer. Use a `Money` value object or dedicated library (dinero.js, py-moneyed) for arithmetic to avoid float rounding.
- Verify webhook signatures on every inbound event. Reject without a valid signature.
- Use idempotency keys on all mutating provider API calls; include the attempt number if supporting retries.
- Fulfill orders only after receiving the `payment_intent.succeeded` (or equivalent) webhook — never on client-side confirmation alone.
- Keep provider keys in a secret manager (AWS Secrets Manager, Vault, Doppler); rotate on suspected exposure.
- Use `HTTPS` everywhere, enforce TLS 1.2+ on webhook endpoints.
- Handle `402`, `429` (rate limit), `500`-range provider errors: retry with exponential backoff, log the error with the provider's request ID.
- Scope PCI surface to provider-hosted fields only (SAQ A). If you ever need to accept raw card data, engage a qualified security assessor.
- Support 3DS2/SCA for EU/UK from day one — retrofitting later is expensive.
- Test cancellations, refunds, and failed charges in staging before every release that touches billing.

**Do not:**
- Log, store, or transmit PANs, CVVs, or full card numbers. Not in application logs, analytics events, Sentry breadcrumbs, or support tickets.
- Trust the client's reported payment status — always confirm server-side via webhook.
- Use floating-point numbers for monetary amounts anywhere in the stack.
- Retry webhook processing without idempotency guards (double-fulfillment risk).
- Use test API keys in production or production keys in test/staging.
- Hard-code prices or amounts client-side where they could be tampered; validate server-side.
- Roll your own recurring billing logic (dunning, proration) when the provider offers it.
- Delete payment or order records while a chargeback window is still open (typically 120 days for Visa/Mastercard, 180 for Amex).

---

## Common mistakes to avoid

- **Double-fulfillment**: processing `payment_intent.succeeded` twice because the worker isn't idempotent. Fix: `INSERT ... ON CONFLICT DO NOTHING` on `webhook_events.event_id`, process only once.
- **Trusting client redirect**: fulfilling an order because the browser returned to the success URL — not because the webhook confirmed. Redirects can be spoofed.
- **Currency mismatch**: creating a PaymentIntent for `$10.00` but storing `10.00` (float) in the database, then comparing to `1000` cents. Use one canonical representation.
- **Missing SCA handling for saved cards**: off-session payments (subscription renewals, saved-card reuse) require `setup_future_usage: off_session` during initial setup and proper handling of `requires_action` on renewal.
- **Webhook secret not verified**: skipping signature verification because "we'll add it later" — this allows anyone to send fake fulfillment events.
- **Provider rate limits in refund storms**: bulk refund flows hit rate limits. Implement a queue with back-pressure.
- **No payout reconciliation**: assuming the provider deposits exactly what you expect. Fees, disputes, and processing errors cause discrepancies; catch them daily.
- **Tax not collected correctly**: treating price as inclusive of tax everywhere vs. exclusive in some jurisdictions. Use a tax calculation service (Stripe Tax, Avalara) rather than hard-coding rates.

---

## Output format

Primary deliverables:
- **Payment integration spec**: `docs/specs/payment-integration.md` (use `.claude/templates/feature-spec.md` + data model section from `.claude/templates/data-model.md`). Cover: provider choice rationale, data model, event flow diagrams, PCI scope decision, SCA strategy, webhook contract, reconciliation approach, test plan.
- **Decision record**: `docs/decisions/payment-provider-<name>.md` (use `.claude/templates/decision-record.md`).
- **Runbook** for payment incidents (webhook processing failures, payout failures, dispute surges): `docs/runbooks/payments.md` (use `.claude/templates/runbook.md`).

Supporting code artifacts (in the project source):
- Server-side payment service (PaymentIntent creation, webhook handling, refunds) with idempotency.
- Webhook event table migration + idempotent worker.
- Reconciliation job/script.
- Test suite covering all card scenarios.

---

## Related checklists
- `.claude/checklists/security.md` — webhook signature verification, secrets, PCI surface.
- `.claude/checklists/production.md` — payment keys in secret manager, monitoring on failed payments.
- `.claude/checklists/qa.md` — payment test coverage (success, decline, 3DS, refund, dispute, dunning).

## Related agents
- `.claude/agents/engineering/payments-engineer.md` — primary implementation owner.
- `.claude/agents/quality/security-auditor.md` — PCI scope review, webhook security, secret management.
- `.claude/agents/quality/production-readiness-auditor.md` — reconciliation, alerting on payment failures.
- `.claude/agents/domain/fintech-domain-expert.md` — regulatory nuances, regional payment methods, compliance.
- `.claude/agents/domain/ecommerce-domain-expert.md` — checkout UX, cart abandonment, refund policies.

