API docs — Super Admin only

API dokumentacija — samo Super Admin

Checking access…

Provjera pristupa…

DigitalChip API

DigitalChip Platform API Reference

API referenca za DigitalChip platformu

Complete Cloud Functions API — IoT gateway, mobile client, B2B, tenant and admin callables.

Kompletan Cloud Functions API — IoT gateway, mobilni klijent, B2B, tenant i admin callable funkcije.

Region: us-central1 · Project: yosh-digitalchip Region: us-central1 · Projekat: yosh-digitalchip

Overview

Pregled

Backend is Firebase Cloud Functions (v2). There are two transport styles:

Backend su Firebase Cloud Functions (v2). Postoje dva tipa transporta:

Base URL pattern for HTTP functions:

Osnovni URL obrazac za HTTP funkcije:

https://us-central1-yosh-digitalchip.cloudfunctions.net/<functionName>

API Catalog

Katalog API-ja

Function Type Who Ko Purpose Svrha
gatewayPing POST IoT controller IoT kontroler deprecated for Digi controllers. Old gateway controllers may still heartbeat and pull commands here. deprecated za Digi kontrolere. Stari gateway kontroleri i dalje mogu ovdje slati heartbeat i vući komande.
gatewayStream GET/POST SSE IoT controller IoT kontroler deprecated Old command push. Digi controllers do not use it. deprecated Stari push komandi. Digi kontroleri ga ne koriste.
MQTT + mqttAuth MQTT / POST IoT + EMQX IoT + EMQX deprecated Old EMQX command topic. Not for mobile apps. Digi pay stays inside activateService. deprecated Stari EMQX topic komandi. Nije za mobilne aplikacije. Digi uplata ostaje unutar activateService.
activateService onCall USER / guest USER / gost Spend tokens and start the machine. Digi devices are confirmed inside this call. Potroši tokene i pokreni mašinu. Digi uređaji se potvrđuju unutar ovog poziva.
getUserProfile onCall Any signed-in user Bilo koji ulogovani Read own profile Čitanje vlastitog profila
updateUserProfile onCall Any signed-in user Bilo koji ulogovani Update name & phone Ažuriranje imena i telefona
changeUserPassword onCall Email/password users Email/lozinka nalozi Change password securely Sigurna promjena lozinke
onboardEmployee onCall B2B_ADMIN / SUPER_ADMIN B2B_ADMIN / SUPER_ADMIN Create/link employee account Kreiranje/povezivanje zaposlenog
dispenseTokens onCall B2B_ADMIN / SUPER_ADMIN B2B_ADMIN / SUPER_ADMIN Company wallet → employee wallet Prenos kompanija → zaposleni
getCompanySpendingReport onCall B2B_ADMIN / SUPER_ADMIN B2B_ADMIN / SUPER_ADMIN Corporate DEBIT spend report Izvještaj potrošnje kompanije
listVehicles / createVehicle / updateVehicle / deactivateVehicle onCall Owner / B2B_ADMIN / SUPER Vlasnik / B2B_ADMIN / SUPER Personal or company fleet CRUD Lična / kompanijska flota
getVehicleWashStats onCall B2B_ADMIN / SUPER_ADMIN B2B_ADMIN / SUPER_ADMIN Per-vehicle wash / token stats Statistika pranja po vozilu
listWashHistory onCall Signed-in user Ulogovani korisnik Wash history (+ optional vehicle filter) Istorija pranja (+ filter vozila)
createTenantWithAdmin onCall SUPER_ADMIN SUPER_ADMIN Create tenant + TENANT_ADMIN login Kreiranje tenanta + TENANT_ADMIN
rotateDeviceApiKey onCall SUPER_ADMIN / TENANT_ADMIN SUPER_ADMIN / TENANT_ADMIN Rotate parent device API key Rotacija API ključa uređaja
getEarningsReport onCall SUPER_ADMIN / TENANT_ADMIN SUPER_ADMIN / TENANT_ADMIN Earnings (gross / per-tenant fee / net) Zarada (bruto / fee po tenantu / neto)
creditOwnWallet onCall Any signed-in user Bilo koji ulogovani Credit / replace own wallet balance Dopuna / zamjena vlastitog stanja
updateSpendingRules onCall B2B_ADMIN / SUPER_ADMIN B2B_ADMIN / SUPER_ADMIN Update company spending rules Ažuriranje pravila potrošnje
createB2bCompany onCall SUPER_ADMIN SUPER_ADMIN Create B2B company + wallet Kreiranje B2B kompanije + novčanik
deleteB2bCompany onCall SUPER_ADMIN SUPER_ADMIN Delete company & unlink employees Brisanje kompanije i odvajanje zaposlenih
assignCompanyAdmin onCall SUPER_ADMIN SUPER_ADMIN Promote user to B2B_ADMIN Postavljanje B2B_ADMIN
removeCompanyEmployee onCall B2B_ADMIN / SUPER_ADMIN B2B_ADMIN / SUPER_ADMIN Unlink employee from company Uklanjanje zaposlenog iz kompanije
registerDevice onCall SUPER_ADMIN / TENANT_ADMIN SUPER_ADMIN / TENANT_ADMIN Register parent device (+ apiKey) Registracija uređaja (+ apiKey)
registerSubDevice onCall SUPER_ADMIN / TENANT_ADMIN SUPER_ADMIN / TENANT_ADMIN Register bay / sub-device Registracija bay / poduređaja
deleteDevice / deleteSubDevice onCall SUPER_ADMIN / TENANT_ADMIN SUPER_ADMIN / TENANT_ADMIN Delete device or sub-device Brisanje uređaja ili poduređaja
linkDeviceToTenant onCall SUPER_ADMIN SUPER_ADMIN Move device to another tenant Premještanje uređaja na drugi tenant
adminUpdateUser onCall SUPER_ADMIN SUPER_ADMIN Update role / company / tenant link Uloga / kompanija / tenant veza
creditUserWallet onCall SUPER_ADMIN SUPER_ADMIN Credit any user wallet Dopuna bilo kojeg novčanika
deleteUserAccount onCall SUPER_ADMIN SUPER_ADMIN Delete Auth user + profile + wallet Brisanje Auth + profil + novčanik
deleteTenantCascade onCall SUPER_ADMIN SUPER_ADMIN Cascade delete tenant + devices Kaskadno brisanje tenanta + uređaja

IoT Device Gateway Ping deprecated

Pingovanje IoT uređaja (Gateway) deprecated

Deprecated for Digi controllers

Zastarjelo za Digi kontrolere

deprecated Digi controllers do not use gatewayPing for commands, bay status, or Live Tracking. Readiness is checked inside createQrSession and again inside activateService. Online in Live Tracking follows the last Digi status (fresh if younger than about 3 minutes). This endpoint remains only for controllers that still use the old gateway channel.

deprecated Digi kontroleri ne koriste gatewayPing za komande, status automata ni Live Tracking. Spremnost se provjerava unutar createQrSession i ponovo unutar activateService. Online u Live Trackingu prati zadnji Digi status (svjež ako je mlađi od oko 3 minute). Endpoint ostaje samo za kontrolere na starom gateway kanalu.

