# Wallet System The wallet system allows users to maintain a balance that can be used to pay for subscriptions, providing an alternative to direct Midtrans payments. ## Architecture ``` User → Wallet (1:1) Wallet → WalletTransaction (1:N) Wallet → TopUp (1:N) User → TopUp (1:N) ``` ## How It Works 1. **Create/Get Wallet** — Each user gets a wallet (auto-created on first access) 2. **Top-Up** — Users add funds via Midtrans payment (bank transfer, GoPay, QRIS, etc.) 3. **Credit** — Once top-up payment settles, wallet is credited automatically via webhook 4. **Pay Subscription** — Users can pay for subscriptions directly from wallet balance 5. **Recurring Billing** — Celery task automatically debits wallet for due subscriptions ## Top-Up Flow ### 1. Create a Top-Up ```python from subscriptions.services import WalletService service = WalletService() topup = service.create_topup( user=user, amount=100000, payment_type="bank_transfer", bank="bca", ) # topup.payment_details contains: # {"bank": "bca", "va_number": "1234567890"} ``` ### 2. User Pays via VA/QR The user transfers money to the virtual account or scans the QR code. ### 3. Webhook Processes Payment When Midtrans sends a `settlement` notification for the `TOPUP-xxx` order: - Top-up status → `success` - Wallet balance is credited - `WalletTransaction` record is created ### 4. API Endpoint ```bash # Create top-up POST /api/subscriptions/topups/ { "amount": 100000, "payment_type": "bank_transfer", "bank": "bca" } # Check wallet balance GET /api/subscriptions/wallet/me/ # List transactions GET /api/subscriptions/wallet/transactions/ ``` ## Paying Subscription from Wallet ### Manual Payment ```python from subscriptions.services import WalletService service = WalletService() txn = service.pay_subscription_from_wallet(subscription) ``` This: 1. Checks wallet has sufficient balance 2. Debits the wallet 3. Activates/renews the subscription 4. Creates a `Payment` record (type: `wallet`) 5. Generates an invoice ### Create Subscription with Wallet ```python subscription = service.create_subscription_from_wallet( user=user, plan=plan, ) ``` ### Automatic Billing The `process_wallet_billing` Celery task runs hourly and: 1. Finds active wallet subscriptions due for billing 2. Debits each user's wallet 3. Marks subscriptions as `past_due` if insufficient balance ## Balance Safety All wallet operations use `select_for_update()` to prevent race conditions: ```python # From services.py wallet = Wallet.objects.select_for_update().get(pk=wallet.pk) ``` This ensures: - Two concurrent top-ups don't result in incorrect balance - A top-up and subscription payment can't conflict - Balance can never go negative (raises `ValueError`) ## Transaction Types | Type | Description | Amount Sign | |------|-------------|-------------| | `top_up` | Funds added via Midtrans payment | Positive (+) | | `subscription_payment` | Subscription charge from wallet | Negative (-) | | `refund` | Refund credited back to wallet | Positive (+) | | `adjustment` | Manual admin adjustment | Either | ## Wallet Signals ```python from subscriptions.signals import topup_completed, wallet_credited, wallet_debited @receiver(topup_completed) def on_topup(sender, instance, **kwargs): """Top-up payment settled.""" print(f"Wallet top-up: {instance.amount}") @receiver(wallet_credited) def on_credit(sender, instance, **kwargs): """Any positive wallet transaction.""" print(f"Wallet credited: +{instance.amount}") @receiver(wallet_debited) def on_debit(sender, instance, **kwargs): """Any negative wallet transaction.""" print(f"Wallet debited: {instance.amount}") ```