Skip to content

Attendant Workflow - Developer Guide

Complete code examples for implementing the attendant-assisted battery swap workflow.

Overview

The attendant workflow handles staff-operated battery swaps at service centers. This workflow provides maximum control and validation, making it ideal for high-value transactions and first-time customers.


Prerequisites

Before starting the attendant workflow, you MUST complete the Pre-Service Setup.

# Phase 0 REQUIRED:
# 1. Odoo Subscription Sync
# 2. Service State Initialization

Why? Without Phase 0: - ❌ Payment status not synced - ❌ serviceStates array is empty - ❌ Service quota tracking unavailable


Phase A1: Customer & Equipment Identification

Customer Identification (QR Code - Primary Method)

QR codes provide instant, error-free customer identification.

⚠️ IMPORTANT - Async Pattern: This is an emit/echo async operation. You publish to the request topic and subscribe to a DIFFERENT response topic to get the result.


Request

Publish Topic:

emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/identify_customer

Payload:

{
  "timestamp": "2025-01-15T09:00:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-customer-id-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "IDENTIFY_CUSTOMER",
    "qr_code_data": "QR_CUSTOMER_TEST_001",
    "attendant_station": "STATION_001"
  }
}


Response (Subscribe to this topic)

Subscribe Topic:

echo/abs/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/identify_customer

MQTT Properties:

QoS: 0
Correlation Data: att-customer-id-001
Message Expiry Interval: 300

User Properties:
  agent_type: BSS_AGENT_V2
  response_type: AGENT_CALCULATION_RESULT
  response_pattern: echo
  platform_version: 2.0.0
  application: abs-platform

Response Payload:

{
  "timestamp": "2025-11-14T10:53:45.067Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-customer-id-001re_abs_identify",
  "actor": {
    "type": "agent",
    "id": "bss-agent-v2"
  },
  "data": {
    "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
    "success": true,
    "signals": [
      "CUSTOMER_IDENTIFIED_SUCCESS"
    ],
    "metadata": {
      "customer_id": "customer-002",
      "identification_method": "QR_CODE",
      "service_plan_data": {
        "servicePlanId": "bss-plan-weekly-freedom-nairobi-v2-plan1",
        "customerId": "customer-002",
        "status": "ACTIVE",
        "serviceState": "BATTERY_RETURNED",
        "paymentState": "CURRENT",
        "serviceAccountId": "SA_1760257691533_nguwif9",
        "serviceStates": [
          {
            "service_id": "svc-battery-fleet-kenya-premium",
            "used": 2,
            "quota": 100000000,
            "current_asset": "BAT_NEW_ATT_002"
          },
          {
            "service_id": "svc-swap-network-kenya",
            "used": 0,
            "quota": 100000000,
            "current_asset": null
          }
        ],
        "quotaUsed": 2,
        "quotaLimit": 200000000,
        "templateId": "bss-template-weekly-freedom-nairobi-v2"
      }
    },
    "timestamp": "2025-11-14T10:53:44.921Z"
  }
}


Understanding the Response

Key Fields: - data.success: true - Operation succeeded - data.signals: ["CUSTOMER_IDENTIFIED_SUCCESS"] - Business signal - correlation_id: Matches your request for tracking async responses - request_topic: Original topic that triggered this response

Service Plan Data (Critical for Frontend): - status: Current plan status (ACTIVE, DRAFT, etc.) - serviceState: Current FSM state (BATTERY_RETURNED, WAIT_BATTERY_ISSUE, etc.) - paymentState: Payment FSM state (CURRENT, OVERDUE, etc.) - serviceStates[]: Array of quota tracking per service - quotaUsed / quotaLimit: Total quota consumption

Agent Diagnostics: - agent_state_update: Agent execution metrics - signal_compression: How signals were processed - fsm_execution: FSM transition results

MQTT Message Properties: - Correlation Data: Matches your correlation_id for request/response matching - Message Expiry Interval: 300 seconds (5 minutes) - response expires if not consumed - User Properties: Metadata for filtering and routing


How to Use in Your Frontend

// Subscribe BEFORE publishing request
client.subscribe('echo/abs/service/plan/+/identify_customer');

// Handle response
client.on('message', (topic, message, packet) => {
  const response = JSON.parse(message.toString());

  // Match correlation ID
  if (response.correlation_id === 'att-customer-id-001') {
    if (response.data.success) {
      const customerData = response.data.metadata.service_plan_data;
      console.log('Customer ID:', customerData.customerId);
      console.log('Service State:', customerData.serviceState);
      console.log('Payment State:', customerData.paymentState);
      console.log('Quota:', customerData.quotaUsed, '/', customerData.quotaLimit);
    }
  }
});

// Publish request
client.publish(
  'emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/identify_customer',
  JSON.stringify(requestPayload)
);

FSM Impact: - No FSM inputs generated (identification only)


Equipment Identification

Identify returned battery and validate fleet membership.

⚠️ IMPORTANT - Chained Async Pattern: This uses a call/rtrn → emit/echo chain. Your request triggers an intermediate response, then the agent makes another call to the service layer. Subscribe to the FINAL echo response to get equipment identification details.


Request

Publish Topic:

call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan2/identify_equipment

Payload:

{
  "timestamp": "2025-01-15T09:15:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan2",
  "correlation_id": "att-equipment-id-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "IDENTIFY_RETURNED_EQUIPMENT",
    "equipment_id": "BAT-IDEM-001",
    "attendant_station": "STATION_001"
  }
}


Intermediate Response (Acknowledgment - Optional to Monitor)

Topic:

rtrn/abs/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan2/identify_equipment_response

Payload:

{
  "signals": ["EQUIPMENT_IDENTIFICATION_REQUESTED"],
  "metadata": {
    "equipment_identified": true,
    "fleet_validation_required": true,
    "pattern": "call/rtrn"
  }
}

What This Tells You: - ✅ Request received and acknowledged - ✅ Agent is now calling the service layer (emit/abs/service/plan/.../identify_equipment) - ⏳ Wait for the final echo response with equipment identification data


Final Response (Subscribe Here - This Has the Equipment Data!)

Subscribe Topic:

echo/abs/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan2/identify_equipment

MQTT Properties:

QoS: 0
Correlation Data: att-equipment-id-001_abs_identify_equip
Message Expiry Interval: 300

User Properties:
  agent_type: BSS_AGENT_V2
  response_type: AGENT_CALCULATION_RESULT
  response_pattern: echo
  platform_version: 2.0.0
  application: abs-platform

Response Payload:

{
  "timestamp": "2025-11-14T11:12:46.232Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan2",
  "correlation_id": "att-equipment-id-43e1_abs_identify_equip",
  "actor": {
    "type": "agent",
    "id": "bss-agent-v2"
  },
  "data": {
    "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan2",
    "success": true,
    "signals": [
      "EQUIPMENT_BELONGS_TO_USER"
    ],
    "metadata": {
      "equipment_belongs_to_user": true,
      "equipment_id": "BAT-IDEM-001",
      "matching_service": {
        "service_id": "svc-battery-fleet-kenya-premium",
        "used": 3,
        "quota": 100000000,
        "current_asset": "BAT-IDEM-001"
      },
      "all_current_assets": [
        "BAT-IDEM-001"
      ],
      "total_services_checked": 2,
      "identification_method": "service_state_current_asset_match"
    },
    "timestamp": "2025-11-14T11:12:46.127Z"
  }
}


Understanding the Response

Key Equipment Fields: - equipment_belongs_to_user: true - Equipment ownership verified - equipment_id: "BAT-IDEM-001" - The identified battery - service_plan_id: Service plan associated with equipment

Matching Service Details:

matching_service: {
  service_id: "svc-battery-fleet-kenya-premium",
  used: 3,           // Customer has used this service 3 times
  quota: 100000000,  // Available quota
  current_asset: "BAT-IDEM-001"  // ✅ Confirmed current battery
}

Signals: - EQUIPMENT_BELONGS_TO_USER: Equipment verified as belonging to this customer - EQUIPMENT_DOES_NOT_BELONG: Would be sent if equipment doesn't match

Identification Details: - all_current_assets: List of all assets currently assigned to customer - total_services_checked: Number of services checked for asset match - identification_method: How equipment was identified (e.g., service_state_current_asset_match)


How to Use in Your Frontend

// Subscribe to FINAL response
client.subscribe('echo/abs/service/plan/+/identify_equipment');

// Handle response
client.on('message', (topic, message, packet) => {
  const response = JSON.parse(message.toString());

  // Only process echo responses with matching correlation
  if (packet.properties?.userProperties?.response_pattern === 'echo' &&
      response.correlation_id.startsWith('att-equipment-id-001')) {

    const metadata = response.data.metadata;

    // Check equipment ownership
    if (metadata.equipment_belongs_to_user) {
      console.log('✅ Equipment verified:', metadata.equipment_id);
      console.log('Service:', metadata.matching_service.service_id);
      console.log('Usage:', metadata.matching_service.used);

      // Proceed with check-in
      proceedWithCheckIn(metadata.equipment_id);
    } else {
      alert('❌ Equipment does not belong to this customer!');
      return;
    }
  }
});

// Publish request
client.publish(
  'call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan2/identify_equipment',
  JSON.stringify(requestPayload)
);

FSM Impact: - No FSM inputs generated (identification only)


Phase A2: Validation

Customer Status Validation

Verify customer is in good standing.

⚠️ IMPORTANT - Chained Async Pattern: This uses a call/rtrn → emit/echo chain. Your request triggers an intermediate response, then the agent makes another call to the service layer. Subscribe to the FINAL echo response to get customer status details.


Request

Publish Topic:

call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/validate_customer

Payload:

{
  "timestamp": "2025-01-15T09:25:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-customer-val-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "VALIDATE_CUSTOMER_STATUS",
    "customer_id": "customer-test-rider-001"
  }
}


Intermediate Response (Acknowledgment - Optional to Monitor)

Topic:

rtrn/abs/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/validate_customer_response

What This Tells You: - ✅ Request received and acknowledged - ✅ Agent is now calling the service layer (emit/abs/service/plan/.../validate_customer_status) - ⏳ Wait for the final echo response with customer status data


Final Response (Subscribe Here - This Has the Customer Status!)

Subscribe Topic:

echo/abs/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/validate_customer_status

MQTT Properties:

QoS: 0
Correlation Data: att-customer-val-wersr3_abs_customer_status
Message Expiry Interval: 300

User Properties:
  agent_type: BSS_AGENT_V2
  response_type: AGENT_CALCULATION_RESULT
  response_pattern: echo
  platform_version: 2.0.0
  application: abs-platform

Response Payload:

{
  "timestamp": "2025-11-14T11:17:14.288Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-customer-val-001_abs_customer_status",
  "actor": {
    "type": "agent",
    "id": "bss-agent-v2"
  },
  "data": {
    "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
    "success": true,
    "signals": [
      "CUSTOMER_STATUS_ACTIVE"
    ],
    "metadata": {
      "customer_is_active": true,
      "customer_is_inactive": false,
      "status_reason": "ACTIVE",
      "customer_id": "customer-002"
    },
    "timestamp": "2025-11-14T11:17:14.176Z"
  }
}


Understanding the Response

Key Customer Status Fields: - customer_is_active: true - Customer can transact - customer_is_inactive: false - No blocking issues - status_reason: "ACTIVE" - Status explanation - customer_id: "customer-002" - Verified customer ID

Signals: - CUSTOMER_STATUS_ACTIVE: Customer is active and can proceed - CUSTOMER_STATUS_INACTIVE: Would be sent if customer is blocked

Status Reasons: - ACTIVE: Customer can perform swaps (service and payment states are good) - INACTIVE: Customer is blocked (service or payment issues)


How to Use in Your Frontend

// Subscribe to FINAL response
client.subscribe('echo/abs/service/plan/+/validate_customer_status');

// Handle response
client.on('message', (topic, message, packet) => {
  const response = JSON.parse(message.toString());

  // Match correlation ID
  if (response.correlation_id.includes('_abs_customer_status')) {
    const metadata = response.data.metadata;

    // Check customer status
    if (metadata.customer_is_active) {
      console.log('✅ Customer active:', metadata.customer_id);
      console.log('Status:', metadata.status_reason);

      // Proceed with service
      proceedWithService();
    } else {
      alert(`❌ Customer inactive: ${metadata.status_reason}`);
      // Show appropriate error message to attendant
      return;
    }
  }
});

// Publish request
client.publish(
  'call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/validate_customer',
  JSON.stringify(requestPayload)
);

FSM Impact: - No FSM inputs generated (validation only)


Payment Status Validation

Verify payment with Odoo before service.

⚠️ IMPORTANT - Async Pattern: This is an emit/echo async operation. You publish to the request topic and subscribe to a DIFFERENT response topic to get the result.


Request

Publish Topic:

emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/validate_payment

Payload:

{
  "timestamp": "2025-01-15T09:30:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-payment-val-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "VALIDATE_PAYMENT_STATUS",
    "emergency_wait_approved": false,
    "asset_ready_to_deploy": true
  }
}


Response (Subscribe to this topic)

Subscribe Topic:

echo/abs/service/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/validate_payment_status

MQTT Properties:

QoS: 0
Correlation Data: att-payment-val-001_abs_payment_status
Message Expiry Interval: 300

User Properties:
  agent_type: BSS_AGENT_V2
  response_type: AGENT_CALCULATION_RESULT
  response_pattern: echo
  platform_version: 2.0.0
  application: abs-platform

Response Payload:

{
  "timestamp": "2025-11-14T11:23:31.003Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-payment-val-001_abs_payment_status",
  "actor": {
    "type": "agent",
    "id": "bss-agent-v2"
  },
  "data": {
    "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
    "success": true,
    "signals": [
      "PAYMENT_STATUS_GOOD"
    ],
    "metadata": {
      "payment_status_reason": "PAYMENT_CURRENT"
    },
    "timestamp": "2025-11-14T11:23:30.897Z"
  }
}


Understanding the Response

Key Payment Field: - payment_status_reason: "PAYMENT_CURRENT" - Payment status explanation

Signals: - PAYMENT_STATUS_GOOD: Payment is current, can proceed with service - PAYMENT_STATUS_REQUIRES_PAYMENT: Payment is due (would be sent if payment needed) - PAYMENT_STATUS_BLOCKED: Payment overdue, service blocked (would be sent if blocked)

Payment Status Reasons: - PAYMENT_CURRENT: Payment is up to date, customer can transact - PAYMENT_DUE: Payment is required before service - PAYMENT_OVERDUE: Payment is overdue, service blocked


How to Use in Your Frontend

// Subscribe BEFORE publishing request
client.subscribe('echo/abs/service/plan/+/validate_payment_status');

// Handle response
client.on('message', (topic, message, packet) => {
  const response = JSON.parse(message.toString());

  // Match correlation ID
  if (response.correlation_id.includes('_abs_payment_status')) {
    const metadata = response.data.metadata;
    const signals = response.data.signals;

    // Check payment status based on signal
    if (signals.includes('PAYMENT_STATUS_GOOD')) {
      console.log('✅ Payment status good - can proceed');
      console.log('Status:', metadata.payment_status_reason);

      // Proceed with swap
      proceedWithSwap();
    } else if (signals.includes('PAYMENT_STATUS_REQUIRES_PAYMENT')) {
      // Payment required
      alert(`⚠️ Payment required: ${metadata.payment_status_reason}`);
      showPaymentScreen();
    } else if (signals.includes('PAYMENT_STATUS_BLOCKED')) {
      // Payment blocked
      alert(`❌ Service blocked: ${metadata.payment_status_reason}`);
      showPaymentBlockedMessage();
    }
  }
});

