# SmartOne Cashbox Fiscal API

This document defines the current HTTP and Serial integration contract for Cashbox Fiscal API.

## 1. Scope

- Same business API is available over HTTP and Serial.
- Transport differs, request/response business payload is the same JSON model.
- This spec covers Cashbox Fiscal API routes.

## 2. Conventions

- Currency amounts are integer minor units (for example cents).
- Item quantity uses fixed-point scale `x1000` (`1000` = `1.000`).
- UTF-8 is required.
- `documentID` is generated by Cashbox and is usually unknown to integrator on the first request.
- For first-send and recovery flows, prefer `documentExtID` as the primary external key.
- Dates:
    - `yyyy-MM-dd HH:mm:ss` for document timestamps.
    - `yyyy-MM-dd` for report date range.

## 3. Transport Profiles

## 3.1 HTTP

- Protocol: HTTP
- Method: `POST` only
- Base URL: deployment-specific (default `http://127.0.0.1:8008`)
- Response content type: `application/json; charset=utf-8`

### HTTP request envelope

HTTP expects form parameters:

- `data` (required): Base64 of UTF-8 JSON business payload
- `sign` (required): signature string

Recommended content type:

- `application/x-www-form-urlencoded`

## 3.2 Serial

- Payload encoding: UTF-8
- Request body inside serial payload is form-encoded key/value pairs

### Serial frame format

```text
+------+-------------------+---------------------+------+
| STX  | Payload (N bytes) | CRC32 (4 bytes, BE) | ETX  |
| 0x02 | UTF-8 text        | over payload only   | 0x03 |
+------+-------------------+---------------------+------+
```

Rules:

- CRC algorithm: `CRC32`.
- CRC byte order in frame: big-endian.
- CRC input: payload bytes only.
- After CRC is calculated, any CRC byte equal to `0x03` is replaced with `0x20`.
- Invalid frame or CRC mismatch must be treated as invalid request/response frame.

### Serial payload format

Form-encoded fields:

- `command` (required): route, for example `/sale` or `sale`
- `data` (required): Base64 of UTF-8 JSON business payload
- `sign` (required): signature string

Example payload string:

```text
command=/check_status&data=eyJkb2N1bWVudEV4dElEIjoiT1JERVItMTAwMSJ9&sign=<SIGN>
```

## 4. Authentication and Signature

`merchantId` is assumed to be present in all deployments, so signature is mandatory for every request.

Formula:

```text
sign = Base64( SHA1_HEX_LOWER( dataBase64 + merchantId ) )
```

Where:

- `dataBase64` is the exact `data` parameter string.
- `merchantId` is configured on Cashbox side.

Signature failures return error `code = 1` (`AUTH_ERROR`).

## 5. Response Model

API responses are flat JSON objects with common fields plus route-specific fields.

Common fields:

- `status`: `success` or `error`
- `code`: integer (`0` for success)
- `message`: optional string (mainly for errors)

Error shape:

```json
{
    "status": "error",
    "code": 3,
    "message": "Missing required parameter"
}
```

Success shape example:

```json
{
    "status": "success",
    "code": 0,
    "documentID": 12345,
    "fiscalID": "..."
}
```

## 6. Routes

All routes are `POST`, except `/supported_operations` which supports `GET` (preferred) and optional `POST` with empty body.

Unknown or unsupported endpoints return HTTP `404 Not Found`.

| Route                   | Status      | Description                        |
|-------------------------|-------------|------------------------------------|
| `/supported_operations` | Implemented | Supported API operations discovery |
| `/sale`                 | Implemented | Sale receipt/fiscalization         |
| `/refund`               | Implemented | Refund receipt                     |
| `/check_status`         | Implemented | Get document status                |
| `/check_copy`           | Implemented | Reprint/get receipt copy           |
| `/x_report`             | Implemented | X report                           |
| `/open_shift`           | Implemented | Open shift                         |
| `/close_shift`          | Implemented | Close shift                        |
| `/check_shift`          | Implemented | Check shift status                 |
| `/deposit`              | Implemented | Cash deposit                       |
| `/withdraw`             | Implemented | Cash withdrawal                    |
| `/get_info`             | Implemented | Fiscal core info                   |
| `/dates_report`         | Implemented | Date range report                  |
| `/abort`                | Implemented | Abort by external document id      |