The physical controller on-site reports its telemetry. Controllers that are not on the Digi channel can still fetch pending relay commands here.

Fizički kontroler na lokaciji prijavljuje telemetriju. Kontroleri koji nisu na Digi kanalu i dalje mogu ovdje preuzeti komande za releje.

POST https://us-central1-yosh-digitalchip.cloudfunctions.net/gatewayPing

Authentication required

Obavezna autentifikacija

Every ping must include the parent device API key in the HTTP header. The key is generated automatically when the controller is registered and is unique per parent device (not per sub-device).

Svaki ping mora sadržati API ključ parent uređaja u HTTP headeru. Ključ se automatski generiše pri registraciji kontrolera i jedinstven je po parent uređaju (ne po poduređaju).

Required Headers

Obavezni headeri

Header Type Requirement Obaveznost Description Opis
Content-Type String RequiredObavezno application/json
X-Device-Api-Key String RequiredObavezno Secret API key stored on the parent device document (devices/{deviceId}.apiKey). Alias header X-Api-Key is also accepted. Tajni API ključ sa parent device dokumenta (devices/{deviceId}.apiKey). Prihvata se i alias header X-Api-Key.
cURL Example cURL primjer
curl -X POST https://us-central1-yosh-digitalchip.cloudfunctions.net/gatewayPing \
  -H "Content-Type: application/json" \
  -H "X-Device-Api-Key: dc_dev_wash_01_api_key_demo_2026" \
  -d '{
    "deviceId": "dev_wash_01",
    "status": "ONLINE",
    "subDevices": [
      { "id": "bay_1", "status": "ACTIVE" },
      { "id": "bay_2", "status": "INACTIVE" }
    ]
  }'

Request Payload

Tijelo zahtjeva (Request Payload)

Field Type Requirement Obaveznost Description Opis
deviceId String RequiredObavezno Unique hardware UUID or MAC address. Jedinstveni UUID ili MAC adresa kontrolera.
status String RequiredObavezno "ONLINE" | "OFFLINE"
subDevices Array RequiredObavezno Current state list of connected sub-modules (e.g. wash bays, washing machines). Trenutno stanje povezanih jedinica (mjesta za pranje, releja).
subDevices[].id String RequiredObavezno Sub-device identifier (e.g., "bay_1"). ID poduređaja (npr. "bay_1").
subDevices[].status String RequiredObavezno "ACTIVE" | "INACTIVE" | "ERROR"
Request Body Example Primjer tijela zahtjeva
{
  "deviceId": "dev_wash_01",
  "status": "ONLINE",
  "subDevices": [
    { "id": "bay_1", "status": "ACTIVE" },
    { "id": "bay_2", "status": "INACTIVE" }
  ]
}

Gateway Response

Odgovor mrežnog prolaza

Field Type Description Opis
timestamp String Server ISO timestamp. Serverska ISO vremenska oznaka.
commands Array List of active activation commands (maximum 5). Status automatically changes to SENT on delivery. Lista aktivnih zapovijedi za aktivaciju (najviše 5). Status automatski prelazi u SENT.
commands[].id String Command ID token. ID oznaka zapovijedi.
commands[].action String "START_SERVICE" or "STOP_SERVICE"
commands[].subDeviceId String Specific sub-device port/bay. Port/relej poduređaja koji treba pokrenuti.
commands[].value Number Token amount / service units (calculated pulses) to apply on the device. Količina tokena / jedinica usluge (izraženo u impulsima) za uređaj.
Response Body Example Primjer odgovora
{
  "timestamp": "2026-07-14T10:52:45.312Z",
  "commands": [
    {
      "id": "cmd_8xL92kPz1",
      "action": "START_SERVICE",
      "subDeviceId": "bay_1",
      "value": 50
    }
  ]
}
Response Body Example (No Pending Commands) Primjer odgovora (Kada nema zapovijedi na čekanju)
{
  "timestamp": "2026-07-14T10:52:48.125Z",
  "commands": []
}

IoT Command Stream (gatewayStream) deprecated

IoT stream komandi (gatewayStream) deprecated

Deprecated

Zastarjelo

Do not use gatewayStream for new washing-machine firmware or for mobile apps. Digi payments are confirmed inside activateService. This stream remains only for controllers that still consume the old PENDING command queue.

Ne koristite gatewayStream za novi firmware praonica ni za mobilne aplikacije. Digi uplate se potvrđuju unutar activateService. Stream ostaje samo za kontrolere koji još troše stari PENDING red komandi.

Outbound Server-Sent Events (SSE) stream. The controller opens a long-lived connection to the cloud (works behind NAT / without a static IP). When a wash is activated, pending commands are pushed immediately using the same JSON shape as gatewayPing. Keep gatewayPing for telemetry / online status (and as command fallback).

Outbound Server-Sent Events (SSE) stream. Kontroler otvara dugotrajnu konekciju ka cloudu (radi iza NAT-a / bez statične IP). Kad se aktivira pranje, PENDING komande se šalju odmah u istom JSON formatu kao gatewayPing. Zadržite gatewayPing za telemetriju / online status (i kao fallback za komande).

GET https://us-central1-yosh-digitalchip.cloudfunctions.net/gatewayStream?deviceId=dev_wash_01

Also accepts POST with JSON body {"deviceId":"..."}. Response Content-Type: text/event-stream. Prihvata i POST sa JSON tijelom {"deviceId":"..."}. Odgovor Content-Type: text/event-stream.

Why not a device webhook URL?

Zašto ne webhook URL na uređaju?

Most controllers have no public/static IP. The cloud cannot reliably POST to the device. The device must open an outbound connection instead.

Većina kontrolera nema javnu/statičnu IP. Cloud ne može pouzdano POST-ovati na uređaj. Uređaj mora otvoriti outbound konekciju.

Required Headers

Obavezni headeri

Header Type Requirement Obaveznost Description Opis
X-Device-Api-Key String RequiredObavezno Same parent-device API key as gatewayPing. Alias X-Api-Key accepted. Isti API ključ parent uređaja kao za gatewayPing. Prihvata se i X-Api-Key.
cURL Example (SSE) cURL primjer (SSE)
curl -N "https://us-central1-yosh-digitalchip.cloudfunctions.net/gatewayStream?deviceId=dev_wash_01" \
  -H "X-Device-Api-Key: dc_dev_wash_01_api_key_demo_2026" \
  -H "Accept: text/event-stream"

SSE event format

Format SSE događaja

Each command push is one SSE data: line whose JSON matches the gatewayPing response body exactly:

Svaki push komandi je jedna SSE data: linija čiji JSON je identičan tijelu odgovora gatewayPing:

Pushed event example Primjer push događaja
data: {"timestamp":"2026-07-30T04:52:45.312Z","commands":[{"id":"cmd_8xL92kPz1","action":"START_SERVICE","subDeviceId":"bay_1","value":50}]}

: keepalive 2026-07-30T04:53:00.000Z

Recommended firmware pattern

