Services¶
The service layer is the single entry point for all business logic. It orchestrates interactions between Django models and the Midtrans API.
SubscriptionService¶
from subscriptions.services import SubscriptionService
service = SubscriptionService()
Constructor¶
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.
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 |
|---|---|---|---|
|
|
Yes |
Django user object |
|
|
Yes |
Plan to subscribe to |
|
|
Yes |
|
|
|
No |
Saved card token ID (credit card only) |
|
|
No |
Linked GoPay account ID |
|
|
No |
Customer info for Midtrans |
|
|
No |
Additional metadata (e.g., |
Returns: Subscription instance
Behavior:
Creates a
Subscriptionwithpendingstatus (ortrialingif 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/chargeAuto-generates invoice if
AUTO_GENERATE_INVOICEis enabled
Charge Creation¶
create_charge_for_subscription(subscription, is_retry=False, retry_count=0)¶
Create a one-time charge for a subscription billing cycle.
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.
# 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.
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).
# 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.
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¶
from subscriptions.services import WalletService
wallet_service = WalletService()
Wallet Management¶
get_or_create_wallet(user) (static)¶
Get or create a wallet for a user.
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.
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 |
|---|---|---|---|
|
|
Yes |
User performing top-up |
|
|
Yes |
Top-up amount |
|
|
Yes |
Payment method |
|
|
No |
Bank for bank_transfer (default: |
|
|
No |
Callback URL for GoPay/ShopeePay |
|
|
No |
Customer details for Midtrans |
|
|
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.
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.
txn = wallet_service.pay_subscription_from_wallet(subscription)
create_subscription_from_wallet(user, plan, ...)¶
Create a new subscription paid entirely from wallet balance.
subscription = wallet_service.create_subscription_from_wallet(
user=request.user,
plan=plan,
customer_details={"first_name": "John", "email": "john@example.com"},
)