Skip to main content

django-mvp-payments

Tests Build Publish Coverage PyPI Development status Python Django Ruff Licence

Payment and subscription UI as Cotton components for django-mvp projects, one namespace per payment backend.

It covers pricing, checkout and subscriptions for django-mvp projects.

Django has no shortage of packages that talk to a payment provider. What none of them give you is the part your users look at. A package hands you a subscription record, a list of prices and an endpoint that starts a checkout, and leaves the plan grid, the subscribe button, the billing panel and the "your card expires next month" notice to be built from raw utility classes — in every project, every time.

This package is that layer, plus the least it takes to keep it fed. It runs no payment logic and depends on no third-party payment backend. For Stripe it has an app of its own, mvp_payments.stripe, which keeps a small copy of each user's subscription in step with Stripe through webhooks. Its pages turn up in django-mvp's Account Center, rather than leaving you to build and route them.

Status

Version 0.0.1. The stripe namespace contributes one entry to the Account Center, Subscription, under a Billing group. That page shows a signed-in person every subscription they currently hold, the way to switch plans, and the way through to Stripe's billing portal. The plans page it leads to is a public page of its own. It draws every recurring price in your Stripe account, with each product's name, description and features, and says so plainly when Stripe cannot be reached or has nothing on offer. Nothing here is stable.

Requirements

  • Python 3.12+
  • Django 5.2, 6.0 or 6.1
  • django-mvp 0.30.0+
  • For Stripe, the stripe-lite extra and a Stripe account

Install

pip install django-mvp-payments

Add it to INSTALLED_APPS, before mvp:

INSTALLED_APPS = [
    ...,
    "mvp_payments",
    "mvp",
]

That order is load-bearing, not a style choice: this package ships its own copy of mvp/account/overview.html and extends the name from inside it, which only resolves when this application is found first.

Then mount its URLs wherever you like:

urlpatterns = [
    ...,
    path("account/billing/", include("mvp_payments.urls")),
]

Mount it where a reader would expect to find it. These are Account Center pages, so inside the Account Center's own prefix and under the label the navigation gives them is the natural place. Addresses carry no backend name: a page is at <your prefix>/subscription/, never <your prefix>/stripe/subscription/, because which library you chose to talk to your payment provider is not a person's business while they read their own subscription.

To offer the plans page, add a second include at the address you choose, outside the Account Center:

urlpatterns = [
    ...,
    path("plans/", include("mvp_payments.plans_urls")),
]

Those two lines are the only wiring. From there, every backend you have installed contributes its own section of the Account Center's navigation and its own card to the Account Center's overview, and a backend you have not installed contributes nothing. There is no settings block, no flag to turn on and no registry to populate — what is in INSTALLED_APPS decides what exists.

The Stripe app

To keep each user's Stripe subscription in your database, install the extra and add the app:

pip install "django-mvp-payments[stripe-lite]"
INSTALLED_APPS = [
    ...,
    "mvp_payments",
    "mvp_payments.stripe",
    "mvp",
]

Then set STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in MVP_PAYMENTS and run python manage.py migrate. How to install the Stripe app has the steps, how to point Stripe at the webhook the rest of the setup, and the Stripe app reference the settings, the records and the webhook.

Namespaces

Components are grouped by the backend they speak to, and the namespace is that backend's name:

<c-stripe.plans :plans="plans" />
<c-stripe.subscription :subscription="subscription" />

Each namespace assumes one backend's records and nothing else. Two namespaces never share markup or a data shape, because the things they are describing are not the same thing wearing different names — a subscription in one library is a different record with different fields from a subscription in the next.

Shipped today: stripe, built on the package's own Stripe app. It contributes a user subscriptions page to the Account Center. That page lists every subscription a person currently holds, reading the Stripe app's records, and hands the reader to Stripe's own billing portal and plan-change screen through views the app mounts. It also carries the way to the plans page, a public page of its own that draws every price on offer in your Stripe account and that a project mounts with mvp_payments.plans_urls. Namespaces for other backends are welcome and do not need this one's agreement about anything: adding one is adding it beside the ones already installed, and changes nothing about their navigation entries, their card or their pages.

docs/reference/user-subscriptions-page.md covers the user subscriptions page: what it puts in the template context, the components it is built from, and how to replace it with your own.

docs/reference/plans-page.md covers the plans page: where to mount it, the parts it is built from and the data each receives, the setting it reads, the {% stripe_plans %} tag, and how to place the plans on your own pages or replace one part.

docs/namespaces.md is how you add a namespace: what one declares, the two questions it answers about itself, and what it may not do.

JavaScript

No component here needs a payment provider's script. The plans are drawn on the server, and checkout, the billing portal and plan changes all happen on Stripe's own pages. This package never emits a script tag for a provider's library, and should a future component need one, loading it is your project's decision.

The one script of its own, mvp_payments/stripe/billing_portal.js, binds the "Manage subscription" and "Switch plans" controls. Include it with {% static %} on any page that shows those controls, as the user subscriptions page reference shows.

Scope & philosophy

What this is. A presentation layer for payment and subscription state, and, for each supported provider, a small app that keeps that state. Templates, the small amount of JavaScript some components need, the Python that puts a page at a URL and an entry in a menu, and for Stripe, mvp_payments.stripe.