Preporučeni pattern u firmveru

  1. Open gatewayStream and keep it open; on disconnect, reconnect with backoff. Otvorite gatewayStream i držite otvorenim; na disconnect reconnect sa backoff-om.
  2. On each data: JSON with a non-empty commands array, execute commands exactly as for gatewayPing. Na svaki data: JSON sa nepraznim commands, izvršite komande isto kao za gatewayPing.
  3. Continue gatewayPing for bay status / Live Tracking (every 3s still OK; with stream you may slow ping to 30–60s if desired). Nastavite gatewayPing za status boksova / Live Tracking (3s i dalje OK; uz stream možete usporiti ping na 30–60s).

EMQX command MQTT deprecated

EMQX MQTT komandi deprecated

Deprecated — not the mobile API

Zastarjelo — nije mobilni API

This EMQX topic (digitalchip/{deviceId}/commands) and mqttAuth are the old command channel. Mobile apps must not connect to any broker. New Digi firmware is reached only by Cloud Functions when the app calls activateService. Broker host and port are not published here.

Ovaj EMQX topic (digitalchip/{deviceId}/commands) i mqttAuth su stari kanal komandi. Mobilne aplikacije ne smiju da se konektuju na broker. Novi Digi firmware se doseže samo iz Cloud Functions kad aplikacija pozove activateService. Host i port brokera se ovdje ne objavljuju.

For larger fleets (tens to hundreds of controllers), prefer MQTT over polling every 3s or long-lived SSE. The device opens an outbound MQTT connection to a managed broker (e.g. EMQX Cloud). DigitalChip publishes commands when a wash is activated — same JSON as gatewayPing. Keep a slower gatewayPing for bay telemetry / Live Tracking.

Za veće flote (desetine do stotine kontrolera) preferirajte MQTT umjesto pinga svake 3s ili dugog SSE. Uređaj otvara outbound MQTT konekciju ka managed brokeru (npr. EMQX Cloud). DigitalChip publish-uje komande kad se aktivira pranje — isti JSON kao gatewayPing. Zadržite sporiji gatewayPing za telemetriju boksova / Live Tracking.

Architecture

Arhitektura

activateService / stopService → Firestore PENDING command → Cloud Function onDeviceCommandCreated publishes to topic → device executes. If MQTT is not configured or publish fails, the command stays/reverts to PENDING and gatewayPing / gatewayStream still deliver it.

activateService / stopService → Firestore PENDING komanda → Cloud Function onDeviceCommandCreated publish na topic → uređaj izvršava. Ako MQTT nije podešen ili publish padne, komanda ostaje/vrati se u PENDING i isporučuju je gatewayPing / gatewayStream.

1. Broker setup (EMQX Cloud)

1. Podešavanje brokera (EMQX Cloud)

  1. Create an EMQX Cloud deployment (Serverless is fine to start). Note the mqtts://…:8883 endpoint. Kreirajte EMQX Cloud deployment (Serverless je OK za početak). Zabilježite mqtts://…:8883 endpoint.
  2. Create a publisher user (e.g. digitalchip_publisher) with ACL allowing publish to digitalchip/+/commands. Kreirajte publisher korisnika (npr. digitalchip_publisher) sa ACL-om koji dozvoljava publish na digitalchip/+/commands.
  3. Add an HTTP authentication authenticator pointing at mqttAuth (below) so devices log in with deviceId + apiKey. Dodajte HTTP authentication authenticator ka mqttAuth (ispod) da se uređaji prijave sa deviceId + apiKey.
  4. Device ACL (in EMQX): each client may subscribe only to digitalchip/${username}/commands (and usually not publish). ACL uređaja (u EMQX): klijent smije samo subscribe na digitalchip/${username}/commands (obično bez publish-a).

2. DigitalChip Cloud params

2. DigitalChip Cloud parametri

Set environment params for Cloud Functions (file functions/.env.yosh-digitalchip or Google Cloud Console → Cloud Functions / Cloud Run env):

Postavite env parametre za Cloud Functions (fajl functions/.env.yosh-digitalchip ili Google Cloud Console → Cloud Functions / Cloud Run env):

functions/.env.yosh-digitalchip
MQTT_URL=mqtts://xxxxx.emqxsl.com:8883
MQTT_USERNAME=digitalchip_publisher
MQTT_PASSWORD=your_publisher_password

If MQTT_URL is empty, MQTT publish is skipped (no error) — ping/stream keep working.

Ako je MQTT_URL prazan, MQTT publish se preskače (bez greške) — ping/stream i dalje rade.

3. HTTP auth endpoint for EMQX

3. HTTP auth endpoint za EMQX

POST https://us-central1-yosh-digitalchip.cloudfunctions.net/mqttAuth

EMQX sends device credentials. DigitalChip checks devices/{username}.apiKey.

EMQX šalje kredencijale uređaja. DigitalChip provjerava devices/{username}.apiKey.

Field Description Opis
username Must equal registered deviceId. Mora biti registrovani deviceId.
password Parent device apiKey (same as X-Device-Api-Key on ping). Parent apiKey (isti kao X-Device-Api-Key na pingu).
Example EMQX → mqttAuth body Primjer tijela EMQX → mqttAuth
{
  "username": "dev_wash_01",
  "password": "dc_…",
  "clientid": "dev_wash_01-fw"
}
Allow response Odgovor allow
{
  "result": "allow",
  "is_superuser": false,
  "acl": [
    { "permission": "allow", "action": "subscribe", "topic": "digitalchip/dev_wash_01/commands" },
    { "permission": "deny", "action": "all", "topic": "#" }
  ]
}

Deny / unknown device → {"result":"deny"} (HTTP 200 — EMQX convention).

Deny / nepoznat uređaj → {"result":"deny"} (HTTP 200 — EMQX konvencija).

4. Device MQTT client

4. MQTT klijent na uređaju

Setting Postavka Value
Broker URL Broker URL mqtts://…:8883 (from EMQX)
Username Korisničko ime deviceId
Password Lozinka apiKey
Subscribe topic Topic za subscribe digitalchip/{deviceId}/commands
QoS QoS 1 recommended
Client ID Client ID {deviceId}-fw

5. Message payload (same as gatewayPing)

5. Payload poruke (isti kao gatewayPing)

UTF-8 JSON body on the topic (not wrapped in SSE). Process exactly like a ping response:

UTF-8 JSON na topicu (nije SSE). Obrada identična odgovoru na ping:

Published message example Primjer publish poruke
{
  "timestamp": "2026-07-30T05:52:45.312Z",
  "commands": [
    {
      "id": "cmd_8xL92kPz1",
      "action": "START_SERVICE",
      "subDeviceId": "bay_1",
      "value": 50
    }
  ]
}

6. Cloud Functions involved

6. Uključene Cloud Functions

Function Type Role Uloga
mqttAuth HTTP POST EMQX HTTP authenticator EMQX HTTP authenticator
onDeviceCommandCreated Firestore trigger Publish on new PENDING command Publish na novu PENDING komandu

Firmware Integration Flow deprecated

Tok integracije na kontroleru deprecated

Old gateway stack

Stari gateway stack

deprecated This flow (gatewayPing, gatewayStream, EMQX topic digitalchip/{deviceId}/commands) is the previous controller channel. Digi firmware does not follow it. Mobile apps never implement this section.

