Skip to main content

Django subscription management with Midtrans payment gateway integration

Project description

**Note:** This package uses the **Midtrans Core API** (server-side charge via `/v2/charge`, `/v1/subscriptions`, `/v1/invoices`), **not** Midtrans Snap. ## Features - **Subscription Plans** — Configurable interval billing (daily/weekly/monthly), trial periods, max billing cycles, feature flags per plan - **Payment Methods** — Credit Card, Bank Transfer (BCA/BNI/BRI/Permata/CIMB), GoPay, ShopeePay, QRIS, E-Channel (Mandiri Bill), Wallet - **Automatic Billing** — Celery Beat periodic tasks handle recurring charges, payment retries, grace periods, expiration - **Invoice System** — Auto-generated invoices with line items, tax/discount support, PDF-ready data - **Wallet & Top-Up** — Internal wallet balance system with top-up via any payment method - **Webhook Processing** — Secure Midtrans notification handler with SHA-512 signature verification, duplicate detection - **Admin Dashboard** — Django Unfold admin with status badges, inline editing, bulk actions, Midtrans sync - **Notification System** — Email, webhook, and in-app notification logging with queued delivery - **Access Middleware** — Path-based subscription enforcement with grace period support - **Django Signals** — 20+ signals for all subscription/payment lifecycle events - **REST API** — Full DRF ViewSet API for plans, subscriptions, payments, invoices, wallet, notifications - **Testing** — Comprehensive test suite with factories, mocked Midtrans responses ## Architecture ``` Django Unfold Admin UI ──────────────────────────────────────────────────────── REST API (DRF ViewSets + Serializers) ──────────────────────────────────────────────────────── Service Layer (SubscriptionService, WalletService) ──────────────────────────────────────────────────────── Models/ORM | MidtransClient | Celery Tasks (Beat) ──────────────────────────────────────────────────────── Midtrans Core API (v2/charge, v1/subscriptions) ``` For detailed Mermaid diagrams, see the [Architecture Diagrams](docs/diagrams/) documentation. ## Quick Start ### Installation From PyPI: ```bash pip install django-subscription-midtrans ``` From source: ```bash git clone https://github.com/rissets/django-subscription-midtrans.git cd django-subscription-midtrans pip install -e ".[dev]" ``` ### Step 1: Add to INSTALLED\_APPS ```python INSTALLED_APPS = [ # Django Unfold (must be before django.contrib.admin) "unfold", "unfold.contrib.filters", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", # Third-party "rest_framework", "django_celery_beat", # Subscription package "subscriptions", ] ``` ### Step 2: Configure Environment Variables Create a `.env` file in your project root: ```env # Django DJANGO_SECRET_KEY= DJANGO_DEBUG=True DJANGO_ALLOWED_HOSTS=*,localhost,127.0.0.1 # Database (default: SQLite, configure for PostgreSQL in production) # DB_ENGINE=django.db.backends.postgresql # DB_NAME=subscription_db # DB_USER= # DB_PASSWORD= # DB_HOST=localhost # DB_PORT=5432 # Celery / Redis CELERY_BROKER_URL=redis://localhost:6379/0 CELERY_RESULT_BACKEND=redis://localhost:6379/0 # Midtrans Credentials (get from https://dashboard.midtrans.com) MIDTRANS_SERVER_KEY= MIDTRANS_CLIENT_KEY= MIDTRANS_MERCHANT_ID= MIDTRANS_IS_PRODUCTION=False # Webhook URL (must be publicly accessible) # For local dev, use ngrok: ngrok http 8000 MIDTRANS_NOTIFICATION_URL= ``` ### Step 3: Add Settings ```python # settings.py from decouple import config # Midtrans MIDTRANS_SERVER_KEY = config("MIDTRANS_SERVER_KEY", default="") MIDTRANS_CLIENT_KEY = config("MIDTRANS_CLIENT_KEY", default="") MIDTRANS_MERCHANT_ID = config("MIDTRANS_MERCHANT_ID", default="") MIDTRANS_IS_PRODUCTION = config("MIDTRANS_IS_PRODUCTION", default=False, cast=bool) MIDTRANS_API_BASE_URL = ( "https://api.midtrans.com" if MIDTRANS_IS_PRODUCTION else "https://api.sandbox.midtrans.com" ) MIDTRANS_NOTIFICATION_URL = config("MIDTRANS_NOTIFICATION_URL", default="") # Celery CELERY_BROKER_URL = config("CELERY_BROKER_URL", default="redis://localhost:6379/0") CELERY_RESULT_BACKEND = config("CELERY_RESULT_BACKEND", default="redis://localhost:6379/0") CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" # REST Framework REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": 20, "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", ], "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.SessionAuthentication", "rest_framework.authentication.BasicAuthentication", ], } # Subscription Settings (all optional, these are defaults) SUBSCRIPTION_SETTINGS = { "PROTECTED_PATHS": [], "SUBSCRIPTION_REQUIRED_REDIRECT": "/pricing/", "GRACE_PERIOD_DAYS": 3, "PAYMENT_EXPIRY_MINUTES": 60, "AUTO_GENERATE_INVOICE": True, "AUTO_RETRY_FAILED_PAYMENTS": True, "MAX_RETRY_ATTEMPTS": 3, "RETRY_INTERVAL_DAYS": 1, "INVOICE_NUMBER_PREFIX": "INV", "SEND_EMAIL_NOTIFICATIONS": True, "EXPIRY_REMINDER_DAYS": [7, 3, 1], } ``` ### Step 4: Include URLs ```python # urls.py from django.urls import path, include urlpatterns = [ path("admin/", admin.site.urls), path("api/subscriptions/", include("subscriptions.urls")), ] ``` ### Step 5: Run Migrations and Setup ```bash # Apply database migrations python manage.py migrate # Create periodic Celery tasks python manage.py setup_periodic_tasks # Seed sample subscription plans (optional, for development) python manage.py seed_plans # Create admin superuser python manage.py createsuperuser ``` ### Step 6: Start Services You need **4 processes** running: ```bash # Terminal 1: Redis (message broker) redis-server # Terminal 2: Django development server python manage.py runserver # Terminal 3: Celery worker (processes async tasks) celery -A config worker -l info # Terminal 4: Celery beat (schedules periodic tasks) celery -A config beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler ``` ## Development Setup ### Prerequisites - Python 3.10+ - Redis server - Git ### Clone & Install ```bash git clone https://github.com/rissets/django-subscription-midtrans.git cd django-subscription-midtrans # Create virtual environment python -m venv .venv source .venv/bin/activate # macOS/Linux # .venv\Scripts\activate # Windows # Install dependencies pip install -r requirements.txt # Copy environment file cp .env.example .env # Edit .env with your Midtrans sandbox credentials ``` ### Midtrans Sandbox Setup 1. Register at [https://dashboard.sandbox.midtrans.com](https://dashboard.sandbox.midtrans.com) 2. Get your **Server Key** and **Client Key** from Settings → Access Keys 3. Add them to your `.env` file 4. For webhooks, expose your local server: ```bash # Install ngrok: https://ngrok.com ngrok http 8000 # Copy the HTTPS URL and set MIDTRANS_NOTIFICATION_URL in .env ``` ### Run the Example App The project includes a full example application demonstrating all features: ```bash # Apply migrations python manage.py migrate # Seed plans and periodic tasks python manage.py seed_plans --reset python manage.py setup_periodic_tasks --reset # Create a superuser for admin access python manage.py createsuperuser # Start all services (in separate terminals) redis-server python manage.py runserver celery -A config worker -l info celery -A config beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler ``` Then visit: - **Example App**: [http://localhost:8000/example/](http://localhost:8000/example/) - **Admin Dashboard**: [http://localhost:8000/admin/](http://localhost:8000/admin/) - **API Root**: [http://localhost:8000/api/subscriptions/](http://localhost:8000/api/subscriptions/) ### Test Credentials (Sandbox) | Payment Method | Test Details | |----------------|-------------| | Credit Card | Card: `4811 1111 1111 1114`, Exp: `01/29`, CVV: `123`, OTP: `112233` | | BCA VA | Use the VA number from the response, simulate payment in Midtrans dashboard | | BNI VA | Use the VA number from the response | | GoPay | Use the QR code or deeplink from the response | | QRIS | Scan the QR code with any QRIS-compatible app (sandbox mode) | ### Running Tests ```bash # Run all subscription tests python manage.py test subscriptions # Run specific test module python manage.py test subscriptions.tests.test_models python manage.py test subscriptions.tests.test_services python manage.py test subscriptions.tests.test_views python manage.py test subscriptions.tests.test_client python manage.py test subscriptions.tests.test_middleware # Run with verbosity python manage.py test subscriptions -v 2 ``` ## Documentation Full Sphinx documentation is available in the `docs/` directory: ```bash cd docs pip install -r requirements.txt make html # Open docs/_build/html/index.html ``` ## API Reference ### Plans | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | `GET` | `/api/subscriptions/plans/` | No | List all active plans | | `GET` | `/api/subscriptions/plans/{slug}/` | No | Get plan details by slug | ### Subscriptions | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | `GET` | `/api/subscriptions/subscriptions/` | Yes | List user's subscriptions | | `POST` | `/api/subscriptions/subscriptions/` | Yes | Create subscription | | `GET` | `/api/subscriptions/subscriptions/{id}/` | Yes | Get subscription detail | | `GET` | `/api/subscriptions/subscriptions/active/` | Yes | Get active subscription | | `POST` | `/api/subscriptions/subscriptions/{id}/cancel/` | Yes | Cancel subscription | | `POST` | `/api/subscriptions/subscriptions/{id}/pause/` | Yes | Pause subscription | | `POST` | `/api/subscriptions/subscriptions/{id}/resume/` | Yes | Resume subscription | | `POST` | `/api/subscriptions/subscriptions/{id}/change-plan/` | Yes | Change subscription plan | | `GET` | `/api/subscriptions/subscriptions/{id}/sync/` | Yes | Sync with Midtrans | ### Payments | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | `GET` | `/api/subscriptions/payments/` | Yes | List user's payments | | `GET` | `/api/subscriptions/payments/{id}/` | Yes | Get payment detail | | `POST` | `/api/subscriptions/payments/charge/` | Yes | Create charge for subscription | | `POST` | `/api/subscriptions/payments/{id}/refund/` | Yes | Refund a payment | | `GET` | `/api/subscriptions/payments/{id}/check-status/` | Yes | Check Midtrans status | ### Invoices | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | `GET` | `/api/subscriptions/invoices/` | Yes | List user's invoices | | `GET` | `/api/subscriptions/invoices/{id}/` | Yes | Get invoice detail | ### Wallet | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | `GET` | `/api/subscriptions/wallet/me/` | Yes | Get user's wallet | | `GET` | `/api/subscriptions/wallet/transactions/` | Yes | List wallet transactions | ### Top-Ups | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | `GET` | `/api/subscriptions/topups/` | Yes | List user's top-ups | | `POST` | `/api/subscriptions/topups/` | Yes | Create a top-up | | `GET` | `/api/subscriptions/topups/{id}/` | Yes | Get top-up detail | ### Notifications | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | `GET` | `/api/subscriptions/notifications/` | Yes | List notifications | | `GET` | `/api/subscriptions/notifications/unread/` | Yes | Get unread notifications | | `POST` | `/api/subscriptions/notifications/{id}/mark-read/` | Yes | Mark notification as read | ### Webhook | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | `POST` | `/api/subscriptions/webhook/notification/` | No | Midtrans webhook handler | ### API Usage Examples
Create Subscription (Bank Transfer - BCA) ```bash curl -X POST http://localhost:8000/api/subscriptions/subscriptions/ \ -H "Content-Type: application/json" \ -b "sessionid=" \ -d '{ "plan_id": "", "payment_type": "bank_transfer", "customer_details": { "first_name": "John", "last_name": "Doe", "email": "john@example.com", "phone": "081234567890" } }' ``` **Response:** ```json { "id": "uuid", "plan": { "name": "Pro Monthly", "price": "299000.00" }, "status": "pending", "payment_type": "bank_transfer", "payments": [{ "order_id": "SUB-xxxxx", "status": "pending", "payment_details": { "bank": "bca", "va_number": "9898123456789" } }] } ```
Create Subscription (GoPay) ```bash curl -X POST http://localhost:8000/api/subscriptions/subscriptions/ \ -H "Content-Type: application/json" \ -b "sessionid=" \ -d '{ "plan_id": "", "payment_type": "gopay" }' ``` **Response includes deeplink/QR for GoPay payment.**
Create Subscription (QRIS) ```bash curl -X POST http://localhost:8000/api/subscriptions/subscriptions/ \ -H "Content-Type: application/json" \ -b "sessionid=" \ -d '{ "plan_id": "", "payment_type": "qris" }' ``` **Response includes QR code URL for scanning.**
Create Subscription (Credit Card) ```bash curl -X POST http://localhost:8000/api/subscriptions/subscriptions/ \ -H "Content-Type: application/json" \ -b "sessionid=" \ -d '{ "plan_id": "", "payment_type": "credit_card", "token": "" }' ```
Create Subscription (Wallet) ```bash curl -X POST http://localhost:8000/api/subscriptions/subscriptions/ \ -H "Content-Type: application/json" \ -b "sessionid=" \ -d '{ "plan_id": "", "payment_type": "wallet" }' ```
Cancel Subscription ```bash curl -X POST http://localhost:8000/api/subscriptions/subscriptions//cancel/ \ -H "Content-Type: application/json" \ -b "sessionid=" \ -d '{"reason": "Switching provider", "immediate": false}' ```
Top Up Wallet ```bash curl -X POST http://localhost:8000/api/subscriptions/topups/ \ -H "Content-Type: application/json" \ -b "sessionid=" \ -d '{ "amount": 500000, "payment_type": "bank_transfer", "bank": "bca" }' ```
Refund Payment ```bash curl -X POST http://localhost:8000/api/subscriptions/payments//refund/ \ -H "Content-Type: application/json" \ -b "sessionid=" \ -d '{"amount": 150000, "reason": "Partial refund"}' ```
## Configuration Reference All settings are configured via `SUBSCRIPTION_SETTINGS` dict in your Django settings: ```python SUBSCRIPTION_SETTINGS = { # Access Control "PROTECTED_PATHS": ["/api/premium/", "/pro-features/"], "SUBSCRIPTION_REQUIRED_REDIRECT": "/pricing/", # Grace Period "GRACE_PERIOD_DAYS": 3, # Days after failed payment to keep access # Payment "PAYMENT_EXPIRY_MINUTES": 60, # Payment token/VA expiry time # Invoicing "AUTO_GENERATE_INVOICE": True, # Auto-create invoice on charge "INVOICE_NUMBER_PREFIX": "INV", # Invoice number format: INV-2026-0001 # Retry Logic "AUTO_RETRY_FAILED_PAYMENTS": True, "MAX_RETRY_ATTEMPTS": 3, "RETRY_INTERVAL_DAYS": 1, # Notifications "SEND_EMAIL_NOTIFICATIONS": True, "EXPIRY_REMINDER_DAYS": [7, 3, 1], # Days before expiry to send reminders } ``` ## Subscription Lifecycle Subscription states and transitions: | From | Event | To | |------|-------|----| | — | `create_subscription()` | **PENDING** | | PENDING | Payment success | **ACTIVE** | | PENDING | Plan has trial days | **TRIALING** | | PENDING | Payment expired/failed | **EXPIRED** | | TRIALING | Trial ends + payment success | **ACTIVE** | | TRIALING | Trial ends + payment failed | **EXPIRED** | | ACTIVE | Renewal payment failed | **PAST_DUE** | | ACTIVE | User pauses | **PAUSED** | | ACTIVE | User cancels (immediate) | **CANCELLED** | | ACTIVE | Period ends + cancel_at_period_end | **CANCELLED** | | PAST_DUE | Retry payment success | **ACTIVE** | | PAST_DUE | Grace period expired | **EXPIRED** | | PAUSED | User resumes | **ACTIVE** | | PAUSED | User cancels | **CANCELLED** | ### Two Subscription Modes | Mode | Payment Types | How It Works | |------|---------------|-------------| | **Native Midtrans Subscription** | Credit Card, GoPay (with account_id) | Uses `/v1/subscriptions` API — Midtrans handles automatic recurring billing | | **Manual Charge Subscription** | Bank Transfer, QRIS, ShopeePay, E-Channel, Wallet | Each billing cycle creates a new `/v2/charge` — managed by Celery Beat | ## Celery Periodic Tasks | Task | Schedule | Description | |------|----------|-------------| | `process_due_subscriptions` | Every 1 hour | Charges subscriptions with `next_billing_date <= now` | | `process_expired_subscriptions` | Every 6 hours | Cancels subscriptions marked `cancel_at_period_end` | | `process_past_due_expiration` | Every 12 hours | Expires subscriptions past grace period | | `retry_failed_payments` | Every 4 hours | Retries failed charges (up to `MAX_RETRY_ATTEMPTS`) | | `process_wallet_billing` | Every 1 hour | Deducts wallet balance for wallet-based subscriptions | | `mark_overdue_invoices` | Daily 1:00 AM | Marks unpaid invoices as overdue | | `send_expiry_reminders` | Daily 9:00 AM | Sends reminders N days before subscription expiry | | `sync_midtrans_subscriptions` | Every 6 hours | Syncs native Midtrans subscription statuses | | `send_pending_email_notifications` | Every 5 min | Processes queued email notifications | ## Webhook Setup ### Configure Midtrans Dashboard 1. Log in at [Midtrans Dashboard](https://dashboard.midtrans.com) (or sandbox) 2. Go to **Settings → Configuration** 3. Set **Payment Notification URL**: `https://yourdomain.com/api/subscriptions/webhook/notification/` 4. Enable notifications for all transaction statuses ### Local Development with ngrok ```bash ngrok http 8000 # Example output: https://abc123.ngrok-free.app # Set in .env: # MIDTRANS_NOTIFICATION_URL=https://abc123.ngrok-free.app/api/subscriptions/webhook/notification/ ``` ### Signature Verification All incoming webhooks are verified using SHA-512: ``` SHA512(order_id + status_code + gross_amount + server_key) == signature_key ``` Invalid signatures are rejected and logged. Duplicate notifications are detected and skipped. ## Django Signals Connect to subscription events in your app: ```python from django.dispatch import receiver from subscriptions.signals import ( subscription_activated, payment_success, payment_failed, subscription_plan_changed, ) @receiver(subscription_activated) def on_subscription_activated(sender, subscription, **kwargs): # Grant access to premium features pass @receiver(payment_success) def on_payment_success(sender, payment, **kwargs): # Send receipt email pass @receiver(payment_failed) def on_payment_failed(sender, payment, **kwargs): # Notify user of failed payment pass @receiver(subscription_plan_changed) def on_plan_changed(sender, subscription, old_plan, new_plan, **kwargs): # Adjust user features pass ``` ### Available Signals | Signal | Arguments | Triggered When | |--------|-----------|---------------| | `subscription_created` | `subscription` | New subscription created | | `subscription_activated` | `subscription` | Subscription becomes active | | `subscription_cancelled` | `subscription` | Subscription cancelled | | `subscription_paused` | `subscription` | Subscription paused | | `subscription_resumed` | `subscription` | Subscription resumed | | `subscription_expired` | `subscription` | Subscription expired | | `subscription_renewed` | `subscription` | Billing cycle renewed | | `subscription_plan_changed` | `subscription`, `old_plan`, `new_plan` | Plan changed | | `payment_created` | `payment` | New payment created | | `payment_success` | `payment` | Payment settled | | `payment_failed` | `payment` | Payment failed | | `payment_refunded` | `payment` | Payment refunded | | `invoice_created` | `invoice` | Invoice generated | | `invoice_paid` | `invoice` | Invoice paid | | `invoice_overdue` | `invoice` | Invoice past due date | | `invoice_voided` | `invoice` | Invoice voided | | `webhook_received` | `webhook_log` | Midtrans webhook received | | `webhook_processed` | `webhook_log` | Webhook processed | | `topup_completed` | `topup` | Wallet top-up completed | | `wallet_credited` | `transaction` | Wallet balance increased | | `wallet_debited` | `transaction` | Wallet balance decreased | ## Middleware Protect routes based on subscription status: ```python # settings.py MIDDLEWARE = [ # ... other middleware "subscriptions.middleware.SubscriptionAccessMiddleware", ] SUBSCRIPTION_SETTINGS = { "PROTECTED_PATHS": ["/api/premium/", "/dashboard/pro/"], "SUBSCRIPTION_REQUIRED_REDIRECT": "/pricing/", } ``` **Behavior:** - **API requests** without active subscription → `403 JSON` response - **HTML requests** without active subscription → redirect to pricing page - **Staff users** always bypass the check - **Grace period** is respected (users with `PAST_DUE` within grace period still have access) ## Admin Dashboard The package uses [Django Unfold](https://github.com/unfoldadmin/django-unfold) for a modern admin interface: - **Plan Management** — Create/edit plans, activate/deactivate, view subscriber counts - **Subscription Management** — Status badges, pause/resume/cancel actions, sync from Midtrans - **Payment Tracking** — View payments with fraud status, check/cancel/expire from admin - **Invoice Management** — View invoices with line items, mark paid, void - **Webhook Logs** — Audit trail of all Midtrans notifications - **Wallet & Top-Up** — View user wallets and transaction history Access at: `http://localhost:8000/admin/` ## Extending the Package ### Custom Signal Handlers ```python # your_app/signals.py from django.dispatch import receiver from subscriptions.signals import subscription_activated @receiver(subscription_activated) def grant_premium_access(sender, subscription, **kwargs): user = subscription.user user.profile.is_premium = True user.profile.save() ``` ### Override Service Methods ```python from subscriptions.services import SubscriptionService class CustomSubscriptionService(SubscriptionService): def activate_subscription(self, subscription): result = super().activate_subscription(subscription) send_welcome_package(subscription.user) return result ``` ### Proxy Models ```python from subscriptions.models import Subscription class PremiumSubscription(Subscription): class Meta: proxy = True def has_feature(self, feature_name): return self.plan.features.get(feature_name, False) ``` ## Production Deployment ### Environment Variables ```env DJANGO_SECRET_KEY= DJANGO_DEBUG=False DJANGO_ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com # PostgreSQL (recommended for production) DB_ENGINE=django.db.backends.postgresql DB_NAME=subscription_db DB_USER= DB_PASSWORD= DB_HOST=localhost DB_PORT=5432 # Redis CELERY_BROKER_URL=redis://localhost:6379/0 CELERY_RESULT_BACKEND=redis://localhost:6379/0 # Midtrans Production MIDTRANS_SERVER_KEY= MIDTRANS_CLIENT_KEY= MIDTRANS_MERCHANT_ID= MIDTRANS_IS_PRODUCTION=True MIDTRANS_NOTIFICATION_URL=https://yourdomain.com/api/subscriptions/webhook/notification/ ``` ### Production Checklist - [ ] Set `DEBUG=False` - [ ] Use PostgreSQL (not SQLite) - [ ] Set `MIDTRANS_IS_PRODUCTION=True` - [ ] Use production Midtrans keys (not sandbox) - [ ] Configure `MIDTRANS_NOTIFICATION_URL` to your public HTTPS URL - [ ] Run `python manage.py collectstatic` - [ ] Run `python manage.py migrate` - [ ] Run `python manage.py setup_periodic_tasks` - [ ] Use a process manager (systemd, supervisor) for Celery workers - [ ] Configure HTTPS (required by Midtrans for production) - [ ] Set up monitoring for Celery tasks and webhook logs ### Systemd Services
Celery Worker ```ini # /etc/systemd/system/celery-worker.service [Unit] Description=Celery Worker After=network.target redis.service [Service] Type=simple User=www-data WorkingDirectory=/path/to/project ExecStart=/path/to/venv/bin/celery -A config worker -l info Restart=always [Install] WantedBy=multi-user.target ```
Celery Beat ```ini # /etc/systemd/system/celery-beat.service [Unit] Description=Celery Beat Scheduler After=network.target redis.service [Service] Type=simple User=www-data WorkingDirectory=/path/to/project ExecStart=/path/to/venv/bin/celery -A config beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler Restart=always [Install] WantedBy=multi-user.target ```
### Docker Compose
docker-compose.yml ```yaml version: "3.8" services: web: build: . command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 volumes: - .:/app ports: - "8000:8000" env_file: .env depends_on: - db - redis db: image: postgres:16 environment: POSTGRES_DB: ${DB_NAME:-subscription_db} POSTGRES_USER: ${DB_USER:-dbuser} POSTGRES_PASSWORD: ${DB_PASSWORD:-secret} volumes: - pgdata:/var/lib/postgresql/data redis: image: redis:7-alpine celery-worker: build: . command: celery -A config worker -l info env_file: .env depends_on: - db - redis celery-beat: build: . command: celery -A config beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler env_file: .env depends_on: - db - redis volumes: pgdata: ```
## Project Structure ``` django-subscription-midtrans/ ├── config/ # Django project configuration │ ├── __init__.py # Celery app import │ ├── celery.py # Celery configuration │ ├── settings.py # Django settings │ ├── urls.py # Root URL routing │ └── wsgi.py # WSGI entry point ├── subscriptions/ # Main reusable package │ ├── __init__.py # App config │ ├── admin.py # Django Unfold admin │ ├── apps.py # AppConfig │ ├── client.py # Midtrans Core API client │ ├── middleware.py # Subscription access middleware │ ├── models.py # Database models (10 models) │ ├── serializers.py # DRF serializers │ ├── services.py # Business logic layer │ ├── signals.py # Django signals (20+ events) │ ├── tasks.py # Celery periodic tasks │ ├── urls.py # API URL routing │ ├── views.py # DRF ViewSets │ ├── management/commands/ │ │ ├── seed_plans.py # Create sample plans │ │ └── setup_periodic_tasks.py │ ├── migrations/ │ └── tests/ │ ├── factories.py │ ├── test_client.py │ ├── test_middleware.py │ ├── test_models.py │ ├── test_services.py │ └── test_views.py ├── example/ # Example Django app (demo) │ ├── views.py # Template-based views │ ├── urls.py # Example URL routing │ └── templatetags/ │ └── subscription_tags.py ├── templates/example/ # HTML templates (Tailwind CSS) ├── docs/ # Sphinx documentation ├── pyproject.toml # PyPI package config ├── requirements.txt # Dependencies ├── .env.example # Environment template └── README.md ``` ## License This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details. ## Contributing 1. Fork the repository 2. Create a feature branch: `git checkout -b feature/my-feature` 3. Commit your changes: `git commit -am 'Add my feature'` 4. Push to the branch: `git push origin feature/my-feature` 5. Open a Pull Request ## Resources - [Midtrans Core API Documentation](https://docs.midtrans.com/) - [Midtrans Sandbox Dashboard](https://dashboard.sandbox.midtrans.com) - [Django REST Framework](https://www.django-rest-framework.org/) - [Celery Documentation](https://docs.celeryq.dev/) - [Django Unfold Admin](https://github.com/unfoldadmin/django-unfold) ]]>

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_subscription_midtrans-0.1.1.tar.gz (68.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_subscription_midtrans-0.1.1-py3-none-any.whl (62.8 kB view details)

Uploaded Python 3

File details

Details for the file django_subscription_midtrans-0.1.1.tar.gz.

File metadata

File hashes

Hashes for django_subscription_midtrans-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6eea6adad7e82f902b52c44d0974472f4ff6810f3e1709e33b8b37312a7ca523
MD5 a5faa5f39ab0b0938293ad96b3d0ccb8
BLAKE2b-256 c45ea4053a96263e3d3675256f393e117c12715207c39aa88504155952c560d3

See more details on using hashes here.

File details

Details for the file django_subscription_midtrans-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for django_subscription_midtrans-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c91bf84d5fb5f6dee2bdc4615d3ba8e17b286ea91d2af62ce9319b8c6e44319a
MD5 ab6134d937b076c881445a73a45bdbfd
BLAKE2b-256 f4cd905d9faa4d051643d4dbf2fa1a7a2ba766b6c5f05e6f786ebd11fb278df2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page