Project

General

Profile

Feature #9507 » POS_screen_splitchanges.txt

changes what i do document - Teja Sriram Sangani, 07/02/2026 10:38 AM

 
# 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).
    (1-1/1)