deprecated Ovaj tok (gatewayPing, gatewayStream, EMQX topic digitalchip/{deviceId}/commands) je prethodni kanal kontrolera. Digi firmware ga ne prati. Mobilne aplikacije ne implementiraju ovu sekciju.

Previous stack, only if the controller is still on the gateway channel: EMQX MQTT for commands plus gatewayPing every 30–60s for telemetry. gatewayStream and a 3s ping are older fallbacks, also deprecated.

Prethodni stack, samo ako je kontroler još na gateway kanalu: EMQX MQTT za komande plus gatewayPing svakih 30–60s za telemetriju. gatewayStream i ping na 3s su stariji fallback, takođe deprecated.

  1. Boot & Connect Pokretanje i mrežna veza: Connect to network (Wi-Fi/LTE), load the provisioned deviceId + apiKey from secure storage, and sync time. Povežite se na mrežu (Wi-Fi/LTE), učitajte dodijeljeni deviceId + apiKey iz sigurnog skladišta i sinhronizujte sat ako je moguće.
  2. MQTT (recommended) MQTT (preporučeno): Connect to the broker with username=deviceId, password=apiKey. Subscribe to digitalchip/{deviceId}/commands. On message, parse JSON and run commands like a ping response. See MQTT section. Konektujte se na broker sa username=deviceId, password=apiKey. Subscribe na digitalchip/{deviceId}/commands. Na poruku parsirajte JSON i izvršite commands kao kod ping odgovora. Vidi MQTT sekciju.
  3. Command stream SSE (optional fallback) SSE stream (opcioni fallback): Open gatewayStream if MQTT is unavailable. Process pushed commands the same way. Otvorite gatewayStream ako MQTT nije dostupan. Obradjujte pushane commands isto.
  4. Poll Loop (required for telemetry) Petlja pinga (obavezna za telemetriju): deprecated for Digi. Old gateway controllers POST gatewayPing with X-Device-Api-Key. That stamps lastPing. Live Tracking counts those devices online only when that ping is younger than about 3 minutes. Digi devices ignore this ping. deprecated za Digi. Stari gateway kontroleri šalju POST gatewayPing sa X-Device-Api-Key. To upisuje lastPing. Live Tracking te uređaje broji online samo kad je taj ping mlađi od oko 3 minute. Digi uređaji ignorišu ovaj ping.
  5. Command Processing Obrada zapovijedi:
    • If `commands` is not empty (from MQTT, ping, or stream), loop through all received command items.
    • Ako niz `commands` nije prazan (iz MQTT, pinga ili streama), prođite kroz sve zapovijedi u petlji.
    • For each command, map the `subDeviceId` to the physical relay port and trigger the relay to generate value pulses (or run for `value` seconds).
    • Za svaku zapovijed, povežite `subDeviceId` sa odgovarajućim fizičkim portom releja i aktivirajte ga tako da generiše value impulsa (ili radi `value` sekundi).
    • Do NOT trigger multiple overlapping activations on the same relay; queue them internally or ignore if busy. Deduplicate by command id.
    • NEMOJTE pokretati više preklapajućih aktivacija na istom releju istovremeno; skladištite ih u internom redu ili ignorišite ako je relej zauzet. Deduplikujte po id zapovijedi.
  6. Completion Završetak: There is no callback needed to confirm execution. The server marks commands as SENT when delivering them via MQTT, ping, or stream. Nema potrebe za povratnom potvrdom. Server označava zapovijedi kao SENT kad ih isporuči preko MQTT, pinga ili streama.

Device Command Safety

Bezbjednost rada uređaja

Ensure that in case of watchdog reset or network interruption, the controller fails safely (all relays opened/off). The device should handle deduplication by ignoring commands with `id`s it has recently executed.

Osigurajte da u slučaju resetovanja kontrolera (watchdog) ili nestanka mrežne veze svi releji odu u bezbjedno isključeno stanje (open/off). Kontroler bi trebao da ignoriše zapovijedi sa ID-jevima koje je već izvršio u skorije vrijeme.

API key errors

Greške API ključa

Missing or wrong X-Device-Api-Key returns 401 Unauthorized. An unregistered deviceId returns 404. Unknown devices are never auto-created from a ping.

Nedostajući ili pogrešan X-Device-Api-Key vraća 401 Unauthorized. Neregistrovan deviceId vraća 404. Nepoznati uređaji se nikad ne kreiraju automatski iz pinga.

Native external app contract

Ugovor za eksternu native aplikaciju

Android, iOS and other clients use the Firebase project yosh-digitalchip, region us-central1. They never open MQTT. Every wash goes through the callables below. Direct Firestore writes to wallets, QR sessions, vehicles and commands are denied.

Android, iOS i ostali klijenti koriste Firebase projekat yosh-digitalchip, region us-central1. Ne otvaraju MQTT. Svako pranje ide kroz callable funkcije ispod. Direktan Firestore upis u novčanike, QR sesije, vozila i komande je zabranjen.

Sign-in

Prijava

Method Način How Kako
Email and password E-mail i lozinka Firebase Auth SDK. Then call callables with the ID token. Firebase Auth SDK. Zatim callable pozivi sa ID tokenom.
Guest Gost Anonymous Auth. Must buy tokens before a wash and spend the full purchased balance in one activation. Anonimni Auth. Mora kupiti tokene prije pranja i potrošiti cijeli kupljeni saldo u jednoj aktivaciji.
b2bCodeLogin b2bCodeLogin Callable, no auth header. Body { "data": { "company": "Acme", "code": "123456" } }. Result token is a Firebase custom token. Sign in with signInWithCustomToken, then use that session. Callable, bez auth zaglavlja. Tijelo { "data": { "company": "Acme", "code": "123456" } }. token u odgovoru je Firebase custom token. Prijavite se sa signInWithCustomToken, pa koristite tu sesiju.

QR

QR

The printed code is unchanged: deviceId:subDeviceId. Parse those two ids and send them to the callables. Do not invent a second hardware id.

Odštampani kod se ne mijenja: deviceId:subDeviceId. Raspakujte ta dva id-a i pošaljite ih u callable. Ne uvodite drugi hardverski id.

Wash sequence

