Uzum Integration
Uzum is a fast-growing payment system in Uzbekistan. This guide shows how to integrate Uzum with PayTechUZ.
Installation
pip install paytechuz
Basic usage
Create a gateway
from paytechuz.gateways.uzum.client import UzumGateway
# Initialise the Uzum gateway (Biller/open-service)
uzum = UzumGateway(
service_id="your_service_id", # Uzum Service ID
is_test_mode=True # Set to False in production
)
Create a payment
# Build an Uzum Biller payment link
# URL format: https://www.uzumbank.uz/open-service?serviceId=...&order_id=...&amount=...&redirectUrl=...
uzum_link = uzum.create_payment(
id="order_123", # Order ID (the order_id parameter)
amount=100000, # Amount in so'm (converted to tiyin)
return_url="https://example.com/callback" # The redirectUrl parameter
)
# Result: https://www.uzumbank.uz/open-service?serviceId=your_service_id&order_id=order_123&amount=10000000&redirectUrl=https%3A%2F%2Fexample.com%2Fcallback
Django integration
Settings.py
Add the UZUM block to your PAYTECHUZ settings:
# settings.py
INSTALLED_APPS = [
# ...
'paytechuz.integrations.django',
]
PAYTECHUZ = {
# ... other gateways
'UZUM': {
'SERVICE_ID': 'your_service_id', # Uzum Service ID for the Biller URL
'USERNAME': 'your_uzum_username', # For webhook Basic Auth
'PASSWORD': 'your_uzum_password', # For webhook Basic Auth
'ALLOWED_IPS': ['...'], # Uzum's addresses; omit to accept any
'ACCOUNT_MODEL': 'your_app.models.Order',
'ACCOUNT_FIELD': 'id', # or 'order_id'
'AMOUNT_FIELD': 'amount',
'ONE_TIME_PAYMENT': True, # Set to False if you want to allow multiple payments for the same invoice
}
}
Views.py
Create a webhook view by subclassing BaseUzumWebhookView:
# views.py
from paytechuz.integrations.django.views import BaseUzumWebhookView
from .models import Order
class UzumWebhookView(BaseUzumWebhookView):
def successfully_payment(self, params, transaction):
order = Order.objects.get(id=transaction.account_id)
order.status = 'paid'
order.save()
def cancelled_payment(self, params, transaction):
order = Order.objects.get(id=transaction.account_id)
order.status = 'cancelled'
order.save()
URLs.py
Add the webhook URL. Note that Uzum webhooks carry an action parameter in the path:
# urls.py
from django.urls import path
from .views import UzumWebhookView
urlpatterns = [
# ...
path('payments/webhook/uzum/<str:action>/', UzumWebhookView.as_view(), name='uzum_webhook'),
]
What the webhook checks
Every failure answers HTTP 400 with status: FAILED and Uzum's errorCode:
| Code | When |
|---|---|
10001 | wrong login or password, or a caller outside ALLOWED_IPS |
10002 | unreadable JSON (only once the caller is authenticated) |
10003 | unknown action or a non-POST request |
10005 | a required parameter is missing |
10006 | the serviceId is not the kassa's |
10007 | the account does not exist |
10008 | the account is paid already |
10010 | the transId was seen before |
10011 | the amount is not the account's |
10014 | unknown transId |
10015 | the transaction is cancelled — also a confirm more than 30 minutes after create, or for an account paid meanwhile, which cancels it |
10016 | already confirmed |
10017 / 10018 | cannot be reversed / already reversed |
99999 | your hook raised; nothing was recorded |
successfully_payment runs in the same database transaction as the confirm.
cancelled_payment is called only when a confirmed payment is reversed; a
reverse before confirm tells nobody and leaves the order payable.
Allowed IPs
Uzum calls from a fixed set of addresses. List them per kassa:
PAYTECHUZ = {
'UZUM': {
'KASSAS': {
'shop': {'SERVICE_ID': '...', 'USERNAME': '...', 'PASSWORD': '...',
'ALLOWED_IPS': ['...']},
},
'IP_HEADER': 'HTTP_X_REAL_IP', # where your proxy puts the caller's address
'BEHIND_CLOUDFLARE': True, # read CF-Connecting-IP, from Cloudflare's edge only
# ACCOUNT_MODEL etc. as above
},
}
IP_HEADER defaults to REMOTE_ADDR. With BEHIND_CLOUDFLARE,
CF-Connecting-IP is believed only when the request itself came from one of
Cloudflare's published ranges, so it cannot be forged.
Sandbox
A kassa with IS_SANDBOX: True confirms payments without delivering them:
sandbox_payment(params, transaction) is called instead of
successfully_payment, and the order stays payable for Uzum's tester. See
Multiple kassas.