Multiple Kassas
A business often holds several merchant accounts with one provider: a live kassa and a sandbox one, or one per brand. Since 0.5.0 each Django webhook serves all of them from one URL. Single-kassa settings keep working unchanged.
How a call finds its kassa
List the kassas under KASSAS. The provider's own credentials decide which
one a call belongs to:
| Gateway | A call is matched by |
|---|---|
| Payme | the key in its Basic auth header (PAYME_KEY) |
| Click | service_id in the body, signed with that kassa's SECRET_KEY |
| Paynet | serviceId in the body, else the login (USERNAME) |
| Uzum | the Basic auth login (USERNAME); the call must then name that kassa's serviceId |
A call that matches no kassa is refused as unauthenticated.
Settings
PAYTECHUZ = {
'PAYME': {
'KASSAS': {
'shop': {'PAYME_ID': '...', 'PAYME_KEY': '...'},
'gifts': {'PAYME_ID': '...', 'PAYME_KEY': '...'},
},
'KASSA_FIELD': 'kassa', # optional, see below
'ACCOUNT_MODEL': 'shop.models.Order',
'ACCOUNT_FIELD': 'id',
'AMOUNT_FIELD': 'amount',
'ONE_TIME_PAYMENT': True,
},
'CLICK': {
'KASSAS': {
'shop': {'SERVICE_ID': '...', 'SECRET_KEY': '...'},
},
# ACCOUNT_MODEL etc. as above
},
'PAYNET': {
'KASSAS': {
'live': {'SERVICE_ID': '1001', 'USERNAME': '...', 'PASSWORD': '...'},
'test': {'SERVICE_ID': '1003', 'USERNAME': '...', 'PASSWORD': '...',
'IS_SANDBOX': True},
},
},
'UZUM': {
'KASSAS': {
'shop': {'SERVICE_ID': '...', 'USERNAME': '...', 'PASSWORD': '...',
'ALLOWED_IPS': ['...']},
},
'IP_HEADER': 'HTTP_X_REAL_IP',
'BEHIND_CLOUDFLARE': True,
},
}
Your view does not change: the same BasePaymeWebhookView subclass on the
same URL serves every kassa. Inside a hook, self.kassa is the name of the
kassa that called.
Separate books
Every row in the payments table carries its kassa, and every lookup is
scoped to the kassa that called. One kassa never sees, performs, cancels or
reports another's transactions, and the same provider transaction id may exist
once per kassa. Rows from before 0.5 have kassa = '', which is what a
single-kassa setup uses.
Which kassa may take an order
Set KASSA_FIELD to a field on your account model that holds the kassa the
account must be paid through. A call from any other kassa is told the account
does not exist. A Paynet kassa may set ACCOUNT_KASSA when the value stored
on accounts differs from its key in KASSAS — useful for a sandbox and a live
service that share accounts.
Without KASSA_FIELD, any kassa may pay any account.
Sandbox kassas
Paynet and Uzum kassas take IS_SANDBOX: True. Payments are recorded, but
sandbox_payment(params, transaction) is called instead of
successfully_payment, so nothing is delivered. The account stays payable for
the provider's tester, and sandbox rows stay out of live statements.
Moving an existing setup to KASSAS
- Upgrade and migrate:
pip install "paytechuz[django]>=0.5", thenpython manage.py migrate. - Existing rows have
kassa = ''. Before switching toKASSAS, set each row'skassato the name it will have, otherwise the new kassa cannot see them (for refunds, checks and statements). - Switch the settings and deploy.
Rolling back to 0.4 on Django 5+ needs no reverse migration: the column has a database default and the old code ignores it.