|
# 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).
|