# Models All models use UUIDv4 primary keys and include `created_at`/`updated_at` timestamps via the `TimeStampedModel` abstract base class. ## Plan Defines a subscription plan with pricing and billing schedule. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `name` | `CharField(255)` | Plan display name | | `slug` | `SlugField` (unique) | URL-friendly identifier, used as lookup field | | `description` | `TextField` | Plan description | | `price` | `DecimalField(12,2)` | Price per interval | | `currency` | `CharField(3)` | Currency code (default: `IDR`) | | `interval` | `PositiveIntegerField` | Number of interval units (default: `1`) | | `interval_unit` | `CharField(10)` | `day`, `week`, or `month` | | `trial_period_days` | `PositiveIntegerField` | Trial duration in days (default: `0`) | | `max_cycles` | `PositiveIntegerField` | Max billing cycles, `0` = unlimited | | `status` | `CharField(10)` | `active`, `inactive`, or `archived` | | `features` | `JSONField` | Dict of feature flags/limits | | `metadata` | `JSONField` | Arbitrary metadata | | `sort_order` | `PositiveIntegerField` | Display ordering | **Ordering**: `sort_order`, `price` --- ## Subscription Represents a user's subscription to a plan, synced with Midtrans. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `user` | `ForeignKey(User)` | Subscriber (CASCADE) | | `plan` | `ForeignKey(Plan)` | Associated plan (PROTECT) | | `status` | `CharField(20)` | See status choices below | | `payment_type` | `CharField(20)` | Payment method used | | `midtrans_subscription_id` | `CharField(255)` | Midtrans subscription ID for native recurring | | `midtrans_token` | `CharField(500)` | Saved card token or GoPay account ID | | `current_period_start` | `DateTimeField` | Start of current billing period | | `current_period_end` | `DateTimeField` | End of current billing period | | `trial_end` | `DateTimeField` | Trial period end date | | `billing_cycle_count` | `PositiveIntegerField` | Number of completed billing cycles | | `next_billing_date` | `DateTimeField` | When next charge will be created | | `cancel_at_period_end` | `BooleanField` | Cancel when current period ends | | `cancelled_at` | `DateTimeField` | When cancellation was requested | | `cancellation_reason` | `TextField` | Reason for cancellation | | `customer_first_name` | `CharField(100)` | Customer first name for Midtrans | | `customer_last_name` | `CharField(100)` | Customer last name | | `customer_email` | `EmailField` | Customer email | | `customer_phone` | `CharField(20)` | Customer phone | | `metadata` | `JSONField` | Arbitrary metadata | ### Subscription Status Choices | Status | Description | |--------|-------------| | `pending` | Created, awaiting first payment | | `trialing` | In free trial period | | `active` | Active and paid | | `past_due` | Payment failed, in grace period | | `paused` | Temporarily paused by user/admin | | `cancelled` | Permanently cancelled | | `expired` | Expired (max cycles reached or grace period ended) | ### Subscription Payment Types `credit_card`, `gopay`, `bank_transfer`, `echannel` (Mandiri Bill), `qris`, `shopeepay`, `wallet` ### Computed Properties - `is_active` → `True` if status is `active` or `trialing` - `is_in_grace_period` → `True` if `past_due` and within `GRACE_PERIOD_DAYS` - `has_access` → `True` if `is_active` or `is_in_grace_period` --- ## Payment Individual payment transaction tracked via Midtrans Core API. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `subscription` | `ForeignKey(Subscription)` | Related subscription (nullable for standalone payments) | | `user` | `ForeignKey(User)` | Payer | | `order_id` | `CharField(255)` | Unique Midtrans order ID (e.g., `SUB-A1B2C3D4E5F6`) | | `midtrans_transaction_id` | `CharField(255)` | Midtrans transaction ID | | `payment_type` | `CharField(30)` | Payment method | | `status` | `CharField(20)` | Transaction status from Midtrans | | `fraud_status` | `CharField(20)` | Fraud detection result | | `gross_amount` | `DecimalField(12,2)` | Total charge amount | | `currency` | `CharField(3)` | Currency code | | `refund_amount` | `DecimalField(12,2)` | Amount refunded | | `payment_details` | `JSONField` | VA numbers, QR URLs, redirect URLs | | `transaction_time` | `DateTimeField` | When Midtrans created the transaction | | `settlement_time` | `DateTimeField` | When payment was settled | | `expiry_time` | `DateTimeField` | When payment expires | | `midtrans_response` | `JSONField` | Full Midtrans API response | | `is_retry` | `BooleanField` | Whether this is a retry charge | | `retry_count` | `PositiveIntegerField` | Retry attempt number | | `metadata` | `JSONField` | Arbitrary metadata | ### Payment Status Choices `pending`, `capture`, `settlement`, `deny`, `cancel`, `expire`, `refund`, `partial_refund`, `authorize`, `failure` ### Computed Properties - `is_successful` → `True` if `settlement` or `capture` - `is_paid` → `True` if `settlement` or (`capture` + `fraud_status == "accept"`) --- ## Invoice Invoice generated for each billing cycle. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `invoice_number` | `CharField(50)` | Unique number (e.g., `INV-202501-00001`) | | `subscription` | `ForeignKey(Subscription)` | Related subscription | | `payment` | `OneToOneField(Payment)` | Related payment (nullable) | | `user` | `ForeignKey(User)` | Invoice recipient | | `status` | `CharField(10)` | `draft`, `issued`, `paid`, `void`, `overdue`, `refunded` | | `subtotal` | `DecimalField(12,2)` | Pre-tax amount | | `tax_amount` | `DecimalField(12,2)` | Tax amount | | `discount_amount` | `DecimalField(12,2)` | Discount amount | | `total_amount` | `DecimalField(12,2)` | Final total | | `currency` | `CharField(3)` | Currency code | | `issue_date` | `DateField` | Date invoice was issued | | `due_date` | `DateField` | Payment due date | | `paid_date` | `DateField` | Date payment was received | | `period_start` | `DateTimeField` | Billing period start | | `period_end` | `DateTimeField` | Billing period end | | `notes` | `TextField` | Invoice notes | | `void_reason` | `TextField` | Reason for voiding | --- ## InvoiceItem Line item on an invoice. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `invoice` | `ForeignKey(Invoice)` | Parent invoice | | `description` | `CharField(500)` | Item description | | `quantity` | `PositiveIntegerField` | Quantity (default: `1`) | | `unit_price` | `DecimalField(12,2)` | Price per unit | | `total_price` | `DecimalField(12,2)` | Auto-calculated: `quantity × unit_price` | --- ## WebhookLog Logs every incoming Midtrans webhook notification. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `order_id` | `CharField(255)` | Order ID from notification | | `midtrans_transaction_id` | `CharField(255)` | Midtrans transaction ID | | `transaction_status` | `CharField(30)` | Status from Midtrans | | `payment_type` | `CharField(30)` | Payment type | | `fraud_status` | `CharField(20)` | Fraud detection result | | `gross_amount` | `CharField(30)` | Amount (as string from Midtrans) | | `signature_key` | `TextField` | SHA-512 signature from Midtrans | | `status` | `CharField(20)` | Processing status: `received`, `processed`, `failed`, `invalid`, `duplicate` | | `raw_payload` | `JSONField` | Full webhook payload | | `error_message` | `TextField` | Error details if processing failed | | `ip_address` | `GenericIPAddressField` | Sender IP | | `processed_at` | `DateTimeField` | When processing completed | --- ## NotificationLog Logs outgoing notifications (email, in-app) sent to users. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `user` | `ForeignKey(User)` | Recipient user | | `subscription` | `ForeignKey(Subscription)` | Related subscription (nullable) | | `payment` | `ForeignKey(Payment)` | Related payment (nullable) | | `notification_type` | `CharField(30)` | Type (see choices below) | | `channel` | `CharField(10)` | `email`, `webhook`, or `in_app` | | `status` | `CharField(10)` | `queued`, `sent`, `failed`, `skipped` | | `subject` | `CharField(500)` | Message subject | | `body` | `TextField` | Message body | | `recipient` | `CharField(255)` | Recipient address | | `error_message` | `TextField` | Error details | | `sent_at` | `DateTimeField` | When notification was sent | ### Notification Types `payment_success`, `payment_failed`, `payment_pending`, `subscription_created`, `subscription_activated`, `subscription_cancelled`, `subscription_expired`, `subscription_renewed`, `subscription_paused`, `subscription_resumed`, `invoice_issued`, `invoice_paid`, `invoice_overdue`, `expiry_reminder`, `payment_retry`, `refund_processed` --- ## Wallet User wallet for holding balance used to pay subscriptions. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `user` | `OneToOneField(User)` | Wallet owner | | `balance` | `DecimalField(15,2)` | Current balance | | `currency` | `CharField(3)` | Currency code | | `is_active` | `BooleanField` | Whether wallet is active | ### Computed Properties - `has_sufficient_balance` → `True` if `balance > 0` - `can_afford(amount)` → `True` if `is_active` and `balance >= amount` --- ## WalletTransaction Record of every wallet balance change. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `wallet` | `ForeignKey(Wallet)` | Parent wallet | | `transaction_type` | `CharField(30)` | `top_up`, `subscription_payment`, `refund`, `adjustment` | | `amount` | `DecimalField(15,2)` | Positive = credit, negative = debit | | `balance_before` | `DecimalField(15,2)` | Balance before transaction | | `balance_after` | `DecimalField(15,2)` | Balance after transaction | | `reference_type` | `CharField(50)` | Related object type (`topup`, `subscription`, `payment`) | | `reference_id` | `CharField(255)` | UUID of related object | | `description` | `CharField(500)` | Human-readable description | --- ## TopUp Top-up request linked to a Midtrans payment for crediting wallet. | Field | Type | Description | |-------|------|-------------| | `id` | `UUIDField` (PK) | Auto-generated UUID | | `wallet` | `ForeignKey(Wallet)` | Target wallet | | `user` | `ForeignKey(User)` | Requester | | `amount` | `DecimalField(15,2)` | Top-up amount | | `payment_type` | `CharField(20)` | Payment method | | `status` | `CharField(20)` | `pending`, `success`, `failed`, `expired` | | `order_id` | `CharField(255)` | Unique Midtrans order ID (e.g., `TOPUP-A1B2C3D4E5F6`) | | `midtrans_transaction_id` | `CharField(255)` | Midtrans transaction ID | | `payment_details` | `JSONField` | VA numbers, QR URLs | | `midtrans_response` | `JSONField` | Full Midtrans response | --- ## Entity Relationship Diagram See {doc}`diagrams` for the full ER diagram of all models.