The pages look like the rest of your django-mvp site, because they are drawn with the same daisyUI classes and follow the same theme. They show a person where they stand. Anything that changes what they pay, such as choosing a plan, switching, cancelling or updating a card, is handed to the provider's own hosted pages, which already do it well.

The Stripe app keeps the least it can: who each user's Stripe customer is, and that customer's subscription with its status, price and period. Stripe is the source of truth. The app learns of changes from Stripe's webhooks, checks each one's signature and then asks Stripe for the current state, rather than trusting what the webhook says. It is installed with django-mvp-payments[stripe-lite] (ADR 0010).

The standing directions this package works toward are in GOALS.md.

What this deliberately is not.

  • Not a full Stripe integration. No copy of Stripe's products, prices, invoices or payment methods, no taxes, no metered billing, no one-off payments. If you need those, dj-stripe is built for it.
  • Not a decider. Nothing here computes a price, a proration or a trial, and nothing decides what a subscription grants. The Stripe app can tell your views whether someone is subscribed. What that lets them see is your project's call, enforced in your views.
  • Not a place card details pass through. Cards are typed into the provider's own pages and iframes, never into markup written here.
  • Not a way to run two backends at once. Whose subscription a person is looking at stops being answerable, and nothing here is built to arbitrate it.
  • Not a backend abstraction. There is no common interface that every payment provider gets adapted onto. That abstraction is the classic trap in this domain: it fits the first provider, bends for the second and is a liability by the third, because subscription models genuinely differ. A namespace per backend costs some duplication and buys the freedom to match each one exactly.
  • Not a CSS framework. Components render the daisyUI classes django-mvp already ships. No stylesheet, no build step, no theme of its own.

Tie-breaks. When two of these pull against each other:

  • match the provider rather than generalise
  • keep less rather than more, and ask the provider rather than guess
  • route and render rather than compute
  • render less rather than assume how a page is laid out

Running the demo

uv sync
uv run python manage.py migrate
uv run python manage.py seed_demo
uv run python manage.py runserver 8020

Sign in as regular.user, staff.user or super.user, password password. Out of the box the pages run on fake Stripe values, so the billing portal says it could not be reached, while seed_demo puts a demo set of plans (Basic, Pro and Team, monthly and yearly) in the demo's file cache for the plans page. To see the portal and your own plans working, copy demo/.env.example to demo/.env and fill it in from your own Stripe sandbox. That file is gitignored. Then run seed_demo again so the demo accounts get real sandbox customers and subscriptions, recorded by the Stripe app as well, where super.user can look at them in the admin at /admin/. The file lists the one portal setting "Switch plans" needs from your sandbox.

To have the demo record subscriptions made in the sandbox, forward Stripe's webhooks to it with the Stripe CLI in a second terminal:

stripe listen --forward-to localhost:8020/account/billing/stripe/webhook/ \
  --events checkout.session.completed,customer.subscription.created,customer.subscription.updated,customer.subscription.deleted,customer.subscription.paused,customer.subscription.resumed,customer.deleted,product.created,product.updated,product.deleted,price.created,price.updated,price.deleted

It prints a whsec_... signing secret. Put it in demo/.env as STRIPE_TEST_WEBHOOK_SECRET and restart the demo. Subscribing, switching plans and cancelling in the sandbox then show up in the demo's records. The demo starts without the command running, and python manage.py stripe_sync brings the records up to date after a gap.

Contributing

Changes follow CONSTITUTION.md, the testing standards, the code documentation standards and the Django standards.

Changelog

See CHANGELOG.md.

Licence

MIT.

Metadata

Release files for django-mvp-payments 0.1.0

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

Source distribution (sdist)

Source distribution for django-mvp-payments 0.1.0
File Size Uploaded Provenance
django_mvp_payments-0.1.0.tar.gz 39.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for django-mvp-payments 0.1.0
File Interpreter ABI Platform
django_mvp_payments-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 96.0 kB

Release files / django_mvp_payments-0.1.0.tar.gz

Download URL django_mvp_payments-0.1.0.tar.gz
Size 39.8 kB
Tags Source
SHA-256 checksum
How to use checksums
957085db2776e1be5632b0333b17472ed74ed4adb2acebbd97eabfd62feb840a
BLAKE2b-256 checksum
How to use checksums
16d7bc8e3a4ba057b9e8e4bc77deb6ee3bcac0b447498f5700013e06c011ef49
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 23, 2026.

Transparency log

Release files / django_mvp_payments-0.1.0-py3-none-any.whl

Download URL django_mvp_payments-0.1.0-py3-none-any.whl
Size 56.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e6672094dc1ff25f8ad269b14777e259815124ad23e7fd9d787103e9230a4d33
BLAKE2b-256 checksum
How to use checksums
8b5acacbc2ef53c331d865ff846bfd696ad9cf04b683f172f23c84ec76459a41
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Oct 23, 2026.

Transparency log

Security for django-mvp-payments 0.1.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_mvp_payments-0.1.0.tar.gz

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 Oct 23, 2026.

Transparency log

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full 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