ASPLUS Docs
AI Platform · AIRA — WhatsApp AI Agent

AIRA — WhatsApp AI Agent

The practical guide to AIRA, the ASPLUS WhatsApp AI Agent: what it is, how an owner uses it, and how to test it. Companion build plan: docs/ai-agent-v1-plan.md. Sister channel (same capabilities over ChatGPT/Claude): docs/ai-connect-guide.md.

Status: LIVE on production, Pro / Lifetime tier only (gated per company — see §A6). Persona is fixed as AIRA. Onboarding is the shared ASPLUS number + OTP model (Model A). Feature flag AI_AGENT_ENABLED; the inbound webhook is never gated by the flag.


1. What it is (in one paragraph)

AIRA lets a company owner run their accounting by messaging one shared official ASPLUS WhatsApp number. The owner writes in Bahasa Melayu or English; AIRA answers questions (sales, invoices, outstanding, reports…) and can record entries (money in/out, invoice/bill payment, draft invoice/quotation). It also reads a receipt photo and drafts the expense, transcribes a voice note, and can send a report PDF back to the chat. Every action that changes the books is confirmed by the owner first. AIRA reuses the existing ASPLUS accounting logic — it never posts its own numbers.

Scope: owner-only; identity-based tenancy (the sender's verified number picks the company); text + voice + receipt-image; read + confirmed writes. Not included: customer/supplier chatbots, broadcast/automation, auto-posting without confirmation.


PART A — User Guide (for the business owner)

A1. One-time setup (verify your number)

  1. In ASPLUS, open AI (top navbar) → AIRA.
  2. Verify your WhatsApp number. Enter the personal WhatsApp number you'll message from → AIRA sends a one-time code to that number → enter it back in ASPLUS. This proves you control the number; only verified numbers can reach your business data. (There is no "connect your own WhatsApp API" step — you use the shared ASPLUS number.)
  3. Once verified, tap Chat with AIRA (opens WhatsApp to the official number) and say hello. That's it.

Multi-company: verify the same number and AIRA routes to whichever company the number is authorized for. If a number is authorized for more than one company, AIRA asks which company before acting on writes.

A2. What you can ask (examples)

Message the AIRA number like you'd text a staff member:

You say AIRA does
"Berapa jualan bulan ni?" Sales summary for this month
"Untung rugi bulan lepas" Profit & loss last month
"Invois mana belum bayar?" Outstanding invoices + total
"Status invoice INV00012" That invoice's status + amounts
"Cari customer Ahmad" Customer search
"Baki bank sekarang" Cash & bank balance
"Balance sheet" / "Trial balance" Statement summaries
"Hantar P&L bulan ni PDF" Sends a report PDF to the chat
(send a receipt photo) Reads it, drafts a Money-Out → asks you to confirm
(send a voice note) Transcribes it, then acts on the request
"Rekod duit masuk RM500 jualan tunai" Proposes a Money-In → confirm
"Bayar invoice INV00012" Proposes a payment → confirm
"Buat invoice untuk Ahmad, 2 x Widget" Proposes a draft invoice → confirm

A3. How confirmation keeps you safe

Anything that creates or changes a record never happens silently. AIRA replies with a summary and two buttons — Confirm ✅ / Cancel ❌ (or reply ya / tidak). Nothing is saved until you confirm, and confirming twice never duplicates. This cannot be switched off.

A4. Prices are never invented

For an invoice/quotation the price comes from your product list (the Item's saved price) and SST from your tax settings. If AIRA can't match a product or customer, it asks you instead of guessing.

A5. AI Credits (per company, channel-weighted)

AIRA runs over WhatsApp, which carries a Meta per-message cost the in-app tools don't, so AIRA turns are weighted:

  • Text / voice message → 2 AI Credits (per answered turn).
  • Receipt / bill photo → 3 AI Credits (OCR + draft).

Tapping Confirm/Cancel and failed/unreadable replies are free. Credits come from that company's own wallet and are not shared between your companies — each has its own balance, monthly allocation (by its own plan) and top-ups. If a company runs out, AIRA tells you to top up. (The web AI Assistant and web OCR stay at 1 credit; AI Connect reads are free — those channels have no Meta cost.)

Config: ai_agent.credits_per_message (env AI_AGENT_CREDITS_PER_MESSAGE, =2) and ai_agent.credits_per_receipt (env AI_AGENT_CREDITS_PER_RECEIPT, =3).

A6. Who can use it (Pro, per company)

AIRA is available on Pro / Lifetime plans, checked against the active company's own subscription. A company on a free/expired plan is declined even if another company in the same account is Pro — entitlement follows the company, not the account.

A7. Good to know (current limits)

  • Invoices/quotations are created as drafts — you issue them in ASPLUS (that's when e-Invoice/MyInvois submission happens).
  • Owner-only. Anyone texting the number who isn't a verified owner gets a polite "not authorized" reply and sees no data.
  • Voice notes and receipt/bill photos are processed; other file types get a safe decline. Large media is rejected (audio ≈ 1 MB, image ≈ 5 MB caps).
  • A per-sender rate limit (default 20/min) protects the number and your credits from a runaway sender.

PART B — Testing Guide (QA / internal)

B1. Environment & flags

  • Feature flag AI_AGENT_ENABLED (reveal to users). Shared-number mode AI_AGENT_SHARED_NUMBER=true (owners OTP-verify against the shared channel provisioned by php artisan ai-agent:provision-shared). Safety belt AI_AGENT_LIVE_MODE (false = no real WhatsApp send; the LLM still runs).
  • Webhook: AI_AGENT_WA_VERIFY_TOKEN, AI_AGENT_WA_APP_SECRET (AI_AGENT_WA_VERIFY_SIGNATURE=true in prod). OTP template: AI_AGENT_OTP_TEMPLATE, AI_AGENT_OTP_TEMPLATE_LANG (e.g. en_US), AI_AGENT_OTP_TEMPLATE_APP (the {{2}} value, e.g. AIRA, when the approved template has a second body variable — else Meta rejects with #132000).
  • Model: AI_AGENT_OPENAI_API_KEY (or OPENAI_API_KEY), AI_AGENT_MODEL (default gpt-4o-mini). Receipt OCR needs the OCR/GCP config.
  • Queue: inbound dispatches to the ai-agent queue (prod = database driver + RunCloud worker). Sync driver runs inline.

B2. Prepare a test tenant

Use a Pro staging company with products, a customer, some invoices and a supplier bill. Verify an owner number (OTP) against the shared channel. To exercise the brain in dry-run, POST a signed sample Meta payload to /webhooks/ai-agent/whatsapp with the shared channel's phone_number_id and an authorized from; inspect ai_agent_messages, ai_agent_pending_actions, ai_agent_usage_logs, ai_credit_transactions, and storage/logs/laravel.log.

B3. Read scenarios (tenant-scoped, must tie out to the reports)

  • [ ] Sales / expense / P&L for a period → matches the Reporting screens.
  • [ ] Outstanding invoices + total AR → matches the AR list.
  • [ ] get_balance_sheet (Assets = Liabilities + Equity), get_trial_balance (Debit = Credit), get_cash_flow → tie out.
  • [ ] find invoice / bill / contact / transaction → rows for this company only.
  • [ ] "hantar … PDF" → a report PDF is delivered to the chat.

B4. Write + confirmation scenarios

  • [ ] "Record money in RM500 …" → confirmation + a pending row; ya → Transaction + balanced JournalEntry; ya again → no duplicate.
  • [ ] "Bayar invoice X" → confirm → Payment; invoice flips Paid when settled.
  • [ ] "Buat invoice untuk [customer]: 2 x [product]" → draft preview (prices from master, SST line, total) → confirm → draft, no JE.
  • [ ] Quotation → status new, no tax, correct total.
  • [ ] "Bayar bill [number]" → confirm → PurchasePayment + JE; bill flips Paid.
  • [ ] Send a receipt photo → drafted Money-Out preview → confirm → recorded; the scan debits 1 credit (refunded if discarded).
  • [ ] Reply tidak / Cancel → nothing saved.

B5. Guardrails (must all fail safely)

  • [ ] Unauthorized sender → safe "not authorized" reply, no data.
  • [ ] Non-Pro / expired company → upgrade nudge; nothing runs, nothing charged.
  • [ ] Unknown product / customer → AIRA asks; never invents a price.
  • [ ] Overpay or already-paid bill/invoice → refused.
  • [ ] Capability off in Settings → those tools disappear (e.g. Payments off → record_bill_payment blocked).
  • [ ] Out of credits (this company) → asks to top up; failed turns not charged.
  • [ ] Rate limit exceeded → brief "slow down" reply; not run, not charged.

B6. Run the automated suite (locally / CI)

composer install --ignore-platform-req=ext-gd
php artisan test tests/Unit/AiAgent tests/Feature/AiAgent

Covers the parser, tenant isolation, unauthorized sender, confirmation idempotency, webhook HMAC (fail-closed), tool registry + permission filtering, orchestrator loop, permission enforcement, SST maths, sales-document creation, overpayment/already-paid, multi-company routing and the per-company credit wallet.

B7. Troubleshooting

Symptom Likely cause / check
No reply ai-agent queue worker not running; or LIVE_MODE=false (dry-run — check logs)
"not authorized" for the owner Number not verified (OTP), or format mismatch
OTP never arrives Template name/lang wrong (#132001) or {{2}} param missing (#132000 → set AI_AGENT_OTP_TEMPLATE_APP=AIRA); cold number needs an Authentication template
Webhook 403 AI_AGENT_WA_APP_SECRET missing/wrong (fails closed)
Upgrade nudge for a paid account Active company is the free/expired one — entitlement is per-company
Balance shows 0 unexpectedly Per-company wallet — that company's own balance (siblings don't share)

PART C — Reference

C1. Flow

WhatsApp inbound → /webhooks/ai-agent/whatsapp (HMAC verify, 200 fast)
  → ProcessAgentInboundJob (dedupe by wamid)
    → resolve shared channel → authorize sender (verified number) → pin company
       → unknown number: drop · unknown sender: safe reply · owner: continue
    → entitlement gate (active company's own Pro sub) → rate limit → credit pre-check
    → media: voice → transcribe · image → receipt OCR (drafts a Money-Out)
    → AgentOrchestrator (LLM + tools)
        read tool  → answer (or report PDF)
        write tool → pending action + Confirm/Cancel → (on Confirm) execute once
    → reply + persist + debit 1 credit (this company) + usage log

C2. Tools (22)

Read (16): get_sales_summary, get_expense_summary, get_profit_and_loss, get_cash_bank_balance, get_outstanding_invoices, find_invoice, get_invoice_status, find_contact, find_transaction, get_balance_sheet, get_trial_balance, get_cash_flow, get_outstanding_bills, find_bill, get_bill_status, send_report_pdf. Write (6, all confirmed): create_money_in, create_money_out, record_invoice_payment, create_invoice (draft), create_quotation, record_bill_payment.

C3. Permission groups (Settings)

view_business_data · reports · create_transactions · sales_documents · payments. Effective permission = what the owner is granted ∩ what the company enabled in Settings.

C4. Data model

whatsapp_channels (shared channel), ai_agent_authorized_users (verified numbers + OTP state), ai_agent_conversations, ai_agent_messages (wamid-deduped), ai_agent_pending_actions (confirm state machine, idempotency key), ai_agent_usage_logs (per-company telemetry), ai_agent_settings. Credits flow through ai_credit_transactions (per-company wallet on companies.ai_credits).

C5. Config / env

config/ai_agent.php — feature flag, Meta base/version, App-level webhook verify token + secret, live_mode, shared_number, OTP template settings, owner_limit (+ device add-on), per-minute rate limit, model provider/name/key, fixed persona = AIRA, official_number, history + tool-iteration limits, entitled_tiers (pro/lifetime), credits_per_message, media caps (voice/image), and the tool registry.

C6. Security properties

Identity-based tenancy via AgentTenantContext (explicit team/company scoping — no web session, no Auth::login); the company is pinned server-side from the verified sender, never chosen by the model. X-Hub-Signature-256 HMAC verify (fail-closed); wamid dedupe + confirm-once idempotency; unknown-sender safe reply; mandatory confirmation for all writes; per-company entitlement + credit gates; encrypted channel credentials.

C7. Known limits / deferred

  • Invoices/quotations are drafts — issuing (JE + e-Invoice/MyInvois) stays a web action.
  • One authorized role (owner); staff/supplier/shareholder + step-up PIN are schema-ready but not enabled.
  • Admin monitor at /admin/ai-agent (read-only usage feed).
  • The same accounting capabilities are also reachable over MCP from ChatGPT/Claude — see docs/ai-connect-guide.md.