// Publish request
client.publish(
  'emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/validate_payment',
  JSON.stringify(requestPayload)
);

FSM Impact: - No FSM inputs generated (validation only)


Equipment Condition Validation

Assess returned equipment condition.

Publish Topic:

call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/validate_equipment_condition

Payload:

{
  "timestamp": "2025-01-15T09:40:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-condition-val-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "VALIDATE_EQUIPMENT_CONDITION",
    "equipment_id": "BAT_RETURN_ATT_001",
    "damage_assessment_required": false
  }
}

Subscribe Topic:

rtrn/abs/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/validate_equipment_condition_response


Service Quota Validation

Verify customer has available quota.

⚠️ IMPORTANT - Chained Async Pattern: This uses a call/rtrn → emit/echo chain. Your request triggers an intermediate response, then the agent makes another call to the service layer. Subscribe to the FINAL echo response to get quota details.


Request

Publish Topic:

call/uxi/attendant/plan/service-plan-basic-newest-c/validate_quota

Payload:

{
  "timestamp": "2025-01-15T09:50:00Z",
  "plan_id": "service-plan-basic-newest-c",
  "correlation_id": "att-quota-val-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "VALIDATE_SERVICE_QUOTA"
  }
}


Intermediate Response (Acknowledgment - Optional to Monitor)

Topic:

rtrn/abs/attendant/plan/service-plan-basic-newest-c/validate_quota

MQTT Properties:

QoS: 0
Correlation Data: att-quota-val-001
Message Expiry Interval: 300

User Properties:
  agent_type: BSS_AGENT_V2
  response_type: AGENT_CALCULATION_RESULT
  response_pattern: rtrn
  platform_version: 2.0.0
  application: abs-platform

Payload:

{
  "timestamp": "2025-10-23T07:47:33.170Z",
  "plan_id": "service-plan-basic-newest-c",
  "correlation_id": "att-quota-val-001",
  "actor": {
    "type": "agent",
    "id": "bss-agent-v2"
  },
  "request_topic": "call/uxi/attendant/plan/service-plan-basic-newest-c/validate_quota",
  "data": {
    "plan_id": "service-plan-basic-newest-c",
    "success": true,
    "signals": ["SERVICE_QUOTA_VALIDATION_REQUESTED"],
    "metadata": {
      "called": "validateServiceQuota",
      "timestamp": "2025-10-23T07:47:33.071Z",
      "quota_validated": true,
      "service_plan_based": true,
      "quota_exhaustion_checked": true,
      "abs_integration": true,
      "abs_action": "VALIDATE_SERVICE_QUOTA_ABS",
      "call_topic": "emit/abs/service/plan/service-plan-basic-newest-c/validate_service_quota",
      "return_topic": "abs/service/plan/service-plan-basic-newest-c/validate_service_quota/response",
      "timeout_seconds": 15,
      "retry_attempts": 3,
      "retry_backoff": [3, 8, 15],
      "pattern": "call/rtrn",
      "mqtt_delivered": true,
      "correlation_id": "att-quota-val-001"
    },
    "timestamp": "2025-10-23T07:47:33.071Z",
    "fsmInputs": []
  }
}

What This Tells You: - ✅ Request received and acknowledged - ✅ Agent is now calling the service layer (emit/abs/service/plan/.../validate_service_quota) - ⏳ Wait for the final echo response with actual quota data


Final Response (Subscribe Here - This Has the Quota Data!)

Subscribe Topic:

echo/abs/service/plan/service-plan-basic-newest-c/validate_service_quota

MQTT Properties:

QoS: 0
Correlation Data: att-quota-val-001
Message Expiry Interval: 300

User Properties:
  agent_type: BSS_AGENT_V2
  response_type: AGENT_CALCULATION_RESULT
  response_pattern: echo
  platform_version: 2.0.0
  application: abs-platform

Response Payload:

{
  "timestamp": "2025-11-14T11:28:22.475Z",
  "plan_id": "service-plan-basic-newest-c",
  "correlation_id": "att-quota-val-001_abs_quota",
  "actor": {
    "type": "agent",
    "id": "bss-agent-v2"
  },
  "data": {
    "plan_id": "service-plan-basic-newest-c",
    "success": true,
    "signals": [
      "QUOTA_AVAILABLE"
    ],
    "metadata": {
      "quota_exhausted": false,
      "quota_used": 95,
      "quota_limit": 120,
      "quota_available": 25,
      "quota_percentage": 79.16666666666666,
      "service_quota_details": [
        {
          "service_id": "svc-swap-station-latest-a",
          "used": 0,
          "quota": 50,
          "available": 50,
          "exhausted": false
        },
        {
          "service_id": "svc-battery-fleet-latest-a",
          "used": 95,
          "quota": 70,
          "available": -25,
          "exhausted": true
        }
      ],
      "quota_calculation": {
        "total_services": 2,
        "services_exhausted": 1,
        "calculation_method": "aggregate_from_service_states"
      }
    },
    "timestamp": "2025-11-14T11:28:22.367Z"
  }
}


Understanding the Response

Key Quota Fields: - quota_exhausted: false - Overall quota status - quota_used: 95 - Total quota consumed - quota_limit: 120 - Maximum allowed quota - quota_available: 25 - Remaining quota - quota_percentage: 79.17% - Usage percentage

Service-Level Quota Breakdown:

service_quota_details: [
  {
    service_id: "svc-swap-station-latest-a",
    used: 0,
    quota: 50,
    available: 50,
    exhausted: false  // ✅ Swap station access OK
  },
  {
    service_id: "svc-battery-fleet-latest-a",
    used: 95,
    quota: 70,
    available: -25,    // ⚠️ OVER QUOTA by 25!
    exhausted: true    // ❌ Battery fleet exhausted
  }
]

Quota Calculation: - total_services: Total number of services checked - services_exhausted: Count of services that have exhausted their quota - calculation_method: How quota was calculated (e.g., aggregate_from_service_states)

Signals: - QUOTA_AVAILABLE: Service can proceed (even if one service is exhausted, overall quota allows it) - QUOTA_EXHAUSTED: Would be sent if overall quota is depleted


How to Use in Your Frontend

// Subscribe to FINAL response (not the intermediate rtrn)
client.subscribe('echo/abs/service/plan/+/validate_service_quota');

// Handle response
client.on('message', (topic, message, packet) => {
  const response = JSON.parse(message.toString());

  // Match correlation ID
  if (response.correlation_id.includes('_abs_quota')) {
    const metadata = response.data.metadata;
    const signals = response.data.signals;

    // Check overall quota
    if (metadata.quota_exhausted) {
      alert('❌ Quota exhausted! Please top-up.');
      return;
    }

    // Display quota status
    console.log(`Quota: ${metadata.quota_used}/${metadata.quota_limit}`);
    console.log(`Available: ${metadata.quota_available} (${metadata.quota_percentage.toFixed(2)}%)`);

    // Check individual services
    metadata.service_quota_details.forEach(service => {
      if (service.exhausted) {
        console.warn(`⚠️ ${service.service_id} exhausted!`);
      }
    });

    // Display quota calculation info
    console.log(`Services checked: ${metadata.quota_calculation.total_services}`);
    console.log(`Services exhausted: ${metadata.quota_calculation.services_exhausted}`);

    // Proceed with swap if quota available
    if (signals.includes('QUOTA_AVAILABLE')) {
      proceedWithSwap();
    }
  }
});

// Publish request
client.publish(
  'call/uxi/attendant/plan/service-plan-basic-newest-c/validate_quota',
  JSON.stringify(requestPayload)
);

Message Flow Diagram

