> ## Documentation Index
> Fetch the complete documentation index at: https://firmngin.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Device Billing Flows

> Handle payments, verification, and active sessions from Arduino firmware

Use Device Billing callbacks when the device needs to react to paid orders, PIN verification, or active service sessions.

\[image:paid IoT session flow from checkout to device active service]

> [!NOTE]
>
> Firmware should react to the typed events it receives. It should not expose internal broker topics, raw device secrets, or backend-only identifiers to end users.

## Payment events

The `Payments` object handles both pending and successful payment events. Use it to update a display, unlock a device after payment, or log order information locally.

```cpp theme={null}
fngin.on(PAYMENTS, [](Payments &p) {
  if (p.isPending()) {
    Serial.println("Payment pending...");
  }

  if (p.isSuccess()) {
    Serial.println("Payment success!");
    unlockDevice();
  }

  Serial.print("Item: ");
  Serial.println(p.itemTitle());
  Serial.print("Price: ");
  Serial.println(p.price());
  Serial.print("Order: ");
  Serial.println(p.orderId());
});
```

Common values include item title, price, order ID, quantity, and metadata.

## Verification events

The `Verifications` object handles PIN display and verification result events. Use it when a customer must enter a PIN or pass a precondition before using the device.

```cpp theme={null}
fngin.on(VERIFICATIONS, [](Verifications &v) {
  if (v.hasPinNumber()) {
    Serial.print("PIN: ");
    Serial.println(v.pin());
    Serial.print("Valid for seconds: ");
    Serial.println(v.ttl());
  }

  if (v.hasResult()) {
    if (v.pinMet() && v.preconditionMet()) {
      Serial.println("Verification passed");
    } else {
      Serial.println("Verification failed");
    }
  }
});
```

## Device state

The `DeviceStates` object helps firmware react to state changes such as idle, pending payment, expired payment, successful payment, maintenance, or active service.

```cpp theme={null}
fngin.on(DEVICE_STATUS, [](DeviceStates &ds) {
  if (ds.isIdle()) {
    Serial.println("Device is idle");
  }
  if (ds.isPendingPayment()) {
    Serial.println("Waiting for payment");
  }
  if (ds.isSuccessPayment()) {
    Serial.println("Payment success");
  }
  if (ds.isMaintenance()) {
    Serial.println("Maintenance mode");
  }
  if (ds.isOnActiveService()) {
    Serial.println("Active service running");
  }
});
```

## Init flow

The `Inits` object provides the initial device configuration after connection. It can include entity configuration, merchant status, active order ID, verification flags, and business mode (service or vending).

```cpp theme={null}
fngin.on(INIT, [](Inits &i) {
  Serial.print("Entities: ");
  Serial.println(i.entities());

  Serial.print("Merchant status: ");
  Serial.println(i.merchantStatus());

  Serial.print("Active order: ");
  Serial.println(i.activeOrderId());

  if (i.isVendingMode()) {
    Serial.println("Business mode: vending");
  }
  if (i.isServiceMode()) {
    Serial.println("Business mode: service");
  }

  if (i.isVerificationRequired()) {
    Serial.println("Verification is required");
  }
});
```

## Vending dispense

When the device business mode is **vending**, a successful prepaid payment delivers catalog SKUs to dispense. Use the typed `Dispenses` object (or `ON_DISPENSES`) to drive motors or servos.

```cpp theme={null}
fngin.on(DISPENSES, [](Dispenses &d) {
  if (!d.isValid()) {
    Serial.println("Invalid dispense payload");
    return;
  }

  for (int i = 0; i < d.itemCount(); i++) {
    Serial.print("Dispense SKU: ");
    Serial.println(d.skuAt(i));
  }
});
```

Common helpers: `isValid()`, `itemCount()`, `skuAt(i)`, `metadata()`.

Vending checkout requires **prepaid** catalog items. Postpaid items are not available in vending mode.

## Active session end

The `ActiveSession` callback lets firmware end an active paid session from the device when local rules are met. This is useful for usage limits, sensor thresholds, or device-side safety rules.

```cpp theme={null}
Entity energyKwh("energy_kwh");

fngin.on(ON_ACTIVE_SESSION, [](ActiveSession &s) {
  if (s.entity(energyKwh).toFloat() >= 10.0) {
    stopMachine();
    s.endSession();
  }
});
```

Use this for device-side business rules only. Customer payment status and session ownership should still come from Firmngin.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.