Skip to main content

A secure, flexible virtual wallet system for Django applications

Project description

dj-wallets

PyPI version Python Versions Django Versions License Coverage Code Style: Ruff PRs Welcome

A secure, flexible, and powerful virtual wallet system for Django applications.

Inspired by laravel-wallet


What is a Virtual Wallet?

Think of this as a "digital bank account" inside your app. It doesn't handle real money directly (like Stripe or PayPal), but it keeps track of a virtual balance for your users.

  • Deposit: Adds "money" to the user's balance.
  • Withdraw: Takes "money" away from the balance.
  • Pay: Automatically deducts the cost of an item from the user's wallet and (optionally) transfers it to the seller.
  • Safe: Behind the scenes, the library ensures that two transactions can't happen at the exact same time to break the balance (Race Condition Protection).

Features

  • Multi-Wallet Support: Each user can have multiple wallets (default, savings, USD, etc.).
  • Atomic Transactions: Ensures data integrity during concurrent operations.
  • Transfers & Exchanges: Move funds between users or between different wallets of the same user.
  • Product Purchases: Built-in support for purchasing items using wallet balance.
  • Polymorphic Holders: Attach wallets to any Django model (Users, Organizations, Teams).

Installation

pip install dj-wallet

Add to your INSTALLED_APPS:

INSTALLED_APPS = [
    # ...
    'dj_wallet',
]

Run migrations:

python manage.py migrate

Quick Start

1. Simple Setup

Add the WalletMixin to your custom User model to give it wallet capabilities.

from django.contrib.auth.models import AbstractUser
from dj_wallet.mixins import WalletMixin

class User(WalletMixin, AbstractUser):
    pass

2. Standard Operations

user = User.objects.create(username="khaled")

# Deposit: Adds to balance
user.deposit(500.00)

# Check balance
print(user.balance) # 500.00

# Withdraw: Deducts from balance
user.withdraw(100.00)

# Transfer: Deducts from one, adds to another
recipient = User.objects.create(username="friend")
user.transfer(recipient, 50.00)

🛒 Buying Things (ProductMixin)

The library includes a robust system for handling product purchases. To make any Django model "buyable," apply the ProductMixin. This allows wallets to interact directly with your business logic.

1. Implementation

To make an item purchasable, implement the ProductMixin in your model:

from dj_wallet.mixins import ProductMixin
from django.db import models

class DigitalCourse(ProductMixin, models.Model):
    title = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    def get_amount_product(self, customer):
        """REQUIRED: Return the price for this specific customer."""
        return self.price

    def can_buy(self, customer, quantity=1):
        """OPTIONAL: Check inventory or eligibility (default: True)."""
        return True

    def get_meta_product(self):
        """OPTIONAL: Add transaction metadata (default: {})."""
        return {"course_id": self.id}

2. Available Methods (ProductMixin)

These methods should be defined in your product class:

  • get_amount_product(customer): Returns the cost of the product. This is where you can implement dynamic pricing, discounts, or multi-currency logic.
  • can_buy(customer, quantity): Validation logic before purchase. Return False to block the transaction (e.g., if out of stock).
  • get_meta_product(): Provide extra data that will be saved in the transaction's meta JSON field for auditing.

3. Processing a Purchase

A holder can pay for a product using the .pay() method.

course = DigitalCourse.objects.get(id=1)

# This single line checks balance, validates availability, and transfers funds
try:
    transaction = user.pay(course)
    print("Course purchased successfully!")
except InsufficientFunds:
    print("Insufficient funds in wallet.")
except ProductNotAvailable:
    print("This item is currently out of stock.")

Core Services

Django Wallets uses a component-based architecture where logic is encapsulated in services.

  • WalletService: Base wallet operations (deposit, withdraw, reversals).
  • TransferService: Fund movements between holders, refunds, and gifts.
  • ExchangeService: Internal conversions between a holder's different wallets.
  • PurchaseService: High-level logic for processing product payments.

🛠️ Available Methods Reference

