How to Build a customer support Agent Using LlamaIndex in TypeScript for payments

By Cyprian AaronsUpdated 2026-04-21
customer-supportllamaindextypescriptpayments

A customer support agent for payments answers product and policy questions, looks up transaction context, and drafts safe responses for disputes, refunds, failed charges, and payout issues. It matters because payment support sits at the intersection of money movement, compliance, and customer trust — so the agent has to be accurate, auditable, and constrained enough to avoid inventing policy or exposing sensitive data.

Architecture

  • Chat interface

    • Receives the customer message plus a support case ID or transaction reference.
    • Keeps the interaction scoped to one account and one issue.
  • Retriever over approved knowledge

    • Indexes only sanctioned sources: refund policy, dispute playbooks, payout SLAs, fee docs, and operational runbooks.
    • Uses VectorStoreIndex and a retriever to ground answers in approved content.
  • Payments context lookup

    • Pulls structured data from your internal payments API: transaction status, authorization result, settlement state, chargeback stage.
    • Never lets the LLM infer these values from free text.
  • Response synthesizer

    • Uses LlamaIndex’s query engine to combine retrieved policy snippets with case context.
    • Produces a draft response for an agent or a customer-facing channel.
  • Guardrails layer

    • Redacts PANs, bank account numbers, email addresses where needed.
    • Blocks requests that would expose regulated data or trigger unsupported actions.
  • Audit logging

    • Stores prompt inputs, retrieved document IDs, model output, and final action taken.
    • Needed for dispute handling, compliance review, and incident investigation.

Implementation

1) Install the TypeScript packages

Use the TypeScript package for LlamaIndex and a provider SDK. The example below uses OpenAI through LlamaIndex’s OpenAI class.

npm install llamaindex dotenv

Set your environment variables:

export OPENAI_API_KEY="your-key"

2) Load approved payment support docs into a vector index

Keep your source set narrow. For payments support, that usually means policy PDFs converted to text, internal FAQ articles, escalation rules, and refund/dispute procedures.

import "dotenv/config";
import { Document, VectorStoreIndex } from "llamaindex";

async function buildIndex() {
  const docs = [
    new Document({
      text: `
Refund policy:
- Card refunds are issued back to the original payment method.
- Refunds can take 5-10 business days depending on issuer.
- Partial refunds are allowed if the order was partially fulfilled.
`,
      metadata: { source: "refund-policy", version: "2025-01" },
    }),
    new Document({
      text: `
Chargeback guidance:
- Do not promise chargeback outcomes.
- Collect ARN / RRN only when required by operations.
- Escalate disputes older than 120 days to the risk queue.
`,
      metadata: { source: "chargeback-playbook", version: "2025-01" },
    }),
    new Document({
      text: `
Failed payment troubleshooting:
- If authorization declined with do_not_honor, advise customer to contact their bank.
- If settlement is pending beyond SLA, open an ops ticket.
`,
      metadata: { source: "failed-payments-runbook", version: "2025-01" },
    }),
  ];

  const index = await VectorStoreIndex.fromDocuments(docs);
  return index;
}

3) Add a payments context fetcher and query engine

The key pattern is: retrieve structured transaction state from your system of record first, then ask LlamaIndex to answer using both that context and approved documents.

import "dotenv/config";
import {
  Document,
  OpenAI,
  VectorStoreIndex,
} from "llamaindex";

type PaymentCase = {
  caseId: string;
  transactionId: string;
  status: string;
  authCode?: string;
  settlementState?: string;
};

async function fetchPaymentCase(caseId: string): Promise<PaymentCase> {
  // Replace with real API call to your payments backend
  return {
    caseId,
    transactionId: "txn_123",
    status: "declined",
    authCode: "05",
    settlementState: "not_settled",
  };
}

async function main() {
  const docs = [
    new Document({
      text: `Refund policy: Refunds go back to original payment method. Processing time is 5-10 business days.`,
      metadata: { source: "refund-policy" },
    }),
    new Document({
      text: `Declines with code 05 should be explained as generic bank decline. Do not speculate on exact bank reason.`,
      metadata: { source: "decline-guidance" },
    }),
    new Document({
      text: `Do not expose full PAN or bank details in customer-facing replies.`,
      metadata: { source: "security-policy" },
    }),
  ];

  const index = await VectorStoreIndex.fromDocuments(docs);
  const queryEngine = index.asQueryEngine({
    llm: new OpenAI({ model: "gpt-4o-mini" }),
    similarityTopK: 2,
  });

  const paymentCase = await fetchPaymentCase("case_987");
  const prompt = `
You are a payments support agent.
Use only the provided case context and retrieved policy excerpts.
If information is missing, say what needs escalation.

Case context:
${JSON.stringify(paymentCase, null, 2)}

Customer question:
Why was my card charged declined?
`;

  const response = await queryEngine.query({ queryStr: prompt });
  console.log(response.toString());
}

main().catch(console.error);

This is the basic production pattern:

  1. Build an approved-documents index with VectorStoreIndex.
  2. Fetch live case data from your payments system.
  3. Query through asQueryEngine() so retrieval grounds the answer.
  4. Return a draft response or an internal note.

4) Add a simple guardrail before sending output

For payments support you need hard checks before anything leaves the system. At minimum block raw card data and unsupported promises.

function guardrail(outputText: string) {
  const blockedPatterns = [
    /\b\d{13,19}\b/, // naive PAN check; replace with proper detector
    /guarantee/i,
    /will definitely succeed/i,
    /full card number/i,
  ];

  for (const pattern of blockedPatterns) {
    if (pattern.test(outputText)) {
      throw new Error("Guardrail triggered on payment-sensitive content");
    }
  }

  return outputText;
}

Use this after generation but before display or ticket write-back. In regulated environments you should also log which rule fired and which document IDs were used for grounding.

Production Considerations

  • Deployment

    • Keep retrieval data in-region if you have residency requirements for EU/UK or specific banking jurisdictions.
    • Separate customer-facing generation from internal agent-assist flows so you can apply stricter controls on outbound messages.
  • Monitoring


Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides