Skip to Content

Django

QPay V2 payment integration for Django. Built on top of qpay-py , this package provides a Django app with models, views, template tags, signals, and admin integration.


Installation

pip install django-qpay

Add "django_qpay" to your INSTALLED_APPS:

# settings.py INSTALLED_APPS = [ # ... "django_qpay", ]

Run migrations to create the PaymentLog model:

python manage.py migrate

Configuration

Django Settings

Add the QPAY dictionary to your settings.py:

# settings.py QPAY = { "BASE_URL": "https://merchant.qpay.mn", "USERNAME": "your_username", "PASSWORD": "your_password", "INVOICE_CODE": "your_invoice_code", "CALLBACK_URL": "https://yoursite.com/qpay/webhook/", }

Using Environment Variables

For production, read from environment variables:

import os QPAY = { "BASE_URL": os.environ.get("QPAY_BASE_URL", "https://merchant.qpay.mn"), "USERNAME": os.environ["QPAY_USERNAME"], "PASSWORD": os.environ["QPAY_PASSWORD"], "INVOICE_CODE": os.environ["QPAY_INVOICE_CODE"], "CALLBACK_URL": os.environ["QPAY_CALLBACK_URL"], }

Configuration Options

KeyDefaultDescription
BASE_URLhttps://merchant.qpay.mnQPay API base URL
USERNAME""Merchant username
PASSWORD""Merchant password
INVOICE_CODE""Default invoice code
CALLBACK_URL""Webhook callback URL

Quick Start

Include URLs

Add the QPay URL configuration to your project:

# urls.py from django.urls import path, include urlpatterns = [ # ... path("qpay/", include("django_qpay.urls")), ]

This registers the webhook endpoint at POST /qpay/webhook/.

Create an Invoice

from django_qpay.client import get_client from qpay.types import CreateSimpleInvoiceRequest client = get_client() invoice = client.create_simple_invoice(CreateSimpleInvoiceRequest( invoice_code="YOUR_CODE", sender_invoice_no="ORDER-001", amount=10000, callback_url="https://yoursite.com/qpay/webhook/", )) # invoice.invoice_id -- QPay invoice ID # invoice.qr_image -- Base64-encoded QR code # invoice.qr_text -- QR code text # invoice.qpay_short_url -- Short payment URL # invoice.urls -- Bank deep links

Create Invoice

Simple Invoice

from django_qpay.client import get_client from qpay.types import CreateSimpleInvoiceRequest client = get_client() invoice = client.create_simple_invoice(CreateSimpleInvoiceRequest( invoice_code="YOUR_CODE", sender_invoice_no=f"ORDER-{order.id}", amount=order.total, callback_url="https://yoursite.com/qpay/webhook/", ))

Full Invoice

from qpay.types import CreateInvoiceRequest invoice = client.create_invoice(CreateInvoiceRequest( invoice_code="YOUR_CODE", sender_invoice_no=f"ORDER-{order.id}", invoice_receiver_code="receiver_001", invoice_description=f"Payment for Order #{order.id}", amount=order.total, callback_url="https://yoursite.com/qpay/webhook/", ))

Check Payment

from qpay.types import PaymentCheckRequest result = client.check_payment(PaymentCheckRequest( object_type="INVOICE", object_id=invoice_id, )) if result.rows: print("Payment confirmed!")

Cancel Invoice

client.cancel_invoice(invoice_id)

Using in Views

from django.shortcuts import render from django_qpay.client import get_client from qpay.types import CreateSimpleInvoiceRequest def create_payment(request): client = get_client() invoice = client.create_simple_invoice(CreateSimpleInvoiceRequest( invoice_code="YOUR_CODE", sender_invoice_no="ORDER-001", amount=10000, callback_url="https://yoursite.com/qpay/webhook/", )) return render(request, "payment.html", {"invoice": invoice})

Webhook Handling

The package includes a built-in webhook view at POST /qpay/webhook/ (when you include the URLs as shown above). The CSRF exemption is handled automatically.

When QPay sends a callback:

  1. The view parses the invoice_id from the JSON body
  2. Calls check_payment to verify the payment status
  3. Sends the payment_received signal if payment rows are found
  4. Sends the payment_failed signal if no payment is found or an error occurs
  5. Returns a JSON response

Webhook URL Pattern

# django_qpay/urls.py app_name = "django_qpay" urlpatterns = [ path("webhook/", WebhookView.as_view(), name="webhook"), ]

Signals

The package provides two Django signals:

payment_received

Fired when a payment is confirmed.

from django.dispatch import receiver from django_qpay.signals import payment_received @receiver(payment_received) def on_payment(sender, invoice_id, result, **kwargs): """ sender -- None invoice_id -- str, QPay invoice ID result -- PaymentCheckResponse, full check result """ order = Order.objects.get(qpay_invoice_id=invoice_id) order.status = "paid" order.save()

payment_failed

Fired when no payment is found or an error occurs.

from django.dispatch import receiver from django_qpay.signals import payment_failed @receiver(payment_failed) def on_payment_failed(sender, invoice_id, reason, **kwargs): """ sender -- None invoice_id -- str, QPay invoice ID reason -- str, failure reason """ import logging logger = logging.getLogger(__name__) logger.warning(f"Payment failed for {invoice_id}: {reason}")

Template Tags

Load the QPay template tags in your templates:

{% load qpay_tags %}

QR Code

Renders a base64-encoded QR code image:

{% load qpay_tags %} {% qpay_qr invoice.qr_image %} {# With custom size #} {% qpay_qr invoice.qr_image 200 %}

Output: A centered <img> tag with the QR code.

Renders bank deep link buttons:

{% load qpay_tags %} {% qpay_payment_links invoice.urls %}

Output: A flex container with styled bank link buttons including logos and names.


Models

PaymentLog

The package includes a PaymentLog model for tracking payments:

from django_qpay.models import PaymentLog
FieldTypeDescription
invoice_idCharField(255)QPay invoice ID (indexed)
payment_idCharField(255)QPay payment ID
amountDecimalField(12, 2)Payment amount
statusCharField(32)Status: pending, paid, etc. (indexed)
raw_responseJSONFieldRaw API response data
created_atDateTimeFieldCreated timestamp
updated_atDateTimeFieldUpdated timestamp

Admin Integration

The PaymentLog model is automatically registered in Django admin with:

  • List display: invoice_id, payment_id, amount, status, created_at
  • Filters: status
  • Search: invoice_id, payment_id

Testing

Mocking the Client

from unittest.mock import patch, MagicMock from django.test import TestCase class PaymentTest(TestCase): @patch("django_qpay.client.get_client") def test_create_invoice(self, mock_get_client): mock_client = MagicMock() mock_client.create_simple_invoice.return_value = MagicMock( invoice_id="inv_123", qr_image="base64data", ) mock_get_client.return_value = mock_client # Your test code here

Testing the Webhook

from django.test import TestCase, Client from unittest.mock import patch, MagicMock class WebhookTest(TestCase): @patch("django_qpay.views.get_client") def test_webhook_paid(self, mock_get_client): mock_client = MagicMock() mock_result = MagicMock() mock_result.rows = [{"payment_id": "pay_123"}] mock_client.check_payment.return_value = mock_result mock_get_client.return_value = mock_client response = self.client.post( "/qpay/webhook/", data={"invoice_id": "inv_123"}, content_type="application/json", ) self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["status"], "paid")

Testing Signals

from django_qpay.signals import payment_received def test_signal_received(self): handler = MagicMock() payment_received.connect(handler) # Trigger webhook... handler.assert_called_once() args = handler.call_args self.assertEqual(args.kwargs["invoice_id"], "inv_123")

API Reference

Functions

FunctionModuleDescription
get_client()django_qpay.clientReturns a singleton QPayClient instance

Signals

SignalModuleParameters
payment_receiveddjango_qpay.signalssender, invoice_id, result
payment_faileddjango_qpay.signalssender, invoice_id, reason

Template Tags

TagParametersDescription
{% qpay_qr %}qr_image, size=256Render QR code image
{% qpay_payment_links %}urlsRender bank payment links

Models

ModelModuleDescription
PaymentLogdjango_qpay.modelsPayment tracking model

Views

ViewURLMethodDescription
WebhookView/webhook/POSTWebhook callback handler

Last updated on