A Django virtual wallet system for in-app credits, points, and purchases. Secure, flexible, and easy to integrate. Ideal for e‑commerce, LMS, and SaaS platforms.
dj-wallets
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 balance system" inside your app. It doesn't handle real money directly (like Stripe or PayPal), but it keeps track of any kind of value for your users – whether that's money, points, credits, tokens, or any other unit.
Use Cases:
- 💰 Virtual Currency: E-commerce credits, in-app coins
- 🎮 Gaming Points: XP, gems, energy, lives
- 📚 Learning Platforms: Course credits, study tokens
- 🏆 Loyalty Programs: Reward points, cashback
- 🎁 Gift Cards: Prepaid balances, vouchers
- Deposit: Adds value to the user's balance.
- Withdraw: Takes value 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-Purpose Wallets: Not just for money – use for points, credits, tokens, XP, or any value type.
- Multi-Wallet Support: Each user can have multiple wallets (default, savings, points, 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).
- Admin Panel Integration: Full Django admin support for managing wallets, transactions, and transfers.
- Django Signals: Hook into wallet events (deposits, withdrawals, transfers, etc.) for custom logic.
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 theWalletMixin 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)
🏢 Polymorphic Wallet Holders
Wallets aren't just for users! The WalletMixin can be applied to any Django model, making it perfect for:
- Organizations with shared budgets
- Teams with allocated resources
- Projects with dedicated funds
- Departments with expense tracking
- Game Characters with in-game currency
Example: Organization Wallets
from django.db import models
from dj_wallet.mixins import WalletMixin
class Organization(WalletMixin, models.Model): name = models.CharField(max_length=100)
Now organizations can have wallets just like users!
org = Organization.objects.create(name="Acme Corp")
org.deposit(10000.00) # Company budget
Create multiple wallets for different purposes
marketingwallet = org.getwallet("marketing")
marketing_wallet.deposit(5000.00)
developmentwallet = org.getwallet("development") development_wallet.deposit(3000.00)
Example: Team Resource Allocation
class Team(WalletMixin, models.Model):
name = models.CharField(max_length=100)
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
Allocate resources from organization to team
team = Team.objects.create(name="Backend Team", organization=org)
org.transfer(team, 2000.00) # Transfer budget to team
print(team.balance) # 2000.00
This polymorphic design means you can model complex financial hierarchies: companies → departments → teams → employees, each with their own wallets and transfer capabilities.
🛒 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 theProductMixin 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(maxdigits=10, decimalplaces=2)
def getamountproduct(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 getmetaproduct(self): """OPTIONAL: Add transaction metadata (default: {}).""" return {"course_id": self.id}
2. Available Methods (ProductMixin)
These methods should be defined in your product class:getamountproduct(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. ReturnFalseto block the transaction (e.g., if out of stock).getmetaproduct(): Provide extra data that will be saved in the transaction'smetaJSON 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. RaisesInsufficientFundsif 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 implementingProductMixin..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..getpendingtransactions(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 fromPENDINGtoCOMPLETEDand 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, fromslug, toslug, amount): Moves funds between two wallets owned by the same holder.
Customization
1. Models
Extend the default models to add custom fields.from djwallet.abstractmodels import AbstractWallet
class MyWallet(AbstractWallet): tax_exempt = models.BooleanField(default=False)
2. Mixins
Override existing logic or add helpers by extending theWalletMixin.
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 = {
'WALLETMIXINCLASS': '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 = {
'WALLETSERVICECLASS': 'myapp.services.MyWalletService',
}
🛡️ Admin Panel Integration
Django Wallets comes with full Django Admin support out of the box. All models are registered with customized admin interfaces.
📡 Django Signals
Hook into wallet events for custom business logic, notifications, or integrations:
from dj_wallet.signals import (
balance_changed,
transaction_created,
wallet_created,
transfer_completed,
transaction_confirmed,
pre_withdraw,
pre_deposit,
)
from django.dispatch import receiver
@receiver(balance_changed) def notifyuserofbalancechange(sender, wallet, transaction, **kwargs): """Send notification when balance changes.""" print(f"Wallet {wallet.slug} balance changed by {transaction.amount}")
@receiver(transfer_completed) def logtransfer(sender, transfer, fromwallet, to_wallet, **kwargs): """Log completed transfers for auditing.""" print(f"Transfer from {fromwallet} to {towallet} completed")
@receiver(pre_withdraw) def validate_withdrawal(sender, wallet, amount, meta, **kwargs): """Custom validation before withdrawal.""" if amount > 10000: raise ValueError("Withdrawals over 10,000 require approval")
Available Signals
| Signal | Trigger | Arguments | |--------|---------|----------| | balance_changed | After deposit/withdraw is confirmed | wallet, transaction | | transaction_created | When any transaction is created | transaction | | wallet_created | When a new wallet is created | wallet, holder | | transfercompleted | After a transfer finishes | transfer, fromwallet, to_wallet | | transaction_confirmed | When pending transaction is confirmed | transaction | | pre_withdraw | Before a withdrawal (for validation) | wallet, amount, meta | | pre_deposit | Before a deposit (for validation) | wallet, amount, meta |
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.