Skip to main content

karni-payments

Payments module for Karni: hosted checkout, refunds and webhook-driven payment state for Stripe (Checkout Sessions) and Razorpay (Payment Links), plus an in-memory test provider.

karni add payments        # wires dependency + INSTALLED_APPS + agent guidance
pip install "karni-payments[stripe]"    # or [razorpay]

One line of project wiring (the viewsets ride Karni's auto-router):

# config/urls.py
(path("payments/", include("karni_payments.urls")),)  # the webhook endpoint

then make migrate. Register https://<host>/payments/webhook/stripe/ (or .../razorpay/) at the provider.

What you get

  • Models (all on karni.models.BaseModel): Payment, Refund, WebhookEvent (the (provider, event_id) dedupe record). Derived admin via AdminMeta — no admin.py.
  • API (auto-routed, authenticated only):
    • GET /api/payments/ — owner-scoped; the payments-admin role sees all.
    • POST /api/payments/checkout/ — create a pending payment + provider session; redirect the customer to the returned url.
    • POST /api/payments/{id}/refund/ — requires the payments-admin role; omit amount to refund the full unrefunded remainder.
    • GET /api/refunds/ — payments-admin only.
    • Writes to the resources themselves answer 405: state belongs to webhooks.
  • Webhooks: POST /payments/webhook/<provider>/ verifies the signature (400 + warning log on failure, nothing persisted), dedupes on (provider, event_id) and hands processing to a Celery task (process_webhook_event: row lock + monotonic state machine + processed_at stamp in one transaction; 3 retries with backoff). Nothing state-changing runs in the request cycle.
  • Services (karni_payments.services) — the only module project code should call: create_checkout, create_refund (lock-validated against the unrefunded remainder — over-refunds are impossible), apply_event.

Settings (via the karni.settings fragment — all overridable)

Setting (env var of the same name) Default Meaning
KARNI_PAYMENTS_PROVIDER "stripe" "stripe", "razorpay", "test" or a dotted path to your own PaymentProvider subclass
KARNI_PAYMENTS_CURRENCY "usd" default checkout currency
KARNI_PAYMENTS_CHECKOUT_VALIDATOR pass-through dotted path called with (user=, amount=, currency=, metadata=) before a Payment exists — set it; never trust client amounts
KARNI_PAYMENTS_EVENT_HANDLER karni_payments.services.apply_event dotted path the webhook task calls — the state-machine escape hatch
KARNI_PAYMENTS_ALLOWED_REDIRECTS [] extra origins allowed for checkout redirect URLs (on top of KARNI_FRONTEND_URL)
STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET "" validated lazily at provider use
RAZORPAY_KEY_ID / RAZORPAY_KEY_SECRET / RAZORPAY_WEBHOOK_SECRET "" validated lazily at provider use

Semantics worth knowing

  • Monotonic state: pending → processing → succeeded → partially_refunded → refunded; failed/canceled are terminal. Events that would regress a status are recorded as a no-op: note on the WebhookEvent and count as processed. Expired checkout sessions map to canceled.
  • Zero-decimal currencies (JPY, KRW, …) are handled by providers.base.to_minor_units / from_minor_units; 3-decimal currencies (BHD, KWD, …) are unsupported in v0.1 and raise.
  • A provider failure during checkout marks the Payment failed before re-raising. Abandoned pending payments are not reaped in v0.1 — expire them yourself if you need to (sessions that expire at the provider arrive as webhook events and cancel the payment).
  • Razorpay webhooks take their event id from the X-Razorpay-Event-Id header; Payment Links have a single callback_url, so cancel_url is unused there.

Testing your project

Set KARNI_PAYMENTS_PROVIDER = "test" in test settings: TestProvider is in-memory and deterministic (test_cs_1, test_re_1, …), and TestProvider.build_webhook({...}) returns a correctly signed (body, headers) pair for driving the full webhook pipeline. Call TestProvider.reset() between tests.

Development

pip install -e . --group dev
python -m pytest        # standalone suite (sqlite + eager Celery + TestProvider)
ruff check . && ruff format --check .

This module is also the reference implementation of the Karni module contract — see docs/modules.md in the Karni repo.

Metadata

Release files for karni-payments 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for karni-payments 0.3.1
File Size Uploaded Provenance
karni_payments-0.3.1.tar.gz 30.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for karni-payments 0.3.1
File Interpreter ABI Platform
karni_payments-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 70.5 kB

Release files / karni_payments-0.3.1.tar.gz

Download URL karni_payments-0.3.1.tar.gz
Size 30.0 kB
Tags Source
SHA-256 checksum
How to use checksums
449705576f95898b7b27b8900bb95025fdc9dc43b25c10390d81bcf4cce408bf
BLAKE2b-256 checksum
How to use checksums
1ffedd9348ed8ed063672facd2ed443665c52c7137ea5e0ca3fc6a2a80d8aab8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / karni_payments-0.3.1-py3-none-any.whl

Download URL karni_payments-0.3.1-py3-none-any.whl
Size 40.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e6d0fd9bd5967f927997e70e0938370be4198f3ba334d0c459d911fb7f216b2b
BLAKE2b-256 checksum
How to use checksums
cea8ebcd7a95f645960d30c5af6cfb52909aa1769dd3024ddeda5c0b1316a379
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for karni-payments 0.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page