Loyalty.lt
SDKsPython

QR Features

QR login and QR card scan in Python

QR Features

Two QR flows, both driven from your server:

  • QR login — the customer scans to sign in, and you get their JWT.
  • QR card scan — the customer scans at the till, and you get their loyalty card.

Sessions expire after 5 minutes.

Your server opens the session; the browser waits on the realtime channel. That is the path to build — the result lands the instant the customer confirms, with no request per second and no lag at the till. The Python SDK has no subscription helper because the subscription belongs in the page, not on the server.

Receiving the result

The result arrives on a realtime channel, not in a response body. The SDK subscribes for you and blocks until the session resolves:

session = sdk.generate_qr_login(device_name='Till 3', shop_id=shop_id)
# render session['qr_code'], then wait on the channel
result = sdk.subscribe_qr_login(session['session_id'])

if result and result['status'] == 'authenticated':
    token, user = result['token'], result['user']

Pass a callback to watch every frame instead of only the final one. The helper returns None/null if nothing resolved within the timeout — sessions last 5 minutes.

Note

The realtime helpers need a WebSocket client: pip install websocket-client. Without it the SDK still works; only these two methods raise.

Because it blocks, use it from a POS, a CLI or a worker. In a web request, open the session on your server and let the page subscribe — channels, events and payloads are in Realtime.

QR login

Open the session on your server, render the QR code, and let the page wait on the channel above.

session = sdk.generate_qr_login(device_name='Till 3', shop_id=shop_id)
# render session['qr_code'] as a QR image; the page subscribes to
# qr-login.{session['session_id']} and receives the token there

Fallback: polling

Only if the page cannot hold a WebSocket open. Never block a worker in a loop — expose the poll as your own endpoint and call it from the browser:

# Django
def qr_status(request, session_id):
    return JsonResponse(sdk.poll_qr_login(session_id))

The status is authenticated when the customer confirms, and the response then carries token and user.

StatusMeaning
pendingWaiting for the customer to scan
scannedScanned; waiting for confirmation in the app
confirmedConfirmed; the token is being issued
authenticatedDone — token and user are present
cancelledThe customer declined
expiredGenerate a new session

If the customer has no app

sdk.send_app_link('+37060000001', shop_id)

QR card scan

The POS flow: identify the card without asking for a phone number.

session = sdk.generate_qr_card_session(device_name='Till 3', shop_id=shop_id)
# render session['qr_code']; the till subscribes to qr-card.{session['session_id']}
# and receives card_data the moment the customer scans

card_data carries loyalty_card_id, card_number, points and user. If the till cannot hold a WebSocket open, sdk.poll_qr_card_status(session['session_id']) returns the same thing, one request at a time.

Finishing the sale

coupons = sdk.get_card_coupons(shop_id, card_id=card['loyalty_card_id'])
games   = sdk.get_card_games(card['loyalty_card_id'], shop_id)

sdk.create_transaction(
    user_id=card['user']['id'],
    order_id=f'POS-{int(time.time())}',
    order_total=total,
    currency='EUR',
    shop_id=shop_id,
)

for game in games:
    sdk.add_stamps(game['id'], card['loyalty_card_id'], shop_id, stamps_count=1)

On this page