@retalia/pos-components 0.0.12 → 0.0.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retalia/pos-components",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -20,10 +20,6 @@
20
20
  ".": {
21
21
  "types": "./types/retalia-pos-components.d.ts",
22
22
  "default": "./fesm2022/retalia-pos-components.mjs"
23
- },
24
- "./services": {
25
- "types": "./types/retalia-pos-components-services.d.ts",
26
- "default": "./fesm2022/retalia-pos-components-services.mjs"
27
23
  }
28
24
  },
29
25
  "peerDependencies": {
@@ -1,203 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injectable } from '@angular/core';
3
- import { HttpClient, HttpErrorResponse } from '@angular/common/http';
4
- import { throwError, switchMap, from, catchError } from 'rxjs';
5
-
6
- /** Read a document cookie by name. Returns null when missing or not in a browser. */
7
- function readCookie(name) {
8
- if (typeof document === 'undefined' || !name) {
9
- return null;
10
- }
11
- const encodedName = encodeURIComponent(name);
12
- for (const part of document.cookie.split(';')) {
13
- const trimmed = part.trim();
14
- const separator = trimmed.indexOf('=');
15
- if (separator < 0) {
16
- continue;
17
- }
18
- const key = trimmed.slice(0, separator);
19
- if (key !== name && key !== encodedName) {
20
- continue;
21
- }
22
- return decodeURIComponent(trimmed.slice(separator + 1));
23
- }
24
- return null;
25
- }
26
-
27
- const DEFAULT_POS_API_CONFIG = {
28
- apiBaseUrl: '',
29
- tokenCookieName: 'portalApiToken',
30
- shopContextCookieName: 'contextShopId',
31
- };
32
- const POS_API_CONFIG = new InjectionToken('POS_API_CONFIG', {
33
- providedIn: 'root',
34
- factory: () => DEFAULT_POS_API_CONFIG,
35
- });
36
- function readAccessToken(config) {
37
- return config.getAccessToken?.() ?? readCookie(config.tokenCookieName);
38
- }
39
- function readShopContext(config) {
40
- return config.getShopContext?.() ?? readCookie(config.shopContextCookieName);
41
- }
42
- function readClerkContext(config) {
43
- return config.getClerkContext?.()?.trim() || null;
44
- }
45
-
46
- function joinUrl(baseUrl, path) {
47
- if (!baseUrl || /^https?:\/\//i.test(path)) {
48
- return path;
49
- }
50
- const origin = baseUrl.replace(/\/+$/, '');
51
- const suffix = path.replace(/^\/+/, '');
52
- return `${origin}/${suffix}`;
53
- }
54
- /**
55
- * Attaches Bearer + Shop-Context from cookies (or host overrides), and Clerk-Context once the
56
- * host reports a signed-in clerk.
57
- */
58
- const posApiInterceptor = (req, next) => {
59
- const config = inject(POS_API_CONFIG);
60
- const token = readAccessToken(config);
61
- const shopContext = readShopContext(config);
62
- const clerkContext = readClerkContext(config);
63
- let headers = req.headers;
64
- if (token && !headers.has('Authorization')) {
65
- headers = headers.set('Authorization', `Bearer ${token}`);
66
- }
67
- if (shopContext && !headers.has('Shop-Context')) {
68
- headers = headers.set('Shop-Context', shopContext);
69
- }
70
- if (clerkContext && !headers.has('Clerk-Context')) {
71
- headers = headers.set('Clerk-Context', clerkContext);
72
- }
73
- return next(req.clone({
74
- url: joinUrl(config.apiBaseUrl, req.url),
75
- headers,
76
- }));
77
- };
78
-
79
- class ClerkAuthError extends Error {
80
- name = 'ClerkAuthError';
81
- constructor(message) {
82
- super(message);
83
- }
84
- }
85
- const SHA256_HEX = /^[0-9a-f]{64}$/i;
86
- function displayName(clerk, clerkNumber) {
87
- const name = `${clerk.firstName ?? ''} ${clerk.lastName ?? ''}`.trim();
88
- return name || clerk.clerkNumber || clerkNumber;
89
- }
90
- /** HeadlessPOS `SvcClerkService.validateClerk`: SHA-256 of UTF-16 `${identifier}.${pin}`. */
91
- async function hashClerkPin(identifier, pin) {
92
- const message = `${identifier}.${pin}`;
93
- const buffer = new ArrayBuffer(message.length * 2);
94
- const utf16 = new Uint16Array(buffer);
95
- for (let i = 0; i < message.length; i++) {
96
- utf16[i] = message.charCodeAt(i);
97
- }
98
- const digest = await crypto.subtle.digest('SHA-256', buffer);
99
- return Array.from(new Uint8Array(digest))
100
- .map((byte) => byte.toString(16).padStart(2, '0'))
101
- .join('')
102
- .toUpperCase();
103
- }
104
- async function passwordsMatch(submitted, clerk) {
105
- const stored = clerk.password ?? '';
106
- if (SHA256_HEX.test(stored)) {
107
- const hash = await hashClerkPin(clerk.identifier, submitted);
108
- return hash === stored.toUpperCase();
109
- }
110
- return submitted === stored;
111
- }
112
- class ClerkAuthService {
113
- http = inject(HttpClient);
114
- /**
115
- * Looks up the clerk and compares the keypad password in this service.
116
- * Hashed passwords use the HeadlessPOS SHA-256 check. Not a dedicated
117
- * sign-in endpoint — replace when ID-647 / PosIdentity lands.
118
- */
119
- signIn(clerkNumber, password) {
120
- const number = clerkNumber.trim();
121
- if (!number) {
122
- return throwError(() => new ClerkAuthError('Invalid clerk or password'));
123
- }
124
- return this.http.get(`/api/v1.0/clerks/${encodeURIComponent(number)}`).pipe(switchMap((clerk) => from(this.toSession(clerk, number, password))), catchError((error) => {
125
- if (error instanceof ClerkAuthError) {
126
- return throwError(() => error);
127
- }
128
- if (error instanceof HttpErrorResponse) {
129
- if (error.status === 401) {
130
- return throwError(() => new ClerkAuthError('API token missing or expired. Paste portalApiToken into cookies.'));
131
- }
132
- if (error.status === 404) {
133
- return throwError(() => new ClerkAuthError('Invalid clerk or password'));
134
- }
135
- }
136
- return throwError(() => new ClerkAuthError('Unable to sign in'));
137
- }));
138
- }
139
- async toSession(clerk, clerkNumber, password) {
140
- if (!(await passwordsMatch(password, clerk))) {
141
- throw new ClerkAuthError('Invalid clerk or password');
142
- }
143
- return {
144
- clerkNumber: clerk.clerkNumber || clerkNumber,
145
- displayName: displayName(clerk, clerkNumber),
146
- languageCode: clerk.languageCode || 'en',
147
- isManager: clerk.isManager === true,
148
- };
149
- }
150
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ClerkAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
151
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ClerkAuthService, providedIn: 'root' });
152
- }
153
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ClerkAuthService, decorators: [{
154
- type: Injectable,
155
- args: [{ providedIn: 'root' }]
156
- }] });
157
-
158
- function idempotencyKey() {
159
- return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;
160
- }
161
- /**
162
- * Thin HTTP client for open-till sale commands (command in → PosStateEnvelope out).
163
- *
164
- * Keep as one client while the surface is small. When basket, tender, and sale
165
- * lifecycle each grow their own handful of methods, split by subdomain, e.g.:
166
- * - basket: addItem / changeQuantity / clearBasket (+ discounts, modifiers, …)
167
- * - tender: tenderCash (+ card, void payment, …)
168
- * - sale: newSale (+ suspend, recall, …)
169
- * Extract is cheap; avoid splitting early into near-empty clients.
170
- */
171
- class PosCommandsClient {
172
- http = inject(HttpClient);
173
- addItem(command, key = idempotencyKey()) {
174
- return this.http.post('/api/v1.0/pos/commands/add-item', command, {
175
- headers: { 'Idempotency-Key': key },
176
- });
177
- }
178
- changeQuantity(command, key = idempotencyKey()) {
179
- return this.http.post('/api/v1.0/pos/commands/change-quantity', command, { headers: { 'Idempotency-Key': key } });
180
- }
181
- clearBasket(command, key = idempotencyKey()) {
182
- return this.http.post('/api/v1.0/pos/commands/clear-basket', command, { headers: { 'Idempotency-Key': key } });
183
- }
184
- tenderCash(command, key = idempotencyKey()) {
185
- return this.http.post('/api/v1.0/pos/commands/tender-cash', command, { headers: { 'Idempotency-Key': key } });
186
- }
187
- newSale(command, key = idempotencyKey()) {
188
- return this.http.post('/api/v1.0/pos/commands/new-sale', command, { headers: { 'Idempotency-Key': key } });
189
- }
190
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosCommandsClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
191
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosCommandsClient, providedIn: 'root' });
192
- }
193
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosCommandsClient, decorators: [{
194
- type: Injectable,
195
- args: [{ providedIn: 'root' }]
196
- }] });
197
-
198
- /**
199
- * Generated bundle index. Do not edit.
200
- */
201
-
202
- export { ClerkAuthError, ClerkAuthService, DEFAULT_POS_API_CONFIG, POS_API_CONFIG, PosCommandsClient, posApiInterceptor, readAccessToken, readClerkContext, readShopContext };
203
- //# sourceMappingURL=retalia-pos-components-services.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"retalia-pos-components-services.mjs","sources":["../../../projects/pos-components/services/read-cookie.ts","../../../projects/pos-components/services/pos-api-config.ts","../../../projects/pos-components/services/pos-api.interceptor.ts","../../../projects/pos-components/services/clerk-auth.service.ts","../../../projects/pos-components/services/pos-commands.client.ts","../../../projects/pos-components/services/retalia-pos-components-services.ts"],"sourcesContent":["/** Read a document cookie by name. Returns null when missing or not in a browser. */\r\nexport function readCookie(name: string): string | null {\r\n if (typeof document === 'undefined' || !name) {\r\n return null;\r\n }\r\n\r\n const encodedName = encodeURIComponent(name);\r\n for (const part of document.cookie.split(';')) {\r\n const trimmed = part.trim();\r\n const separator = trimmed.indexOf('=');\r\n if (separator < 0) {\r\n continue;\r\n }\r\n\r\n const key = trimmed.slice(0, separator);\r\n if (key !== name && key !== encodedName) {\r\n continue;\r\n }\r\n\r\n return decodeURIComponent(trimmed.slice(separator + 1));\r\n }\r\n\r\n return null;\r\n}\r\n","import { InjectionToken } from '@angular/core';\r\n\r\nimport { readCookie } from './read-cookie';\r\n\r\nexport interface PosApiConfig {\r\n /** Origin for HeadlessAPI. Empty string when the host app proxies `/api`. */\r\n readonly apiBaseUrl: string;\r\n /** Cookie holding the Portal Bearer token (paste in local dev). */\r\n readonly tokenCookieName: string;\r\n /** Cookie holding the shop id sent as `Shop-Context`. */\r\n readonly shopContextCookieName: string;\r\n readonly getAccessToken?: () => string | null;\r\n readonly getShopContext?: () => string | null;\r\n /**\r\n * Signed-in clerk number, sent as `Clerk-Context`. There is no cookie for it: the host app\r\n * returns it once its clerk sign-in (and password check) has passed, null otherwise.\r\n */\r\n readonly getClerkContext?: () => string | null;\r\n}\r\n\r\nexport const DEFAULT_POS_API_CONFIG: PosApiConfig = {\r\n apiBaseUrl: '',\r\n tokenCookieName: 'portalApiToken',\r\n shopContextCookieName: 'contextShopId',\r\n};\r\n\r\nexport const POS_API_CONFIG = new InjectionToken<PosApiConfig>('POS_API_CONFIG', {\r\n providedIn: 'root',\r\n factory: () => DEFAULT_POS_API_CONFIG,\r\n});\r\n\r\nexport function readAccessToken(config: PosApiConfig): string | null {\r\n return config.getAccessToken?.() ?? readCookie(config.tokenCookieName);\r\n}\r\n\r\nexport function readShopContext(config: PosApiConfig): string | null {\r\n return config.getShopContext?.() ?? readCookie(config.shopContextCookieName);\r\n}\r\n\r\nexport function readClerkContext(config: PosApiConfig): string | null {\r\n return config.getClerkContext?.()?.trim() || null;\r\n}\r\n","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\n\r\nimport {\r\n POS_API_CONFIG,\r\n readAccessToken,\r\n readClerkContext,\r\n readShopContext,\r\n} from './pos-api-config';\r\n\r\nfunction joinUrl(baseUrl: string, path: string): string {\r\n if (!baseUrl || /^https?:\\/\\//i.test(path)) {\r\n return path;\r\n }\r\n\r\n const origin = baseUrl.replace(/\\/+$/, '');\r\n const suffix = path.replace(/^\\/+/, '');\r\n return `${origin}/${suffix}`;\r\n}\r\n\r\n/**\r\n * Attaches Bearer + Shop-Context from cookies (or host overrides), and Clerk-Context once the\r\n * host reports a signed-in clerk.\r\n */\r\nexport const posApiInterceptor: HttpInterceptorFn = (req, next) => {\r\n const config = inject(POS_API_CONFIG);\r\n const token = readAccessToken(config);\r\n const shopContext = readShopContext(config);\r\n const clerkContext = readClerkContext(config);\r\n\r\n let headers = req.headers;\r\n if (token && !headers.has('Authorization')) {\r\n headers = headers.set('Authorization', `Bearer ${token}`);\r\n }\r\n if (shopContext && !headers.has('Shop-Context')) {\r\n headers = headers.set('Shop-Context', shopContext);\r\n }\r\n if (clerkContext && !headers.has('Clerk-Context')) {\r\n headers = headers.set('Clerk-Context', clerkContext);\r\n }\r\n\r\n return next(\r\n req.clone({\r\n url: joinUrl(config.apiBaseUrl, req.url),\r\n headers,\r\n }),\r\n );\r\n};\r\n","import { HttpClient, HttpErrorResponse } from '@angular/common/http';\r\nimport { inject, Injectable } from '@angular/core';\r\nimport { catchError, from, Observable, switchMap, throwError } from 'rxjs';\r\n\r\n/** HeadlessAPI clerk payload. Password check is a stub until ID-647. */\r\ninterface ClerkRecord {\r\n identifier?: number;\r\n firstName?: string;\r\n lastName?: string;\r\n password?: string | null;\r\n languageCode?: string;\r\n clerkNumber?: string;\r\n isManager?: boolean;\r\n}\r\n\r\nexport interface ClerkSession {\r\n readonly clerkNumber: string;\r\n readonly displayName: string;\r\n readonly languageCode: string;\r\n readonly isManager: boolean;\r\n}\r\n\r\nexport class ClerkAuthError extends Error {\r\n override readonly name = 'ClerkAuthError';\r\n\r\n constructor(message: string) {\r\n super(message);\r\n }\r\n}\r\n\r\nconst SHA256_HEX = /^[0-9a-f]{64}$/i;\r\n\r\nfunction displayName(clerk: ClerkRecord, clerkNumber: string): string {\r\n const name = `${clerk.firstName ?? ''} ${clerk.lastName ?? ''}`.trim();\r\n return name || clerk.clerkNumber || clerkNumber;\r\n}\r\n\r\n/** HeadlessPOS `SvcClerkService.validateClerk`: SHA-256 of UTF-16 `${identifier}.${pin}`. */\r\nasync function hashClerkPin(identifier: number | undefined, pin: string): Promise<string> {\r\n const message = `${identifier}.${pin}`;\r\n const buffer = new ArrayBuffer(message.length * 2);\r\n const utf16 = new Uint16Array(buffer);\r\n for (let i = 0; i < message.length; i++) {\r\n utf16[i] = message.charCodeAt(i);\r\n }\r\n\r\n const digest = await crypto.subtle.digest('SHA-256', buffer);\r\n return Array.from(new Uint8Array(digest))\r\n .map((byte) => byte.toString(16).padStart(2, '0'))\r\n .join('')\r\n .toUpperCase();\r\n}\r\n\r\nasync function passwordsMatch(submitted: string, clerk: ClerkRecord): Promise<boolean> {\r\n const stored = clerk.password ?? '';\r\n if (SHA256_HEX.test(stored)) {\r\n const hash = await hashClerkPin(clerk.identifier, submitted);\r\n return hash === stored.toUpperCase();\r\n }\r\n\r\n return submitted === stored;\r\n}\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class ClerkAuthService {\r\n private readonly http = inject(HttpClient);\r\n\r\n /**\r\n * Looks up the clerk and compares the keypad password in this service.\r\n * Hashed passwords use the HeadlessPOS SHA-256 check. Not a dedicated\r\n * sign-in endpoint — replace when ID-647 / PosIdentity lands.\r\n */\r\n signIn(clerkNumber: string, password: string): Observable<ClerkSession> {\r\n const number = clerkNumber.trim();\r\n if (!number) {\r\n return throwError(() => new ClerkAuthError('Invalid clerk or password'));\r\n }\r\n\r\n return this.http.get<ClerkRecord>(`/api/v1.0/clerks/${encodeURIComponent(number)}`).pipe(\r\n switchMap((clerk) => from(this.toSession(clerk, number, password))),\r\n catchError((error: unknown) => {\r\n if (error instanceof ClerkAuthError) {\r\n return throwError(() => error);\r\n }\r\n\r\n if (error instanceof HttpErrorResponse) {\r\n if (error.status === 401) {\r\n return throwError(\r\n () =>\r\n new ClerkAuthError(\r\n 'API token missing or expired. Paste portalApiToken into cookies.',\r\n ),\r\n );\r\n }\r\n\r\n if (error.status === 404) {\r\n return throwError(() => new ClerkAuthError('Invalid clerk or password'));\r\n }\r\n }\r\n\r\n return throwError(() => new ClerkAuthError('Unable to sign in'));\r\n }),\r\n );\r\n }\r\n\r\n private async toSession(\r\n clerk: ClerkRecord,\r\n clerkNumber: string,\r\n password: string,\r\n ): Promise<ClerkSession> {\r\n if (!(await passwordsMatch(password, clerk))) {\r\n throw new ClerkAuthError('Invalid clerk or password');\r\n }\r\n\r\n return {\r\n clerkNumber: clerk.clerkNumber || clerkNumber,\r\n displayName: displayName(clerk, clerkNumber),\r\n languageCode: clerk.languageCode || 'en',\r\n isManager: clerk.isManager === true,\r\n };\r\n }\r\n}\r\n","import { HttpClient } from '@angular/common/http';\r\nimport { inject, Injectable } from '@angular/core';\r\nimport { Observable } from 'rxjs';\r\n\r\nimport { PosStateEnvelope } from './pos-state-envelope';\r\n\r\nexport interface AddItemCommand {\r\n readonly basketId: string;\r\n readonly sku: string;\r\n readonly qty: number;\r\n}\r\n\r\nexport interface ChangeQuantityCommand {\r\n readonly basketId: string;\r\n readonly lineId: number;\r\n readonly qty: number;\r\n}\r\n\r\nexport interface ClearBasketCommand {\r\n readonly basketId: string;\r\n}\r\n\r\nexport interface TenderCashCommand {\r\n readonly basketId: string;\r\n readonly amount: number;\r\n}\r\n\r\nexport interface NewSaleCommand {\r\n readonly basketId: string;\r\n}\r\n\r\nfunction idempotencyKey(): string {\r\n return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;\r\n}\r\n\r\n/**\r\n * Thin HTTP client for open-till sale commands (command in → PosStateEnvelope out).\r\n *\r\n * Keep as one client while the surface is small. When basket, tender, and sale\r\n * lifecycle each grow their own handful of methods, split by subdomain, e.g.:\r\n * - basket: addItem / changeQuantity / clearBasket (+ discounts, modifiers, …)\r\n * - tender: tenderCash (+ card, void payment, …)\r\n * - sale: newSale (+ suspend, recall, …)\r\n * Extract is cheap; avoid splitting early into near-empty clients.\r\n */\r\n@Injectable({ providedIn: 'root' })\r\nexport class PosCommandsClient {\r\n private readonly http = inject(HttpClient);\r\n\r\n addItem(\r\n command: AddItemCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>('/api/v1.0/pos/commands/add-item', command, {\r\n headers: { 'Idempotency-Key': key },\r\n });\r\n }\r\n\r\n changeQuantity(\r\n command: ChangeQuantityCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>(\r\n '/api/v1.0/pos/commands/change-quantity',\r\n command,\r\n { headers: { 'Idempotency-Key': key } },\r\n );\r\n }\r\n\r\n clearBasket(\r\n command: ClearBasketCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>(\r\n '/api/v1.0/pos/commands/clear-basket',\r\n command,\r\n { headers: { 'Idempotency-Key': key } },\r\n );\r\n }\r\n\r\n tenderCash(\r\n command: TenderCashCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>(\r\n '/api/v1.0/pos/commands/tender-cash',\r\n command,\r\n { headers: { 'Idempotency-Key': key } },\r\n );\r\n }\r\n\r\n newSale(\r\n command: NewSaleCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>(\r\n '/api/v1.0/pos/commands/new-sale',\r\n command,\r\n { headers: { 'Idempotency-Key': key } },\r\n );\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,179 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { InjectionToken } from '@angular/core';
3
- import { HttpInterceptorFn } from '@angular/common/http';
4
- import { Observable } from 'rxjs';
5
-
6
- interface PosApiConfig {
7
- /** Origin for HeadlessAPI. Empty string when the host app proxies `/api`. */
8
- readonly apiBaseUrl: string;
9
- /** Cookie holding the Portal Bearer token (paste in local dev). */
10
- readonly tokenCookieName: string;
11
- /** Cookie holding the shop id sent as `Shop-Context`. */
12
- readonly shopContextCookieName: string;
13
- readonly getAccessToken?: () => string | null;
14
- readonly getShopContext?: () => string | null;
15
- /**
16
- * Signed-in clerk number, sent as `Clerk-Context`. There is no cookie for it: the host app
17
- * returns it once its clerk sign-in (and password check) has passed, null otherwise.
18
- */
19
- readonly getClerkContext?: () => string | null;
20
- }
21
- declare const DEFAULT_POS_API_CONFIG: PosApiConfig;
22
- declare const POS_API_CONFIG: InjectionToken<PosApiConfig>;
23
- declare function readAccessToken(config: PosApiConfig): string | null;
24
- declare function readShopContext(config: PosApiConfig): string | null;
25
- declare function readClerkContext(config: PosApiConfig): string | null;
26
-
27
- /**
28
- * Attaches Bearer + Shop-Context from cookies (or host overrides), and Clerk-Context once the
29
- * host reports a signed-in clerk.
30
- */
31
- declare const posApiInterceptor: HttpInterceptorFn;
32
-
33
- interface ClerkSession {
34
- readonly clerkNumber: string;
35
- readonly displayName: string;
36
- readonly languageCode: string;
37
- readonly isManager: boolean;
38
- }
39
- declare class ClerkAuthError extends Error {
40
- readonly name = "ClerkAuthError";
41
- constructor(message: string);
42
- }
43
- declare class ClerkAuthService {
44
- private readonly http;
45
- /**
46
- * Looks up the clerk and compares the keypad password in this service.
47
- * Hashed passwords use the HeadlessPOS SHA-256 check. Not a dedicated
48
- * sign-in endpoint — replace when ID-647 / PosIdentity lands.
49
- */
50
- signIn(clerkNumber: string, password: string): Observable<ClerkSession>;
51
- private toSession;
52
- static ɵfac: i0.ɵɵFactoryDeclaration<ClerkAuthService, never>;
53
- static ɵprov: i0.ɵɵInjectableDeclaration<ClerkAuthService>;
54
- }
55
-
56
- /** Canonical POS state envelope (PosSharedPackages 0.0.1). Not a display DTO. */
57
- interface Session {
58
- tenantId: string;
59
- storeId: string;
60
- deviceId: string;
61
- clerkId: string;
62
- mode: string;
63
- locale: string;
64
- offline: boolean;
65
- }
66
- interface Vat {
67
- rate: number;
68
- amount: number;
69
- included: boolean;
70
- }
71
- type LineModifier = Record<string, unknown>;
72
- type LineDiscount = Record<string, unknown>;
73
- interface BasketLine {
74
- lineId: number;
75
- sku: string;
76
- name: string;
77
- qty: number;
78
- unitPrice: number;
79
- lineTotal: number;
80
- vat: Vat;
81
- modifiers: LineModifier[];
82
- discounts: LineDiscount[];
83
- struckThrough: boolean;
84
- }
85
- interface Basket {
86
- basketId: string;
87
- lines: BasketLine[];
88
- }
89
- interface Totals {
90
- gross: number;
91
- net: number;
92
- vat: number;
93
- discount: number;
94
- due: number;
95
- currency: string;
96
- }
97
- interface Payment {
98
- tender: string;
99
- amount: number;
100
- tendered: number;
101
- change: number;
102
- state: string;
103
- }
104
- interface Document {
105
- number: string;
106
- receiptReady: boolean;
107
- }
108
- interface Constraints {
109
- currency: string;
110
- offlineCapable: boolean;
111
- }
112
- interface ValidationIssue {
113
- code: string;
114
- severity: string;
115
- }
116
- interface Workflow {
117
- step: string;
118
- next: string[];
119
- }
120
- type Advisory = Record<string, unknown>;
121
- interface PosStateEnvelope {
122
- schemaVersion: string;
123
- correlationId: string;
124
- session: Session;
125
- state: string;
126
- basket: Basket;
127
- totals: Totals;
128
- payments: Payment[];
129
- document: Document;
130
- availableActions: string[];
131
- constraints: Constraints;
132
- validation: ValidationIssue[];
133
- workflow: Workflow;
134
- advisory: Advisory | null;
135
- }
136
-
137
- interface AddItemCommand {
138
- readonly basketId: string;
139
- readonly sku: string;
140
- readonly qty: number;
141
- }
142
- interface ChangeQuantityCommand {
143
- readonly basketId: string;
144
- readonly lineId: number;
145
- readonly qty: number;
146
- }
147
- interface ClearBasketCommand {
148
- readonly basketId: string;
149
- }
150
- interface TenderCashCommand {
151
- readonly basketId: string;
152
- readonly amount: number;
153
- }
154
- interface NewSaleCommand {
155
- readonly basketId: string;
156
- }
157
- /**
158
- * Thin HTTP client for open-till sale commands (command in → PosStateEnvelope out).
159
- *
160
- * Keep as one client while the surface is small. When basket, tender, and sale
161
- * lifecycle each grow their own handful of methods, split by subdomain, e.g.:
162
- * - basket: addItem / changeQuantity / clearBasket (+ discounts, modifiers, …)
163
- * - tender: tenderCash (+ card, void payment, …)
164
- * - sale: newSale (+ suspend, recall, …)
165
- * Extract is cheap; avoid splitting early into near-empty clients.
166
- */
167
- declare class PosCommandsClient {
168
- private readonly http;
169
- addItem(command: AddItemCommand, key?: string): Observable<PosStateEnvelope>;
170
- changeQuantity(command: ChangeQuantityCommand, key?: string): Observable<PosStateEnvelope>;
171
- clearBasket(command: ClearBasketCommand, key?: string): Observable<PosStateEnvelope>;
172
- tenderCash(command: TenderCashCommand, key?: string): Observable<PosStateEnvelope>;
173
- newSale(command: NewSaleCommand, key?: string): Observable<PosStateEnvelope>;
174
- static ɵfac: i0.ɵɵFactoryDeclaration<PosCommandsClient, never>;
175
- static ɵprov: i0.ɵɵInjectableDeclaration<PosCommandsClient>;
176
- }
177
-
178
- export { ClerkAuthError, ClerkAuthService, DEFAULT_POS_API_CONFIG, POS_API_CONFIG, PosCommandsClient, posApiInterceptor, readAccessToken, readClerkContext, readShopContext };
179
- export type { AddItemCommand, Advisory, Basket, BasketLine, ChangeQuantityCommand, ClearBasketCommand, ClerkSession, Constraints, Document, LineDiscount, LineModifier, NewSaleCommand, Payment, PosApiConfig, PosStateEnvelope, Session, TenderCashCommand, Totals, ValidationIssue, Vat, Workflow };