## 7. Route Contracts (Request + Response)

Only fields that are actually read by current business logic are listed below.

Common response fields for all routes:

| Field     | Type            | Required | Notes                |
|-----------|-----------------|----------|----------------------|
| `status`  | string          | Yes      | `success` or `error` |
| `code`    | integer (int32) | Yes      | `0` on success       |
| `message` | string          | No       | Error details        |

Error-only optional fields:

| Field           | Type            | Required | Notes                       |
|-----------------|-----------------|----------|-----------------------------|
| `printError`    | integer (int32) | No       | Print subsystem error       |
| `documentExtID` | string          | No       | External document reference |

## 7.0 `/supported_operations`

Method:

- `GET` (preferred)
- `POST` (optional), empty body is accepted

Request body:

- Not required.

Success response:

| Field        | Type             | Required | Notes                                               |
|--------------|------------------|----------|-----------------------------------------------------|
| `operations` | array of strings | Yes      | List of supported route names without leading slash |

## 7.1 `/sale`

Request body:

| Field               | Type                           | Required | Format / constraints              |
|---------------------|--------------------------------|----------|-----------------------------------|
| `documentID`        | integer (int64)                | No       | Internal document id              |
| `documentExtID`     | string                         | No       | External document id              |
| `docTime`           | string                         | No       | `yyyy-MM-dd HH:mm:ss`             |
| `docNumber`         | string                         | No       | External document number          |
| `wsName`            | string                         | No       | Workstation name                  |
| `departmentName`    | string                         | No       | Store/department name             |
| `departmentCode`    | string                         | No       | Store/department code             |
| `employeeName`      | string                         | No       | Cashier name                      |
| `employeeFirstName` | string                         | No       | Used when `employeeName` is empty |
| `employeeLastName`  | string                         | No       | Used when `employeeName` is empty |
| `items`             | array of item objects          | Yes      | Must be non-empty                 |
| `payments`          | object                         | No       | Payment split object              |
| `extraPayments`     | array of extra payment objects | No       | Additional payment entries        |

Success response:

| Field           | Type                     | Required | Notes                  |
|-----------------|--------------------------|----------|------------------------|
| `documentID`    | integer (int64)          | No       | Internal document id   |
| `fiscalID`      | string                   | No       | Fiscal document id     |
| `fiscalNum`     | string                   | No       | Fiscal document number |
| `fiscalUrl`     | string                   | No       | Fiscal URL             |
| `docTime`       | string                   | No       | `yyyy-MM-dd HH:mm:ss`  |
| `printTime`     | string                   | No       | `yyyy-MM-dd HH:mm:ss`  |
| `docStatus`     | integer (int32)          | No       | See value map below    |
| `rrn`           | string                   | No       | Card terminal RRN      |
| `auth`          | string                   | No       | Authorization code     |
| `cardNum`       | string                   | No       | Card number (masked)   |
| `checkNum`      | string                   | No       | Terminal check number  |
| `printError`    | integer (int32)          | No       | Print error code       |
| `totalPayments` | array of payment entries | No       | Payment breakdown      |
| `currency_name` | string                   | No       | Currency               |
| `documentExtID` | string                   | No       | External document id   |

Specific values:

- `docStatus`: `0` = open, `1` = closed.

## 7.2 `/refund`

Request body:

| Field               | Type                           | Required | Format / constraints              |
|---------------------|--------------------------------|----------|-----------------------------------|
| `documentID`        | integer (int64)                | No       | Internal document id              |
| `documentExtID`     | string                         | No       | External document id              |
| `docTime`           | string                         | No       | `yyyy-MM-dd HH:mm:ss`             |
| `docNumber`         | string                         | No       | External document number          |
| `employeeName`      | string                         | No       | Cashier name                      |
| `employeeFirstName` | string                         | No       | Used when `employeeName` is empty |
| `employeeLastName`  | string                         | No       | Used when `employeeName` is empty |
| `items`             | array of item objects          | Yes      | Must be non-empty                 |
| `payments`          | object                         | No       | Payment split object              |
| `extraPayments`     | array of extra payment objects | No       | Additional payment entries        |
| `parentDocID`       | string                         | No       | Original sale reference           |
| `parentDocNum`      | string                         | No       | Original sale number              |

