Shop It Docs
Developer ResourcesPOS (In-Store Sales)

POS Module Overview

In-store sales terminal for admin staff — customer lookup, order creation, payment recording, and fulfillment management.

Audience: Admin/back-office developers and frontend developers building the POS terminal UI.

POS Module - Overview

1. What is POS?

The POS (Point of Sale) module is the in-store sales terminal that enables admin staff to create orders on behalf of walk-in customers.

Key facts:

  • All endpoints are mounted at GET|POST /api/admin/pos/*.
  • Orders created via POS have orderSource = "pos_sale" and paymentMethod = "pos".
  • Swagger tag: POS.
  • Auth: JwtAuthGuard + RoleGuard on all endpoints.
  • No customer-facing POS routes exist. All POS routes are admin-only.

2. Permissions Required

PermissionEndpoints
Pos_READGET /customers/lookup, GET /orders, GET /orders/export, GET /analytics
Pos_CREATEPOST /customers, POST /orders, POST /emi-inquiry
Pos_RECORD_PAYMENTPOST /orders/:orderId/payment
Pos_UPDATEPOST /orders/:orderId/payment-failure, POST /orders/:orderId/pickup-confirm

Seeds required:

  • Add "Pos" to the modules const array in packages/db/src/seed/seed-auth.ts.
  • Custom permissions Pos_RECORD_PAYMENT and Pos_PROCESS_REFUND must be seeded separately.

3. POS Admin User Flows

3.1 Walk-in customer order (new customer)

  1. Staff calls GET /api/admin/pos/customers/lookup?email=<email> → returns null (not found).
  2. Staff calls POST /api/admin/pos/customers (body: name, email, phone?).
    • Creates account with a random temp password.
    • Password-reset email is sent to the customer automatically.
    • Returns customerId.
  3. Staff calls POST /api/admin/pos/orders (body: customerId, items[], couponDiscount?, shippingFee?, receiverName?, shippingAddress*).
    • Header: idempotency-key (required, UUID).
    • Returns { requestId, status: "completed", id: orderId }.
  4. Staff calls POST /api/admin/pos/orders/:orderId/payment (body: gateway, amountNpr, transactionReference?).
    • Header: idempotency-key (required, UUID — different from the order creation key).
    • Returns { paymentId, orderId }.
    • Payment event emitted → order status transitions to paid → fulfillment moves to received.
  5. (Optional) Staff calls POST /api/admin/pos/orders/:orderId/pickup-confirm.
    • If fulfillmentStatus is "received" → advances through "ready""picked_up".
    • If fulfillmentStatus is "ready" → advances to "picked_up".
    • If already "picked_up" → no-op, returns completed.

3.2 Existing customer order

  1. Staff calls GET /api/admin/pos/customers/lookup?email=<email> → returns { exists: true, customerId, name, email, phone, image }.
  2. Proceed directly to step 3 of the walk-in flow above.

3.3 Payment failure recording

  1. Staff records payment failed (gateway declined, etc.).
  2. POST /api/admin/pos/orders/:orderId/payment-failure (body: reason).
    • Adds "[POS Payment Failure] <reason>" as an admin note on the order.
    • Does NOT change order status — the order remains in payment_pending.
    • Staff can retry POST /orders/:orderId/payment afterwards.

3.4 EMI inquiry on behalf of walk-in

  1. POST /api/admin/pos/emi-inquiry (body: EmiInquirySubmitDto).
    • Delegates to EmiCustomerService.submitInquiry().
    • Creates an EMI inquiry record.
    • Returns EmiInquirySubmitResponseDto.

3.5 Analytics and export

GET /api/admin/pos/analytics?startDate=2026-01-01&endDate=2026-01-31
→ Returns totalRevenue, orderCount, gatewayBreakdown[]

GET /api/admin/pos/orders/export?startDate=2026-01-01&endDate=2026-01-31
→ Returns flat array of all POS orders in range (max 90-day window)
→ Error: ORDER_DATE_RANGE_TOO_LARGE if range > 90 days

GET /api/admin/pos/orders?page=1&limit=20&startDate=...&endDate=...
→ Returns paginated list of POS orders with customer snapshot

4. Rate Limiting

Walk-in customer creation (POST /customers) is rate-limited using IpThrottlerGuard with keyStrategy: "user" — the rate-limit key is the JWT sub (adminId), NOT the IP. This is by design because admin staff share a single office IP.

Limit: 10 requests per 3600 seconds per admin user.

5. State Model

POS orders follow the same order state lifecycle as standard orders:

created → payment_pending → paid → [fulfillment: received → ready → picked_up]

POS-specific behavioral notes:

  • Orders skip notifyOrderCreated. POS calls OrderService.createFromPosItems() directly; OrderCustomerService.dispatchOrderCreatedNotification() is never called for POS.
  • Payment completion emits PAYMENT_EVENTS.COMPLETED via EventEmitter2 (not a BullMQ queue job). The existing OrderPaymentListener handles this event and transitions the order status to paid and triggers fulfillment to received.
  • No new BullMQ queues were added for POS. All downstream processing reuses existing order queue infrastructure.
  • POS does NOT use BullMQ directly. There is no POS-specific BullMQ queue or processor.

6. Idempotency

Two endpoints use idempotency:

EndpointScopeHeader
POST /orderspos_create_orderidempotency-key (required)
POST /orders/:orderId/paymentpos_record_paymentidempotency-key (required)

Both use the async_requests table with a unique index on (actorType, actorId, scope, idempotencyKey). onConflictDoNothing prevents race-condition duplicates. Replaying the same key returns the cached result without re-processing.


See Also