Django integration for ATH Móvil payments (Puerto Rico's mobile payment system).
See this README in spanish: README_ES.md
Webhook-driven payment and refund synchronization. ATH Móvil sends definitive transaction data via webhooks, providing complete audit trails with fees, net amounts, and customer information.
- Webhook handling with idempotency
- Transaction persistence with refunds and client records for complete audit trails
- Read-only Django Admin with refund actions and webhook management
- Django signals for payment lifecycle events (completed, cancelled, expired, refunded)
- Transaction reconciliation via the
athm_syncmanagement command - Optional payment button template tag with zero-dependency JavaScript for quick integration
- Python 3.10 - 3.14
- Django 5.1 - 6.0
pip install django-athmAdd to your INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"django_athm",
]Add your ATH Móvil Business API tokens (find these in the mobile app's settings):
DJANGO_ATHM_PUBLIC_TOKEN = "your-public-token"
DJANGO_ATHM_PRIVATE_TOKEN = "your-private-token"Include the URLs:
# urls.py
from django.urls import include, path
urlpatterns = [
# ...
path("athm/", include("django_athm.urls")),
]Run migrations:
python manage.py migrate django_athm# views.py
from django.shortcuts import render
def checkout(request):
athm_config = {
"total": 25.00,
"subtotal": 23.36,
"tax": 1.64,
"metadata_1": "order-123",
"items": [
{"name": "Widget", "price": 23.36, "quantity": 1}
],
"success_url": "/order/complete/",
"failure_url": "/order/failed/",
}
return render(request, "checkout.html", {"ATHM_CONFIG": athm_config}){% load django_athm %}
<h1>Checkout</h1>
{% athm_button ATHM_CONFIG %}# signals.py
from django.dispatch import receiver
from django_athm.signals import payment_completed
@receiver(payment_completed)
def handle_payment_completed(sender, payment, **kwargs):
# Update your order status, send confirmation email, etc.
print(f"Payment {payment.reference_number} completed for ${payment.total}")Webhooks provide definitive transaction data with idempotency guarantees:
ATH Móvil webhook -> Idempotent processing -> Payment/Refund sync -> Django signals
For quick integration, use the included template tag:
- Initiate: User clicks button, backend creates payment via ATH Móvil API
- Confirm: User confirms payment in ATH Móvil app
- Authorize: Backend authorizes the confirmed payment
- Webhook: ATH Móvil sends completion event with final details
User clicks -> Backend creates -> User confirms -> Backend authorizes -> Webhook received
ATH Móvil payment (OPEN) in app (CONFIRM) payment (COMPLETED) (final details)
button
You can also build your own payment UI and use only the webhook synchronization features.
Recommended: Use Django Admin (auto-detects your webhook URL):
- Navigate to ATH Móvil Webhook Events in the admin
- Click Install Webhooks button
- Verify the auto-detected URL (edit if needed)
- Click submit to register with ATH Móvil
Alternative: Management command
# Auto-detect from DJANGO_ATHM_WEBHOOK_URL setting
python manage.py install_webhook
# Or provide explicit URL
python manage.py install_webhook https://yourdomain.com/athm/webhook/Reconcile local records with ATH Movil's Transaction Report API to recover missed webhooks or backfill historical data:
# Sync transactions for a date range
python manage.py athm_sync --from-date 2025-01-01 --to-date 2025-01-31
# Preview changes without modifying database
python manage.py athm_sync --from-date 2025-01-01 --to-date 2025-01-31 --dry-runAll webhooks are processed idempotently using deterministic keys based on the event payload. Duplicate events are automatically detected and ignored.
If you need custom logic before/after webhook processing:
from django.views.decorators.csrf import csrf_exempt
from django_athm.views import process_webhook_request
@csrf_exempt
def my_custom_webhook(request):
# Pre-processing (logging, rate limiting, etc.)
log_webhook(request)
# Call django-athm handler (maintains idempotency)
response = process_webhook_request(request)
# Post-processing (notifications, analytics, etc.)
notify_team()
return responseThe package provides a read-only admin interface:
- Payments: View all transactions, filter by status/date, bulk refund action
- Refunds: View refund records
- Clients: View customer records linked by phone number
- Webhook Events: View webhook history, reprocess failed events, install webhooks
All models are read-only to preserve data integrity - payments can only be refunded, not edited.
The athm_button template tag is optional - you can build your own payment UI and use only the webhook features.
athm_config = {
# Required
"total": 25.00, # Payment amount (1.00 - 1500.00)
# Optional
"subtotal": 23.36, # Subtotal for display
"tax": 1.64, # Tax amount
"metadata_1": "order-123", # Custom field (max 40 chars)
"metadata_2": "customer-456",# Custom field (max 40 chars)
"items": [...], # List of item objects
"theme": "btn", # Button theme: btn, btn-dark, btn-light
"lang": "es", # Language: es, en
"success_url": "/thanks/", # Redirect on success (adds ?reference_number=...&ecommerce_id=...)
"failure_url": "/failed/", # Redirect on failure
}Subscribe to payment lifecycle events:
from django_athm.signals import (
payment_completed, # Payment successful (webhook)
payment_cancelled, # Payment cancelled (webhook)
payment_expired, # Payment expired (webhook)
refund_sent, # Refund processed (webhook)
)This project uses uv for package management.
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install dependencies
uv sync
# Run tests
DJANGO_SETTINGS_MODULE=tests.settings pytest --cov django_athm
# Run full test matrix
tox
# Run linting
tox -e lintThis project is not affiliated with or endorsed by Evertec, Inc. or ATH Móvil.
- athm-python - ATH Móvil API client
Have suggestions or ideas for improving django-athm? We'd love to hear from you! Submit feedback.
MIT License - see LICENSE for details.