Skip to main content

stapel-billing

CI coverage pypi downloads python license llms.txt

Payments and billing: per-user credit wallets held as expiry-aware credit lots over an immutable ledger, reservations (hold/capture/release) for work priced only after it runs, partial charges that record what a wallet could not cover as a collectable debt, plan bundles granted with or without a payment provider, one-off credit packages and subscription plans, Stripe-backed checkout, customer portal and refund clawback, idempotent webhooks and a service-to-service debit endpoint.

Part of the Stapel framework — composable Django apps that deploy as a monolith or as microservices without changing module code.

Install

pip install stapel-billing

At a glance

Fact Value
Version 0.21.3
Python >=3.11 (3.11, 3.12, 3.13, 3.14)
HTTP operations 11
Config axes 1
Usage surface 69
Extension points 6
Error codes 56
Fleet dependencies stapel-auth (optional) · stapel-core

Documentation

Errors: English · Español · Русский · OpenAPI · capabilities.json · llms.txt (for agents)

Quick start

# settings.py
INSTALLED_APPS = [
    ...
    'stapel_billing',
]

from stapel_billing.tasks import get_billing_beat_schedule

CELERY_BEAT_SCHEDULE = {
    **get_billing_beat_schedule(),   # credit expiry, hold sweep, reconcile
}

Register the beat schedule (or run the three callables in stapel_billing.tasks from your own cron): credits granted with an expiry only expire because something runs. System check stapel_billing.W105 says so if nothing does.

Wallets are lots

A wallet is a set of CreditLot rows — each one knows where its credits came from (purchase, subscription, grant, adjustment, hold_release) and when they die (expires_at, NULL = never). Spend walks the lots expiring-soonest first, so a subscription bundle is used before the non-expiring credits a customer paid cash for. Wallet.balance is a maintained cache of that total, recomputed from the lots inside the same row lock as every mutation.

from stapel_billing import credit, debit, hold, capture, release
from stapel_billing.models import LotSource, TransactionType

credit(user=user, credits=3000, type=TransactionType.SUBSCRIPTION_BONUS,
       source=LotSource.SUBSCRIPTION, expires_at=sub.current_period_end)

held = hold(user=user, credits=15, type=TransactionType.AI_CHARGE,
            idempotency_key=f"mic:{recording_id}")
try:
    used = do_the_work()
except Exception:
    release(hold_id=held.id)      # credits go back with their original expiry
else:
    capture(hold_id=held.id, actual_credits=used)

Granting credits by hand

Credits move without a payment more often than a payments library likes to admit: staff testing the product, goodwill after an outage, an invoiced agreement. There is one audited path for it, reachable from a terminal —

manage.py billing_grant_credits --account <id|e-mail> --credits 100 \
    --reason "staff testing" --actor ops@example.com \
    --idempotency-key grant-2026-09-16-01

— and the same service (services.grant_credits) behind the admin's Grant credits action, so a grant made in a browser and a grant made over ssh are the same ledger row: type adjustment, never expiring, carrying the reason and the actor. The command refuses an unknown or ambiguous account, a non-positive amount and an empty reason or actor with a non-zero exit; with an idempotency key a repeat grants once and says so.

Your own staff should not have to pay to test

STAPEL_BILLING = {"INTERNAL_ACCOUNT_POLICY": "meter_only"}

An internal account is then metered but not charged: debit, can_afford, hold and capture all still run, the ledger row is still written with its type, description and metadata — and credits_delta is 0, with metadata.waived_credits recording what it would have cost. No credits move, no CreditDebt is opened, and "what did our own testing consume this month" is still a query. Off by default; who counts as internal is INTERNAL_ACCOUNT_RESOLVER (default is_staff or is_superuser).

Bus events

Emits

| payment.completed | schema | A payment transaction completed successfully. | | payment.failed | schema | A payment attempt was declined. Nothing was granted, so nothing is clawed back — the fact exists to be told to the payer. | | subscription.changed | schema | User subscription plan or status changed. Carries cancel_at_period_end, which no consumer can infer from status. |

Consumes

| user.deleted | schema | | user.deletion_initiated | schema |

This module also subscribes to its own payment.completed, payment.failed and subscription.changed — see stapel_billing/notifications.py. That is not a loop: the emit records that money moved, and the subscriber turns it into the letter the payer is owed. It lives here rather than in each host because the gap is identical in every host that installs this library, and the template it asks for lives in stapel-notifications (≥ 0.20.0) because copy, channels and languages are that module's job and not this one's.

License

MIT — see LICENSE.


This page is assembled by stapel-readme from docs/readme.md plus the contract artifacts in docs/. Edit the prose in docs/readme.md; the badges, facts and links above and below it are generated — do not hand-edit README.md.

Release files for stapel-billing 0.21.3

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

Source distribution (sdist)

Source distribution for stapel-billing 0.21.3
File Size Uploaded
stapel_billing-0.21.3.tar.gz 310.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for stapel-billing 0.21.3
File Interpreter ABI Platform
stapel_billing-0.21.3-py3-none-any.whl Python 3 none any Details

Total release size: 537.6 kB

Release files / stapel_billing-0.21.3.tar.gz

Download URL stapel_billing-0.21.3.tar.gz
Size 310.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b037fa5bdd3cadb22072bc0c6152df3e44f61df3fdfa5fc02fbf01717fcd7718
BLAKE2b-256 checksum
How to use checksums
68822b2b4d18a4013a275ea3135fcd621bf059b3a6679fcbea30a03acc126660
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / stapel_billing-0.21.3-py3-none-any.whl

Download URL stapel_billing-0.21.3-py3-none-any.whl
Size 227.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e459345e2e33d1224dda106e2580cf3c6cbe58245f2928aaafb67a0f78e5c814
BLAKE2b-256 checksum
How to use checksums
7f69c5ad29fdebd6db1f910f7f6159f9d588f19e58792247f4af4a96306926a5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.21.3 This release

2 release files

0.21.1

2 release files

0.21.0

2 release files

0.20.2

2 release files

0.20.1

2 release files

0.19.0

2 release files

0.18.2

2 release files

0.18.1

2 release files

0.18.0

2 release files

0.17.1

2 release files

0.17.0

2 release files

0.16.1

2 release files

0.16.0

2 release files

0.15.0

2 release files

0.14.0

2 release files

0.13.0

2 release files

0.12.0

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.16

2 release files

0.4.15

2 release files

0.4.12

2 release files

0.4.11

2 release files

0.4.10

2 release files

0.4.9

2 release files

0.4.4

2 release files

0.4.1

2 release files

0.4.0

1 release file

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