# Services The service layer is the single entry point for all business logic. It orchestrates interactions between Django models and the Midtrans API. ## SubscriptionService ```python from subscriptions.services import SubscriptionService service = SubscriptionService() ``` ### Constructor ```python SubscriptionService(client: MidtransClient = None) ``` If no client is provided, a default client is created from Django settings. ### Subscription Creation #### `create_subscription(user, plan, payment_type, ...)` Create a new subscription and register it with Midtrans. ```python subscription = service.create_subscription( user=request.user, plan=plan, payment_type="bank_transfer", customer_details={ "first_name": "John", "last_name": "Doe", "email": "john@example.com", "phone": "08123456789", }, metadata={"bank": "bca"}, ) ``` **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `user` | `User` | Yes | Django user object | | `plan` | `Plan` | Yes | Plan to subscribe to | | `payment_type` | `str` | Yes | `credit_card`, `gopay`, `bank_transfer`, `echannel`, `qris`, `shopeepay`, `wallet` | | `token` | `str` | No | Saved card token ID (credit card only) | | `gopay_account_id` | `str` | No | Linked GoPay account ID | | `customer_details` | `dict` | No | Customer info for Midtrans | | `metadata` | `dict` | No | Additional metadata (e.g., `{"bank": "bca"}`) | **Returns:** `Subscription` instance **Behavior:** - Creates a `Subscription` with `pending` status (or `trialing` if plan has trial) - For GoPay with linked account: creates a native Midtrans subscription - For all other payment types: creates a one-time charge via `/v2/charge` - Auto-generates invoice if `AUTO_GENERATE_INVOICE` is enabled ### Charge Creation #### `create_charge_for_subscription(subscription, is_retry=False, retry_count=0)` Create a one-time charge for a subscription billing cycle. ```python payment = service.create_charge_for_subscription(subscription) ``` **Returns:** `Payment` instance with `payment_details` containing VA numbers, QR URLs, etc. ### Subscription Management #### `activate_subscription(subscription)` Activate a subscription after successful payment. Sets the billing period and increments the cycle count. #### `renew_subscription(subscription)` Renew for the next billing cycle. Extends the period and increments the cycle count. #### `pause_subscription(subscription)` Pause a subscription. If a native Midtrans subscription exists, disables it via API. #### `resume_subscription(subscription)` Resume a paused subscription. Re-enables native Midtrans subscription if applicable. #### `cancel_subscription(subscription, reason="", immediate=False)` Cancel a subscription. ```python # Cancel at period end service.cancel_subscription(subscription, reason="Too expensive") # Cancel immediately service.cancel_subscription(subscription, reason="Requested", immediate=True) ``` #### `expire_subscription(subscription)` Mark a subscription as expired (max cycles reached or grace period ended). #### `mark_past_due(subscription)` Mark a subscription as past due (payment failed). #### `change_plan(subscription, new_plan, immediate=True)` Change the subscription plan. Updates Midtrans subscription if native. Records plan change history in metadata. ```python new_plan = Plan.objects.get(slug="premium") service.change_plan(subscription, new_plan) ``` #### `sync_subscription_status(subscription)` Sync the subscription status from Midtrans API. Only works for subscriptions with `midtrans_subscription_id`. ### Refund #### `refund_payment(payment, amount=None, reason="", direct=False)` Refund a payment (full or partial). ```python # Full refund service.refund_payment(payment, reason="Customer request") # Partial refund service.refund_payment(payment, amount=50000, reason="Partial refund") # Direct refund (GoPay, ShopeePay) service.refund_payment(payment, direct=True) ``` ### Webhook Processing #### `process_notification(payload, ip_address=None)` Process an incoming Midtrans webhook notification. Verifies the signature, updates payment status, and triggers side effects. ```python webhook_log = service.process_notification( payload=request.data, ip_address="103.x.x.x", ) ``` **Returns:** `WebhookLog` instance with processing status. ### Query Helpers #### `get_active_subscription(user)` (static) Get the user's active subscription (status `active` or `trialing`). #### `get_subscriptions_due_for_billing()` (static) Get all subscriptions that need billing (active, past `next_billing_date`, not cancelled). #### `get_expiring_subscriptions(days)` (static) Get subscriptions expiring within N days. #### `get_subscriptions_to_cancel()` (static) Get subscriptions marked for cancellation at period end. #### `get_overdue_invoices()` (static) Get invoices past their due date. --- ## WalletService ```python from subscriptions.services import WalletService wallet_service = WalletService() ``` ### Wallet Management #### `get_or_create_wallet(user)` (static) Get or create a wallet for a user. ```python wallet = WalletService.get_or_create_wallet(user) ``` #### `get_wallet(user)` (static) Get a user's wallet. Returns `None` if not found. ### Top-Up #### `create_topup(user, amount, payment_type, ...)` Create a top-up request and charge via Midtrans. ```python topup = wallet_service.create_topup( user=request.user, amount=100000, payment_type="bank_transfer", bank="bca", customer_details={"first_name": "John", "email": "john@example.com"}, ) # topup.payment_details contains VA number, QR code, etc. ``` **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `user` | `User` | Yes | User performing top-up | | `amount` | `int` | Yes | Top-up amount | | `payment_type` | `str` | Yes | Payment method | | `bank` | `str` | No | Bank for bank_transfer (default: `bca`) | | `callback_url` | `str` | No | Callback URL for GoPay/ShopeePay | | `customer_details` | `dict` | No | Customer details for Midtrans | | `token` | `str` | No | Card token for credit card payments | ### Balance Operations #### `credit_wallet(wallet, amount, transaction_type, ...)` Add funds to a wallet. Uses `select_for_update()` to prevent race conditions. ```python txn = wallet_service.credit_wallet( wallet=wallet, amount=Decimal("100000"), transaction_type="top_up", reference_type="topup", reference_id=str(topup.id), description="Top up via BCA", ) ``` #### `debit_wallet(wallet, amount, transaction_type, ...)` Deduct funds from a wallet. Raises `ValueError` if insufficient balance. ### Subscription Payment from Wallet #### `pay_subscription_from_wallet(subscription)` Pay a subscription from wallet balance. Debits the wallet, activates the subscription, and generates an invoice. ```python txn = wallet_service.pay_subscription_from_wallet(subscription) ``` #### `create_subscription_from_wallet(user, plan, ...)` Create a new subscription paid entirely from wallet balance. ```python subscription = wallet_service.create_subscription_from_wallet( user=request.user, plan=plan, customer_details={"first_name": "John", "email": "john@example.com"}, ) ```