Frontend                Agent                Service Layer
   |                      |                        |
   |--call/uxi---------->|                        |
   |  validate_quota     |                        |
   |                     |                        |
   |<--rtrn/abs----------|                        |
   |  (acknowledged)     |                        |
   |                     |                        |
   |                     |--emit/abs------------->|
   |                     |  validate_service_quota|
   |                     |                        |
   |                     |<--echo/abs-------------|
   |<--------------------|  (quota details)       |
   |                     |                        |

Important Notes: 1. The rtrn/abs response is immediate acknowledgment 2. The echo/abs response contains the actual quota data 3. Both responses share the same correlation_id 4. Frontend should only act on the final echo/abs response

FSM Impact: - No FSM inputs generated (validation only)


Topup Payment Validation

Verify if topup payment is required and authorized for quota replenishment.

⚠️ IMPORTANT - Chained Async Pattern: This uses a call/rtrn → emit/echo chain. Your request triggers an intermediate response, then the agent makes another call to the service layer. Subscribe to the FINAL echo response to get topup authorization details.


Request

Publish Topic:

call/uxi/attendant/plan/service-plan-basic-newest-c/validate_topup

Payload:

{
  "timestamp": "2025-01-15T09:55:00Z",
  "plan_id": "service-plan-basic-newest-c",
  "correlation_id": "att-topup-val-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "VALIDATE_TOPUP_PAYMENT",
    "topup_required": true,
    "topup_amount": 50.0
  }
}


Intermediate Response (Acknowledgment - Optional to Monitor)

Topic:

rtrn/abs/attendant/plan/service-plan-basic-newest-c/validate_topup

MQTT Properties:

QoS: 0
Correlation Data: att-topup-val-001
Message Expiry Interval: 300

User Properties:
  agent_type: BSS_AGENT_V2
  response_type: AGENT_CALCULATION_RESULT
  response_pattern: rtrn
  platform_version: 2.0.0
  application: abs-platform

Payload:

{
  "timestamp": "2025-10-23T07:57:13.212Z",
  "plan_id": "service-plan-basic-newest-c",
  "correlation_id": "att-topup-val-001",
  "actor": {
    "type": "agent",
    "id": "bss-agent-v2"
  },
  "request_topic": "call/uxi/attendant/plan/service-plan-basic-newest-c/validate_topup",
  "data": {
    "plan_id": "service-plan-basic-newest-c",
    "success": true,
    "signals": ["TOPUP_PAYMENT_VALIDATION_REQUESTED"],
    "metadata": {
      "called": "validateTopupPayment",
      "timestamp": "2025-10-23T07:57:13.104Z",
      "topup_validated": true,
      "payment_authorization_checked": true,
      "abs_integration": true,
      "abs_action": "VALIDATE_TOPUP_PAYMENT_ABS",
      "call_topic": "emit/abs/service/plan/service-plan-basic-newest-c/validate_topup_payment",
      "return_topic": "abs/service/plan/service-plan-basic-newest-c/validate_topup_payment/response",
      "timeout_seconds": 15,
      "retry_attempts": 3,
      "retry_backoff": [3, 8, 15],
      "pattern": "call/rtrn",
      "mqtt_delivered": true,
      "correlation_id": "att-topup-val-001",
      "agent_state_update": {
        "agent_version": "2.0.0",
        "last_activity": "2025-10-23T07:57:13.104Z",
        "execution_count": 1,
        "swaps_today": 0,
        "suspended_until": null
      },
      "signal_compression": {
        "signals_provided": 1,
        "fsm_inputs_generated": 0,
        "compression_applied": false
      },
      "fsm_execution": {
        "transitions_executed": 0,
        "successful_transitions": 0,
        "fsm_results": []
      }
    },
    "timestamp": "2025-10-23T07:57:13.104Z",
    "fsmInputs": []
  }
}

What This Tells You: - ✅ Request received and acknowledged - ✅ Agent is now calling the service layer (emit/abs/service/plan/.../validate_topup_payment) - ⏳ Wait for the final echo response with authorization result


Final Response (Subscribe Here - This Has the Authorization Data!)

Subscribe Topic:

echo/abs/service/plan/service-plan-basic-newest-c/validate_topup_payment

MQTT Properties:

QoS: 0
Correlation Data: att-topup-val-001
Message Expiry Interval: 300

User Properties:
  agent_type: BSS_AGENT_V2
  response_type: AGENT_CALCULATION_RESULT
  response_pattern: echo
  platform_version: 2.0.0
  application: abs-platform

Response Payload:

{
  "timestamp": "2025-11-14T11:31:02.793Z",
  "plan_id": "service-plan-basic-newest-c",
  "correlation_id": "att-topup-val-erew5_abs_topup",
  "actor": {
    "type": "agent",
    "id": "bss-agent-v2"
  },
  "data": {
    "plan_id": "service-plan-basic-newest-c",
    "success": true,
    "signals": [
      "TOPUP_PAYMENT_NOT_REQUIRED"
    ],
    "metadata": {
      "topup_required": false,
      "topup_amount": 50,
      "payment_authorized": true,
      "payment_state": "INITIAL",
      "service_state": "BATTERY_ISSUED",
      "topup_calculation": {
        "states_checked": [
          "RENEWAL_DUE",
          "DEPOSIT_DUE",
          "FINAL_DUE"
        ],
        "quota_check": {
          "used": 95,
          "limit": 120,
          "exhausted": false
        },
        "amount_source": "request_provided"
      }
    },
    "timestamp": "2025-11-14T11:31:02.689Z"
  }
}


Understanding the Response

Key Authorization Fields: - topup_required: false - No topup needed at this time - topup_amount: 50 - Amount requested/validated - payment_authorized: true - Payment is authorized if needed - payment_state: Current payment FSM state (INITIAL, RENEWAL_DUE, DEPOSIT_DUE, FINAL_DUE, CURRENT, COMPLETE) - service_state: Current service FSM state (INITIAL, WAIT_BATTERY_ISSUE, BATTERY_ISSUED, BATTERY_RETURNED, BATTERY_LOST, COMPLETE)

Topup Calculation Details:

topup_calculation: {
  states_checked: ["RENEWAL_DUE", "DEPOSIT_DUE", "FINAL_DUE"],  // Payment states that trigger topup requirement
  quota_check: {
    used: 95,
    limit: 120,
    exhausted: false
  },
  amount_source: "request_provided"  // or "system_calculated"
}

Signals: - TOPUP_PAYMENT_NOT_REQUIRED: Quota sufficient, no topup needed - TOPUP_PAYMENT_REQUIRED: Quota low, topup required - TOPUP_PAYMENT_AUTHORIZED: Payment cleared, can proceed with topup - TOPUP_PAYMENT_DECLINED: Payment not authorized


How to Use in Your Frontend

// Subscribe to FINAL response
client.subscribe('echo/abs/service/plan/+/validate_topup_payment');

// Handle response
client.on('message', (topic, message, packet) => {
  const response = JSON.parse(message.toString());

  // Match correlation ID
  if (response.correlation_id.includes('_abs_topup')) {
    const metadata = response.data.metadata;
    const signals = response.data.signals;

    // Check if topup is required
    if (signals.includes('TOPUP_PAYMENT_REQUIRED')) {
      // Show topup payment UI
      console.log('⚠️ Topup required:', metadata.topup_amount);
      showTopupPaymentForm(metadata.topup_amount);
    } else if (signals.includes('TOPUP_PAYMENT_NOT_REQUIRED')) {
      // Quota is sufficient, proceed without topup
      console.log('✅ Quota sufficient, no topup needed');
      console.log('Payment State:', metadata.payment_state);
      console.log('Service State:', metadata.service_state);
      proceedWithSwap();
    } else if (signals.includes('TOPUP_PAYMENT_AUTHORIZED')) {
      console.log('✅ Topup payment authorized');
      processTopupPayment(metadata.topup_amount);
    } else if (signals.includes('TOPUP_PAYMENT_DECLINED')) {
      console.log('❌ Payment declined');
      showPaymentFailedMessage();
    }

    // Display quota status from calculation
    const quota = metadata.topup_calculation.quota_check;
    console.log(`Quota: ${quota.used}/${quota.limit} (Exhausted: ${quota.exhausted})`);
    console.log('Amount source:', metadata.topup_calculation.amount_source);
  }
});

