Company fiscal API and rollout

📄 General
← Back to Documentation
# 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`.