@qrcommunication/viva-local-terminal-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/index.cjs +422 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +593 -0
- package/dist/index.d.ts +593 -0
- package/dist/index.js +409 -0
- package/dist/index.js.map +1 -0
- package/package.json +85 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2026-06-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Initial release of `@qrcommunication/viva-local-terminal-sdk`.
|
|
12
|
+
- `VivaLocalTerminalClient` — peer-to-peer (P2P) client that talks directly to
|
|
13
|
+
an EFT POS terminal over the LAN (no cloud endpoint, no authentication).
|
|
14
|
+
- `transactions` resource: `sale`, `capturePreauth`, `refund`,
|
|
15
|
+
`unreferencedRefund`, `abort`, `aadeFimControl`, `retrieve`.
|
|
16
|
+
- `sessions` resource: `get` (poll a session for its final outcome).
|
|
17
|
+
- `device` resource: `screenControl`, `brightness`.
|
|
18
|
+
- TLS-aware transport: when `verifyTls: false`, an `undici` `Agent` is wired
|
|
19
|
+
automatically so the terminal's self-signed certificate is accepted (the
|
|
20
|
+
native `fetch` API has no per-call TLS toggle). A custom `dispatcher` can also
|
|
21
|
+
be supplied.
|
|
22
|
+
- ISV endpoint support via `useIsvEndpoints` (trailing-slash path variants).
|
|
23
|
+
- `ApiError` / `VivaError` that preserve the terminal's **plain-string** 400
|
|
24
|
+
error bodies verbatim.
|
|
25
|
+
- Enums: `PaymentMethod`, `SessionState`, `SessionType`, `TransactionTypeId`.
|
|
26
|
+
- ESM + CommonJS builds with full TypeScript declarations.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QrCommunication
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# @qrcommunication/viva-local-terminal-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for the **Viva.com Local Terminal API** (`/pos/v1/`).
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> This SDK is **peer-to-peer (P2P)**. It talks **directly to an EFT POS card
|
|
7
|
+
> terminal over your local network** — there is **no Viva cloud endpoint** and
|
|
8
|
+
> **no authentication**. You address the terminal's own IP address and port.
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/@qrcommunication/viva-local-terminal-sdk)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## The P2P model in one minute
|
|
16
|
+
|
|
17
|
+
Unlike the Viva.com cloud APIs (`api.vivapayments.com`, OAuth2), the Local
|
|
18
|
+
Terminal API is a **closed-network protocol**:
|
|
19
|
+
|
|
20
|
+
| Cloud APIs | **Local Terminal API (this SDK)** |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `https://api.vivapayments.com` | `https://<terminal-ip>:<port>` (the device itself) |
|
|
23
|
+
| OAuth2 / API key auth | **No authentication at all** |
|
|
24
|
+
| Public CA TLS | **Self-signed terminal certificate** |
|
|
25
|
+
| JSON errors | **Plain-string error bodies on 400** |
|
|
26
|
+
|
|
27
|
+
The official Viva documentation states verbatim:
|
|
28
|
+
|
|
29
|
+
> _"In a closed network environment, authentication is not required for
|
|
30
|
+
> peer-to-peer communication. [...] eliminating the need to include an
|
|
31
|
+
> Authorization tag in the header."_
|
|
32
|
+
|
|
33
|
+
This SDK reproduces that model faithfully: requests go straight to the terminal,
|
|
34
|
+
carry **no `Authorization` header**, and the transport is TLS-aware for the
|
|
35
|
+
terminal's self-signed certificate.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Network prerequisites
|
|
40
|
+
|
|
41
|
+
1. Your application host (POS / ECR / kiosk) and the terminal are on the **same
|
|
42
|
+
LAN / VLAN**, with no firewall blocking the terminal's HTTPS port.
|
|
43
|
+
2. You know the terminal's **IP address and port**. Discover it via:
|
|
44
|
+
- the terminal's on-device network settings screen, or
|
|
45
|
+
- your router/DHCP lease table, or
|
|
46
|
+
- mDNS / Bonjour / `zeroconf` discovery on the subnet.
|
|
47
|
+
3. The terminal serves **HTTPS with a self-signed certificate**. You either:
|
|
48
|
+
- pass `verifyTls: false` (accepting the closed-LAN trust model — the common
|
|
49
|
+
case), or
|
|
50
|
+
- install the terminal's CA in your host's trust store and keep
|
|
51
|
+
`verifyTls: true`.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pnpm add @qrcommunication/viva-local-terminal-sdk
|
|
59
|
+
# or: npm install / yarn add
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **Node 18+** (uses the global `fetch`; bundles `undici` for the self-signed
|
|
63
|
+
TLS path). Also works in any runtime with a global `fetch`.
|
|
64
|
+
- ESM and CommonJS builds are both shipped.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick start
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { VivaLocalTerminalClient } from "@qrcommunication/viva-local-terminal-sdk";
|
|
72
|
+
import { randomUUID } from "node:crypto";
|
|
73
|
+
|
|
74
|
+
const terminal = new VivaLocalTerminalClient({
|
|
75
|
+
terminalBaseUrl: "https://192.168.1.50:8080", // the terminal's IP + port
|
|
76
|
+
verifyTls: false, // self-signed terminal cert on a trusted closed LAN
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 1. Start a sale. Amounts are ALWAYS integers in cents.
|
|
80
|
+
const sessionId = randomUUID(); // you generate the session UUID
|
|
81
|
+
const started = await terminal.transactions.sale({
|
|
82
|
+
sessionId,
|
|
83
|
+
amount: 1170, // 11.70 EUR
|
|
84
|
+
currencyCode: 978, // EUR (ISO 4217 numeric)
|
|
85
|
+
merchantReference: "order-123",
|
|
86
|
+
});
|
|
87
|
+
// started.state === "PROCESSING"
|
|
88
|
+
|
|
89
|
+
// 2. The terminal is now waiting for the card. Poll the session for the outcome.
|
|
90
|
+
const session = await terminal.sessions.get(sessionId);
|
|
91
|
+
// session.state === "SUCCESS" once the cardholder has paid
|
|
92
|
+
// session.payloadData holds the full transaction details once completed
|
|
93
|
+
|
|
94
|
+
// 3. Control the physical device.
|
|
95
|
+
await terminal.device.screenControl(true); // wake + lock the screen
|
|
96
|
+
await terminal.device.brightness(0.5); // (0, 1]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Polling helper
|
|
100
|
+
|
|
101
|
+
The terminal returns `PROCESSING` immediately; poll until the session resolves:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { SessionState } from "@qrcommunication/viva-local-terminal-sdk";
|
|
105
|
+
|
|
106
|
+
async function waitForOutcome(client, sessionId, timeoutMs = 120_000) {
|
|
107
|
+
const deadline = Date.now() + timeoutMs;
|
|
108
|
+
while (Date.now() < deadline) {
|
|
109
|
+
const session = await client.sessions.get(sessionId);
|
|
110
|
+
if (
|
|
111
|
+
session.state === SessionState.SUCCESS ||
|
|
112
|
+
session.state === SessionState.FAILURE
|
|
113
|
+
) {
|
|
114
|
+
return session;
|
|
115
|
+
}
|
|
116
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
117
|
+
}
|
|
118
|
+
throw new Error("Timed out waiting for the terminal");
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## TLS for self-signed terminal certificates
|
|
125
|
+
|
|
126
|
+
The native `fetch` API has **no per-call TLS toggle**. On Node, this SDK
|
|
127
|
+
therefore wires an [`undici`](https://github.com/nodejs/undici) `Agent` with
|
|
128
|
+
`connect.rejectUnauthorized = false` automatically whenever `verifyTls: false`:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
new VivaLocalTerminalClient({
|
|
132
|
+
terminalBaseUrl: "https://192.168.1.50:8080",
|
|
133
|
+
verifyTls: false, // -> undici Agent accepts the self-signed cert
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Prefer to keep verification on? Install the terminal's CA in the host trust
|
|
138
|
+
store (e.g. `NODE_EXTRA_CA_CERTS=/path/to/terminal-ca.pem`) and leave
|
|
139
|
+
`verifyTls: true` (the default). You can also pass your own `undici` dispatcher:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { Agent } from "undici";
|
|
143
|
+
|
|
144
|
+
new VivaLocalTerminalClient({
|
|
145
|
+
terminalBaseUrl: "https://192.168.1.50:8080",
|
|
146
|
+
dispatcher: new Agent({ connect: { ca: myTerminalCaPem } }),
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> Disabling TLS verification is only acceptable on a **trusted, closed LAN** —
|
|
151
|
+
> exactly the deployment model the Local Terminal API is designed for. Never
|
|
152
|
+
> point this SDK at a host reachable from the public internet with
|
|
153
|
+
> `verifyTls: false`.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## ISV vs merchant endpoints
|
|
158
|
+
|
|
159
|
+
The ISV (Independent Software Vendor) endpoint variants differ only by a
|
|
160
|
+
**trailing slash** (e.g. `/pos/v1/sale/`). Enable them globally:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const terminal = new VivaLocalTerminalClient({
|
|
164
|
+
terminalBaseUrl: "https://192.168.1.50:8080",
|
|
165
|
+
useIsvEndpoints: true, // sale/refund hit `/pos/v1/sale/` and accept isvDetails
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await terminal.transactions.sale({
|
|
169
|
+
sessionId,
|
|
170
|
+
amount: 1170,
|
|
171
|
+
isvDetails: { sourceCode: "your-isv-source" },
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## API surface
|
|
178
|
+
|
|
179
|
+
### `transactions`
|
|
180
|
+
|
|
181
|
+
| Method | Endpoint | Purpose |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| `sale(params)` | `POST /pos/v1/sale` | Start a sale (or pre-authorization). |
|
|
184
|
+
| `capturePreauth(params)` | `POST /pos/v1/preauth-completion` | Capture a pre-authorized sale. |
|
|
185
|
+
| `refund(params)` | `POST /pos/v1/refund` | Referenced refund / cancellation. |
|
|
186
|
+
| `unreferencedRefund(params)` | `POST /pos/v1/unreferenced-refund` | Refund not linked to an original txn. |
|
|
187
|
+
| `abort(sessionId)` | `POST /pos/v1/abort` | Abort an in-progress SALE session. |
|
|
188
|
+
| `aadeFimControl(token)` | `POST /pos/v1/aade-fim-control` | Greek AADE FIM control action. |
|
|
189
|
+
| `retrieve(params?)` | `GET /pos/v1/transactions` | List historical transactions. |
|
|
190
|
+
|
|
191
|
+
### `sessions`
|
|
192
|
+
|
|
193
|
+
| Method | Endpoint | Purpose |
|
|
194
|
+
| --- | --- | --- |
|
|
195
|
+
| `get(sessionId)` | `GET /pos/v1/sessions/{id}` | Poll a session for its final outcome. |
|
|
196
|
+
|
|
197
|
+
### `device`
|
|
198
|
+
|
|
199
|
+
| Method | Endpoint | Purpose |
|
|
200
|
+
| --- | --- | --- |
|
|
201
|
+
| `screenControl(wakeUpLock)` | `POST /pos/v1/awake-lock` | Wake / lock the screen. |
|
|
202
|
+
| `brightness(value)` | `POST /pos/v1/brightness` | Set app brightness, `(0, 1]`. |
|
|
203
|
+
|
|
204
|
+
### Enums
|
|
205
|
+
|
|
206
|
+
`PaymentMethod`, `SessionState`, `SessionType`, `TransactionTypeId`.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Error handling
|
|
211
|
+
|
|
212
|
+
The Local Terminal API returns a **plain string body on 400** (not JSON). This
|
|
213
|
+
SDK throws an `ApiError` whose `message` is that raw text, and preserves the
|
|
214
|
+
exact body:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { ApiError } from "@qrcommunication/viva-local-terminal-sdk";
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
await terminal.transactions.sale({ sessionId, amount: 0 });
|
|
221
|
+
} catch (err) {
|
|
222
|
+
if (err instanceof ApiError) {
|
|
223
|
+
console.error(err.httpStatus); // 400
|
|
224
|
+
console.error(err.message); // "ZeroconfException error message Amount cannot be null"
|
|
225
|
+
console.error(err.getErrorText()); // same raw text
|
|
226
|
+
console.error(err.responseBody); // { raw: "ZeroconfException error message ..." }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
A failure to reach the terminal at all (wrong IP, terminal asleep, firewall)
|
|
232
|
+
throws an `ApiError` with `httpStatus === 0` and a message hinting at LAN
|
|
233
|
+
reachability.
|
|
234
|
+
|
|
235
|
+
| Class | Meaning |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| `VivaError` | Base class for every SDK error. |
|
|
238
|
+
| `ApiError` | HTTP error (4xx/5xx) or transport failure (`httpStatus === 0`). |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Amounts & currency
|
|
243
|
+
|
|
244
|
+
- **All amounts are integers in the currency's minor unit (cents).**
|
|
245
|
+
`1170` = 11.70 EUR.
|
|
246
|
+
- `currencyCode` is the **ISO 4217 numeric** code (`978` = EUR, `826` = GBP).
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
MIT © QrCommunication
|