Innovate anywhere, anytime withruncode.io Your cloud-based dev studio.
Django

Integrate Django-Oscar-Accounts with Django-Oscar

2022-07-19

Django Oscar Accounts is a package that provides the ability to manage accounts with fully managed account functionality to use with Django Oscar.

* Django Oscar Accounts provides managed accounts using double-entry bookkeeping.

* In this package every transaction is recorded twice(once for the source account and once for the destination account) by using double-entry bookkeeping. This make sure that books always balance and there exist full record of all transactions.

Features -

  • A user can have multiple accounts
  • An account can be created with - no users assigned, a single primary user assigned or set of users can be assigned
  • An account consists of start date and end date to restrict its usage in a limited time, also it can be restricted so that it can only be used to pay for a range of products.
  • An account has a credit limit, by default it will be zero.

Installation -

* Install using pip:

pip install django-oscar-accounts

* Next, add "oscar_accounts" to INSTALLED_APPS in settings file.

* After this apply migrations with "manage.py migrate oscar_accounts" command. This will create the appropriate database tables.

* Next use "manage.py oscar_accounts_init" command to create core accounts and account-types. The names of these accounts can be controlled using settings.

* To access accounts via Django Oscar dashboard, you need to update OSCAR_DASHBOARD_NAVIGATION list

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',
            },
        ]
    })

Next, add the below url-pattern to your urls.py

from oscar_accounts.dashboard.app import application as accounts_app

urlpatterns = [
    ...
    url(r'^dashboard/accounts/', include(accounts_app.urls)),
]

NOTE: To override templates, add an additional path(ACCOUNTS_TEMPLATE_DIR) to your TEMPLATE_DIRS

Integrating in Django Oscar Checkout Process

For checkout integration, you have to override PaymentDetailsView in checkout app

Step 1: Display the list of accounts for the user in final step(Payment details Page).

   For this, override the get_context_data() method and filter active user accounts as below.

from oscar_accounts.models import Account

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

Step 2: Override the "post" method to validate the selected account and render them again in the preview view (but as hidden).

Step 3: Override the handle_payment method of your checkout's PaymentDetailsView to transfer the amount(order total amount) from the user account to your account(Ex: Sales Account)

* This package provides an API to make transfers in facade module.

* If the transfer is invalid, an exception is raised. All these exceptions are subclasses of oscar_accounts.exceptions.AccountException. So we just need to handle this exception.

from oscar_accounts.models import Account
from oscar_accounts import facade, exceptions

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, e:
    raise PaymentError("Transfer Failed")
else:
    # Add Payment source and Payment event
    source_type, created = 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 is successful, but something went wrong in placing the order(any exception occurs after post-payment), then you have to roll-back the previous transfer.

from oscar_accounts import facade
try:
    self.place_order()
except Exception, e:
    facade.reverse(transfer, user=user,
                   merchant_reference=order_number,
                   description="Payment Cancellation")

Note: Transfer operation is wrapped in its own db transaction to make sure that only complete transfers are written.