POS Gateway - Services
Location: api/nextango/apps/pos_gateway/services.py
Last Updated: 2026-07-09
Overview
POS Gateway services are thin orchestration wrappers around domain services in transactions, payments, products, and stores. They add POS-specific concerns (terminal session tracking, cache warming) without duplicating business logic. Four service classes exist today: GatewayInitializationService, TransactionGatewayService, ManagerApprovalGatewayService, CashDrawerGatewayService. An earlier InventoryGatewayService and SKULookupService no longer exist in the codebase; SKU and barcode lookup now go straight to the products API from the POS frontend.
GatewayInitializationService
Warms Redis caches when the Django app starts, so the first POS request after deploy doesn't pay a cold-cache database lookup.
initialize(): Runs the two warming methods below, catching and logging any exception rather than blocking app startup. Called from PosGatewayConfig.ready().
_warm_product_cache(): Caches up to 500 active ProductVariant rows (products whose parent product is not hidden), keyed by both SKU and Sanity ID, with a five-minute timeout. Returns the number of variants cached.
_warm_store_cache(): Caches every Store row, keyed by both Sanity ID and database ID, with a ten-minute timeout. Returns the number of stores cached.
Cache warming failures are logged and swallowed; a failed warm-up does not prevent the gateway from serving requests, it just means the first lookup for a given key falls through to the database.
TransactionGatewayService
Orchestrates transaction creation and payment for POS terminals by delegating to TransactionService (transactions app) and PaymentService (payments app).
create_transaction(terminal_session, transaction_data): Formats the incoming dict and calls TransactionService.create_sale_transaction(). On success, increments terminal_session.transaction_count and returns {success: True, transaction: <result>}. Any exception is caught and returned as {success: False, error: str(e)} rather than raised.
process_payment(terminal_session, transaction_id, payment_data): Applies a payment via PaymentService.apply_payment_to_transaction(), passing register_id=terminal_session.terminal_id and operator=terminal_session.user so the payment record carries terminal and employee context. gateway_charge_status is set to 'confirmed' when the caller supplies a processor_transaction_id (the Terminal SDK already confirmed the charge client-side) and 'skipped' otherwise; a webhook reconciles any discrepancy later. Requires payment_method_id and a positive tendered_amount; missing or invalid values return {success: False, error: ...} without calling the payment service. If the resulting transaction status is completed, this method also calls InventoryAdjustmentService.commit_reserved_inventory() to convert the held reservation into an actual inventory deduction. A failure there is logged but does not fail the payment response, since the payment itself already succeeded. If the transaction is still pending (a partial payment), inventory remains reserved and no commit is attempted.
ManagerApprovalGatewayService
Creates and tracks manager approval requests for privileged POS actions.
request_approval(terminal_session, request_type, amount=None, reason="", details=None): Creates a ManagerApprovalRequest row with a ten-minute expiration window, setting both terminal_session and requested_by from the same session. Calls _broadcast_approval_request() (currently a no-op placeholder; the WebSocket notification for a new request is sent directly from TerminalConsumer.handle_manager_approval_request, not from this method; see Consumers). Returns {success: True, request_id, expires_at} on success, {success: False, error} on failure.
check_approval_status(request_id): Looks up the ManagerApprovalRequest by request_id and returns its current status, approved_by, approved_at, and approval_reason. Returns {error: "Approval request not found"} if the request doesn't exist. Intended as a fallback for terminals that lose their WebSocket connection; the primary approval-decision path is the WebSocket broadcast, not polling.
CashDrawerGatewayService
Orchestrates cash drawer operations by delegating to CashDrawerService in the transactions app.
open_drawer(terminal_session, reason="sale"): Calls CashDrawerService.open_drawer() with the terminal session's store and employee, and returns {success: True, drawer_operation: <result>} on success or {success: False, error} on failure. This is the gateway-level hardware trigger; register lifecycle operations beyond opening (close with variance, till count) are handled elsewhere by CashDrawerViewSet, not by this service.
Related Documentation
- Models: TerminalSession, ManagerApprovalRequest
- Authentication: JWT-based terminal authentication
- Consumers: WebSocket real-time communication
- Views: REST API endpoints that call these services