Redoslijed pranja

  1. createQrSession with deviceId and subDeviceId. On a Digi controller this call asks the machine if that bay is ready before it returns sessionId (300 seconds). Wait for it (about 5 seconds). failed-precondition means the bay is not ACTIVE. deadline-exceeded or unavailable means status did not arrive; do not continue to pay.
  2. createQrSession sa deviceId i subDeviceId. Na Digi kontroleru ovaj poziv pita mašinu da li je taj automat spreman prije nego vrati sessionId (300 sekundi). Sačekajte (oko 5 sekundi). failed-precondition znači da automat nije ACTIVE. deadline-exceeded ili unavailable znači da status nije stigao; ne nastavljajte na uplatu.
  3. activateService with userId equal to the signed-in uid, tokensToSpend, qrSessionId, optional vehicleId, and usePersonalWallet: true when a B2B employee pays from a personal wallet.
  4. activateService sa userId jednakim uid-u prijave, tokensToSpend, qrSessionId, opciono vehicleId, i usePersonalWallet: true kad B2B zaposleni plaća iz ličnog novčanika.
  5. On Digi controllers wait for the callable to finish (about 5 seconds). success: true means the machine confirmed and tokens were deducted. remainingBalance is the new balance.
  6. Na Digi kontrolerima sačekajte kraj callable-a (oko 5 sekundi). success: true znači da je mašina potvrdila i da su tokeni skinuti. remainingBalance je novo stanje.
  7. On deadline-exceeded or unavailable, show the message and retry the same qrSessionId. The wallet was not charged.
  8. Na deadline-exceeded ili unavailable prikažite poruku i ponovite isti qrSessionId. Novčanik nije naplaćen.
  9. cancelQrSession with { "data": { "sessionId" } } if the user leaves before paying.
  10. cancelQrSession sa { "data": { "sessionId" } } ako korisnik ode prije plaćanja.

Reads the app may do itself

Čitanja koja aplikacija smije sama

Not for the native wash app

Nije za native aplikaciju pranja

digiActivateService is private. creditOwnWallet is a sandbox top-up, not production checkout. stopService still queues an old STOP_SERVICE command and does nothing on Digi firmware. gatewayPing, gatewayStream and EMQX MQTT are device APIs, not app APIs.

digiActivateService je privatan. creditOwnWallet je sandbox dopuna, nije produkcijsko plaćanje. stopService i dalje stavlja staru STOP_SERVICE komandu i na Digi firmwareu ne radi ništa. gatewayPing, gatewayStream i EMQX MQTT su API-ji uređaja, ne aplikacije.

Activation messages

Poruke aktivacije

failed-precondition message is one of: QR_SESSION_REQUIRED, QR_SESSION_INVALID, QR_SESSION_CONSUMED, QR_SESSION_EXPIRED, QR_SESSION_MISMATCH, GUEST_PURCHASE_REQUIRED, GUEST_MUST_SPEND_ALL, GUEST_PERSONAL_WALLET_ONLY, PERSONAL_WALLET_DISABLED, Insufficient tokens in wallet., or the machine error text. Map the codes in the app. Show unknown text as-is.

failed-precondition poruka je jedna od: QR_SESSION_REQUIRED, QR_SESSION_INVALID, QR_SESSION_CONSUMED, QR_SESSION_EXPIRED, QR_SESSION_MISMATCH, GUEST_PURCHASE_REQUIRED, GUEST_MUST_SPEND_ALL, GUEST_PERSONAL_WALLET_ONLY, PERSONAL_WALLET_DISABLED, Insufficient tokens in wallet., ili tekst greške mašine. Mapirajte kodove u aplikaciji. Nepoznat tekst prikažite kakav jeste.

Mobile apps and machine MQTT

Mobilne aplikacije i MQTT mašine

Native and web clients do not open an MQTT connection. The broker address, port and topics live only in Cloud Functions. The app keeps calling the same HTTPS Callable endpoints. On a Digi controller, createQrSession checks that the scanned bay is ready, and activateService checks again and then waits for the machine to confirm payment.

Native i web klijenti ne otvaraju MQTT vezu. Adresa, port i topic-i brokera žive samo u Cloud Functions. Aplikacija i dalje zove iste HTTPS Callable endpoint-e. Na Digi kontroleru createQrSession provjeri da je skenirani automat spreman, a activateService provjeri ponovo i zatim sačeka da mašina potvrdi uplatu.

Do not embed the broker

Ne ugrađujte broker

Do not put a broker host, port, username, or topic in the mobile app. Those values are not part of this API. A client that needs them is calling the wrong layer.

Ne stavljajte host brokera, port, korisničko ime ni topic u mobilnu aplikaciju. To nije dio ovog API-ja. Klijent kojem to treba zove pogrešan sloj.

Endpoints the app calls

Endpoint-i koje aplikacija zove

Callable When Kad What the Cloud Function does Šta radi Cloud Function
createQrSession After the QR scan, before pay Poslije skena QR-a, prije uplate On a Digi controller, asks the machine whether that bay is ACTIVE, then returns sessionId (300 seconds). The app does not open a broker connection. The same check runs again inside activateService before payment. Na Digi kontroleru pita mašinu da li je taj automat ACTIVE, pa vrati sessionId (300 sekundi). Aplikacija ne otvara vezu ka brokeru. Ista provjera ide ponovo unutar activateService prije uplate.
activateService User confirms tokens Korisnik potvrdi tokene On a Digi controller: sends the payment through the internal broker link and waits for confirmation (about 5 seconds). Tokens are deducted only after success. On any other controller: the existing command queue, unchanged. Na Digi kontroleru: pošalje uplatu preko interne brokerske veze i sačeka potvrdu (oko 5 sekundi). Tokeni se skidaju tek nakon uspjeha. Na ostalim kontrolerima: postojeći red komandi, bez izmjene.
stopService User stops a bay Korisnik zaustavi boks Still the command queue. The Digi channel has no stop message. I dalje red komandi. Digi kanal nema poruku za stop.

Base URL for every callable: https://us-central1-yosh-digitalchip.cloudfunctions.net/<name>. Auth and body shape are in Calling HTTPS Callable functions. activateService fields are in activateService.

Bazni URL svakog callable-a: https://us-central1-yosh-digitalchip.cloudfunctions.net/<name>. Auth i oblik tijela su u Pozivanje HTTPS Callable funkcija. Polja activateService su u activateService.

createQrSession

createQrSession

HTTPS Callable (v2 onCall) https://us-central1-yosh-digitalchip.cloudfunctions.net/createQrSession
Request Zahtjev
{
  "data": {
    "deviceId": "dev_wash_01",
    "subDeviceId": "bay_1"
  }
}
Result Odgovor
{
  "result": {
    "success": true,
    "sessionId": "sess_abc123",
    "deviceId": "dev_wash_01",
    "subDeviceId": "bay_1",
    "tenantId": "tenant_wash_central",
    "expiresAt": 1710000000000,
    "expiresInSec": 300
  }
}

Digi result the app must handle

Digi rezultat koji aplikacija mora obraditi

The request and the success body of activateService stay the same. On a Digi controller the call blocks until the machine answers. Success means the machine accepted the payment and tokens were deducted. Failure means nothing was charged and the same qrSessionId can be sent again.

Zahtjev i uspješan odgovor activateService ostaju isti. Na Digi kontroleru poziv traje dok mašina ne odgovori. Uspjeh znači da je mašina prihvatila uplatu i da su tokeni skinuti. Neuspjeh znači da ništa nije naplaćeno i da se isti qrSessionId može poslati ponovo.

