swiftops-backend / docs /api /expenses /payment-routing.md
kamau1's picture
Fixed timesheet aggregation issues: corrected self-assignment counting, updated expense approval logic, and documented dropped vs cancelled ticket semantics.
207b4a1

Expense Payment Routing - Agent vs Vendor

Overview

Expenses support two payment flows:

  1. Agent Reimbursement: Agent pays with own money, gets reimbursed
  2. Vendor Direct Payment: Company pays vendor directly (e.g., petrol stations, suppliers)

API Endpoint

POST /api/v1/ticket-expenses

Payment Recipient Types

type PaymentRecipientType = "agent" | "vendor"

Payment Methods

type PaymentMethod = 
  | "send_money"           // M-Pesa Send Money (personal)
  | "till_number"          // M-Pesa Buy Goods (Till)
  | "paybill"              // M-Pesa Paybill
  | "pochi_la_biashara"    // M-Pesa Business Wallet
  | "bank_transfer"        // Bank account
  | "cash"                 // Cash payment

Use Case 1: Agent Reimbursement (Default)

When: Agent paid with own money (taxi, meals, small purchases)

Request: Omit payment fields - system auto-populates from agent's financial account

{
  "ticket_assignment_id": "uuid",
  "category": "transport",
  "description": "Taxi to customer site",
  "expense_date": "2024-12-13",
  "total_cost": 500.00
}

System Behavior:

  • Sets payment_recipient_type: "agent"
  • Auto-fills payment_method and payment_details from agent's primary financial account
  • If no financial account exists, leaves null (PM handles manually)

Use Case 2: Vendor Direct Payment

When: High-value expenses, vendor requires direct payment (fuel, materials, equipment)

Request: Provide payment routing details

Example 1: Petrol Station (Till Number)

{
  "ticket_assignment_id": "uuid",
  "category": "transport",
  "description": "Fuel for generator - 50 liters",
  "expense_date": "2024-12-13",
  "quantity": 50,
  "unit": "liters",
  "unit_cost": 150.00,
  "total_cost": 7500.00,
  "payment_recipient_type": "vendor",
  "payment_method": "till_number",
  "payment_details": {
    "till_number": "123456",
    "business_name": "Shell Petrol Station"
  }
}

Example 2: Hardware Store (Paybill)

{
  "ticket_assignment_id": "uuid",
  "category": "materials",
  "description": "Electrical cables and switches",
  "expense_date": "2024-12-13",
  "total_cost": 12000.00,
  "payment_recipient_type": "vendor",
  "payment_method": "paybill",
  "payment_details": {
    "business_number": "888888",
    "account_number": "ACC123",
    "business_name": "ABC Hardware"
  }
}

Example 3: Supplier (Bank Transfer)

{
  "ticket_assignment_id": "uuid",
  "category": "materials",
  "description": "Bulk cement order",
  "expense_date": "2024-12-13",
  "total_cost": 45000.00,
  "payment_recipient_type": "vendor",
  "payment_method": "bank_transfer",
  "payment_details": {
    "bank_name": "Equity Bank",
    "account_number": "0123456789",
    "account_name": "XYZ Supplies Ltd",
    "branch": "Industrial Area"
  }
}

Payment Details Schema by Method

send_money (M-Pesa Personal)

{
  phone_number: string    // Format: +254XXXXXXXXX
  recipient_name: string
}

till_number (M-Pesa Buy Goods)

{
  till_number: string     // 5-7 digits
  business_name: string
}

paybill (M-Pesa Paybill)

{
  business_number: string  // 5-7 digits
  account_number: string
  business_name: string
}

pochi_la_biashara (M-Pesa Business Wallet)

{
  phone_number: string     // Format: +254XXXXXXXXX
  business_name: string
}

bank_transfer

{
  bank_name: string
  account_number: string
  account_name: string
  branch?: string          // Optional
}

cash

{
  recipient_name: string
  id_number?: string       // Optional
}

UI Recommendations

Step 1: Who Paid?

[ ] I paid (reimbursement)
[ ] Pay vendor directly

Step 2: If "Pay vendor directly" selected

Show payment method selector:

[ ] M-Pesa Till Number (Buy Goods)
[ ] M-Pesa Paybill
[ ] Bank Transfer
[ ] M-Pesa Business Wallet
[ ] Cash

Step 3: Collect method-specific details

Dynamic form based on selected payment method

Validation Rules

  1. Agent Reimbursement: No payment fields required
  2. Vendor Payment: All three fields required:
    • payment_recipient_type: "vendor"
    • payment_method: <method>
    • payment_details: <complete details>
  3. Phone numbers: Must match +254[17]XXXXXXXX
  4. Till/Paybill numbers: 5-7 digits
  5. All text fields: Non-empty strings

Common Scenarios

Expense Type Typical Amount Recommended Flow
Taxi/Transport < 2,000 Agent pays
Meals < 1,000 Agent pays
Fuel > 5,000 Pay vendor
Materials > 10,000 Pay vendor
Equipment > 20,000 Pay vendor
Small supplies < 5,000 Agent pays

Response

Same response for both flows:

{
  "id": "uuid",
  "ticket_assignment_id": "uuid",
  "category": "transport",
  "description": "...",
  "total_cost": 500.00,
  "payment_recipient_type": "agent",  // or "vendor"
  "payment_method": "send_money",
  "payment_details": { ... },
  "is_approved": false,
  "is_paid": false,
  ...
}

Notes

  • Agent financial account must be set up for auto-population to work
  • Vendor payments require complete details upfront
  • Both flows go through same approval process
  • Payment export generates Tende Pay compatible CSV