@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 +67 -0
- package/README.md +83 -10
- package/lib/index.cjs +216 -3
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +376 -11
- package/lib/index.d.ts +376 -11
- package/lib/index.global.js +22 -22
- package/lib/index.global.js.map +1 -1
- package/lib/index.js +207 -4
- package/lib/index.js.map +1 -1
- package/package.json +10 -10
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
|
|
117
|
-
|
|
|
118
|
-
| `registerPrepayment(input)`
|
|
119
|
-
| `getPrepayment(id)`
|
|
120
|
-
| `registerPrepaymentRefund(input)`
|
|
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
|
|
142
|
-
|
|
|
143
|
-
| `createOffer(input)`
|
|
144
|
-
| `
|
|
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
|