Callable error Greška callable-a Meaning for the app Značenje za aplikaciju
deadline-exceeded deadline-exceeded No confirmation in time. Show the message and allow retry. Wallet unchanged. Nema potvrde na vrijeme. Prikažite poruku i dozvolite ponovo. Novčanik nije diran.
failed-precondition failed-precondition Machine returned an error string, or balance / QR / bay rules failed. Show message. Do not start a second pay unless the user retries. Mašina je vratila tekst greške, ili nisu prošla pravila balansa / QR-a / boksa. Prikažite message. Ne šaljite drugu uplatu dok korisnik ne pokuša ponovo.
unavailable unavailable Cloud Function could not reach the machine link. Retry later. Wallet unchanged. Cloud Function nije stigla do veze sa mašinom. Pokušajte kasnije. Novčanik nije diran.
JS SDK
const functions = getFunctions();
const session = await httpsCallable(functions, "createQrSession")({
  deviceId, subDeviceId
});
const started = await httpsCallable(functions, "activateService")({
  userId: auth.currentUser.uid,
  deviceId,
  subDeviceId,
  tokensToSpend,
  qrSessionId: session.data.sessionId
});
// started.data.success === true, remainingBalance updated

Device status

Status uređaja

Digi controllers report bay readiness through Cloud Functions. There is no status endpoint for the mobile app and no broker credentials in this document. Super-admin sees every controller. A tenant admin sees only controllers assigned to that tenant.

Digi kontroleri javljaju spremnost automata kroz Cloud Functions. Nema status endpoint-a za mobilnu aplikaciju i nema kredencijala brokera u ovom dokumentu. Super-admin vidi svaki kontroler. Tenant admin vidi samo kontrolere dodijeljene tom tenantu.

Where Gdje What you see Šta vidite
QR scan (createQrSession) Sken QR-a (createQrSession) The scanned bay must be ACTIVE. INACTIVE or ERROR returns failed-precondition and no session. A missing answer returns deadline-exceeded or unavailable. Skenirani automat mora biti ACTIVE. INACTIVE ili ERROR vraća failed-precondition i nema sesije. Ako odgovor ne stigne, vraća se deadline-exceeded ili unavailable.
Payment (activateService) Uplata (activateService) The same readiness check runs again before payment. Tokens are not deducted when the bay is not ready. Ista provjera spremnosti ide ponovo prije uplate. Tokeni se ne skidaju kad automat nije spreman.
Admin device list Admin lista uređaja Column Bay status shows each bay, for example 1: ACTIVE (50). The number in parentheses is the parameter reported with that bay (Wi-Fi strength or another device parameter). Kolona Status automata pokazuje svaki automat, npr. 1: ACTIVE (50). Broj u zagradi je parametar uz taj automat (jačina Wi-Fi signala ili drugi parametar uređaja).
Live Tracking Live Tracking A Digi controller is online when its last status is younger than about 3 minutes. Status is refreshed on a schedule and whenever a QR is scanned. gatewayPing / lastPing do not count for Digi devices. Old gateway controllers are still online from lastPing. Digi kontroler je online kad je zadnji status mlađi od oko 3 minute. Status se osvježava po rasporedu i kad se skenira QR. gatewayPing / lastPing se ne broje za Digi uređaje. Stari gateway kontroleri i dalje idu preko lastPing.

Not a client API

Nije klijentski API

Do not call the machine broker from the app to read status. Use createQrSession. Admin lists and Live Tracking already show the stored result. gatewayPing, gatewayStream and EMQX MQTT are deprecated for this.

Ne zovite broker mašine iz aplikacije da biste pročitali status. Koristite createQrSession. Admin liste i Live Tracking već pokazuju sačuvan rezultat. gatewayPing, gatewayStream i EMQX MQTT su za ovo deprecated.

Mobile Service Activation

Aktivacija usluge sa mobilne aplikacije

Triggered by the mobile client app to deduct wallet balance and start the machine. On a Digi controller the Cloud Function confirms with the machine before charging (see Mobile MQTT via API). Other controllers still queue a command. Supports corporate B2B rules (time schedules, allowed days, and daily user limits).

Pokreće se iz mobilne aplikacije radi skidanja tokena i pokretanja mašine. Na Digi kontroleru Cloud Function prvo dobije potvrdu mašine, pa tek onda naplati (vidi Mobilni MQTT preko API-ja). Ostali kontroleri i dalje stavljaju komandu u red. Podržava B2B kontrolu limita (dozvoljeni dani, vrijeme rada, dnevni limiti po zaposlenom).

HTTPS Callable (v2 onCall) https://us-central1-yosh-digitalchip.cloudfunctions.net/activateService

Authentication Header Required

Potrebna autorizacija

Requires a valid Firebase ID Token in the header: Authorization: Bearer <TOKEN>. Also, the calling user UID must match the userId parameter in the payload.

Zahtijeva ispravan Firebase ID Token u zaglavlju: Authorization: Bearer <TOKEN>. Takođe, UID ulogovanog korisnika mora odgovarati parametru userId.

Request Structure (Wrapped in a "data" property)

Struktura zahtjeva (Umotana u "data" objekat)

Field Type Requirement Obaveznost Description Opis
data.userId String RequiredObavezno The UID of the user purchasing the service. UID korisnika koji aktivira uslugu.
data.tenantId String RequiredObavezno Associated branch location/tenant ID. ID lokacije/poslovnice.
data.deviceId String RequiredObavezno IoT Gateway controller ID. ID mrežnog prolaza (kontrolera).
data.subDeviceId String RequiredObavezno Target relay module (e.g. bay_1). ID specifičnog modula/releja koji se uključuje.
data.tokensToSpend Number RequiredObavezno Quantity of tokens to spend from the wallet. Broj tokena koje korisnik troši.
data.qrSessionId String RequiredObavezno From createQrSession (300s / 5 min presence). Iz createQrSession (300s / 5 min prisustvo).
data.vehicleId String ConditionalUslovno Optional. If set, must be an active vehicle in the caller’s scope (company fleet or personal). Obavezno ako postoji ≥1 aktivno vozilo u opsegu (flota ili lično).
Request Body Example Primjer tela zahtjeva
{
  "data": {
    "userId": "UserUID_12345",
    "deviceId": "dev_wash_01",
    "subDeviceId": "bay_1",
    "tokensToSpend": 5,
    "qrSessionId": "sess_abc123",
    "vehicleId": "veh_xyz"
  }
}

Response Structure (Returned under a "result" property)

Struktura odgovora (Vraća se unutar "result" objekta)

Field Type Description Opis
result.success Boolean Indicates if the balance was successfully debited and the command queued. Označava da li je transakcija uspješno prošla i zapovijed upisana u bazu.
result.message String Transaction output summary message. Tekstualna poruka o statusu aktivacije.
result.commandId String The created command ID document. ID kreirane zapovijedi u bazi podataka.
result.value Number Calculated pulse count based on tokens spent and sub-device calibration factor. Izračunati broj impulsa na osnovu potrošenih tokena i kalibracije uređaja.
result.remainingBalance Number New wallet token balance. Preostalo stanje tokena u novčaniku nakon skidanja.
Response Body Example Primjer odgovora
{
  "result": {
    "success": true,
    "message": "Service activated successfully.",
    "commandId": "cmd_act_912384x",
    "value": 50,
    "remainingBalance": 45
  }
}

Calling HTTPS Callable functions

Pozivanje HTTPS Callable funkcija

