# Company fiscal API and rollout
The non-Filament workspace is `/company-admin/fiscal`, linked from company
settings, invoice details, payment details and the in-person checkout link.
Enable the company device in Settings first, then save separate Windows and
Android connection profiles. A profile identifies the ZFPLab server, not the
physical printer transport: Windows supports serial/TCP; Android Bluetooth/TCP.
## Mobile contract
Base URL: `/api/company-admin/fiscal`. Send `Authorization: Bearer <Sanctum token>`
and `Accept: application/json`. Only the authenticated user's company is used;
company managers and administrators are authorized, ordinary members are not.
There is no client-supplied company ID or unauthenticated printer proxy.
| Method | Path | Purpose |
| --- | --- | --- |
| GET | `/settings` | Enabled flag and both profiles; passwords redacted |
| PUT | `/settings` | Save one profile, selected by `platform`; blank passwords preserve secrets |
| POST | `/test` | `{ "platform": "android" }`: returns local test configuration and read-only command names, **not** a successful connectivity claim |
| POST | `/payments` | Record received cash or an already-approved card payment; does not charge a terminal |
| GET | `/receipts?page=1` | Paginated company fiscal obligations with linked payment/invoice IDs |
| POST | `/receipts/{id}/claim` | `{ "platform": "android" }`: claim once, receive configuration, claim token and server-calculated receipt payload |
| POST | `/receipts/{id}/complete` | Confirm original receipt evidence; identical confirmation can be retried without printing |
| POST | `/receipts/{id}/uncertain` | Preserve an ambiguous result; requires `claim_token`, `error_message` |
| POST | `/receipts/{id}/reconcile` | Explicit manager reset ONLY after stopping the original client and proving no receipt was issued; requires `journal_checked: true` and a detailed `reason`; writes an audit record |
Payment request example:
```json
{
"idempotency_key": "7373122d-58cf-4591-9c1b-87d01a414aa1",
"booking_id": 123,
"invoice_id": 456,
"amount": "25.00",
"payment_method": "cash"
}
```
Supply a booking ID, an invoice ID, or both (they must agree). Standalone invoice
payments are supported. Card requires `terminal_reference` from an approved
terminal transaction. Retain the same idempotency key across network retries;
409 means it was reused inconsistently. Booking/invoice ownership and remaining
balances are checked under database locks. Receipt issuance is separate from
money collection: a completed payment with a pending job is **not fiscalized**.
Before sending anything to hardware, durably store the claim token and job ID.
Execute the returned receipt payload against the selected local ZFPLab server.
Read actual device serial/FM numbers, close the receipt, then read its number
and QR data. Persist that evidence locally before calling `/complete`:
```json
{
"claim_token": "<claim UUID>",
"receipt_number": "000123",
"device_serial": "AB123456",
"fiscal_memory_number": "12345678",
"qr_data": "<original ReadLastReceiptQRcodeData result>"
}
```
Do not retry a sale after timeout, cancellation or app termination. Inspect the
device journal and submit the original confirmation. An issuing/uncertain job
blocks other jobs for that device. Client reports are manager-attested evidence,
not cryptographically verified device responses. Protect tokens/configuration
in mobile secure storage and never log operator/device passwords.
## Tests and deployment
Run the new migration before deploying the observers/API. It adds `invoice_id`
to payments and a unique-per-payment fiscal queue. No historical sales are
automatically printed or backfilled. New completed positive payments created or
transitioned to completed through either Eloquent payment model get an obligation while a fiscal
device is active, including online payments. Bulk SQL bypasses model events and
must not be used to finalize payments. Refund/storno is a separate workflow.
Run `php vendor/phpunit/phpunit/phpunit --filter 'TremolConfigTest|CompanyFiscalApiTest'`
and `node --test tests/tremol-client.test.cjs`. Database tests use a fresh SQLite
in-memory database, not the developer database.
ZFPLab tests execute on the phone/PC: `localhost` on the Laravel host is not the
cashier's server. Verify both connection and protocol result codes; HTTP 200 is
insufficient. HTTPS/mixed-content, local-network permission, certificate trust,
Android network policy and Bluetooth pairing must be validated on actual target
devices. The public documentation router is not a production or test printer.
Hardware acceptance remains required: cash, approved card, partial invoice
payments, paper-out, disconnect after payment, app restart, duplicate callbacks,
online callback while the cashier device is offline, and journal reconciliation.
The bundled definitions must match the installed server/device firmware.
## Current boundaries
The receipt payload uses the company's configured VAT class for the payment
amount. Confirm this with the fiscal installer/accountant; mixed-VAT itemization
and country-specific exemptions need explicit mapping before use. Unmapped
non-cash methods remain pending rather than being silently classified as cash.
No fabricated URN is sent; devices/installations requiring URN need an approved
sequence implementation. Printed delivery is supported by this workflow;
electronic receipt stream retrieval is not yet implemented. A QR string or
application PDF must not be represented as the original electronic receipt.
References: [Tremol ZFPLab BG documentation](https://tremol.bg/bg/support/zfplab/bg/2507141400),
[interactive protocol documentation](http://router.tremol.bg:11444/), and bundled
`public/third-party/tremolFiscalDeviceControl/fp_core.js` / `fp.js`.