// Publish request
client.publish(
  'call/uxi/attendant/plan/service-plan-basic-newest-c/validate_topup',
  JSON.stringify(requestPayload)
);

Message Flow Diagram

Frontend                Agent                Service Layer
   |                      |                        |
   |--call/uxi---------->|                        |
   |  validate_topup     |                        |
   |                     |                        |
   |<--rtrn/abs----------|                        |
   |  (acknowledged)     |                        |
   |                     |                        |
   |                     |--emit/abs------------->|
   |                     |  validate_topup_payment|
   |                     |                        |
   |                     |<--echo/abs-------------|
   |<--------------------|  (authorization)       |
   |                     |                        |

Important Notes: 1. The rtrn/abs response is immediate acknowledgment 2. The echo/abs response contains the actual authorization and topup requirement 3. Both responses share the same correlation_id 4. Frontend should only act on the final echo/abs response 5. Topup may not be required even if requested (system checks current quota)

FSM Impact: - No FSM inputs generated (validation only)


Phase A3: Transaction Execution

Transaction Order Matters!

First-Time Customer (no battery to return): 1. Payment Collection → Equipment Checkout 2. Skip Equipment Check-In

Returning Customer (has battery): 1. Equipment Check-In → Equipment Checkout 2. Optional: Payment Collection (for topup)


Equipment Check-In (Returning Customer Only)

Accept returned battery.

Publish Topic:

call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/equipment_checkin

Payload:

{
  "timestamp": "2025-01-15T10:05:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-checkin-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "EQUIPMENT_CHECKIN",
    "equipment_id": "BAT_RETURN_ATT_001",
    "condition_accepted": true
  }
}

Subscribe Topic:

rtrn/abs/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/equipment_checkin_response

FSM Impact: - service_cycle: BATTERY_ISSUEDBATTERY_RETURNED


Equipment Checkout

Issue replacement battery (with optional energy tracking).

Basic Checkout (Swap Count Only):

Publish Topic:

call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/equipment_checkout

Payload:

{
  "timestamp": "2025-01-15T10:15:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-checkout-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "EQUIPMENT_CHECKOUT",
    "replacement_equipment_id": "BAT_NEW_ATT_001"
  }
}

Subscribe Topic:

rtrn/abs/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/equipment_checkout_response

With Energy Metering (Energy Tracking Plans):

Publish Topic:

call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/equipment_checkout

Payload:

{
  "timestamp": "2025-01-15T10:15:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-checkout-energy-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "EQUIPMENT_CHECKOUT",
    "replacement_equipment_id": "BAT_NEW_ATT_002",
    "energy_transferred": 45.5,
    "service_duration": 240
  }
}

What Happens: 1. ✅ Battery issued to customer 2. ✅ FSM: BATTERY_ISSUED input generated 3. ✅ Automatic service state update (W5 triggered via MQTT) 4. ✅ Battery service: used += 1 5. ✅ Energy service: used += 45.5 kWh (if provided)


Payment Request

Calculate and request payment.

Publish Topic:

call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/payment_request

Payload:

{
  "timestamp": "2025-01-15T10:20:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-payment-req-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "PROCESS_PAYMENT_REQUEST",
    "payment_amount": 100.0,
    "partial_service_attempted": false
  }
}

Subscribe Topic:

rtrn/abs/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/payment_request_response


Payment Collection

Collect payment from customer.

Publish Topic:

call/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/collect_payment

Payload:

{
  "timestamp": "2025-01-15T10:30:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-payment-collect-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "COLLECT_PAYMENT",
    "payment_method": "mobile_money",
    "offline_mode": false,
    "cached_data_available": true,
    "mqtt_connectivity_available": true
  }
}

Subscribe Topic:

rtrn/abs/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/collect_payment_response

FSM Impact (conditional): - From INITIAL: CONTRACT_SIGNED, DEPOSIT_PAID - From DEPOSIT_DUE: DEPOSIT_PAID - From RENEWAL_DUE: RENEWAL_PAID


Report Payment and Service Completion (A3.5)

Overview

The REPORT_PAYMENT_AND_SERVICE_COMPLETION endpoint provides a unified interface for attendants to report both payment received and service rendered in a single transaction. This ensures proper sequencing where:

  1. Payment is processed first → Updates quota (via topup)
  2. Service is completed second → Consumes quota and updates service states

This order is critical to ensure customers have quota available before service consumption is recorded.

Action Name

REPORT_PAYMENT_AND_SERVICE_COMPLETION

Quick Copy-Paste Examples

Topic:

emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/payment_and_service

Example 1: Payment + Service (Full Transaction)

{
  "timestamp": "2025-01-19T10:15:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-txn-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_001",
    "payment_data": {
      "service_id": "svc-electricity-energy-tracking-togo",
      "payment_amount": 15,
      "payment_reference": "MPESA-TXN-123456789",
      "payment_method": "MPESA",
      "payment_type": "TOP_UP"
    },
    "service_data": {
      "old_battery_id": "BAT_OLD_001",
      "new_battery_id": "BAT_NEW_002",
      "energy_transferred": 100,
      "service_duration": 240
    }
  }
}

Example 2: Payment Only (Top-up Quota)

{
  "timestamp": "2025-01-19T11:30:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-payment-002",
  "actor": {
    "type": "attendant",
    "id": "attendant-002"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_002",
    "payment_data": {
      "service_id": "service-battery-swap-nairobi",
      "payment_amount": 1000,
      "payment_reference": "CASH-RECEIPT-20250119",
      "payment_method": "CASH",
      "payment_type": "TOP_UP"
    }
  }
}

Example 3: Service Only (Using Existing Quota)

{
  "timestamp": "2025-01-19T14:20:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-service-003",
  "actor": {
    "type": "attendant",
    "id": "attendant-003"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_003",
    "service_data": {
      "old_battery_id": "BAT_OLD_003",
      "new_battery_id": "BAT_NEW_004",
      "energy_transferred": 52.3,
      "service_duration": 180
    }
  }
}

Example 4: First-Time Customer (No Old Battery)

{
  "timestamp": "2025-01-19T09:00:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-new-customer-004",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_001",
    "payment_data": {
      "service_id": "service-battery-swap-nairobi",
      "payment_amount": 500,
      "payment_reference": "MPESA-TXN-987654321",
      "payment_method": "MPESA",
      "payment_type": "DEPOSIT"
    },
    "service_data": {
      "new_battery_id": "BAT_NEW_005"
    }
  }
}

MQTT Topic

Publish Topic:

emit/uxi/attendant/plan/{plan_id}/payment_and_service

Example:

emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/payment_and_service

Topic Pattern Explanation: - emit - One-way message (fire-and-forget, no echo expected) - uxi - User experience interface (attendant-facing operations) - attendant - Attendant workflow domain - plan/{plan_id} - Service plan scope - payment_and_service - Operation identifier

Subscribe Topic (Optional - For Monitoring):

If you want to monitor the processing status, you can optionally subscribe to:

echo/abs/service/plan/{plan_id}/payment_and_service

However, since this uses direct function calls internally, the response is synchronous and returned immediately in the publish acknowledgment. The echo topic is primarily for audit/logging purposes.


Request Structure

Standard MQTT Format:

{
  "timestamp": "2025-01-15T10:15:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-payment-service-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_001",
    "payment_data": {
      "service_id": "service-battery-swap-nairobi",
      "payment_amount": 500,
      "payment_reference": "MPESA-TXN-123456789",
      "payment_method": "MPESA",
      "payment_type": "TOP_UP"
    },
    "service_data": {
      "old_battery_id": "BAT_OLD_001",
      "new_battery_id": "BAT_NEW_002",
      "energy_transferred": 45.5,
      "service_duration": 240
    }
  }
}

Field Descriptions:

Top-Level Fields: - timestamp: ISO 8601 timestamp - plan_id: Service plan ID (required) - correlation_id: For idempotency tracking (optional but recommended) - actor: Actor information (type: "attendant", id: attendant identifier)

Data Fields: - action: Must be "REPORT_PAYMENT_AND_SERVICE_COMPLETION" - attendant_station: Attendant station identifier (optional, default: 'STATION_DEFAULT') - payment_data: Payment information (optional, at least ONE of payment_data or service_data required) - service_id: Service ID to apply payment to (required) - payment_amount: Payment amount in currency units (required, must be > 0) - payment_reference: External payment reference/transaction ID (required) - payment_method: Payment method (optional, default: 'CASH') - payment_type: Payment type (optional, default: 'TOP_UP') - service_data: Service completion information (optional, at least ONE of payment_data or service_data required) - old_battery_id: Battery being returned (optional, omit for first-time customers) - new_battery_id: New battery being issued (required) - energy_transferred: Energy transferred in kWh (optional) - service_duration: Service duration in seconds (optional)


Processing Flow

Step 1: Payment Processing (if provided)

When payment_data is provided: 1. Validates payment data structure 2. DIRECT CALL to BssStateManagementService.serviceTopup() (no MQTT overhead) 3. System processes payment: - Creates PaymentAction entity - Updates PaymentAccount balance - Calculates quota increase: payment_amount / unit_price - Updates service state quota directly in database

Step 2: Service Completion (if provided)

When service_data is provided: 1. Validates service data structure 2. DIRECT CALL to BssRiderWorkflowService.updateServiceStatesAndBilling() (W5) 3. System processes service completion: - Creates ServiceAction entity - Updates service states (battery swap tracking) - Consumes quota (1 unit per service) - Updates billing information directly in database

Note: Both operations use direct function calls instead of MQTT publishing since they're in the same system. This provides: - ✅ Immediate consistency - ✅ Atomic transactions - ✅ No network latency - ✅ Synchronous error handling


Example Requests

Example 1: Payment + Service Completion (Full Transaction)

{
  "timestamp": "2025-01-19T10:15:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-txn-20250119-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_001",
    "payment_data": {
      "service_id": "service-battery-swap-nairobi",
      "payment_amount": 500,
      "payment_reference": "MPESA-TXN-123456789",
      "payment_method": "MPESA",
      "payment_type": "TOP_UP"
    },
    "service_data": {
      "old_battery_id": "BAT_OLD_001",
      "new_battery_id": "BAT_NEW_002",
      "energy_transferred": 45.5,
      "service_duration": 240
    }
  }
}

Result: Customer pays 500 KES, quota increases by 500 / unit_price units, then service is completed consuming 1 quota unit.

Example 2: Payment Only (Quota Top-up)

{
  "timestamp": "2025-01-19T11:30:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-payment-20250119-002",
  "actor": {
    "type": "attendant",
    "id": "attendant-002"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_002",
    "payment_data": {
      "service_id": "service-battery-swap-nairobi",
      "payment_amount": 1000,
      "payment_reference": "CASH-RECEIPT-2025011902",
      "payment_method": "CASH",
      "payment_type": "TOP_UP"
    }
  }
}

Result: Customer pays 1000 KES, quota increases, no service consumed.

Example 3: Service Only (Using Existing Quota)

{
  "timestamp": "2025-01-19T14:20:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-service-20250119-003",
  "actor": {
    "type": "attendant",
    "id": "attendant-003"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_003",
    "service_data": {
      "old_battery_id": "BAT_OLD_003",
      "new_battery_id": "BAT_NEW_004",
      "energy_transferred": 52.3,
      "service_duration": 180
    }
  }
}

Result: Service completed using customer's existing quota (1 unit consumed).

Example 4: First-Time Customer (No Old Battery)

{
  "timestamp": "2025-01-19T09:00:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-new-customer-20250119-004",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_001",
    "payment_data": {
      "service_id": "service-battery-swap-nairobi",
      "payment_amount": 500,
      "payment_reference": "MPESA-TXN-987654321",
      "payment_method": "MPESA",
      "payment_type": "DEPOSIT"
    },
    "service_data": {
      "new_battery_id": "BAT_NEW_005"
    }
  }
}

Result: First-time customer pays deposit, receives first battery (no old_battery_id needed).


MQTT Usage Examples

Using mosquitto_pub (Command Line)

Full Transaction (Payment + Service):

#!/bin/bash
PLAN_ID="bss-plan-weekly-freedom-nairobi-v2-plan1"
CORRELATION_ID="att-txn-$(date +%s)"

mosquitto_pub -h localhost \
  -t "emit/uxi/attendant/plan/${PLAN_ID}/payment_and_service" \
  -m '{
  "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
  "plan_id": "'${PLAN_ID}'",
  "correlation_id": "'${CORRELATION_ID}'",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_001",
    "payment_data": {
      "service_id": "service-battery-swap-nairobi",
      "payment_amount": 500,
      "payment_reference": "MPESA-'${CORRELATION_ID}'",
      "payment_method": "MPESA",
      "payment_type": "TOP_UP"
    },
    "service_data": {
      "old_battery_id": "BAT_OLD_001",
      "new_battery_id": "BAT_NEW_002",
      "energy_transferred": 45.5,
      "service_duration": 240
    }
  }
}'

echo "✅ Payment and service completion reported with correlation_id: ${CORRELATION_ID}"

Payment Only (Quota Top-up):

mosquitto_pub -h localhost \
  -t "emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/payment_and_service" \
  -m '{
  "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-payment-'$(date +%s)'",
  "actor": {"type": "attendant", "id": "attendant-002"},
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_002",
    "payment_data": {
      "service_id": "service-battery-swap-nairobi",
      "payment_amount": 1000,
      "payment_reference": "CASH-RECEIPT-'$(date +%Y%m%d%H%M%S)'",
      "payment_method": "CASH",
      "payment_type": "TOP_UP"
    }
  }
}'

Service Only (Using Existing Quota):

mosquitto_pub -h localhost \
  -t "emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/payment_and_service" \
  -m '{
  "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-service-'$(date +%s)'",
  "actor": {"type": "attendant", "id": "attendant-003"},
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "attendant_station": "STATION_003",
    "service_data": {
      "old_battery_id": "BAT_OLD_003",
      "new_battery_id": "BAT_NEW_004",
      "energy_transferred": 52.3,
      "service_duration": 180
    }
  }
}'

Using MQTT.js (JavaScript/Node.js):

const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://localhost:1883');

client.on('connect', () => {
  console.log('Connected to MQTT broker');

  // Report payment and service completion
  const payload = {
    timestamp: new Date().toISOString(),
    plan_id: 'bss-plan-weekly-freedom-nairobi-v2-plan1',
    correlation_id: `att-txn-${Date.now()}`,
    actor: {
      type: 'attendant',
      id: 'attendant-001'
    },
    data: {
      action: 'REPORT_PAYMENT_AND_SERVICE_COMPLETION',
      attendant_station: 'STATION_001',
      payment_data: {
        service_id: 'service-battery-swap-nairobi',
        payment_amount: 500,
        payment_reference: 'MPESA-TXN-123456789',
        payment_method: 'MPESA',
        payment_type: 'TOP_UP'
      },
      service_data: {
        old_battery_id: 'BAT_OLD_001',
        new_battery_id: 'BAT_NEW_002',
        energy_transferred: 45.5,
        service_duration: 240
      }
    }
  };

  const topic = `emit/uxi/attendant/plan/${payload.plan_id}/payment_and_service`;

  client.publish(topic, JSON.stringify(payload), { qos: 1 }, (err) => {
    if (err) {
      console.error('Publish failed:', err);
    } else {
      console.log('✅ Payment and service reported:', payload.correlation_id);
    }
    client.end();
  });
});