Success response: same field set and value rules as `/sale`.

## 7.3 `/check_status`

Request body:

| Field           | Type            | Required      | Format / constraints                  |
|-----------------|-----------------|---------------|---------------------------------------|
| `documentID`    | integer (int64) | Conditionally | Required if `documentExtID` is absent |
| `documentExtID` | string          | Conditionally | Required if `documentID` is absent    |

Success response: same field set and value rules as `/sale`.

## 7.4 `/check_copy`

Request body:

| Field        | Type            | Required | Format / constraints                |
|--------------|-----------------|----------|-------------------------------------|
| `documentID` | integer (int64) | Yes      | Mandatory in current implementation |

Success response:

| Field           | Type                       | Required | Notes                  |
|-----------------|----------------------------|----------|------------------------|
| `documentID`    | integer (int64)            | No       | Internal document id   |
| `fiscalID`      | string                     | No       | Fiscal document id     |
| `fiscalNum`     | string                     | No       | Fiscal document number |
| `docStatus`     | integer (int32)            | No       | `0` open, `1` closed   |
| `printTime`     | string                     | No       | `yyyy-MM-dd HH:mm:ss`  |
| `items`         | array of item line objects | No       | Document lines         |
| `totalPayments` | array of payment entries   | No       | Payment breakdown      |
| `currency_name` | string                     | No       | Currency               |

## 7.5 `/abort`

Request body:

| Field           | Type            | Required | Format / constraints     |
|-----------------|-----------------|----------|--------------------------|
| `documentExtID` | string          | Yes      | Mandatory                |
| `docTime`       | string          | No       | `yyyy-MM-dd HH:mm:ss`    |
| `docNumber`     | string          | No       | External document number |
| `amount`        | integer (int64) | No       | Minor units              |

Success response: empty success payload (`status=success`, `code=0`).

## 7.6 `/open_shift`

Request body:

| Field          | Type   | Required | Format / constraints |
|----------------|--------|----------|----------------------|
| `employeeName` | string | No       | Cashier name         |

Success response:

| Field         | Type            | Required | Notes           |
|---------------|-----------------|----------|-----------------|
| `shiftID`     | integer (int32) | No       | Shift id        |
| `fiscalID`    | string          | No       | Fiscal shift id |
| `shiftOpenAt` | string          | No       | Shift open time |

## 7.7 `/close_shift`

Request body:

| Field                 | Type   | Required | Format / constraints |
|-----------------------|--------|----------|----------------------|
| `employeeName`        | string | No       | Cashier name         |
| `openOrdersOperation` | string | No       | See value map below  |

Specific values:

- `openOrdersOperation = delete`: delete unclosed orders and continue closing shift.
- `openOrdersOperation = renew`: transfer unclosed orders and continue closing shift.
- `openOrdersOperation = check` or missing: do not force action, return error if unclosed orders exist.
- Any other value is treated the same as `check`.

Success response:

