Skip to main content

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:

CodeWhen
10001wrong login or password, or a caller outside ALLOWED_IPS
10002unreadable JSON (only once the caller is authenticated)
10003unknown action or a non-POST request
10005a required parameter is missing
10006the serviceId is not the kassa's
10007the account does not exist
10008the account is paid already
10010the transId was seen before
10011the amount is not the account's
10014unknown transId
10015the transaction is cancelled — also a confirm more than 30 minutes after create, or for an account paid meanwhile, which cancels it
10016already confirmed
10017 / 10018cannot be reversed / already reversed
99999your 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.

Further reading​