All onCall functions require a valid Firebase Auth session. Native apps use the Firebase SDK (Android/iOS/Flutter). Raw HTTP clients must send:

Sve onCall funkcije zahtijevaju Firebase Auth sesiju. Native app koristi Firebase SDK. Raw HTTP klijenti šalju:

JS SDK
import { getFunctions, httpsCallable } from "firebase/functions";
const fn = httpsCallable(getFunctions(), "getUserProfile");
const { data } = await fn({});

getUserProfile

getUserProfile

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/getUserProfile

Returns the authenticated user's profile. No request fields required.

Vraća profil ulogovanog korisnika. Nema obaveznih polja u zahtjevu.

Response (result)
{
  "success": true,
  "profile": {
    "userId": "abc123",
    "name": "Marko Marković",
    "email": "marko@alphacorp.com",
    "phone": "+38761111222",
    "role": "USER",
    "b2bCompanyId": "comp_alpha",
    "tenantId": null,
    "isGuest": false,
    "emailVerified": false,
    "providers": ["password"]
  }
}

updateUserProfile

updateUserProfile

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/updateUserProfile

Updates own name and phone. Cannot change role, email, or org links.

Ažurira vlastito name i phone. Ne mijenja ulogu, email ni org veze.

FieldTypeRequiredObaveznoNotesNapomena
data.name String Yes 1–100 chars
data.phone String Optional Max 40 chars (empty string allowed) Max 40 znakova (prazan string OK)
Request
{
  "data": {
    "name": "Petar Petrović",
    "phone": "+38761123456"
  }
}

changeUserPassword

changeUserPassword

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/changeUserPassword

Verifies currentPassword via Identity Toolkit, then sets newPassword (min 6). Guests / anonymous / non-password providers are rejected.

Provjerava currentPassword preko Identity Toolkit-a, zatim postavlja newPassword (min 6). Gosti / anonimni / bez password providera se odbijaju.

FieldTypeRequiredObavezno
data.currentPassword String Yes
data.newPassword String Yes (≥ 6)
Request
{
  "data": {
    "currentPassword": "oldSecret",
    "newPassword": "newSecret123"
  }
}

onboardEmployee

onboardEmployee

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/onboardEmployee

B2B_ADMIN (own company) or SUPER_ADMIN. Creates Auth + Firestore user + wallet if needed, links to company as USER.

B2B_ADMIN (svoja kompanija) ili SUPER_ADMIN. Kreira Auth + Firestore + novčanik ako treba, veže na kompaniju kao USER.

FieldTypeRequiredObavezno
data.companyIdStringYes
data.emailStringYes
data.nameStringYes
data.phoneStringYes (≥ 6)
data.passwordStringYes (≥ 6)
Request
{
  "data": {
    "companyId": "comp_alpha",
    "email": "ana@alphacorp.com",
    "name": "Ana Anić",
    "phone": "+38761123456",
    "password": "Password123!"
  }
}
Response (result)
{
  "success": true,
  "userId": "uid_...",
  "created": true,
  "alreadyLinked": false,
  "email": "ana@alphacorp.com"
}

dispenseTokens

dispenseTokens

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/dispenseTokens

Moves tokens from corporate wallet to employee wallet (atomic). B2B_ADMIN of that company or SUPER_ADMIN.

Prenosi tokene s korporativnog na zaposlenički novčanik (atomski). B2B_ADMIN te kompanije ili SUPER_ADMIN.

FieldTypeRequiredObavezno
data.fromCompanyIdStringYes
data.toUserIdStringYes
data.amountNumberYes (> 0)
Request
{
  "data": {
    "fromCompanyId": "comp_alpha",
    "toUserId": "employeeUid",
    "amount": 50
  }
}
Response (result)
{
  "success": true,
  "newBalance": 450,
  "employeeBalance": 50
}

getCompanySpendingReport

getCompanySpendingReport

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/getCompanySpendingReport

Lists corporate-wallet DEBIT transactions (who / when / where) plus per-user totals.

Lista DEBIT transakcija korporativnog novčanika (ko / kada / gdje) i totali po korisniku.

FieldTypeRequiredObavezno
data.companyIdStringYes
data.limitNumberOptional (default 200, max 500)
Request
{
  "data": { "companyId": "comp_alpha", "limit": 100 }
}
Response (result) — shape
{
  "success": true,
  "companyId": "comp_alpha",
  "companyName": "Alpha Corp",
  "totals": { "gross": 120, "transactionCount": 8 },
  "users": [
    { "userId": "...", "name": "...", "email": "...", "gross": 40, "transactionCount": 3 }
  ],
  "transactions": [
    {
      "id": "...",
      "userId": "...",
      "userName": "...",
      "amount": 5,
      "tenantId": "tenant_wash_central",
      "tenantName": "...",
      "deviceId": "dev_wash_01",
      "subDeviceId": "bay_1",
      "subDeviceName": "Bay 1",
      "timestamp": "2026-07-15T12:00:00.000Z"
    }
  ]
}

createTenantWithAdmin

createTenantWithAdmin

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/createTenantWithAdmin

SUPER_ADMIN only. Creates tenant, TENANT_ADMIN Auth user, wallets (tenant balance starts at 0 — tenants sell services).

Samo SUPER_ADMIN. Kreira tenant, TENANT_ADMIN Auth nalog, novčanike (tenant balance počinje na 0 — tenant prodaje usluge).

FieldTypeRequiredObavezno
data.tenantIdStringYes
data.tenantNameStringYes
data.adminNameStringYes
data.adminEmailStringYes
data.adminPasswordStringYes (≥ 6)
data.industryTypeStringOptional — CAR_WASH | LAUNDRY | EV_CHARGING
data.currencyStringOptional — TOKEN | BAM | EUR | RSD | USD
data.addressStringOptional
data.jibStringOptional
data.platformFeePercentNumberOptional — 0–100, default 15
Request
{
  "data": {
    "tenantId": "tenant_eco_wash",
    "tenantName": "Eco Wash Center",
    "industryType": "CAR_WASH",
    "adminName": "Petar Petrović",
    "adminEmail": "admin@ecowash.com",
    "adminPassword": "Password123!",
    "currency": "BAM",
    "address": "Ulica 1, Sarajevo",
    "jib": "4200123450001",
    "platformFeePercent": 12.5
  }
}

updateTenantFeeTerms

updateTenantFeeTerms

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/updateTenantFeeTerms

SUPER_ADMIN only. Sets tenants/{tenantId}.platformFeePercent (0–100). Used by getEarningsReport. Clients cannot write this field (Firestore rules).

Samo SUPER_ADMIN. Postavlja tenants/{tenantId}.platformFeePercent (0–100). Koristi getEarningsReport. Klijent ne može pisati ovo polje (Firestore rules).

Request / Response
// Request
{ "data": { "tenantId": "tenant_eco_wash", "platformFeePercent": 10 } }

// Response
{ "success": true, "tenantId": "tenant_eco_wash", "platformFeePercent": 10 }

rotateDeviceApiKey

rotateDeviceApiKey

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/rotateDeviceApiKey

SUPER_ADMIN or owning TENANT_ADMIN. Issues a new apiKey for devices/{deviceId}. Old key stops working on gatewayPing.

