# POS Dejavoo (CPP) Integration — Technical Documentation This document describes how Dejavoo card-present payments (CPP) are integrated into the Evergreen POS application, including single payments, split payments, resume flows, cancellation handling, and the UX improvements added for staff. --- ## 1. Overview **Dejavoo** is used as the card-present terminal provider for in-store credit/debit payments. In the UI this is labeled **CPP** (Card Present Payment) with sub-types: | UI Label | `dejavooPaymentType` | Stored method on sale | |-----------------|----------------------|------------------------| | CPP (Credit) | `Credit` | `CPP_Credit` | | CPP (Debit) | `Debit` | `CPP_Debit` | | CPP (generic) | `CPP` | `CPP` | Payments are processed synchronously through the Dejavoo **SPIn API** (`/v2/Payment/Sale`). The terminal prompts the customer; the backend waits for approval/decline before continuing. **Key files** | Layer | Path | |----------|------| | Service | `evergreen_pos_be/src/services/payment/dejavoo.service.js` | | Controller | `evergreen_pos_be/src/controllers/nursery/nursery.product.controller.js` | | Split utils | `evergreen_pos_be/src/utils/splitPayment.utils.js` | | Sale service | `evergreen_pos_be/src/services/nursery/sale.service.js` | | Routes | `evergreen_pos_be/src/routes/nursery/sale.route.js` | | Frontend | `evergreen_pos_fe/src/pages/Nuresy/POS/PaymentComponentNew.tsx` | | Cancel dialog | `evergreen_pos_fe/src/pages/Nuresy/POS/SplitPaymentCancelDialog.tsx` | --- ## 2. Environment Configuration Set these in the backend `.env`: | Variable | Purpose | Example | |----------|---------|---------| | `DEJAVOO_BASE_URL` | SPIn API base URL | `https://dev.spinpos.net` | | `DEJAVOO_TPN` or `DEJAVOO_TERMINAL_ID` | Terminal ID (TPN) | Required | | `DEJAVOO_AUTH_KEY` | Terminal auth key | Required | | `DEJAVOO_AUTH_TOKEN` | Bearer token (optional) | | | `DEJAVOO_REGISTER_ID` | Register ID | | | `DEJAVOO_ISV_ID` | ISV identifier | | | `DEJAVOO_SALE_PATH` | Sale endpoint | `/v2/Payment/Sale` | | `DEJAVOO_STATUS_PATH` | Status endpoint | `/v2/Payment/Status` | | `DEJAVOO_TIMEOUT_MS` | Request timeout | `120000` (2 min) | If `DEJAVOO_TPN` or `DEJAVOO_AUTH_KEY` is missing, Dejavoo calls fail at startup with a configuration error. --- ## 3. Backend — Dejavoo Service ### 3.1 `createSale` Sends a sale request to the terminal: - **Amount** — must be > 0, rounded to 2 decimals in split flow - **PaymentType** — `Credit`, `Debit`, or `CPP` - **ReferenceId** — max **50 characters** (Dejavoo API limit) - **InvoiceNumber** — also capped at 50 characters - **CustomFields** — metadata sanitized (string only, max 200 chars per field) ### 3.2 Reference ID generation Long reference IDs caused **HTTP 400** errors on split payments. A dedicated builder keeps IDs short and unique: ``` Single payment: POS-{timestamp} Split payment: POS-S{splitIndex}-{timestamp} ``` Both are truncated to 50 characters via `buildSaleReferenceId()`. ### 3.3 Response parsing `parseDejavooResponse()` normalizes terminal responses into: - `status` — `approved` | `declined` | `pending` | `canceled` - `transactionId` — from RRN / PNReferenceId / ReferenceId - `authCode`, card last4, EMV data, host messages - `failureDetails` — user-friendly messages for common decline reasons ### 3.4 Error handling Axios failures (e.g. 400) are caught and converted to `CustomError` with messages extracted from Dejavoo’s `GeneralResponse` fields, so the frontend shows actionable text instead of generic “Request failed with status code 400”. Known decline mappings include: - **Host no response** (code 91 / 1015) - **Card declined** - **Terminal canceled** --- ## 4. API Endpoints | Method | Route | Purpose | |--------|-------|---------| | `POST` | `/api/v1/nus-sale/dejavoo-payment` | Single full sale via Dejavoo | | `GET` | `/api/v1/nus-sale/dejavoo-payment/:transactionId` | Poll transaction status | | `POST` | `/api/v1/nus-sale/create-split-sale` | Create pending split sale | | `POST` | `/api/v1/nus-sale/split-payment` | Process all split methods (includes inline Dejavoo) | | `POST` | `/api/v1/nus-sale/resume-split-payment` | Continue partial split after failure/cancel | | `POST` | `/api/v1/nus-sale/record-split-card-paid` | Record Stripe card success on split line | | `POST` | `/api/v1/nus-sale/cancel-reader-payment` | Cancel in-flight Stripe intents | ### Split payment design principle For split sales, only **two main APIs** drive the flow from the frontend: 1. `create-split-sale` — creates the sale record 2. `split-payment` — processes all methods in one call; **Dejavoo is handled inline** (no separate Dejavoo API call from the frontend for each split line) Resume uses `resume-split-payment` when some lines are already paid. --- ## 5. Single Payment Flow ``` Staff selects CPP → enters amount → Process Payment ↓ Frontend: POST /dejavoo-payment (products + amount + dejavooPaymentType) ↓ Backend: DejavooService.createSale() ↓ Terminal: customer taps/inserts card ↓ Approved → SaleService creates completed sale (paymentGateway: "dejavoo") Declined/Canceled → 402/409 with userMessage for staff ``` **Frontend behavior** - Shows loading: “Complete the payment on the Dejavoo terminal…” - On success: marks line `completed`, calls `onPaymentSuccess` - On failure: shows Dejavoo `userMessage` when available --- ## 6. Split Payment Flow (with Dejavoo) ### 6.1 Sequence ``` 1. Staff allocates amounts across methods (CPP + Cash + Zelle + Card, etc.) 2. POST create-split-sale → saleId, status: partial 3. POST split-payment → { saleId, paymentMethods[], readerId? } ``` Inside `SplitPayment`, for each payment method row: | Method | Backend action | |--------|----------------| | `cash` | Recorded immediately as `paid` | | `zelle` | Recorded immediately as `paid` | | `dejavoo` / `cpp` | **Inline** `DejavooService.createSale()` → `recordSplitPaymentDejavooSuccess()` | | `card` | Stripe PaymentIntent created → returned in `cardPayments[]` for frontend queue | | `card_present` | Stripe reader or SDK flow | ### 6.2 Dejavoo split metadata (important fix) Split Dejavoo calls use **minimal metadata** (same shape as single payment): - `dbName`, `userId`, `customerId`, `source`, `dejavooPaymentType`, `savedPaymentMethod` Fields like `saleId`, `splitIndex`, `isSplitPayment` were **removed** from Dejavoo CustomFields to avoid API rejection. ### 6.3 Amount validation `validateSplitPaymentMethodsBody()` ensures: - Payment methods sum to `netAmount` (± $0.01 tolerance) - Max 10 split lines - Card/CPP minimum $0.50 per line - Valid method names and Dejavoo types ### 6.4 Recording CPP success `SaleService.recordSplitPaymentDejavooSuccess()`: - Sets line `status: "paid"` - Stores `transactionId`, `authCode`, `paidAt` - Updates `amountPaid`, `remainingAmount`, `paymentGateway: "dejavoo"` - Finalizes sale when `remainingAmount` reaches zero --- ## 7. Resume Split Payment When some methods are already paid (e.g. CPP + Zelle + Cash) but card manual fails or is canceled: ``` POST resume-split-payment saleId paymentMethods[] ← only NEW / unpaid / adjusted lines ``` **Backend logic** 1. Loads sale; keeps only rows with `status: "paid"` 2. Computes `remaining = netAmount - paidTotal` 3. Validates new methods sum to `remaining` 4. Appends new rows after paid rows; re-indexes `splitIndex` 5. Processes Dejavoo/Stripe for new rows only — **never re-charges paid CPP** **Frontend logic** (`handleResumeSplitPayments`) - Reconciles UI “Paid” state against backend snapshot (`computeReconciledLines`) - Builds **charge units**: full amount for new/failed/cancelled lines; **delta only** for topped-up paid lines (cash/Zelle/CPP) - Updates status chips: Paid, Failed, Cancelled per line --- ## 8. Frontend UX — Payment Status & Adjustments ### 8.1 Per-method status chips | Status | Chip | When | |--------|------|------| | `completed` | Green **Paid** / **Paid $X** | Backend settled | | `completed` + increased amount | **Pay $Y more** (warning) | Top-up pending | | `failed` | Red **Failed** | Decline/error | | `cancelled` | Orange **Cancelled** | Staff canceled in-flight card | | `processing` | Loading state | Terminal/Stripe in progress | Failed or cancelled lines **remain editable** — staff can change amounts or switch methods. ### 8.2 CPP in split UI - Payment method button: **Card Reader** area includes Dejavoo when configured - CPP lines show payment type toggle: **Credit** / **Debit** - Minimum $0.50 enforced (same as Stripe card) ### 8.3 Cancel flows (two levels) | Action | Behavior | |--------|----------| | **Cancel current attempt** (`cancelCurrentPaymentAttempt`) | Cancels in-flight Stripe intent; marks card line **Cancelled**; returns to payment selection; **does not** cancel whole sale | | **Cancel entire sale** (`requestPaymentAbort` / `executePaymentAbort`) | Only when closing modal **after** money collected; shows **Cancel Payment?** dialog with refund breakdown | ### 8.4 Cancel Payment dialog (`SplitPaymentCancelDialog`) Shows amounts staff must return to customer **as cash**: - Via Zelle (confirmed) - Via CPP on terminal - Via Cash - Via Card (Stripe) **Totals:** - **Total to return** — all paid methods - **If cash was NOT collected** — Zelle + CPP + Card only (when cash was in the mix but may not have been physically collected) ### 8.5 Zelle gate If Zelle is in the split and not yet confirmed, staff must confirm the Zelle modal before processing continues (including CPP lines). ### 8.6 Cash payment (split context) - **Received Amount** = total cash for the line (already collected + paying now) - Auto-syncs when amount changes unless manually overridden - Shortage blocks **Process Payment** with tooltip + warning notification - Paid cash lines can be increased (pay more) or decreased (return) with dynamic label: - `($X collected · pay $Y more)` - `($X collected · return $Y)` --- ## 9. Error Scenarios & Fixes | Issue | Cause | Fix | |-------|-------|-----| | `AxiosError 400` on split + Dejavoo | `ReferenceId` > 50 chars; invalid CustomFields | `buildSaleReferenceId()`, `sanitizeCustomFields()` | | CPP shows Paid but backend says $X remaining | Frontend/backend paid state drift | `computeReconciledLines()` + resume reconciliation | | `$92 must equal $98` on resume | Skipped CPP line marked paid in UI but not on server | Reconcile before charge; include unpaid CPP in resume payload | | Cancel card showed “Cancel Split Payment?” | Wrong handler on card cancel | `cancelCurrentPaymentAttempt` vs `requestPaymentAbort` | | Dejavoo generic error message | Raw axios error | `extractAxiosErrorMessage` + `getDejavooFailureDetails` | --- ## 10. Data Model (split payment line) Each entry in `sale.paymentMethods[]`: ```javascript { method: "CPP_Credit" | "CPP_Debit" | "CPP" | "cash" | "zelle" | "card" | "card_present", amount: Number, status: "paid" | "pending" | "failed", splitIndex: Number, referenceId: String, // Dejavoo transaction / Stripe PI id transactionId: String, authCode: String, paidAt: Date, // cash only: cashReceived: Number, changeReturned: Number, dejavooPaymentType: "Credit" | "Debit" | "CPP" // when applicable } ``` --- ## 11. Testing Checklist ### Single CPP - [ ] Credit sale completes and sale is `paid` - [ ] Debit sale completes - [ ] Declined card shows friendly message - [ ] Terminal cancel shows canceled message - [ ] Timeout/host-no-response shows reconnect guidance ### Split with CPP - [ ] CPP + Cash + Zelle — all settle in one `split-payment` call - [ ] CPP runs on terminal before cash/zelle finalize in loop order - [ ] Reference IDs stay ≤ 50 characters - [ ] Paid chip appears on CPP line after approval ### Split failure & resume - [ ] CPP paid → card manual canceled → line shows **Cancelled** - [ ] Staff adjusts amounts and resumes — only remaining charged - [ ] CPP not double-charged on resume - [ ] Close modal after partial pay → cancel dialog shows CPP amount to return ### Cash + CPP combinations - [ ] Cash received auto-fills; editable manually - [ ] Shortage disables Process Payment - [ ] Top-up on paid cash shows collected / pay more / return labels --- ## 12. Flow Diagram (Split with Dejavoo) ```mermaid sequenceDiagram participant Staff participant POS as PaymentComponentNew participant API as Backend API participant DJ as Dejavoo Terminal participant Stripe Staff->>POS: Allocate CPP + Cash + Card Staff->>POS: Process Payment POS->>API: POST create-split-sale API-->>POS: saleId POS->>API: POST split-payment API->>DJ: createSale (CPP line) DJ-->>API: approved API->>API: recordSplitPaymentDejavooSuccess API->>API: Record cash/zelle paid API->>Stripe: Create PaymentIntent (card) API-->>POS: cardPayments[], updated sale POS->>POS: Card queue / reader UI alt Card success POS->>API: record-split-card-paid API-->>POS: sale paid else Card canceled POS->>POS: Mark Cancelled, stay on selection Staff->>POS: Adjust & resume POS->>API: POST resume-split-payment API->>Stripe: New intent for remaining only end ``` --- ## 13. Summary The POS Dejavoo integration provides: 1. **Single-shot CPP sales** via `/dejavoo-payment` 2. **Inline CPP inside split payment** — no extra frontend Dejavoo calls per split 3. **Robust SPIn API compliance** — short reference IDs, sanitized metadata, clear errors 4. **Resume without double-charge** — paid CPP/cash/zelle preserved; only remainder processed 5. **Staff-friendly UX** — per-line status, smart cancel vs abort, refund breakdown dialog, flexible adjustments after failure For questions or changes, start with `dejavoo.service.js` (terminal API), `nursery.product.controller.js` (`SplitPayment` / `ResumeSplitPayment`), and `PaymentComponentNew.tsx` (UI orchestration).