@blob-solutions/vcr-am-sdk 0.11.0 → 0.15.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 CHANGED
@@ -2,6 +2,73 @@
2
2
 
3
3
  All notable changes to `@blob-solutions/vcr-am-sdk`. The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project follows [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## [0.15.0] — 2026-07-09
6
+
7
+ Fixes the foreign-currency request contract, which 0.14.0 modeled incorrectly, and closes the last two gaps against `/api/v1` (`autoSettle` and `GET /exchange-rate`).
8
+
9
+ ### Fixed — foreign-currency contract (breaking, but 0.14.0's shape never worked)
10
+
11
+ - **Removed `RegisterSaleInput.currencyConversion` and the `CurrencyConversionInput` type.** The API never accepted a top-level `currencyConversion` block — `POST /sales` silently stripped it, so a "foreign-currency" sale sent via 0.14.0 was fiscalized with no HO-234-N conversion trail at all. The real contract is **per-item**: set `SaleItem.currency` (ISO 4217, e.g. `"USD"`) and denominate that item's `price` in the foreign currency; VCR converts every line to AMD server-side at the previous-business-day CBA rate. All foreign-priced items in one sale must share one currency, and mixing AMD with foreign lines is rejected.
12
+
13
+ ```diff
14
+ - items: [{ offer: { id: 5 }, department: { id: 1 }, quantity: "1", price: "4000", unit: "pc" }],
15
+ - currencyConversion: { currency: "USD", ratePerUnit: "400", rateDate: "2026-07-07", lines: [{ foreignUnitPrice: "10" }] },
16
+ - amount: { cash: "4000" },
17
+ + items: [{ offer: { id: 5 }, department: { id: 1 }, quantity: "1", price: "10", currency: "USD", unit: "pc" }],
18
+ + autoSettle: { tender: "cash" },
19
+ ```
20
+
21
+ ### Added
22
+
23
+ - `SaleItem.currency?: string` — per-item ISO 4217 input currency (see above). Omit or `"AMD"` for a native AMD line.
24
+ - `RegisterSaleInput.autoSettle?: { tender: "cash" | "nonCash" }` — derived-total payment: VCR computes the AMD cart total and charges the whole of it to one tender. A sale now carries **exactly one** of `amount` (explicit AMD) or `autoSettle`; the types enforce the XOR, mirroring the server's cross-field validation. The essential companion to foreign-currency sales, where the AMD total is not knowable client-side. New exported types `AutoSettle`, `SalePayment`.
25
+ - `getExchangeRate({ currency })` — wraps `GET /exchange-rate`. Previews the AMD conversion rate VCR would apply to a foreign-currency sale registered now (CBA mid-market rate, previous business day, HO-234-N). Read-only; rejects `AMD`. New type `ExchangeRate`, new schema `exchangeRateResponseSchema`.
26
+
27
+ ### Changed
28
+
29
+ - `RegisterSaleInput.amount` is no longer required on its own — it is one arm of the `amount` / `autoSettle` union. Existing `{ ..., amount }` call sites are unaffected.
30
+ - Dev tooling patch bumps (`@tsconfig/node24`, `@tsconfig/strictest`, `vite`). No change to the published package. TypeScript 7, pnpm 11, and `@types/node` 26 are available but intentionally deferred (kept aligned with the platform monorepo, which pins TypeScript 5.9).
31
+
32
+ ## [0.14.0] — 2026-07-08
33
+
34
+ Closes the drift that had accumulated against `/api/v1` since 0.13.0. Everything here is additive — no call-site changes required.
35
+
36
+ ### Added
37
+
38
+ - `whoami()` — wraps `GET /whoami`. Returns `{ vcrId, crn, mode, tradingPlatformName, businessEntity }`, where `mode` is `"production"` / `"sandbox"` and `crn` is `null` for imported-but-not-yet-activated VCRs. Useful as a cheap SDK health-check and for telling production keys apart from sandbox ones. New type `Whoami`, new schema `whoamiResponseSchema`.
39
+ - `listOffers({ externalId?, type?, includeArchived? })` — wraps `GET /offers`. Returns up to 500 `OfferListItem[]`. Check whether an offer already exists (by `externalId`) before creating it.
40
+ - `getOffer(id)` — wraps `GET /offers/{id}`. Returns a single `OfferListItem`.
41
+ - `updateOffer(id, { title })` — wraps `PATCH /offers/{id}`. Renames an offer's title going forward; already-issued receipts and the SRC fiscal record are unchanged. Accepts the canonical `OfferTitle` (`localized` / `universal`).
42
+ - New types `OfferListItem`, `ListOffersFilter`, `UpdateOfferInput`, `CurrencyConversionInput`; new schemas `offerListItemSchema`, `offerListResponseSchema`.
43
+ - `RegisterSaleInput.currencyConversion?` — foreign-currency sales (HO-234-N). Enter the sale in USD/EUR/RUB etc.; VCR converts to AMD at the previous-business-day CBA rate and rejects a stale rate. The AMD `price` values in `items[]` stay authoritative.
44
+ - `SaleItem.emarks?: string[]` — per-line eMark codes for marked goods (Govt Decision 1976-N). Previously only refund items could carry eMarks.
45
+ - `Offer` (sale-item offer) gained the by-internal-id reference shape `{ id: number }`, alongside the existing new-offer and `{ externalId }` shapes.
46
+ - `VCRApiError.body.requestId` — the server's 5xx correlation ID is now preserved instead of being stripped. Include it when contacting support.
47
+
48
+ ### Changed
49
+
50
+ - `CreateOfferInput.title` now also accepts the canonical `OfferTitle` shape (`{ type: "localized" | "universal", ... }`), unifying it with inline sale offers and `updateOffer`, and unlocking `universal` (brand-name) titles. The legacy `{ value, localizationStrategy }` shape (now typed as `LegacyOfferTitle`) still works but is **deprecated** — the server plans to drop it.
51
+ - Dependency refresh: `zod` `^4.4.1` → `^4.4.3`. Dev tooling bumped (`vite` `^8.0.10` → `^8.1.3`, clearing two Windows-only dev-server advisories; `@biomejs/biome`, `vitest`, `@vitest/coverage-v8`, `publint`, `@arethetypeswrong/cli`). An `esbuild` `>=0.28.1` override clears a low-severity build-time advisory. None of these ship in the published package.
52
+
53
+ ## [0.13.0] — 2026-05-27
54
+
55
+ ### Added
56
+
57
+ - `listDepartments()` — wraps `GET /departments`. Returns `DepartmentListItem[]` with `internalId`, `externalId`, `taxRegime`, and the localized `title`. Only departments confirmed by the tax service are included, so each `internalId` is safe to reference from a sale item's `department.id`. Closes the gap where the SDK could create departments but not list them.
58
+ - New exported types: `DepartmentListItem`. New exported schemas: `departmentListItemSchema`, `departmentListResponseSchema`.
59
+
60
+ ## [0.12.0] — 2026-05-26
61
+
62
+ ### Added
63
+
64
+ - `listPrepayments({ customerRef?, state? })` — wraps `GET /prepayments`. Returns up to 500 rows; each item carries the ledger-derived `remaining` and `state` (`open` / `consumed` / `refunded`). `state: "all"` is accepted as an explicit no-op filter.
65
+ - `getCustomerPrepaymentBalance({ customerRef })` — wraps `GET /prepayments/balance`. Returns the open balance for that customer, scoped to the BusinessEntity that owns the calling VCR's API key, plus the FIFO-ordered list of contributing open prepayments.
66
+ - New exported types: `PrepaymentListItem`, `PrepaymentState`, `CustomerPrepaymentBalance`, `ListPrepaymentsFilter`. New exported schemas: `prepaymentListItemSchema`, `prepaymentListResponseSchema`, `prepaymentStateSchema`, `customerPrepaymentBalanceResponseSchema`.
67
+
68
+ ### Changed
69
+
70
+ - `PrepaymentDetail` (the `getPrepayment` response) now includes `remaining: number` and `state: PrepaymentState`. The server has been sending these since the ledger landed; the SDK schema is catching up. Strictly additive — existing consumers keep working.
71
+
5
72
  ## [0.11.0] — 2026-05-13
6
73
 
7
74
  ### Breaking
package/README.md CHANGED
@@ -19,6 +19,7 @@ Official TypeScript / JavaScript SDK for the [VCR.AM](https://vcr.am) Virtual Ca
19
19
  - [Quick start](#quick-start)
20
20
  - [Configuration](#configuration)
21
21
  - [API reference](#api-reference)
22
+ - [Account](#account)
22
23
  - [Sales](#sales)
23
24
  - [Prepayments](#prepayments)
24
25
  - [Cashiers](#cashiers)
@@ -93,6 +94,19 @@ await vcr.registerSale(sale, {
93
94
 
94
95
  Every method returns a typed, schema-validated response and accepts an optional `RequestOptions` (`{ signal?, timeoutMs? }`) as its last argument.
95
96
 
97
+ ### Account
98
+
99
+ | Method | Endpoint | Returns |
100
+ | ------------ | ------------- | -------- |
101
+ | `whoami()` | `GET /whoami` | `Whoami` |
102
+
103
+ `whoami()` resolves which VCR the API key belongs to and whether it is a `production` or `sandbox` key — a cheap health-check. It works on freshly-imported registers that have not been activated yet (`crn` is `null` in that case).
104
+
105
+ ```typescript
106
+ const me = await vcr.whoami();
107
+ console.log(me.vcrId, me.mode, me.businessEntity.tin);
108
+ ```
109
+
96
110
  ### Sales
97
111
 
98
112
  | Method | Endpoint | Returns |
@@ -111,13 +125,38 @@ const refund = await vcr.registerSaleRefund({
111
125
  });
112
126
  ```
113
127
 
128
+ **Payment: `amount` or `autoSettle`.** A sale settles by *exactly one* of these — never both (the types enforce it):
129
+
130
+ - `amount` — explicit AMD per tender (`{ cash?, nonCash?, prepayment?, compensation? }`), as in the quick-start.
131
+ - `autoSettle: { tender: "cash" | "nonCash" }` — VCR computes the AMD cart total and charges the whole of it to that one tender. Zero-tap: you don't sum the cart yourself, and it's the only sane option for a foreign-currency sale (you can't know the AMD total up front).
132
+
133
+ **eMark codes** (marked goods, Govt Decision 1976-N) go on each sale item as `emarks: string[]`; the same codes are echoed on refund items.
134
+
135
+ **Foreign-currency sales** (HO-234-N): set `currency` on each item and price that item in the foreign currency. VCR converts every line to AMD server-side at the previous-business-day CBA mid-market rate; the fiscal receipt is always AMD. All foreign-priced items in one sale must share the same currency, and mixing AMD with foreign lines is rejected. Preview the rate first with [`getExchangeRate`](#exchange-rate).
136
+
137
+ ```typescript
138
+ await vcr.registerSale({
139
+ cashier: { id: 1 },
140
+ items: [
141
+ // price is in USD; VCR converts to AMD.
142
+ { offer: { id: 5 }, department: { id: 1 }, quantity: "1", price: "10", currency: "USD", unit: "pc" },
143
+ ],
144
+ autoSettle: { tender: "cash" }, // AMD total is server-derived
145
+ buyer: { type: "individual" },
146
+ });
147
+ ```
148
+
114
149
  ### Prepayments
115
150
 
116
- | Method | Endpoint | Returns |
117
- | --------------------------------- | ------------------------- | ------------------------------------ |
118
- | `registerPrepayment(input)` | `POST /prepayments` | `RegisterPrepaymentResponse` |
119
- | `getPrepayment(id)` | `GET /prepayments/{id}` | `PrepaymentDetail` |
120
- | `registerPrepaymentRefund(input)` | `POST /prepayments/refund`| `RegisterPrepaymentRefundResponse` |
151
+ | Method | Endpoint | Returns |
152
+ | ------------------------------------------ | ---------------------------- | ---------------------------------- |
153
+ | `registerPrepayment(input)` | `POST /prepayments` | `RegisterPrepaymentResponse` |
154
+ | `getPrepayment(id)` | `GET /prepayments/{id}` | `PrepaymentDetail` |
155
+ | `registerPrepaymentRefund(input)` | `POST /prepayments/refund` | `RegisterPrepaymentRefundResponse` |
156
+ | `listPrepayments(filter?)` | `GET /prepayments` | `PrepaymentListItem[]` |
157
+ | `getCustomerPrepaymentBalance({ ... })` | `GET /prepayments/balance` | `CustomerPrepaymentBalance` |
158
+
159
+ `listPrepayments({ customerRef?, state? })` returns up to 500 rows, each carrying the ledger-derived `remaining` and `state` (`open` / `consumed` / `refunded`). `getCustomerPrepaymentBalance({ customerRef })` returns the customer's open balance, scoped to the business entity that owns the calling VCR's key.
121
160
 
122
161
  ### Cashiers
123
162
 
@@ -138,10 +177,30 @@ const cashier = await vcr.createCashier({
138
177
 
139
178
  ### Offers and departments
140
179
 
141
- | Method | Endpoint | Returns |
142
- | ------------------------- | -------------------- | --------------------------- |
143
- | `createOffer(input)` | `POST /offers` | `CreateOfferResponse` |
144
- | `createDepartment(input)` | `POST /departments` | `CreateDepartmentResponse` |
180
+ | Method | Endpoint | Returns |
181
+ | ---------------------------- | -------------------- | -------------------------- |
182
+ | `createOffer(input)` | `POST /offers` | `CreateOfferResponse` |
183
+ | `listOffers(filter?)` | `GET /offers` | `OfferListItem[]` |
184
+ | `getOffer(id)` | `GET /offers/{id}` | `OfferListItem` |
185
+ | `updateOffer(id, { title })` | `PATCH /offers/{id}` | `OfferListItem` |
186
+ | `createDepartment(input)` | `POST /departments` | `CreateDepartmentResponse` |
187
+ | `listDepartments()` | `GET /departments` | `DepartmentListItem[]` |
188
+
189
+ `listOffers({ externalId?, type?, includeArchived? })` is handy for checking whether an offer already exists (by `externalId`) before creating it. `updateOffer` renames an offer's title going forward — already-issued receipts are unchanged. Offer titles use the canonical `OfferTitle` shape (`{ type: "localized" | "universal", ... }`); `createOffer` also still accepts the deprecated legacy `{ value, localizationStrategy }` shape.
190
+
191
+ ```typescript
192
+ const [existing] = await vcr.listOffers({ externalId: "sku-coffee" });
193
+ if (existing === undefined) {
194
+ await vcr.createOffer({
195
+ type: "product",
196
+ classifierCode: "0901",
197
+ title: { type: "localized", content: { hy: "Սուրճ" }, localizationStrategy: "translation" },
198
+ defaultMeasureUnit: "pc",
199
+ defaultDepartment: { id: 1 },
200
+ externalId: "sku-coffee",
201
+ });
202
+ }
203
+ ```
145
204
 
146
205
  ### Classifier search
147
206
 
@@ -153,6 +212,20 @@ const matches = await vcr.searchClassifier({
153
212
  });
154
213
  ```
155
214
 
215
+ ### Exchange rate
216
+
217
+ | Method | Endpoint | Returns |
218
+ | ----------------------------------- | -------------------- | -------------- |
219
+ | `getExchangeRate({ currency })` | `GET /exchange-rate` | `ExchangeRate` |
220
+
221
+ Previews the AMD conversion rate VCR would apply to a foreign-currency sale registered now — the CBA mid-market rate published on the previous business day (HO-234-N). Read-only; it does not fiscalize anything. `currency` is a 3-letter ISO 4217 code (case-insensitive); `AMD` is rejected.
222
+
223
+ ```typescript
224
+ const rate = await vcr.getExchangeRate({ currency: "USD" });
225
+ // { currency: "USD", ratePerUnit: 397.12, amount: 1, rateDate: "2026-07-08", ... }
226
+ const amdEquivalent = Number(usdPrice) * rate.ratePerUnit;
227
+ ```
228
+
156
229
  ## Error handling
157
230
 
158
231
  All SDK errors derive from `VCRError`. Catch the base class for a single handler, or narrow on the specific subclass.
@@ -203,7 +276,7 @@ The whole request flow — connection, body read, schema parse — is bounded by
203
276
 
204
277
  ## Idempotency and retries
205
278
 
206
- **The SDK does not retry.** This is intentional. The fiscal API does not currently support idempotency keys, so retrying a `registerSale` after a network blip can double-fiscalize a receipt. Implement application-level retry only on operations you have confirmed are safe to repeat (typically reads: `getSale`, `getPrepayment`, `listCashiers`, `searchClassifier`).
279
+ **The SDK does not retry.** This is intentional. The fiscal API does not currently support idempotency keys, so retrying a `registerSale` after a network blip can double-fiscalize a receipt. Implement application-level retry only on operations you have confirmed are safe to repeat (typically reads: `whoami`, `getSale`, `getPrepayment`, `listPrepayments`, `listCashiers`, `listOffers`, `getOffer`, `listDepartments`, `searchClassifier`, `getExchangeRate`).
207
280
 
208
281
  ## Browser usage
209
282
 
package/lib/index.cjs CHANGED
@@ -98,7 +98,11 @@ var apiErrorBodySchema = zod.z.object({
98
98
  message: zod.z.string(),
99
99
  code: zod.z.string()
100
100
  })
101
- ).optional()
101
+ ).optional(),
102
+ // Server-generated correlation ID present on unexpected 5xx responses.
103
+ // Kept lenient (plain string, not `.uuid()`) so a malformed value can never
104
+ // make the error envelope fail to parse and mask the real API error.
105
+ requestId: zod.z.string().optional()
102
106
  });
103
107
  var isoDateTimeSchema = zod.z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, "Invalid ISO datetime");
104
108
  var detailLanguageSchema = zod.z.enum([...AVAILABLE_LANGUAGES, "multi"]);
@@ -151,6 +155,32 @@ var createDepartmentResponseSchema = zod.z.object({
151
155
  message: zod.z.string(),
152
156
  department: zod.z.number().int().nonnegative()
153
157
  });
158
+ var exchangeRateResponseSchema = zod.z.object({
159
+ /** ISO 4217 code, uppercased (echoes the requested currency). */
160
+ currency: zod.z.string(),
161
+ /** AMD per one unit of `currency` (CBA rate / amount). */
162
+ ratePerUnit: zod.z.number(),
163
+ /** CBA multi-lot multiplier (1, 100, 1000, ...). */
164
+ amount: zod.z.number().int(),
165
+ /** Yerevan date whose rate was applied — the previous business day. */
166
+ rateDate: zod.z.string(),
167
+ /** Yerevan calendar date the rate resolves for (server time). */
168
+ saleDate: zod.z.string(),
169
+ /** Legal rule version applied (e.g. "HO-234-N"). */
170
+ ruleVersion: zod.z.string(),
171
+ source: zod.z.literal("CBA")
172
+ });
173
+ var whoamiResponseSchema = zod.z.object({
174
+ vcrId: zod.z.number().int().nonnegative(),
175
+ // `null` until the VCR is activated (freshly-imported registers have no CRN).
176
+ crn: zod.z.string().nullable(),
177
+ mode: zod.z.enum(["production", "sandbox"]),
178
+ tradingPlatformName: zod.z.string(),
179
+ businessEntity: zod.z.object({
180
+ tin: zod.z.string(),
181
+ name: zod.z.string()
182
+ })
183
+ });
154
184
  var cashierNameLocalizationSchema = zod.z.object({
155
185
  language: zod.z.enum([...AVAILABLE_LANGUAGES, "multi"]),
156
186
  content: zod.z.string()
@@ -161,6 +191,16 @@ var cashierListItemSchema = zod.z.object({
161
191
  name: zod.z.record(zod.z.string(), cashierNameLocalizationSchema)
162
192
  });
163
193
  var cashierListResponseSchema = zod.z.array(cashierListItemSchema);
194
+ var departmentListItemSchema = zod.z.object({
195
+ internalId: zod.z.number().int().nonnegative(),
196
+ externalId: zod.z.string().nullable(),
197
+ taxRegime: taxRegimeSchema,
198
+ // Map from language code -> { language, content }, matching the wire
199
+ // format produced by makeLocalizationObject on the server. May be empty
200
+ // for legacy departments created before titles were mandatory.
201
+ title: zod.z.record(zod.z.string(), localizationEntrySchema)
202
+ });
203
+ var departmentListResponseSchema = zod.z.array(departmentListItemSchema);
164
204
  var classifierSearchItemSchema = zod.z.object({
165
205
  code: zod.z.string(),
166
206
  // Optional: server may emit `{ code }` without `title` when the classifier
@@ -168,6 +208,20 @@ var classifierSearchItemSchema = zod.z.object({
168
208
  title: zod.z.string().optional()
169
209
  });
170
210
  var classifierSearchResponseSchema = zod.z.array(classifierSearchItemSchema);
211
+ var offerListItemSchema = zod.z.object({
212
+ id: zod.z.number().int().nonnegative(),
213
+ externalId: zod.z.string().nullable(),
214
+ type: offerTypeSchema,
215
+ classifierCode: zod.z.string(),
216
+ // Server types this as a plain string (not the unit enum) to stay faithful
217
+ // to whatever is stored; mirror it so a legacy unit can never fail parsing.
218
+ defaultMeasureUnit: zod.z.string(),
219
+ defaultDepartment: zod.z.object({ internalId: zod.z.number().int().nonnegative() }),
220
+ title: zod.z.array(localizationEntrySchema),
221
+ archivedAt: isoDateTimeSchema.nullable(),
222
+ createdAt: isoDateTimeSchema
223
+ });
224
+ var offerListResponseSchema = zod.z.array(offerListItemSchema);
171
225
  var receiptPickSchema = zod.z.object({
172
226
  srcId: zod.z.number().int(),
173
227
  time: zod.z.string().regex(/^\d+$/, "Invalid bigint string"),
@@ -230,6 +284,7 @@ var prepaymentRefundPickSchema = zod.z.object({
230
284
  cashAmount: zod.z.number(),
231
285
  receipt: receiptPickSchema.nullable()
232
286
  });
287
+ var prepaymentStateSchema = zod.z.enum(["open", "consumed", "refunded"]);
233
288
  var prepaymentDetailResponseSchema = zod.z.object({
234
289
  id: zod.z.number().int().nonnegative(),
235
290
  createdAt: isoDateTimeSchema,
@@ -238,7 +293,36 @@ var prepaymentDetailResponseSchema = zod.z.object({
238
293
  nonCashAmount: zod.z.number(),
239
294
  receipt: receiptPickSchema.nullable(),
240
295
  refund: prepaymentRefundPickSchema.nullable(),
241
- cashier: cashierPickSchema
296
+ cashier: cashierPickSchema,
297
+ /** SUM(amount) across all ledger rows for this prepayment. */
298
+ remaining: zod.z.number(),
299
+ state: prepaymentStateSchema
300
+ });
301
+ var prepaymentListItemSchema = zod.z.object({
302
+ id: zod.z.number().int().nonnegative(),
303
+ createdAt: isoDateTimeSchema,
304
+ buyerTin: zod.z.string().nullable(),
305
+ cashAmount: zod.z.number(),
306
+ nonCashAmount: zod.z.number(),
307
+ remaining: zod.z.number(),
308
+ state: prepaymentStateSchema
309
+ });
310
+ var prepaymentListResponseSchema = zod.z.array(prepaymentListItemSchema);
311
+ var customerOpenPrepaymentSchema = zod.z.object({
312
+ prepaymentId: zod.z.number().int().nonnegative(),
313
+ createdAt: isoDateTimeSchema,
314
+ cashAmount: zod.z.number(),
315
+ nonCashAmount: zod.z.number(),
316
+ buyerTin: zod.z.string().nullable(),
317
+ remaining: zod.z.number()
318
+ });
319
+ var customerPrepaymentBalanceResponseSchema = zod.z.object({
320
+ entityId: zod.z.number().int().nonnegative(),
321
+ customerRef: zod.z.string(),
322
+ /** Total remaining across all open prepayments matching customerRef. */
323
+ balance: zod.z.number(),
324
+ /** FIFO-ordered open prepayments contributing to the balance. */
325
+ openPrepayments: zod.z.array(customerOpenPrepaymentSchema)
242
326
  });
243
327
 
244
328
  // src/fetch.ts
@@ -299,7 +383,8 @@ function buildApiError(status, rawJson, url) {
299
383
  if (parsed.success) {
300
384
  const body = {
301
385
  error: parsed.data.error,
302
- ...parsed.data.issues !== void 0 && { issues: parsed.data.issues }
386
+ ...parsed.data.issues !== void 0 && { issues: parsed.data.issues },
387
+ ...parsed.data.requestId !== void 0 && { requestId: parsed.data.requestId }
303
388
  };
304
389
  return new VCRApiError(status, body, url);
305
390
  }
@@ -347,6 +432,17 @@ var VCRClient = class {
347
432
  this.#fetch = options.fetch === void 0 ? candidate.bind(globalThis) : candidate;
348
433
  this.#defaultTimeoutMs = options.timeoutMs === void 0 ? DEFAULT_TIMEOUT_MS : options.timeoutMs;
349
434
  }
435
+ // ─── Account ──────────────────────────────────────────────────────────────
436
+ /**
437
+ * Resolve which VCR the calling API key belongs to. Returns the register's
438
+ * identity, its `mode` (`production` / `sandbox`), and the owning business
439
+ * entity. Works on freshly-imported VCRs that have not been activated yet
440
+ * (in which case `crn` is `null`) — handy as a cheap SDK health-check and to
441
+ * tell production keys apart from sandbox ones.
442
+ */
443
+ whoami(options = {}) {
444
+ return this.#json("GET", "/whoami", whoamiResponseSchema, void 0, options);
445
+ }
350
446
  // ─── Sales ─────────────────────────────────────────────────────────────────
351
447
  registerSale(data, options = {}) {
352
448
  return this.#json("POST", "/sales", registerSaleResponseSchema, data, options);
@@ -381,6 +477,48 @@ var VCRClient = class {
381
477
  options
382
478
  );
383
479
  }
480
+ /**
481
+ * List prepayments registered through the calling VCR. Server caps the
482
+ * response at 500 rows; for larger sets, narrow with `customerRef` or
483
+ * paginate by `id` outside the SDK.
484
+ *
485
+ * `remaining` and `state` are derived from the ledger — call this whenever
486
+ * you want a current snapshot rather than caching the result.
487
+ */
488
+ listPrepayments(filter = {}, options = {}) {
489
+ const query = {};
490
+ if (filter.customerRef !== void 0) query["customerRef"] = filter.customerRef;
491
+ if (filter.state !== void 0) query["state"] = filter.state;
492
+ return this.#json(
493
+ "GET",
494
+ "/prepayments",
495
+ prepaymentListResponseSchema,
496
+ void 0,
497
+ options,
498
+ Object.keys(query).length > 0 ? query : void 0
499
+ );
500
+ }
501
+ /**
502
+ * Get a customer's open prepayment balance scoped to the BusinessEntity
503
+ * that owns the calling VCR's API key. Because the ledger is entity-scoped,
504
+ * the result reflects deposits made through any VCR belonging to the same
505
+ * entity — merchants who run more than one VCR see a single wallet.
506
+ *
507
+ * `customerRef` is matched exactly against the ledger — typically a TIN,
508
+ * normalized E.164 phone, or lowercased email. Use whichever identifier the
509
+ * customer gave at deposit time; identities are not auto-merged across
510
+ * different ref types.
511
+ */
512
+ getCustomerPrepaymentBalance(input, options = {}) {
513
+ return this.#json(
514
+ "GET",
515
+ "/prepayments/balance",
516
+ customerPrepaymentBalanceResponseSchema,
517
+ void 0,
518
+ options,
519
+ { customerRef: input.customerRef }
520
+ );
521
+ }
384
522
  // ─── Cashiers ─────────────────────────────────────────────────────────────
385
523
  listCashiers(options = {}) {
386
524
  return this.#json("GET", "/cashiers", cashierListResponseSchema, void 0, options);
@@ -392,9 +530,57 @@ var VCRClient = class {
392
530
  createOffer(data, options = {}) {
393
531
  return this.#json("POST", "/offers", createOfferResponseSchema, data, options);
394
532
  }
533
+ /**
534
+ * List offers belonging to the calling VCR. Server caps the response at 500
535
+ * rows; narrow with `externalId` / `type` for larger catalogs. Archived
536
+ * offers are excluded unless `includeArchived` is set.
537
+ *
538
+ * A common use is checking whether an offer already exists (by `externalId`)
539
+ * before creating it.
540
+ */
541
+ listOffers(filter = {}, options = {}) {
542
+ const query = {};
543
+ if (filter.externalId !== void 0) query["externalId"] = filter.externalId;
544
+ if (filter.type !== void 0) query["type"] = filter.type;
545
+ if (filter.includeArchived !== void 0) {
546
+ query["includeArchived"] = filter.includeArchived ? "true" : "false";
547
+ }
548
+ return this.#json(
549
+ "GET",
550
+ "/offers",
551
+ offerListResponseSchema,
552
+ void 0,
553
+ options,
554
+ Object.keys(query).length > 0 ? query : void 0
555
+ );
556
+ }
557
+ getOffer(offerId, options = {}) {
558
+ assertNonNegativeInt(offerId, "offerId");
559
+ return this.#json("GET", `/offers/${offerId}`, offerListItemSchema, void 0, options);
560
+ }
561
+ /**
562
+ * Rename an offer's title. Affects only future receipts — already-issued
563
+ * receipts keep the title they were created with, and the SRC fiscal record
564
+ * is unchanged. Missing `en`/`ru` are filled per `localizationStrategy`,
565
+ * exactly as on offer creation. Returns the updated offer.
566
+ */
567
+ updateOffer(offerId, data, options = {}) {
568
+ assertNonNegativeInt(offerId, "offerId");
569
+ return this.#json("PATCH", `/offers/${offerId}`, offerListItemSchema, data, options);
570
+ }
395
571
  createDepartment(data, options = {}) {
396
572
  return this.#json("POST", "/departments", createDepartmentResponseSchema, data, options);
397
573
  }
574
+ /**
575
+ * List departments configured on the calling VCR — `internalId`,
576
+ * `externalId`, `taxRegime`, and the localized `title`. Mirrors the
577
+ * dashboard catalog: only departments confirmed by the tax service are
578
+ * returned, so the `internalId` field is safe to reference from a sale
579
+ * item's `department.id`.
580
+ */
581
+ listDepartments(options = {}) {
582
+ return this.#json("GET", "/departments", departmentListResponseSchema, void 0, options);
583
+ }
398
584
  // ─── Classifier search ────────────────────────────────────────────────────
399
585
  searchClassifier(input, options = {}) {
400
586
  return this.#json(
@@ -414,6 +600,23 @@ var VCRClient = class {
414
600
  searchClassifierCategory(query, type, language, options = {}) {
415
601
  return this.searchClassifier({ query, type, language }, options);
416
602
  }
603
+ // ─── Exchange rate ────────────────────────────────────────────────────────
604
+ /**
605
+ * Preview the AMD conversion rate VCR would apply to a foreign-currency sale
606
+ * registered now — the CBA mid-market rate published on the previous business
607
+ * day (Tax Code art. 16, HO-234-N). Use it to show a buyer the AMD equivalent
608
+ * before charging. The sale itself is converted server-side from each item's
609
+ * `currency` + `price` (see {@link RegisterSaleInput}); this endpoint is a
610
+ * read-only preview and does not fiscalize anything.
611
+ *
612
+ * `currency` is a 3-letter ISO 4217 code, case-insensitive. `AMD` is rejected
613
+ * (it is the native receipt currency and needs no conversion).
614
+ */
615
+ getExchangeRate(input, options = {}) {
616
+ return this.#json("GET", "/exchange-rate", exchangeRateResponseSchema, void 0, options, {
617
+ currency: input.currency
618
+ });
619
+ }
417
620
  // ─── Internal ─────────────────────────────────────────────────────────────
418
621
  #json(method, path, schema, body, options, query) {
419
622
  const url = query === void 0 ? `${this.#baseUrl}${path}` : appendQuery(`${this.#baseUrl}${path}`, query);
@@ -461,12 +664,22 @@ exports.classifierSearchResponseSchema = classifierSearchResponseSchema;
461
664
  exports.createCashierResponseSchema = createCashierResponseSchema;
462
665
  exports.createDepartmentResponseSchema = createDepartmentResponseSchema;
463
666
  exports.createOfferResponseSchema = createOfferResponseSchema;
667
+ exports.customerPrepaymentBalanceResponseSchema = customerPrepaymentBalanceResponseSchema;
668
+ exports.departmentListItemSchema = departmentListItemSchema;
669
+ exports.departmentListResponseSchema = departmentListResponseSchema;
670
+ exports.exchangeRateResponseSchema = exchangeRateResponseSchema;
671
+ exports.offerListItemSchema = offerListItemSchema;
672
+ exports.offerListResponseSchema = offerListResponseSchema;
464
673
  exports.prepaymentDetailResponseSchema = prepaymentDetailResponseSchema;
674
+ exports.prepaymentListItemSchema = prepaymentListItemSchema;
675
+ exports.prepaymentListResponseSchema = prepaymentListResponseSchema;
676
+ exports.prepaymentStateSchema = prepaymentStateSchema;
465
677
  exports.registerPrepaymentRefundResponseSchema = registerPrepaymentRefundResponseSchema;
466
678
  exports.registerPrepaymentResponseSchema = registerPrepaymentResponseSchema;
467
679
  exports.registerSaleRefundResponseSchema = registerSaleRefundResponseSchema;
468
680
  exports.registerSaleResponseSchema = registerSaleResponseSchema;
469
681
  exports.saleDetailResponseSchema = saleDetailResponseSchema;
470
682
  exports.unitsKeys = unitsKeys;
683
+ exports.whoamiResponseSchema = whoamiResponseSchema;
471
684
  //# sourceMappingURL=index.cjs.map
472
685
  //# sourceMappingURL=index.cjs.map