| Field                  | Type                 | Required | Notes               |
|------------------------|----------------------|----------|---------------------|
| `shiftID`              | integer (int32)      | No       | Shift id            |
| `fiscalShiftID`        | string               | No       | Fiscal shift id     |
| `fiscalShiftNum`       | string               | No       | Fiscal shift number |
| `shiftOpenAt`          | string               | No       | Shift open time     |
| `cash`                 | integer (int64)      | No       | Cash balance        |
| `saleCount`            | integer (int32)      | No       | Sale count          |
| `saleSum`              | integer (int64)      | No       | Sale sum            |
| `saleCashSum`          | integer (int64)      | No       | Cash sale sum       |
| `saleCashlessSum`      | integer (int64)      | No       | Cashless sale sum   |
| `saleCreditSum`        | integer (int64)      | No       | Credit sale sum     |
| `saleBonusSum`         | integer (int64)      | No       | Bonus sale sum      |
| `saleVatAmounts`       | array of VAT entries | No       | Sale VAT totals     |
| `depositCount`         | integer (int32)      | No       | Deposit count       |
| `depositSum`           | integer (int64)      | No       | Deposit sum         |
| `withdrawCount`        | integer (int32)      | No       | Withdraw count      |
| `withdrawSum`          | integer (int64)      | No       | Withdraw sum        |
| `moneyBackCount`       | integer (int32)      | No       | Refund count        |
| `moneyBackSum`         | integer (int64)      | No       | Refund sum          |
| `moneyBackCashSum`     | integer (int64)      | No       | Cash refund sum     |
| `moneyBackCashlessSum` | integer (int64)      | No       | Cashless refund sum |
| `moneyBackCreditSum`   | integer (int64)      | No       | Credit refund sum   |
| `moneyBackBonusSum`    | integer (int64)      | No       | Bonus refund sum    |
| `moneyBackVatAmounts`  | array of VAT entries | No       | Refund VAT totals   |
| `currency_name`        | string               | No       | Currency            |

## 7.8 `/check_shift`

Request body: empty JSON object `{}` is acceptable.

Success response:

| Field         | Type            | Required | Notes                |
|---------------|-----------------|----------|----------------------|
| `shiftID`     | integer (int32) | No       | Shift id             |
| `isShiftOpen` | string          | No       | See value map below  |
| `shiftStatus` | integer (int32) | No       | See value map below  |
| `shiftOpenAt` | string          | No       | Shift open time      |
| `cash`        | integer (int64) | No       | Current cash balance |

Specific values:

- `isShiftOpen = "true"`: shift is open.
- `isShiftOpen = "false"`: shift is closed.
- `shiftStatus = 1`: shift is open.
- `shiftStatus = 2`: shift is closed.

## 7.9 `/x_report`

Request body:

| Field              | Type    | Required | Format / constraints   |
|--------------------|---------|----------|------------------------|
| `employeeName`     | string  | No       | Cashier name           |
| `skipReceipt`      | boolean | No       | Print control          |
| `skipReceiptPrint` | boolean | No       | Alias of `skipReceipt` |

Specific values:

- `skipReceipt = true`: skip printing/report output step.
- `skipReceipt = false` or missing: execute normal print flow.

Success response: same metric fields as `/close_shift`, except `fiscalShiftID` and `fiscalShiftNum` are not returned.

## 7.10 `/deposit` and `/withdraw`

Request body:

| Field           | Type            | Required | Format / constraints |
|-----------------|-----------------|----------|----------------------|
| `documentID`    | integer (int64) | No       | Internal document id |
| `documentExtID` | string          | No       | External document id |
| `amount`        | integer (int64) | Yes      | Must be `> 0`        |
| `employeeName`  | string          | No       | Cashier name         |

Success response:

| Field        | Type            | Required | Notes                      |
|--------------|-----------------|----------|----------------------------|
| `documentID` | integer (int64) | No       | Cash operation document id |
| `fiscalID`   | string          | No       | Fiscal id                  |
| `fiscalNum`  | string          | No       | Fiscal number              |
| `fiscalUrl`  | string          | No       | Fiscal URL                 |

Specific recovery behavior:

- For retry/resume, server matches an existing cash document by operation type (`deposit`/`withdraw`) and cash amount.
- `documentExtID` (or `documentID`) is used as retry key to locate/reuse the same document safely.
- If neither `documentExtID` nor `documentID` is provided, deterministic recovery is not guaranteed.

## 7.11 `/dates_report`

Request body:

| Field          | Type   | Required | Format / constraints |
|----------------|--------|----------|----------------------|
| `employeeName` | string | No       | Cashier name         |
| `dateFrom`     | string | No       | `yyyy-MM-dd`         |
| `dateFor`      | string | No       | `yyyy-MM-dd`         |

Specific values:

- Invalid date format may return `code=14`.

Success response: provider-specific reporting payload.

## 7.12 `/get_info`

Request body: empty JSON object `{}` is acceptable.

Success response:

| Field        | Type           | Required | Notes                     |
|--------------|----------------|----------|---------------------------|
| `fiscalData` | raw JSON value | No       | Provider-specific payload |

## 7.13 Shared nested objects

Item object:

| Field        | Type                 | Required | Notes                           |
|--------------|----------------------|----------|---------------------------------|
| `itemId`     | string               | No       | Item id                         |
| `itemName`   | string               | No       | Item name                       |
| `itemUnit`   | string               | No       | Unit name                       |
| `itemQty`    | integer (int32)      | No       | Quantity * 1000, default `1000` |
| `itemAmount` | integer (int64)      | No       | Line amount                     |
| `itemTaxes`  | array of tax objects | No       | Tax lines                       |

Tax object:

| Field     | Type            | Required | Notes                          |
|-----------|-----------------|----------|--------------------------------|
| `taxCode` | string          | No       | Symbolic tax code              |
| `taxPrc`  | integer (int32) | No       | Percent * 100 (`1800` = `18%`) |

Payment split object:

| Field              | Type            | Required | Notes       |
|--------------------|-----------------|----------|-------------|
| `cashAmount`       | integer (int64) | No       | Default `0` |
| `cashlessAmount`   | integer (int64) | No       | Default `0` |
| `creditAmount`     | integer (int64) | No       | Default `0` |
| `bonusesAmount`    | integer (int64) | No       | Default `0` |
| `prepaymentAmount` | integer (int64) | No       | Default `0` |

Extra payment object:

| Field       | Type            | Required | Notes                |
|-------------|-----------------|----------|----------------------|
| `code`      | string          | No       | Payment code         |
| `amount`    | integer (int64) | No       | Default `0`          |
| `trxParams` | object          | No       | Transaction metadata |

Extra payment transaction metadata object:

| Field        | Type   | Required | Notes                     |
|--------------|--------|----------|---------------------------|
| `rrn`        | string | No       | Terminal reference number |
| `cardNumber` | string | No       | Card number (masked)      |
| `bankName`   | string | No       | Bank name                 |

VAT entry object:

| Field           | Type            | Required | Notes                  |
|-----------------|-----------------|----------|------------------------|
| `vatPercent`    | integer (int32) | Yes      | VAT rate percent * 100 |
| `vatAmount`     | integer (int64) | Yes      | VAT amount             |
| `currency_name` | string          | No       | Currency               |

## 8. Error Codes

| Code | Name                          | Meaning                            |
|------|-------------------------------|------------------------------------|
| `0`  | `OK`                          | Success                            |
| `1`  | `AUTH_ERROR`                  | Signature/auth error               |
| `2`  | `INVALID_JSON`                | JSON/Base64 parse error            |
| `3`  | `MISSING_PARAMS`              | Required fields missing            |
| `4`  | `AMOUNT_MISMATCH`             | Amount mismatch                    |
| `5`  | `INTERNAL_ERROR`              | Internal/not implemented           |
| `6`  | `SHIFT_NOT_OPEN`              | Shift is not open                  |
| `7`  | `PAYMENT_TYPE_NOT_SUPPORTED`  | Unsupported payment type           |
| `8`  | `DOCUMENT_NOT_PAID`           | Document not fully paid            |
| `9`  | `DOCUMENT_NOT_FOUND`          | Document not found                 |
| `10` | `DOCUMENT_NOT_FISCALIZED`     | Document not fiscalized            |
| `11` | `CHANGE_ONLY_FOR_CASH`        | Change allowed only for cash       |
| `12` | `DOC_TIME_BEFORE_SHIFT`       | Document time before shift open    |
| `13` | `CREDIT_SINGLE_PAYMENT`       | Credit payment constraint violated |
| `14` | `INVALID_DATE_FORMAT`         | Invalid date format                |
| `15` | `DOCUMENT_NOT_PRINTED`        | Document not printed               |
| `23` | `BANK_TERMINAL_NOT_CONNECTED` | Bank terminal not connected        |
| `44` | `DOCUMENT_STILL_PROCESSING`   | Document processing in progress    |

## 9. HTTP Examples (Full Payloads)

Each example below uses all fields documented for that route.

