Integrate django-oscar-accounts with Django Oscar

Blog / Django · November 7, 2015 · Updated June 10, 2026 · 8 min read
Integrate django-oscar-accounts with Django Oscar

django-oscar-accounts is the official managed-accounts add-on for Django Oscar. It adds a double-entry bookkeeping ledger to your store so you can offer store credit, gift cards, loyalty points, refund wallets and prepaid balances without hard-coding money logic into your order models. The short version: install it with pip, add oscar_accounts to INSTALLED_APPS, run migrations, seed the core accounts with the oscar_accounts_init command, then move money during checkout with a single call — facade.transfer(source, destination, amount). Every transfer debits one account and credits another, so the books always reconcile and you keep a complete audit trail of every balance change.

If you are still standing up the storefront itself, start with building an e-commerce shop with Django Oscar and then layer accounts on top.

Key takeaways

  • What it is: a managed-accounts ledger for Django Oscar, backed by double-entry bookkeeping so balances always reconcile.
  • What you build with it: store credit, gift cards, loyalty/reward points, refund-to-wallet and B2B prepaid accounts.
  • Core API: facade.transfer() to move funds and facade.reverse() to undo a transfer — both wrapped in a database transaction.
  • 2026 status: works with Django Oscar 3.x on Django 4.2+, but the package is lightly maintained, so pin versions and test before upgrading.
  • Golden rule: never let an account go negative unintentionally and never do balance maths outside a transaction; let the ledger be the single source of truth.

What can you build with django-oscar-accounts?

An "account" is simply a pot of money with a balance, an optional owner, an optional credit limit and optional start/end dates. Because the same primitive backs every feature, one package covers a surprising range of money features:

Use case How accounts model it
Store credit A per-customer account topped up on refunds and spent at checkout
Gift cards An account with a code, a fixed balance and an expiry date, not tied to one user
Loyalty / reward points An account credited on each order and redeemed for discounts
Refund-to-wallet Reverse a transfer back into the customer's account instead of the card
B2B prepaid / credit A company account with a credit limit so it can run a controlled negative balance

Each account can be restricted to a date window or to a range of products, which is what makes gift-card and promotion rules possible without extra tables.

How does double-entry bookkeeping work here?

Double-entry is the accounting rule that every transfer touches two accounts: it debits a source and credits a destination by the same amount. Nothing is created or destroyed in isolation, so the sum of all balances always nets to zero and you can prove where every unit of value came from.

In practice that means you never "set" a balance — you transfer into or out of it. When a customer redeems 20 of store credit, the ledger debits their account and credits your sales account by 20. When you issue a 50 gift card, you transfer 50 from a bank/source account into the gift-card account. Because each movement is a Transfer made of two Transaction rows, you get a tamper-evident audit trail for free, which is exactly what finance and compliance teams ask for.

How do you install django-oscar-accounts?

The package targets Django Oscar 3.x on Django 4.2+ and Python 3.10+. Install it from PyPI:

pip install django-oscar-accounts

Add oscar_accounts to INSTALLED_APPS in your settings, then create the database tables and seed the core accounts and account types:

python manage.py migrate oscar_accounts
python manage.py oscar_accounts_init

The oscar_accounts_init command creates the core ledger accounts (bank, sales, redemptions, lapsed and so on); their names are configurable through settings. To expose accounts in the Oscar dashboard, extend OSCAR_DASHBOARD_NAVIGATION:

from oscar.defaults import *

OSCAR_DASHBOARD_NAVIGATION.append({
    'label': 'Accounts',
    'icon': 'icon-globe',
    'children': [
        {'label': 'Accounts', 'url_name': 'accounts-list'},
        {'label': 'Transfers', 'url_name': 'transfers-list'},
        {'label': 'Deferred income report', 'url_name': 'report-deferred-income'},
        {'label': 'Profit/loss report', 'url_name': 'report-profit-loss'},
    ],
})

Finally, wire the dashboard URLs into your root urls.py. On modern Django use path() — the old url() helper was removed in Django 4.0:

from django.urls import path, include
from oscar_accounts.dashboard.app import application as accounts_app

urlpatterns = [
    # ... your other patterns ...
    path('dashboard/accounts/', include(accounts_app.urls)),
]

What account types and hierarchy does it create?

Accounts are grouped under account types, which form a tree modelled on a standard chart of accounts. oscar_accounts_init seeds a sensible default hierarchy that you can extend:

Account type Role
Assets Money the business holds, e.g. the bank/source account
Liabilities -> Deferred income Customer-owned balances: store credit, gift cards, points (you owe goods)
Income -> Redemptions Where redeemed balances land once an order is paid
Income -> Lapsed Where expired balances are recognised after they pass their end date
Expenses Costs such as bonus credit you gift to customers

Customer wallets sit under liabilities on purpose: a prepaid balance is money you owe the customer in goods, not income, until it is actually redeemed.

How do you transfer funds and plug it into checkout?

All money movement goes through the facade module. The methods you use day to day are few:

API What it does
facade.transfer(source, destination, amount, ...) Moves money; debits source, credits destination, returns a Transfer
facade.reverse(transfer, ...) Reverses an earlier transfer (refunds / failed orders)
Account.active.filter(...) Queries accounts that are not expired
account.balance The current balance, derived from the ledger
account.is_debit_permitted(amount) Checks a debit is allowed before you attempt it

To accept account balances as a payment method, override your checkout PaymentDetailsView (see customising Django Oscar models, views and URLs for the override pattern). First, show the customer their usable accounts:

from oscar_accounts.models import Account

accounts = Account.active.filter(primary_user=user, balance__gt=0)

Then, in handle_payment, transfer the order total from the customer's account to your sales account. Wrap it so any AccountException becomes a clean payment error. Note the Python 3 except ... as e: syntax — the old Python 2 comma form no longer parses:

from oscar_accounts.models import Account
from oscar_accounts import facade, exceptions
from oscar.apps.payment.exceptions import PaymentError
from oscar.apps.payment.models import Source, SourceType

source_account = user_selected_account
destination_account = Account.objects.get(name="Sales")

try:
    transfer = facade.transfer(
        source_account,
        destination_account,
        order_total,
        user=user,
        merchant_reference=order_number,
        description="Redeemed to pay for order %s" % order_number,
    )
except exceptions.AccountException as e:
    raise PaymentError("Transfer failed: %s" % e)

# Record the payment source and event against the order
source_type, _ = SourceType.objects.get_or_create(name="Accounts")
source = Source(
    source_type=source_type,
    amount_allocated=order_total,
    amount_debited=transfer.amount,
    reference=transfer.reference,
)
self.add_payment_source(source)
self.add_payment_event("Transferred", transfer.amount, transfer.reference)

If the transfer succeeds but placing the order then fails, reverse the transfer so the customer is not left paying for an order that never existed:

from oscar_accounts import facade

try:
    self.place_order()
except Exception:
    facade.reverse(
        transfer,
        user=user,
        merchant_reference=order_number,
        description="Order placement failed - payment cancelled",
    )
    raise

How do you keep balances safe and accurate?

Money bugs are the worst kind of bug, so a few rules matter:

  • Never let an account go negative unless you have deliberately given it a credit limit. The ledger enforces this, but validate first with account.is_debit_permitted(amount).
  • Do every balance change through facade.transfer/reverse, never with raw UPDATEs. Each transfer is wrapped in its own database transaction, so only complete, balanced transfers are ever written.
  • Freeze funds during checkout. Allocate the balance when the customer reaches payment and only finalise on a successful order, reversing if anything downstream fails — exactly the rollback pattern above.
  • Reconcile regularly. Because every entry is doubled, a quick sum of all balances should net to zero; alert if it ever does not.

These are the same guarantees you would want from any payment integration — see our walkthrough of PayPal payment integration with Django for the card side of the same checkout flow.

Is django-oscar-accounts still maintained in 2026?

Honestly, it is lightly maintained. Releases are infrequent and the package tracks Django Oscar's release cadence rather than Django's, so there can be a lag after a new Django or Oscar version ships. It does work with Oscar 3.x on Django 4.2+, but you should pin exact versions, keep the dashboard app import in step with your Oscar version, and run your checkout tests before every upgrade.

For many stores the package is still the fastest way to add store credit and gift cards. But if your requirements are narrow — say, loyalty points only — or you need guarantees the package cannot give, a small custom double-entry ledger (two tables, accounts and transactions, with every write doubled inside a transaction) is a few days of work and removes the dependency risk. If you would rather not weigh that trade-off alone, our Django development services team integrates and maintains Oscar stores, custom ledgers included.

Frequently Asked Questions

What is django-oscar-accounts used for?

It adds managed accounts to a Django Oscar store so you can run store credit, gift cards, loyalty points, refund-to-wallet and B2B prepaid balances. Each account is a balance backed by a double-entry ledger, so the money side stays auditable and always reconciled.

Does django-oscar-accounts work with Django Oscar 3.x and Django 4.2+?

Yes. Current releases support Oscar 3.x on Django 4.2+ and Python 3.10+. Because the package is lightly maintained and follows Oscar's release cycle, pin exact versions and run your checkout tests before upgrading Django or Oscar.

How does a transfer actually work?

You call facade.transfer(source, destination, amount, ...), which debits the source account and credits the destination by the same amount inside a single database transaction. It returns a Transfer object; if anything is invalid it raises a subclass of AccountException that you catch and turn into a payment error.

How do I refund or cancel a payment made from an account?

Use facade.reverse(transfer, ...) with the original Transfer. It books the opposite entries, returning the funds to the customer's account. This is the rollback to run if order placement fails after a successful transfer.

Can an account balance go negative?

Not by default — debits that would push a balance below zero are rejected. The exception is an account given an explicit credit limit (useful for B2B accounts), which may go negative up to that limit. Always check is_debit_permitted() before debiting.

Should I use django-oscar-accounts or build my own ledger?

Use the package when you want store credit and gift cards quickly and you are comfortable tracking its release cadence. Build a small custom double-entry ledger when your needs are narrow or you want to drop the dependency — it is only two tables (accounts and transactions) with every write doubled inside a transaction.

Share this article