Skip to main content

Django plans payments

https://badge.fury.io/py/django-plans-payments.svg https://github.com/PetrDlouhy/django-plans-payments/actions/workflows/main.yml/badge.svg https://codecov.io/gh/PetrDlouhy/django-plans-payments/branch/master/graph/badge.svg

Almost automatic integration between django-plans and django-payments. This will add payment buttons to the order page and automatically confirm the Order after the payment. Optionally, it can return the corresponding order when a payment is refunded.

Documentation

The full documentation is at https://django-plans-payments.readthedocs.io.

Quickstart

Install and configure django-plans and django-payments apps. Capture mode is not yet supported, so PAYMENT_VARINANTS with 'capture': False will not get confirmed.

Install Django plans payments:

pip install django-plans-payments

Add it to your INSTALLED_APPS, before the plans:

INSTALLED_APPS = (
    ...
    'related_admin',
    'plans_payments',
    'plans',
    ...
)

Add Django plans_payments to the URL patterns:

urlpatterns = [
    ...
    url(r'^plans-payments', include('plans_payments.urls')),
    ...
]

Set django-plans settings and set model to:

PAYMENT_MODEL = 'plans_payments.Payment'

Charge-attempt idempotency

CreatePaymentView refuses to fire duplicate live charge attempts: a request while a previous payment is still in flight (or just captured) redirects to that payment instead of creating a twin, and a request right after a decline waits out a short cooldown, because banks read rapid-fire retries as fraud. Both windows look across all the user’s orders and are configurable:

PLANS_PAYMENTS_JOIN_IN_FLIGHT_SECONDS = 180  # 0 disables joining
PLANS_PAYMENTS_DECLINE_COOLDOWN_SECONDS = 60  # 0 disables the cooldown

Customer IP address

Customer IP address is stored in Payment model and used for some payment providers (i.e. PayU). For security reasons django-plans-payments does acquire the IP only from request REMOTE_ADDR parameter. If you are behind proxy, you will need to setup some mechanism to populate this variable from HTTP_X_FORWARDED_FOR parameter. The suggested solution is to use django-httpforwardedfor or django-xff application for that.

Running Tests

Does the code actually work?

source <YOURVIRTUALENV>/bin/activate
(myenv) $ pip install tox
(myenv) $ tox

Credits

Tools used in rendering this package:

History

2.3.2 (2026-09-15)

  • CreatePaymentView’s in-flight join and decline cooldown consider only payments of the requested variant. A checkout page that pre-creates a card payment for an embedded widget on page load no longer captures a click on another method (every “Pay with PayPal” click within the join window was redirected to the card payment), and a buyer whose card was just declined can switch to another method without waiting out the cooldown.

2.3.1 (2026-09-07)

  • Payment.set_renew_token on a plan-change order (an order without a pricing) stores only the token-related values on the existing RecurringUserPlan instead of re-arming it from the order, which left the renewal with pricing=None and the one-off difference as its amount.

2.3.0 (2026-08-19)

  • make CreatePaymentView idempotent: an attempt while a previous payment is in flight joins it instead of creating a twin, and an attempt right after a decline waits out a short cooldown (banks read rapid-fire retries as fraud). Configurable via PLANS_PAYMENTS_JOIN_IN_FLIGHT_SECONDS (default 180) and PLANS_PAYMENTS_DECLINE_COOLDOWN_SECONDS (default 60); 0 disables.

  • fix create_payment_object crashing with BillingInfo.DoesNotExist for users without saved billing info – billing fields fall back to empty strings.

  • fix renew_accounts charging automatic renewals for free plans: an armed RecurringUserPlan surviving a switch to a free plan took the customer’s money and extended nothing. The renewal is skipped with a warning instead.

2.2.0 (2026-07-23)

  • add Payment.invalidate_renew_token() - payment providers call it when the gateway reports the stored recurring token as permanently dead (e.g. PayU INVALID_TOKEN). The token is marked unverified so renewal tasks stop selecting the account and get_renew_token() returns None; the new plans_payments.signals.renew_token_invalidated signal lets host apps prompt the user to update their payment method.

  • add a pre-commit config (black/isort/flake8 at line length 119 plus hygiene hooks) and run it in CI, so local and CI lint cannot drift.

