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_ISSUED → BATTERY_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:
- Payment is processed first → Updates quota (via topup)
- 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:
- Service States: Updates
service_states[].used(increments by 1) - Service States: Updates
service_states[].quota(increases by payment_amount / unit_price) - 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¶
- Processing Order: Payment MUST be processed before service completion to ensure quota availability
- Quota Calculation:
additional_quota = payment_amount / service.unitPrice - Service Consumption: Each swap consumes exactly 1 quota unit
- Idempotency: Use
correlation_idto prevent duplicate processing - 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¶
- Always provide correlation_id for idempotency and tracking
- Use descriptive payment references from external systems (M-Pesa, cash receipt numbers)
- Include energy_transferred and service_duration when available for analytics
- Handle both operations atomically - if one fails, retry both
- Monitor echo responses to confirm successful processing
Related Operations¶
- 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¶
- Rider Workflow Guide - Mobile app-driven swaps
- Service State Management - Quota tracking
- Odoo Sync Workflow - Payment integration