How to Build a customer support Agent Using CrewAI in TypeScript for payments
A customer support agent for payments handles the stuff that burns support teams the most: failed card charges, duplicate captures, refund status checks, chargeback questions, and “where is my money?” tickets. The point is not just deflecting tickets; it’s doing it with payment-safe guardrails so you don’t leak PAN data, violate PCI scope, or give a customer an answer that conflicts with ledger reality.
Architecture
Build this agent as a small workflow, not a single prompt.
- •Intent router
- •Classifies the ticket into payment support categories like
refund_status,authorization_failed,chargeback,dispute_evidence, orbilling_explanation.
- •Classifies the ticket into payment support categories like
- •Payment policy checker
- •Enforces what the agent can and cannot say.
- •Blocks sensitive fields like full card numbers, CVV, bank account details, and raw transaction tokens.
- •Support knowledge retriever
- •Pulls from approved docs: refund SLAs, retry rules, settlement windows, dispute timelines, and merchant policy.
- •Payment operations tool layer
- •Queries internal systems for order status, refund state, authorization result, or dispute case metadata.
- •Response composer
- •Produces a customer-facing answer with the right tone, next step, and escalation path.
- •Audit logger
- •Stores the input intent, tools used, decision path, and final response for compliance review.
Implementation
1) Install CrewAI for TypeScript and define your domain types
Use a narrow set of payment-safe inputs. Do not pass raw sensitive payloads into the agent.
npm install @crew-ai/crewai zod
// src/types.ts
import { z } from "zod";
export const PaymentSupportTicketSchema = z.object({
ticketId: z.string(),
customerId: z.string(),
orderId: z.string().optional(),
issueType: z.enum([
"refund_status",
"authorization_failed",
"chargeback",
"duplicate_charge",
"billing_question",
"other",
]),
message: z.string(),
});
export type PaymentSupportTicket = z.infer<typeof PaymentSupportTicketSchema>;
2) Create tools for approved payment lookups
Keep tools deterministic and scoped. The agent should ask tools for facts; it should not invent payment states.
// src/tools.ts
import { Tool } from "@crew-ai/crewai";
export const getRefundStatusTool = new Tool({
name: "get_refund_status",
description: "Fetches refund state for a given order ID from the payments system.",
async execute(input: { orderId: string }) {
// Replace with real API call
return {
orderId: input.orderId,
refundStatus: "pending",
expectedSettlementDays: 5,
reference: "RF_12345",
};
},
});
export const getAuthorizationFailureTool = new Tool({
name: "get_authorization_failure",
description: "Fetches safe authorization failure metadata without exposing PAN data.",
async execute(input: { orderId: string }) {
return {
orderId: input.orderId,
failureCode: "do_not_honor",
issuerMessage: "Issuer declined the transaction",
retryAllowed: false,
};
},
});
3) Build agents and crew orchestration
For this pattern, use one triage agent plus one resolution agent. That keeps routing clean and makes audits easier.
// src/crew.ts
import { Agent, Crew, Task } from "@crew-ai/crewai";
import { getRefundStatusTool, getAuthorizationFailureTool } from "./tools";
import { PaymentSupportTicket } from "./types";
const triageAgent = new Agent({
role: "Payment Support Triage Agent",
goal: "Classify payment support requests and choose the safest resolution path.",
backstory:
"You work in regulated payments support. You never expose sensitive payment data.",
});
const resolutionAgent = new Agent({
role: "Payment Resolution Agent",
goal: "Resolve payment support issues using approved tools and policy-safe language.",
backstory:
"You explain payment outcomes clearly and escalate when a human review is required.",
tools: [getRefundStatusTool, getAuthorizationFailureTool],
});
export async function handlePaymentTicket(ticket: PaymentSupportTicket) {
const triageTask = new Task({
description: `
Classify this ticket:
${JSON.stringify(ticket)}
Return only the issue category and whether escalation is required.
`,
expectedOutput:
'A JSON object with keys: category, escalationRequired, reason.',
agent: triageAgent,
});
const resolutionTask = new Task({
description:
"Use the classified issue to produce a customer-safe response with next steps.",
expectedOutput:
"A concise support reply that avoids sensitive data and includes escalation if needed.",
agent: resolutionAgent,
context: [triageTask],
});
const crew = new Crew({
agents: [triageAgent, resolutionAgent],
tasks: [triageTask, resolutionTask],
verbose: true,
});
return await crew.kickoff();
}
4) Expose it through an API handler with validation and audit logging
This is where you keep yourself out of trouble. Validate inputs before they hit CrewAI and log every decision path.
// src/server.ts
import express from "express";
import { PaymentSupportTicketSchema } from "./types";
import { handlePaymentTicket } from "./crew";
const app = express();
app.use(express.json());
app.post("/support/payment", async (req, res) => {
const parsed = PaymentSupportTicketSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: "Invalid ticket payload" });
}
const ticket = parsed.data;
const result = await handlePaymentTicket(ticket);
res.json({
ticketId: ticket.ticketId,
responseText: result,
auditTag: "payments-support-agent-v1",
});
});
app.listen(3000);
Production Considerations
- •Data residency
- •Keep logs and model calls in-region if your payment data must stay within a specific jurisdiction.
- •Do not send raw cardholder data to external services unless they are explicitly in scope for PCI controls.
- •Compliance controls
- •Redact PANs, CVVs, bank account numbers, IBANs, token values that can be reversed externally if your policy treats them as sensitive.
- •Add policy checks before tool execution so the model never sees more than it needs.
- •Monitoring
- •Track escalation rate by issue type, tool failure rate, hallucination reports from QA review, and average time to first useful answer.
- •Store traces with ticket ID, tool names used, and final response version for auditability.
- •Human handoff
- •Route disputes with legal exposure — chargebacks beyond standard windows, fraud claims, sanctions-related holds — to a human queue immediately.
Common Pitfalls
- •
Passing raw payment data into prompts
- •Mistake: sending full transaction payloads or card details to the agent.
- •Fix: pre-redact inputs and only pass safe metadata like last4 if your policy allows it.
- •
Letting the model invent settlement facts
- •Mistake: asking for refund status without calling a tool.
- •Fix: require tool-backed answers for anything tied to money movement.
- •
Skipping audit trails
- •Mistake: shipping responses without storing what was asked, which tools were called, and why escalation happened. Fix: log every run with immutable metadata so compliance teams can reconstruct decisions later.
If you keep the scope tight — classify first, retrieve facts second, respond last — CrewAI works well for payment support without turning your assistant into a compliance liability.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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