Note: examples for first-send operations intentionally omit `documentID`, because it is assigned by server.

## 9.1 `/sale` full request payload

```json
{
    "documentExtID": "ORDER-1001",
    "docTime": "2026-06-10 14:30:00",
    "docNumber": "POS-001-1001",
    "wsName": "POS-01",
    "departmentName": "Main Store",
    "departmentCode": "MS01",
    "employeeName": "John Doe",
    "employeeFirstName": "John",
    "employeeLastName": "Doe",
    "items": [
        {
            "itemId": "SKU-001",
            "itemName": "Coffee",
            "itemUnit": "pcs",
            "itemQty": 1000,
            "itemAmount": 12500,
            "itemTaxes": [
                {
                    "taxCode": "A",
                    "taxPrc": 1200
                }
            ]
        }
    ],
    "payments": {
        "cashAmount": 0,
        "cashlessAmount": 12000,
        "creditAmount": 0,
        "bonusesAmount": 0,
        "prepaymentAmount": 0
    },
    "extraPayments": [
        {
            "code": "M",
            "amount": 500,
            "trxParams": {
                "rrn": "123456789012",
                "cardNumber": "8600********1234",
                "bankName": "DemoBank"
            }
        }
    ]
}
```

## 9.2 `/refund` full request payload

```json
{
    "documentExtID": "REFUND-1001",
    "docTime": "2026-06-10 15:00:00",
    "docNumber": "POS-001-R1001",
    "employeeName": "John Doe",
    "employeeFirstName": "John",
    "employeeLastName": "Doe",
    "items": [
        {
            "itemId": "SKU-001",
            "itemName": "Coffee",
            "itemUnit": "pcs",
            "itemQty": 1000,
            "itemAmount": 12500,
            "itemTaxes": [
                {
                    "taxCode": "A",
                    "taxPrc": 1200
                }
            ]
        }
    ],
    "payments": {
        "cashAmount": 0,
        "cashlessAmount": 12000,
        "creditAmount": 0,
        "bonusesAmount": 0,
        "prepaymentAmount": 0
    },
    "extraPayments": [
        {
            "code": "M",
            "amount": 500,
            "trxParams": {
                "rrn": "123456789012",
                "cardNumber": "8600********1234",
                "bankName": "DemoBank"
            }
        }
    ],
    "parentDocID": "FISCAL-000001",
    "parentDocNum": "000001"
}
```

## 9.3 `/check_status` full request payload

```json
{
    "documentExtID": "ORDER-1001"
}
```

## 9.4 `/check_copy` full request payload

```json
{
    "documentID": 1001
}
```

## 9.5 `/abort` full request payload

```json
{
    "documentExtID": "ORDER-1001",
    "docTime": "2026-06-10 14:30:00",
    "docNumber": "POS-001-1001",
    "amount": 12500
}
```

## 9.6 `/open_shift` full request payload

```json
{
    "employeeName": "John Doe"
}
```

## 9.7 `/close_shift` full request payload

```json
{
    "employeeName": "John Doe",
    "openOrdersOperation": "check"
}
```

## 9.8 `/x_report` full request payload

```json
{
    "employeeName": "John Doe",
    "skipReceipt": false,
    "skipReceiptPrint": false
}
```

## 9.9 `/deposit` or `/withdraw` full request payload

```json
{
    "documentExtID": "CASH-2001",
    "amount": 500000,
    "employeeName": "John Doe"
}
```

## 9.10 `/dates_report` full request payload

```json
{
    "employeeName": "John Doe",
    "dateFrom": "2026-06-01",
    "dateFor": "2026-06-10"
}
```

## 9.11 `/check_shift` and `/get_info` request payload

```json
{}
```

## 9.12 Send any request

```bash
curl -X POST "http://127.0.0.1:8008/<route>" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "data=<BASE64_JSON>" \
  --data-urlencode "sign=<SIGN>"
```

## 10. Serial Example

Serial payload text:

```text
command=/check_status&data=eyJkb2N1bWVudEV4dElEIjoiT1JERVItMTAwMSJ9&sign=<SIGN>
```

Then frame it as:

```text
STX + payload_utf8 + CRC32_BE + ETX
```

## 11. Calculation Examples (`sign`, `CRC32`)

## 11.1 `sign` calculation example

Input values:

- `data` = `eyJkb2N1bWVudEV4dElEIjoiT1JERVItMTAwMSJ9`
- `merchantId` = `9662a13f5b4f46dbb1751bbbf86ed402`

Step 1: concatenate strings

```text
data + merchantId
eyJkb2N1bWVudEV4dElEIjoiT1JERVItMTAwMSJ99662a13f5b4f46dbb1751bbbf86ed402
```

Step 2: SHA-1 in lowercase hex

```text
eb22ef5cacd7eebf2f376f6ead31b8dc949e3c82
```

Step 3: Base64 over UTF-8 bytes of the hex string

```text
ZWIyMmVmNWNhY2Q3ZWViZjJmMzc2ZjZlYWQzMWI4ZGM5NDllM2M4Mg==
```

Final `sign` value:

```text
ZWIyMmVmNWNhY2Q3ZWViZjJmMzc2ZjZlYWQzMWI4ZGM5NDllM2M4Mg==
```

## 11.2 `CRC32` calculation example for serial frame

Payload string (before framing):

```text
command=/check_status&data=eyJkb2N1bWVudEV4dElEIjoiT1JERVItMTAwMSJ9&sign=ZWIyMmVmNWNhY2Q3ZWViZjJmMzc2ZjZlYWQzMWI4ZGM5NDllM2M4Mg==
```

CRC input rules:

- Encode payload as UTF-8 bytes.
- Compute `CRC32` over payload bytes.
- Replace CRC bytes `0x03` with `0x20` before writing CRC to frame.

Computed CRC:

- `crc32_hex` = `61387ab8`
- `CRC32_BE` bytes = `61 38 7a b8`

Final frame layout:

```text
02 <payload_utf8_bytes> 61 38 7a b8 03
```

Where:

- `02` is `STX`
- `03` is `ETX`

## 12. Recovery Flow (Error Handling and State Recovery)

This section defines how to recover safely when request/response delivery is unreliable or API returns an error.

## 12.1 Core principle

For financial operations (`/sale`, `/refund`), treat transport failures as "state unknown" until proven otherwise.

- Do not assume failure on timeout.
- Use the same `documentExtID` for retries of the same business operation.
- Always send and persist your own unique `documentExtID` per operation.

Practical default for `/sale` and `/refund`: resend the same request with the same `documentExtID`.
The server-side idempotency logic will either return an existing result or continue processing the same operation.

## 12.2 Transport-level failures

HTTP examples:

- TCP disconnect before response
- HTTP timeout on client side

Serial examples:

- No response frame before timeout
- CRC mismatch in received frame
- Broken/incomplete frame

Recovery steps for unknown delivery state (timeout/disconnect/CRC failure):

1. Mark operation as `UNKNOWN` in your integrator state.
2. Wait short backoff (`1-2 s`).
3. Resend the same operation with the same `documentExtID`.
4. If response is final (`docStatus = 1`), stop.
5. If response indicates still processing (`code = 44` or `docStatus = 0`), switch to `/check_status` polling.

When to prefer `/check_status` first instead of resend:

- If your client cannot safely reconstruct the original payload byte-for-byte.
- If your policy requires a read-only recovery step before any write retry.
- For non-receipt operations where idempotency is not guaranteed by `documentExtID`.

## 12.3 API error recovery by code

| Code | Meaning                         | Retry strategy                                                                                                         |
|------|---------------------------------|------------------------------------------------------------------------------------------------------------------------|
| `1`  | Signature/auth error            | Do not retry same request. Rebuild `sign`, verify `merchantId`, resend once.                                           |
| `2`  | Invalid JSON/Base64             | Do not retry same payload. Fix serializer/envelope first.                                                              |
| `3`  | Missing/invalid required params | Do not retry until payload is corrected.                                                                               |
| `5`  | Internal error                  | For `/sale` and `/refund`: resend same operation with same `documentExtID`; for others: route-specific retry policy.   |
| `9`  | Document not found              | In unknown-delivery recovery: resend same operation with same `documentExtID`; in normal flow: verify ids and payload. |
| `14` | Invalid date format             | Correct date format (`yyyy-MM-dd`) and resend.                                                                         |
| `44` | Document still processing       | Do not resend operation; poll `/check_status` until final state.                                                       |
| `15` | Document not printed            | Business operation may be completed; run print-error recovery flow below.                                              |