SUPER_ADMIN ili vlasnički TENANT_ADMIN. Izdaje novi apiKey za devices/{deviceId}. Stari ključ prestaje raditi na gatewayPing.

Request / Response
// Request
{ "data": { "deviceId": "dev_wash_01" } }

// Response
{ "success": true, "deviceId": "dev_wash_01", "apiKey": "dc_a1b2c3..." }

getEarningsReport

getEarningsReport

HTTPS Callable https://us-central1-yosh-digitalchip.cloudfunctions.net/getEarningsReport

Aggregates DEBIT spend by tenant → device → sub-device. Fee = each tenant’s platformFeePercent (default 15% if missing). Totals sum per-tenant fee math. SUPER_ADMIN (optional tenantId filter) or TENANT_ADMIN (own tenant only).

Agregira DEBIT potrošnju tenant → uređaj → poduređaj. Fee = platformFeePercent po tenantu (podrazumevano 15% ako nedostaje). Totals su zbir po tenantima. SUPER_ADMIN (opcionalni filter tenantId) ili TENANT_ADMIN (samo svoj tenant).

Request
{
  "data": { "tenantId": "tenant_wash_central" }
}
Response (result) — shape
{
  "success": true,
  "platformFeePercent": 12.5,
  "defaultPlatformFeePercent": 15,
  "transactionCount": 42,
  "totals": { "gross": 1000, "platformFee": 125, "net": 875, "transactionCount": 42 },
  "tenants": [
    {
      "tenantId": "tenant_wash_central",
      "tenantName": "Wash Central",
      "platformFeePercent": 12.5,
      "gross": 1000,
      "platformFee": 125,
      "net": 875,
      "transactionCount": 42,
      "devices": [
        {
          "deviceId": "dev_wash_01",
          "gross": 1000,
          "platformFee": 125,
          "net": 875,
          "platformFeePercent": 12.5,
          "subDevices": [
            { "subDeviceId": "bay_1", "name": "Bay 1", "gross": 600, "platformFee": 75, "net": 525, "platformFeePercent": 12.5 }
          ]
        }
      ]
    }
  ]
}

Vehicles API (mobile-ready)

API vozila (za mobilnu app)

Collection /vehicles/{vehicleId}. Client writes denied — all mutations via callables. Wash ledger stores vehicleId + vehiclePlate on DEBIT txs.

Kolekcija /vehicles/{vehicleId}. Client write zabranjen — mutacije preko callable. Ledger čuva vehicleId + vehiclePlate na DEBIT tx.

listVehicles

{ "companyId": "comp_alpha" }  // omit → personal vehicles
// includeInactive?: boolean
// Auth: personal owner; company members; SUPER_ADMIN

createVehicle

{ "plate": "A12-B-345", "label": "Van 1", "companyId": "comp_alpha" }
// omit companyId → personal vehicle for caller
// companyId → B2B_ADMIN of company or SUPER_ADMIN

updateVehicle / deactivateVehicle

{ "vehicleId": "...", "plate": "...", "label": "...", "active": true }
{ "vehicleId": "..." }  // deactivateVehicle → active:false

getVehicleWashStats

{ "companyId": "comp_alpha" }
// B2B_ADMIN / SUPER → per-vehicle washCount, tokensSpent, lastWashAt

listWashHistory

{ "vehicleId": "optional", "limit": 50 }
// Caller DEBITs (personal wallet or company wallet where userId=caller)
// Filter by vehicleId when set

Admin / B2B / Device Mutations

Admin / B2B / Device mutacije

All platform writes used by the PWA (and native apps) go through these callables — not direct Firestore client writes.

Sve platformerske mutacije koje koristi PWA (i native appovi) idu preko ovih callable funkcija — ne direktnim Firestore write-ovima sa klijenta.

Auth

Auth

Same as other onCall: Firebase ID token. Role checks happen server-side.

Isto kao i ostali onCall: Firebase ID token. Provjera uloge je na serveru.

createB2bCompany

{
  "companyId": "comp_alpha",
  "name": "Alpha Corp",
  "adminEmail": "admin@alphacorp.com",
  "initialBalance": 500,
  "dailyLimit": 50,
  "currency": "BAM",
  "address": "Ulica 1",
  "jib": "4200123450001"
}

updateSpendingRules

{
  "companyId": "comp_alpha",
  "rules": {
    "startTime": "08:00",
    "endTime": "20:00",
    "dailyLimit": 50,
    "allowedDays": [1, 2, 3, 4, 5]
  }
}

registerDevice

{
  "deviceId": "dev_wash_02",
  "tenantId": "tenant_wash_central",
  "firmwareVersion": "1.0.0",
  "status": "OFFLINE"
}
/* Response includes server-generated apiKey */

registerSubDevice

{
  "deviceId": "dev_wash_02",
  "subDeviceId": "bay_3",
  "name": "Bay 3",
  "qrCodePayload": "bay_3",
  "pulsePerToken": 10
}

adminUpdateUser / creditUserWallet / creditOwnWallet

/* adminUpdateUser — SUPER_ADMIN */
{ "userId": "...", "role": "B2B_ADMIN", "b2bCompanyId": "comp_alpha", "tenantId": null }

/* creditUserWallet — SUPER_ADMIN */
{ "userId": "...", "amount": 100 }

/* creditOwnWallet — any signed-in user */
{ "amount": 10, "replace": false }

Other mutation IDs

Ostali mutacijski ID-jevi

deleteB2bCompany · assignCompanyAdmin · removeCompanyEmployee · deleteDevice · deleteSubDevice · linkDeviceToTenant · deleteUserAccount · deleteTenantCascade · updateTenantFeeTerms

Error Handling & Status Codes

Rukovanje greškama i statusni kodovi

HTTP gatewayPing / gatewayStream

HTTP gatewayPing / gatewayStream

HTTPS Callable (onCall)

HTTPS Callable (onCall)

Errors throw Firebase HttpsError. Client SDKs expose code and message.

Greške bacaju Firebase HttpsError. Client SDK vraća code i message.

code Typical meaning Značenje
unauthenticatedMissing / expired ID tokenNedostaje / istekao ID token
permission-deniedWrong role or wrong company/tenant scopePogrešna uloga ili scope kompanije/tenanta
invalid-argumentMissing / invalid fieldsNedostaju / neispravna polja
not-foundUser, company, device, or wallet missingNedostaje user, kompanija, uređaj ili novčanik
failed-preconditionBusiness rule (hours, guest password, etc.)Poslovno pravilo (radno vrijeme, gost lozinka…)
already-existsTenant ID or email already takenTenant ID ili email već postoji
resource-exhaustedDaily B2B limit / too many attemptsDnevni B2B limit / previše pokušaja
deadline-exceededDigi machine did not confirm in time. Safe to retry the same QR session.Digi mašina nije potvrdila na vrijeme. Isti QR session se smije ponoviti.
unavailableCloud Function could not reach the machine link. Wallet unchanged.Cloud Function nije stigla do veze sa mašinom. Novčanik nije diran.
internalUnexpected server failureNeočekivana greška servera