2.1.0 (2026-07-21)

  • Don’t disable token_verified on an unsuccessful renewal attempt, so a transient payment failure no longer permanently disarms automatic renewals

  • Add get_renew_data() and pass provider metadata (e.g. Stripe customer_id) through set_renew_token(**kwargs)

  • Add migration for the new django-payments PaymentStatus.CANCELLED (choices-only, database no-op)

  • Tests: drop the unused pytz dependency, satisfy current black

2.0.2 (2025-05-29)

  • fix the release

2.0.1 (2025-05-29)

  • fix the release

2.0.0 (2025-05-29)

  • Implement the wallet logic

  • Drop support for Python 3.8, Django 4.1

  • Add support for Django 5.2

  • don’t obscure Exceptions by catching them

1.5.0 (2025-02-28)

  • Drop support for Python 3.7

  • Add support for Python 3.13, Django 5.1

  • fix the wrong assumption that Payment.extra_data["response"] contains a "transactions" key

1.4.1 (2024-04-24)

  • do not check whether a confirmed payment of a completed order is left anymore

1.4.0 (2024-04-15)

  • migrate to RecurringUserPlan.renewal_triggered_by

  • add renewal_triggered_by parameter to Payment.set_renew_token

  • deprecate automatic_renewal parameter of Payment.set_renew_token; use renewal_triggered_by parameter instead

  • deprecate None value of renewal_triggered_by parameter of Payment.set_renew_token; set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead

1.3.1 (2024-04-15)

  • fix typo in payment description

1.3.0 (2024-04-12)

  • add optional returning orders when payments are refunded

1.2.2 (2023-12-20)

  • add change_reason for django-simple-history

1.2.1 (2023-12-19)

  • specify sender=Payment for change_payment_status receiver

1.2.0 (2023-10-16)

  • bugfix release (fix prevoius bad release)

1.1.3 (2023-10-15)

  • add some indexes to Payment model

1.1.2 (2023-03-29)

  • reword Payment description to ommit word “Subscribtion” which might raise warnings for banks/card providers

1.1.1 (2023-01-27)

  • correction release, include wheel update, correctly rebase to master

1.1.0 (2023-01-27)

  • Fix transaction fee double counting

1.0.1 (2022-12-09)

  • Fix migrations

1.0.0 (2022-12-08)

  • Recurring payments functionality

0.2.0 (2018-08-05)

  • Payment process without capturing should work

  • Automatic buttons generation

0.1.0 (2018-07-23)

  • First release on PyPI.

Release files for django-plans-payments 2.3.2

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-plans-payments 2.3.2
File Size Uploaded
django_plans_payments-2.3.2.tar.gz 27.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-plans-payments 2.3.2
File Interpreter ABI Platform
django_plans_payments-2.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 51.5 kB

Release files / django_plans_payments-2.3.2.tar.gz

Download URL django_plans_payments-2.3.2.tar.gz
Size 27.9 kB
Tags Source
SHA-256 checksum
How to use checksums
9f7aa672aeee35708ccf218a5a4aaa986d557cf5bb1e11e95a285aa1c4726971
BLAKE2b-256 checksum
How to use checksums
3d7dbc6e16518928e8113f31bc84ff7ff6253b050001f1abd06f9448f05844b1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / django_plans_payments-2.3.2-py3-none-any.whl

Download URL django_plans_payments-2.3.2-py3-none-any.whl
Size 23.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5072aab79a6339c26daa24bf8045017af525f8b2e23e3d0e3e8fa07a64eee2e9
BLAKE2b-256 checksum
How to use checksums
8309e78c809c823ef6f18f57016696c0477aeccfee6a1aca8448952a2f534d55
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

2.3.2 This release

2 release files

2.3.1

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.2

2 release files

2.0.0

2 release files

1.5.0

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.3

2 release files

1.1.2

1 release file

1.1.1

1 release file

1.1.0

1 release file

1.0.1

1 release file

1.0.0

1 release file

0.5.8

1 release file

0.5.7

1 release file

0.5.6

1 release file

0.5.5

1 release file

0.5.4

1 release file

0.5.3

1 release file

0.5.2

1 release file

0.5.1

1 release file

0.5.0

1 release file

0.4.1

1 release file

0.4.0

1 release file

0.3.0

1 release file

0.2.0

1 release file

0.1.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