## 12.4 Safe retry rules

- `/sale`, `/refund`:
    - Never generate a new `documentExtID` during retry of the same business operation.
    - Preferred recovery from unknown delivery: resend same request with same `documentExtID`.
    - If still in progress (`code=44` or `docStatus=0`), poll `/check_status`.
- `/deposit`, `/withdraw`:
    - If timeout happens after send, use `/check_status` first before attempting another cash operation.
    - If `/check_status` returns `code = 9` after unknown delivery, resend same request with same `documentExtID`.
    - Note: for some execution failures before finalization, the server may roll back and delete the open cash document; in that case
      `code = 9` is expected and resend is the correct recovery action.
    - Note: for print failures (`code = 15`), document is typically kept for print recovery, so `/check_status` and `/check_copy` should be
      used.
- `/close_shift`:
    - If failed due to unclosed checks, use explicit `openOrdersOperation` (`delete` or `renew`) per business policy and resend.

## 12.5 Print error recovery flow (`code = 15`)

`code = 15` means print stage failed (`printError` may contain printer-specific reason), but business operation can already be committed.

Recommended flow:

1. Receive error response with `code = 15`.
2. Save `documentExtID` and full response payload to incident log.
3. Call `/check_status` for the same document.
4. If `docStatus = 1`, treat payment/fiscal operation as completed and recover only printing:
    - call `/check_copy` (requires `documentID`) after printer issue is resolved.
5. If `docStatus = 0` or `code = 44`, continue polling `/check_status`.
6. If document is still not found in an unknown-delivery scenario, resend same operation with same `documentExtID`.

## 12.6 Suggested polling profile for `/check_status`

- Initial delay: `1 s`
- Poll interval: `1-2 s`
- Maximum attempts: deployment-specific (recommended bounded retry window, for example 2-5 minutes)

Stop polling when:

- Success response with `docStatus = 1`, or
- A terminal non-retriable error is returned by policy.

## 12.7 Recovery decision table

| Situation                                                                  | Recommended action                                                                                             |
|----------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
| `/sale` or `/refund` timeout / disconnect / serial CRC failure             | Resend the same request with the same `documentExtID`.                                                         |
| `/sale` or `/refund` returns `code = 44` or `docStatus = 0`                | Poll `/check_status` until final state.                                                                        |
| `/sale` or `/refund` returns `code = 9` in unknown-delivery scenario       | Resend same request with same `documentExtID`.                                                                 |
| `/sale` or `/refund` returns `code = 9` in normal confirmed flow           | Treat as terminal input issue, verify ids/payload.                                                             |
| `/deposit` or `/withdraw` timeout with known `documentExtID`               | Call `/check_status` first.                                                                                    |
| `/deposit` or `/withdraw` timeout, then `/check_status` returns `code = 9` | Resend same request with same `documentExtID` (document may have been rolled back/deleted).                    |
| `/deposit` or `/withdraw` retry correlation                                | Keep same amount and same operation type; use same `documentExtID` (or `documentID`) for deterministic resume. |
| Any route returns `code = 15` (`DOCUMENT_NOT_PRINTED`)                     | Do not assume business failure; run print-error flow (`/check_status`, then `/check_copy` when applicable).    |
| Client cannot safely reproduce original payload                            | Prefer `/check_status` first, then decide on resend.                                                           |

## 13. Integration Checklist

1. Implement Base64 envelope (`data`) for every request.
2. Implement signature formula and send `sign` on every request.
3. For financial operations, always send unique `documentExtID` from your system.
4. Support both HTTP and Serial transport with identical business payload.
5. Handle all API error codes, especially `1`, `2`, `3`, `5`, `9`, `44`.
6. Log raw route, request correlation id, and final API `code` for supportability.