Using Python (paho-mqtt):

import paho.mqtt.client as mqtt
import json
from datetime import datetime

def on_connect(client, userdata, flags, rc):
    print(f"Connected with result code {rc}")

    # Prepare payload
    payload = {
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
        "correlation_id": f"att-txn-{int(datetime.now().timestamp())}",
        "actor": {
            "type": "attendant",
            "id": "attendant-001"
        },
        "data": {
            "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
            "attendant_station": "STATION_001",
            "payment_data": {
                "service_id": "service-battery-swap-nairobi",
                "payment_amount": 500,
                "payment_reference": "MPESA-TXN-123456789",
                "payment_method": "MPESA",
                "payment_type": "TOP_UP"
            },
            "service_data": {
                "old_battery_id": "BAT_OLD_001",
                "new_battery_id": "BAT_NEW_002",
                "energy_transferred": 45.5,
                "service_duration": 240
            }
        }
    }

    topic = f"emit/uxi/attendant/plan/{payload['plan_id']}/payment_and_service"

    # Publish with QoS 1
    result = client.publish(topic, json.dumps(payload), qos=1)

    if result.rc == mqtt.MQTT_ERR_SUCCESS:
        print(f"✅ Payment and service reported: {payload['correlation_id']}")
    else:
        print(f"❌ Publish failed with code: {result.rc}")

    client.disconnect()

client = mqtt.Client()
client.on_connect = on_connect
client.connect("localhost", 1883, 60)
client.loop_forever()

Monitoring Responses (Optional):

# Terminal 1: Subscribe to responses
mosquitto_sub -h localhost \
  -t "echo/abs/service/plan/+/payment_and_service" \
  -v

# Terminal 2: Publish request
# (use any of the examples above)

Response Structure

Success Response:

{
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "success": true,
  "signals": ["PAYMENT_AND_SERVICE_COMPLETED"],
  "metadata": {
    "called": "reportPaymentAndServiceCompletion",
    "timestamp": "2025-01-19T10:15:00Z",
    "payment_processed": true,
    "service_completed": true,
    "quota_updated": true,
    "service_states_updated": true,
    "processing_order": "payment_first_then_service",
    "processing_method": "direct_function_calls",
    "payment_details": {
      "service_id": "service-battery-swap-nairobi",
      "payment_amount": 500,
      "additional_quota": 10,
      "quota_before": 5,
      "quota_after": 15
    },
    "service_details": {
      "new_battery_id": "BAT_NEW_002",
      "old_battery_id": "BAT_OLD_001",
      "quota_consumed": 1
    },
    "correlation_id": "att-txn-20250119-001",
    "note": "Payment increased quota, then service consumed quota"
  },
  "timestamp": "2025-01-19T10:15:00Z",
  "fsmInputs": []
}

Error Responses:

Missing Both payment_data and service_data:

{
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "success": false,
  "signals": ["INVALID_REQUEST"],
  "metadata": {
    "called": "reportPaymentAndServiceCompletion",
    "validation_failed": true,
    "reason": "At least one of payment_data or service_data must be provided"
  }
}

Invalid Payment Data:

{
  "signals": ["PAYMENT_AMOUNT_INVALID"],
  "metadata": {
    "validation_failed": true,
    "failed_step": "payment_validation",
    "failed_check": "PAYMENT_AMOUNT_INVALID"
  }
}

Missing Service Plan ID:

{
  "signals": ["SERVICE_PLAN_ID_MISSING"],
  "metadata": {
    "validation_failed": true,
    "failed_check": "SERVICE_PLAN_ID_MISSING"
  }
}

Processing Architecture

Direct Function Calls (Not MQTT)

Unlike other workflow steps that require external system coordination, this endpoint uses direct function calls for optimal performance:

Payment Processing: - Calls: BssStateManagementService.serviceTopup() - Direct database access - Immediate quota updates - No MQTT overhead

Service Completion: - Calls: BssRiderWorkflowService.updateServiceStatesAndBilling() (W5) - Direct database access - Immediate state updates - No MQTT overhead

Why Direct Calls? - Both operations are within the same system - No need for asynchronous coordination - Faster execution (no network latency) - Atomic transactions ensure data consistency - Synchronous error handling for better reliability


Database Updates

PaymentAction Entity:

When payment is processed:

{
  paymentActionId: string;      // Generated: "payment_action_{timestamp}"
  paymentAccountId: string;     // From ServicePlan.paymentAccountId
  paymentType: string;          // From payment_data.payment_type
  paymentAmount: number;        // From payment_data.payment_amount
  createdAt: Date;
  updatedAt: Date;
}

ServiceAction Entity:

When service is completed:

{
  serviceActionId: string;      // Generated: "service_action_{timestamp}"
  serviceAccountId: string;     // From ServicePlan.serviceAccountId
  serviceType: string;          // "BATTERY_SWAP"
  serviceAmount: number;        // 1.0 (quota units consumed)
  createdAt: Date;
  updatedAt: Date;
}

ServicePlan Updates:

  1. Service States: Updates service_states[].used (increments by 1)
  2. Service States: Updates service_states[].quota (increases by payment_amount / unit_price)
  3. Service States: Updates service_states[].current_asset (to new_battery_id)

Integration with Other Systems

ABS (Agent Business Services): - Receives payment and service messages - Processes quota calculations - Updates entity records - Publishes echo responses

ARM (Asset Relationship Management): - Receives battery allocation notifications - Updates asset tracking - Manages inventory

Odoo (Billing Integration): - Receives payment notifications - Updates subscription billing - Generates invoices


Business Rules

  1. Processing Order: Payment MUST be processed before service completion to ensure quota availability
  2. Quota Calculation: additional_quota = payment_amount / service.unitPrice
  3. Service Consumption: Each swap consumes exactly 1 quota unit
  4. Idempotency: Use correlation_id to prevent duplicate processing
  5. Atomic Operations: Both payment and service use direct function calls for immediate consistency

Error Handling

Validation Errors: - PAYMENT_SERVICE_ID_MISSING: service_id required in payment_data - PAYMENT_AMOUNT_INVALID: payment_amount must be > 0 - PAYMENT_REFERENCE_MISSING: payment_reference required - NEW_BATTERY_ID_MISSING: new_battery_id required in service_data - SERVICE_PLAN_ID_MISSING: plan_id required

Processing Errors: - PAYMENT_PROCESSING_FAILED: Payment processing failed - SERVICE_COMPLETION_FAILED: Service completion failed - QUOTA_UPDATE_FAILED: Quota update failed


Best Practices

  1. Always provide correlation_id for idempotency and tracking
  2. Use descriptive payment references from external systems (M-Pesa, cash receipt numbers)
  3. Include energy_transferred and service_duration when available for analytics
  4. Handle both operations atomically - if one fails, retry both
  5. Monitor echo responses to confirm successful processing

  • A3.3: PROCESS_PAYMENT_REQUEST - Request payment authorization
  • A3.4: COLLECT_PAYMENT - Collect payment with offline support
  • A3.2: EQUIPMENT_CHECKOUT - Issue equipment only (no payment)
  • W5: UPDATE_SERVICE_STATES_AND_BILLING - Direct service state update

Troubleshooting

Payment Processed but Service Failed:

If payment succeeds but service fails, customer will have extra quota. Retry service completion with same correlation_id:

{
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-txn-20250119-001_service",
  "data": {
    "action": "REPORT_PAYMENT_AND_SERVICE_COMPLETION",
    "service_data": {
      "old_battery_id": "BAT_OLD_001",
      "new_battery_id": "BAT_NEW_002"
    }
  }
}

Duplicate Detection:

If same correlation_id is submitted twice, idempotency service will: - Return cached result if operation completed - Return pending status if operation in progress - Allow retry if operation failed

Quota Exhaustion After Payment:

This should not occur if processing order is correct. If it does: 1. Check payment was processed (PaymentAction created) 2. Check quota was updated (service_states[].quota increased) 3. Verify service consumed correct amount (should be 1 unit)


Phase A4: Reporting & Integration

Real-Time Activity Reporting

Report attendant activities.

Publish Topic:

emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/activity_report

Payload:

{
  "timestamp": "2025-01-15T10:40:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-activity-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "REPORT_ATTENDANT_ACTIVITY",
    "activity_type": "battery_swap_completed",
    "activity_data": "{\"duration\": 180, \"customer_satisfaction\": \"high\"}",
    "attendant_station": "STATION_001"
  }
}


Workflow State Updates

Track workflow progress.

Publish Topic:

emit/uxi/attendant/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/workflow_update

Payload:

{
  "timestamp": "2025-01-15T10:50:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-workflow-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "UPDATE_WORKFLOW_STATE",
    "workflow_stage": "A3",
    "stage_transition": "A2_to_A3",
    "process_status": "completed",
    "performance_metrics": "{\"duration\": 300, \"efficiency\": 0.95}"
  }
}


Usage Reporting to Odoo

Report completed swap for billing.

Publish Topic:

emit/uxi/billing/plan/bss-plan-weekly-freedom-nairobi-v2-plan1/usage_report

Payload:

{
  "timestamp": "2025-01-15T11:05:00Z",
  "plan_id": "bss-plan-weekly-freedom-nairobi-v2-plan1",
  "correlation_id": "att-usage-report-001",
  "actor": {
    "type": "attendant",
    "id": "attendant-001"
  },
  "data": {
    "action": "REPORT_SERVICE_USAGE_TO_ODOO",
    "usage_type": "battery_swap_completed",
    "service_completion_details": {
      "old_battery_id": "BAT_RETURN_ATT_001",
      "new_battery_id": "BAT_NEW_ATT_001",
      "energy_transferred": 48.5,
      "service_duration": 240,
      "attendant_station": "STATION_001"
    }
  }
}

Subscribe Topic:

echo/odo/billing/plan/+/billing_processed


Complete End-to-End Example

First-Time Customer Flow

#!/bin/bash
# Complete attendant workflow for first-time customer

PLAN_ID="bss-plan-weekly-freedom-nairobi-v2-plan1"
ATTENDANT_ID="attendant-001"
STATION="STATION_001"

# Phase 0: Pre-Service Setup (see odoo-billing-sync-guide.md)
# (Must be completed first!)

# Phase A1: Identification
echo "A1.1: Customer Identification..."
mosquitto_pub -h localhost -t "emit/uxi/attendant/plan/$PLAN_ID/identify_customer" -m "{
  \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
  \"plan_id\": \"$PLAN_ID\",
  \"correlation_id\": \"flow-001-id\",
  \"actor\": {\"type\": \"attendant\", \"id\": \"$ATTENDANT_ID\"},
  \"data\": {
    \"action\": \"IDENTIFY_CUSTOMER\",
    \"qr_code_data\": \"QR_CUSTOMER_001\",
    \"attendant_station\": \"$STATION\"
  }
}"
sleep 2

# Phase A2: Validation
echo "A2.1: Customer Status Validation..."
mosquitto_pub -h localhost -t "call/uxi/attendant/plan/$PLAN_ID/validate_customer" -m "{
  \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
  \"plan_id\": \"$PLAN_ID\",
  \"correlation_id\": \"flow-001-val\",
  \"actor\": {\"type\": \"attendant\", \"id\": \"$ATTENDANT_ID\"},
  \"data\": {\"action\": \"VALIDATE_CUSTOMER_STATUS\"}
}"
sleep 2

echo "A2.2: Payment Validation..."
# (Add payment validation here)

echo "A2.4: Quota Validation..."
# (Add quota validation here)

# Phase A3: Transaction (First-Time Customer - No Check-In)
echo "A3.4: Payment Collection..."
mosquitto_pub -h localhost -t "call/uxi/attendant/plan/$PLAN_ID/collect_payment" -m "{
  \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
  \"plan_id\": \"$PLAN_ID\",
  \"correlation_id\": \"flow-001-payment\",
  \"actor\": {\"type\": \"attendant\", \"id\": \"$ATTENDANT_ID\"},
  \"data\": {
    \"action\": \"COLLECT_PAYMENT\",
    \"payment_method\": \"mobile_money\"
  }
}"
sleep 3

echo "A3.2: Equipment Checkout (First Battery)..."
mosquitto_pub -h localhost -t "call/uxi/attendant/plan/$PLAN_ID/equipment_checkout" -m "{
  \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
  \"plan_id\": \"$PLAN_ID\",
  \"correlation_id\": \"flow-001-checkout\",
  \"actor\": {\"type\": \"attendant\", \"id\": \"$ATTENDANT_ID\"},
  \"data\": {
    \"action\": \"EQUIPMENT_CHECKOUT\",
    \"replacement_equipment_id\": \"BAT_NEW_001\",
    \"energy_transferred\": 45.5,
    \"service_duration\": 180
  }
}"
sleep 2

# Phase A4: Reporting
echo "A4.1: Activity Reporting..."
mosquitto_pub -h localhost -t "emit/uxi/attendant/plan/$PLAN_ID/activity_report" -m "{
  \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
  \"plan_id\": \"$PLAN_ID\",
  \"correlation_id\": \"flow-001-activity\",
  \"actor\": {\"type\": \"attendant\", \"id\": \"$ATTENDANT_ID\"},
  \"data\": {
    \"action\": \"REPORT_ATTENDANT_ACTIVITY\",
    \"activity_type\": \"battery_swap_completed\",
    \"attendant_station\": \"$STATION\"
  }
}"

echo "A4.4: Usage Reporting to Odoo..."
mosquitto_pub -h localhost -t "emit/uxi/billing/plan/$PLAN_ID/usage_report" -m "{
  \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
  \"plan_id\": \"$PLAN_ID\",
  \"correlation_id\": \"flow-001-billing\",
  \"actor\": {\"type\": \"attendant\", \"id\": \"$ATTENDANT_ID\"},
  \"data\": {
    \"action\": \"REPORT_SERVICE_USAGE_TO_ODOO\",
    \"usage_type\": \"battery_swap_completed\",
    \"service_completion_details\": {
      \"new_battery_id\": \"BAT_NEW_001\",
      \"energy_transferred\": 45.5,
      \"service_duration\": 180
    }
  }
}"

echo "✅ Attendant workflow completed!"

Monitoring

Subscribe to all attendant topics:

Terminal 1: Attendant requests/responses

emit/uxi/attendant/#
echo/abs/attendant/#
call/uxi/attendant/#
rtrn/abs/attendant/#

Terminal 2: Billing and integration

emit/uxi/billing/#
echo/odo/billing/#

Terminal 3: Activity and workflow

stat/abs/attendant/#
meta/abs/attendant/#


Common Errors

Error Signal Cause Solution
CUSTOMER_IDENTIFICATION_DATA_MISSING No QR code provided Scan customer QR code
EQUIPMENT_ID_MISSING Battery ID not scanned Scan battery barcode
CUSTOMER_INACTIVE Plan not active Complete Phase 0 first
QUOTA_EXHAUSTED No quota remaining Process quota top-up
PAYMENT_OVERDUE Payment not current Collect payment first

Next Steps