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

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.

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

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.

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"},
)