Production Deployment
This guide covers deploying django_midtrans in a production environment.
Environment Variables
Set these in your server environment (not in code):
# Midtrans Credentials (Production)
MIDTRANS_SERVER_KEY=Mid-server-xxxxxxxxxxxxxxxxxxxx
MIDTRANS_CLIENT_KEY=Mid-client-xxxxxxxxxxxxxxxxxxxx
MIDTRANS_MERCHANT_ID=Mxxxxxxxxx
MIDTRANS_IS_PRODUCTION=True
MIDTRANS_NOTIFICATION_URL=https://yourdomain.com/midtrans/api/notification/
# settings.py — Read from environment
import os
MIDTRANS = {
"SERVER_KEY": os.environ["MIDTRANS_SERVER_KEY"],
"CLIENT_KEY": os.environ["MIDTRANS_CLIENT_KEY"],
"MERCHANT_ID": os.environ["MIDTRANS_MERCHANT_ID"],
"IS_PRODUCTION": os.environ.get("MIDTRANS_IS_PRODUCTION", "").lower() == "true",
"NOTIFICATION_URL": os.environ["MIDTRANS_NOTIFICATION_URL"],
}
Production Checklist
Item |
Description |
|---|---|
Use production keys |
Replace |
Set |
Switches API base URL to |
Set |
Must be HTTPS and publicly accessible |
Configure |
Include your domain |
Enable |
Required for webhooks and 3DS redirects |
Set |
Never run debug mode in production |
Use a process manager |
Gunicorn, uWSGI, or Daphne |
Run Celery workers |
For background tasks and periodic jobs |
Run Celery Beat |
For periodic payment checks |
Set up database backups |
Payment data is critical |
Configure logging |
Log payment events for audit trails |
HTTPS Configuration
Midtrans requires HTTPS for:
Webhook notification URLs
3DS redirect callbacks
ShopeePay/DANA redirect URLs
# settings.py
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Docker Deployment
Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir .[dev]
COPY . .
RUN python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
docker-compose.yml
services:
web:
build: .
ports:
- "8000:8000"
env_file: .env
depends_on:
- db
- redis
celery:
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
env_file: .env
depends_on:
- db
- redis
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: midtrans_db
POSTGRES_USER: midtrans
POSTGRES_PASSWORD: ${DB_PASSWORD}
redis:
image: redis:7-alpine
volumes:
pgdata:
Nginx Configuration
upstream django {
server web:8000;
}
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/ssl/certs/yourdomain.crt;
ssl_certificate_key /etc/ssl/private/yourdomain.key;
location /static/ {
alias /app/staticfiles/;
}
location / {
proxy_pass http://django;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Logging
Configure payment-specific logging:
# settings.py
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{levelname} {asctime} {module} {message}",
"style": "{",
},
},
"handlers": {
"payment_file": {
"level": "INFO",
"class": "logging.FileHandler",
"filename": "/var/log/app/payments.log",
"formatter": "verbose",
},
},
"loggers": {
"django_midtrans": {
"handlers": ["payment_file"],
"level": "INFO",
"propagate": True,
},
},
}
Database Considerations
Use PostgreSQL for production (SQLite is not suitable)
Index
order_idandtransaction_statusfields (already indexed by default)Set up regular database backups
Consider read replicas for high-traffic dashboards
# settings.py
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ.get("DB_HOST", "localhost"),
"PORT": os.environ.get("DB_PORT", "5432"),
}
}
Monitoring
Key metrics to monitor:
Metric |
Why |
|---|---|
Pending payment count |
Detect stuck payments |
Webhook response time |
Ensure fast processing |
Failed notification count |
Detect integration issues |
Payment success rate |
Business health indicator |
Celery queue length |
Detect backlog |
Use Django signals to emit metrics to your monitoring system:
import time
from django_midtrans.signals import payment_settled, payment_failed
@receiver(payment_settled)
def track_settlement(sender, payment, **kwargs):
statsd.incr("payments.settled")
statsd.gauge("payments.amount", float(payment.gross_amount))
@receiver(payment_failed)
def track_failure(sender, payment, **kwargs):
statsd.incr("payments.failed")