User/Holder Methods (via WalletMixin)

  • .balance: Property that returns the current balance of the default wallet.
  • .deposit(amount, meta=None, confirmed=True): Adds funds to the default wallet. Supports metadata and unconfirmed states.
  • .withdraw(amount, meta=None, confirmed=True): Deducts funds. Raises InsufficientFunds if the balance is too low.
  • .force_withdraw(amount): Deducts funds regardless of balance (allows negative balance).
  • .transfer(to_holder, amount): Moves funds from this holder to another.
  • .pay(product): High-level method to purchase an item implementing ProductMixin.
  • .get_wallet(slug): Returns a specific wallet by name (creates it if it doesn't exist).
  • .has_wallet(slug): Checks if a wallet exists without creating it.
  • .freeze_wallet(slug): Locks a wallet from all incoming/outgoing transactions.
  • .unfreeze_wallet(slug): Re-enables a frozen wallet.
  • .get_pending_transactions(slug): Returns a QuerySet of transactions awaiting confirmation.

🏗️ Service Methods

For advanced usage, you can call services directly:

  • WalletService.confirm_transaction(txn): Moves a transaction from PENDING to COMPLETED and updates the wallet balance.
  • WalletService.reverse_transaction(txn): Perfectly undoes a transaction and records the reversal.
  • TransferService.refund(transfer): Reverses a transfer and returns money to the sender.
  • ExchangeService.exchange(holder, from_slug, to_slug, amount): Moves funds between two wallets owned by the same holder.

Customization

1. Models

Extend the default models to add custom fields.

from dj_wallet.abstract_models import AbstractWallet

class MyWallet(AbstractWallet):
    tax_exempt = models.BooleanField(default=False)

2. Mixins

Override existing logic or add helpers by extending the WalletMixin.

from dj_wallet.mixins import WalletMixin

class MyCustomMixin(WalletMixin):
    def deposit(self, amount, meta=None, confirmed=True):
        print(f"User is depositing {amount}")
        return super().deposit(amount, meta, confirmed)

# settings.py
dj_wallet = {
    'WALLET_MIXIN_CLASS': 'myapp.mixins.MyCustomMixin',
}

3. Services

Override core business logic by extending the service classes.

from dj_wallet.services.common import WalletService

class MyWalletService(WalletService):
    @classmethod
    def deposit(cls, wallet, amount, **kwargs):
        # Your custom logic here
        return super().deposit(wallet, amount, **kwargs)

# settings.py
dj_wallet = {
    'WALLET_SERVICE_CLASS': 'myapp.services.MyWalletService',
}

Support Us

If you find this project useful, please consider supporting its development.

Star the Repository

Show some love by starring the project on GitHub!

Sponsorship & Donations

  • BTC: 13X8aZ23pFNCH2FPW6YpRTw4PGxo7AvFkN
  • USDT (TRC20): TEitNDQMm4upYmNvFeMpxTRGEJGdord3S5
  • USDT (BEP20): 0xc491a2ba6f386ddbf26cdc906939230036473f5d

License

MIT License. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dj_wallet-0.1.0.tar.gz (21.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dj_wallet-0.1.0-py3-none-any.whl (23.6 kB view details)

Uploaded Python 3

File details

Details for the file dj_wallet-0.1.0.tar.gz.

File metadata

  • Download URL: dj_wallet-0.1.0.tar.gz
  • Upload date:
  • Size: 21.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.2 CPython/3.13.2 Windows/11

File hashes

Hashes for dj_wallet-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6003f15a7cdb246c6252848ffbb0c472999586c12f3a2bb80c36123759f40ef8
MD5 b3c020499a4a33858958c2ee345c0583
BLAKE2b-256 289e86705d2d31c306d2ab91568272e45da5afe8f6c65e77d908b3878479070c

See more details on using hashes here.

File details

Details for the file dj_wallet-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: dj_wallet-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 23.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.2 CPython/3.13.2 Windows/11

File hashes

Hashes for dj_wallet-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 451e0dd68c74462fedf559c54a5f63cd6f875c78ab9f796a7be44071ab2626bc
MD5 f7ea1db9dafb89448aefa297c7c22234
BLAKE2b-256 254b6a098ec531e14b912e176645bdddcc156b5ac4c119d77bb2c612ccf8d71d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page