Why agents need payment capabilities
The average e-commerce operation in Brazil involves six systems per sale: a payment gateway to charge via Pix, a fiscal service to issue the NF-e, a logistics platform to generate a shipping label, WhatsApp to notify the customer, an ERP to record the transaction, and a banking API to reconcile the funds. A human operator touches all six. For every order.
AI agents can orchestrate these systems faster, more reliably, and at a fraction of the cost. But they need the right tools. An LLM that "knows about Pix" cannot actually call the Zoop API, handle the webhook for payment confirmation, or parse SEFAZ's response codes. Knowledge without tools is trivia. Tools without orchestration is plumbing. You need both.
This tutorial walks you through building an agent that handles the complete commerce flow using the CodeSpar SDK. By the end, you'll have an agent that charges via Pix, issues an NF-e, and notifies the customer on WhatsApp. Then we'll combine all three into a single Complete Loop call.
Prerequisites
You need three things: Node.js 18+, a CodeSpar API key, and about 20 minutes.
Start by installing the SDK:
npm install @codespar/sdk@0.2.0
Get your API key from the CodeSpar dashboard. The free tier includes 1,000 tool calls per month, which is more than enough for this tutorial.
Create a .env file in your project root:
CODESPAR_ENV=sandbox
Setting CODESPAR_ENV=sandbox routes all tool calls to sandbox endpoints. No real charges. No real invoices. Switch to production when you're ready.
Step 1: Create a session with payment servers
Every interaction with CodeSpar starts with a session. A session is an immutable context container that tells the SDK which MCP servers to connect to, which credentials to use, and which user is making the request.
import { CodeSpar } from "@codespar/sdk";
const codespar = new CodeSpar({
apiKey: process.env.CODESPAR_API_KEY,
});
// Create a session with the Brazilian commerce preset
const session = await codespar.session.create({
userId: "user_abc123",
preset: "brazilian",
servers: ["zoop", "nuvem-fiscal", "z-api"],
});
console.log(session.id);
// → "ses_7kZm9qR3xW..."
console.log(session.tools);
// → ["codespar_pay", "codespar_invoice", "codespar_notify", ...]The preset: "brazilian" flag is important. It tells the SDK to load the servers relevant to Brazilian commerce: Zoop for Pix payments, Nuvem Fiscal for NF-e issuance, Z-API for WhatsApp notifications, Melhor Envio for logistics, Omie for ERP, and Stark Bank for banking.
You can also specify individual servers with the servers array. In this tutorial, we start with three: payment, invoicing, and messaging. We'll add logistics and ERP later.
Sessions solve three problems at once. First, credential isolation: each session binds to a specific user's OAuth tokens and API keys. Second, context scoping: the LLM only sees the tools available in the session, not the entire catalog. Third, billing attribution: every tool call in a session is metered and attributed to the organization that created it.
Step 2: Execute a Pix charge
With the session created, you can call codespar_pay to charge a customer via Pix. This is a meta tool — it doesn't call the Zoop API directly. Instead, it routes through the CodeSpar proxy, which handles authentication, retries, and webhook registration.
const charge = await session.send({
tool: "codespar_pay",
input: {
method: "pix",
amount: 15000, // R$150.00 in centavos
currency: "BRL",
description: "Premium Plan - April 2026",
customer: {
name: "Maria Silva",
cpf: "123.456.789-00",
email: "maria@example.com",
},
},
});
console.log(charge.result);
// → {
// transactionId: "txn_8xKm...",
// pixCode: "00020126580014br.gov.bcb.pix...",
// qrCodeUrl: "https://cdn.zoop.com/qr/txn_8xKm.png",
// status: "pending",
// expiresAt: "2026-04-15T15:30:00Z",
// }The response includes a Pix copy-paste code and a QR code URL. You can send either to the customer. The charge expires in 30 minutes by default (configurable via expiresInMinutes).
Handling payment confirmation
Pix payments confirm in real time. When the customer scans the QR code and authorizes the payment, the BACEN (Central Bank) settles the funds instantly. CodeSpar listens for the webhook from Zoop and emits a session event:
// Option A: Poll for status
const status = await session.waitFor("payment.confirmed", {
transactionId: charge.result.transactionId,
timeout: 1800_000, // 30 minutes
});
// Option B: Webhook (production)
// Register a webhook URL in the dashboard.
// CodeSpar POSTs { event: "payment.confirmed", data: {...} }
// to your endpoint when the payment settles.In sandbox mode, payments auto-confirm after 3 seconds so you can test the full flow without waiting. In production, you'll want to use webhooks for reliability.
Pix settles in under 10 seconds. Compare that to credit cards (D+30 in Brazil) or ACH in the US (2-3 business days). Instant settlement makes Pix structurally superior for agent-driven commerce.
Step 3: Issue the NF-e automatically
In Brazil, every sale requires a Nota Fiscal Eletronica (NF-e). This isn't optional. SEFAZ (the state tax authority) validates every invoice in real time. An incorrect CFOP, a wrong ICMS rate, or a malformed XML and the invoice is rejected. Businesses face fines for issuing invoices late or incorrectly.
The codespar_invoice meta tool wraps the Nuvem Fiscal API and handles the hard parts: CFOP determination based on the product type and origin/destination states, ICMS/IPI/PIS/COFINS calculation, XML generation and digital signing, SEFAZ submission and response parsing.
const invoice = await session.send({
tool: "codespar_invoice",
input: {
type: "nfe",
transactionId: charge.result.transactionId,
emitter: {
cnpj: "12.345.678/0001-99",
ie: "123456789", // Inscricao Estadual
regime: "simples", // Simples Nacional
},
items: [
{
description: "Premium Plan - SaaS",
ncm: "85234990", // NCM code for software
quantity: 1,
unitPrice: 15000,
cfop: "5933", // CFOP for service within state
},
],
customer: {
cpf: "123.456.789-00",
name: "Maria Silva",
},
},
});
console.log(invoice.result);
// → {
// nfeId: "nfe_Qm3x...",
// accessKey: "35260412345678000199550010000001231234567890",
// status: "authorized",
// pdfUrl: "https://cdn.codespar.com/nfe/nfe_Qm3x.pdf",
// xmlUrl: "https://cdn.codespar.com/nfe/nfe_Qm3x.xml",
// }Tax calculation: ICMS rates vary across 26 Brazilian states. A product shipped from SP to RJ has a different rate than SP to BA. The MCP server looks up the correct rate based on origin/destination UF and product NCM.
XML signing: NF-e requires a valid A1 digital certificate. The server manages certificate storage, signing, and renewal.
SEFAZ retry logic: SEFAZ occasionally returns 108 (service temporarily unavailable) or 109 (too many requests). The server retries with exponential backoff, up to 3 attempts.
Contingency mode: If SEFAZ is fully down (rare, but it happens), the server automatically switches to NFC-e contingency mode and queues the NF-e for resubmission.
Step 4: Notify the customer via WhatsApp
In Latin America, WhatsApp isn't a nice-to-have notification channel. It's the primary channel. Over 500 million people in the region use it daily. Customers expect order confirmations, payment receipts, and tracking updates on WhatsApp, not email.
The codespar_notify meta tool routes to the Z-API MCP server:
const notification = await session.send({
tool: "codespar_notify",
input: {
channel: "whatsapp",
to: "+5511999887766",
template: "payment_confirmed",
variables: {
customerName: "Maria",
amount: "R$ 150,00",
description: "Premium Plan - April 2026",
nfeUrl: invoice.result.pdfUrl,
pixTransactionId: charge.result.transactionId,
},
},
});
console.log(notification.result);
// → {
// messageId: "msg_Zk9w...",
// status: "sent",
// channel: "whatsapp",
// deliveredAt: null, // updates via webhook
// }The template field references a pre-approved WhatsApp Business template. Z-API handles the Meta Business API requirements: template submission, approval tracking, and rate limiting. You can also send freeform messages for ongoing conversations (within the 24-hour session window).
Why not email?
Open rates tell the story. WhatsApp messages in Brazil have a 90%+ open rate. Email marketing sits around 15-20%. For transactional messages (payment confirmation, shipping updates), WhatsApp delivery is nearly instant, and read receipts are built in. The agent knows when the customer has seen the message.
Step 5: The Complete Loop
Steps 2-4 each call a single tool. That's useful for testing, but in production you want the agent to orchestrate all steps autonomously. This is what the Complete Loop does: charge, invoice, ship, notify, ERP, reconcile — in one call.
First, create a session with the full preset:
const session = await codespar.session.create({
userId: "user_abc123",
preset: "brazilian",
// All 6 servers: zoop, nuvem-fiscal, melhor-envio,
// z-api, omie, stark-bank
});Then send a natural language instruction:
const loop = await session.loop({
instruction: `
Charge Maria Silva (CPF 123.456.789-00) R$150 via Pix
for Premium Plan. After payment confirms, issue the NF-e
from CNPJ 12.345.678/0001-99 (Simples Nacional).
Generate a shipping label via Melhor Envio for a 500g
package from CEP 01310-100 to CEP 22041-080.
Notify Maria on WhatsApp (+5511999887766) with the
payment receipt and tracking code. Register the sale
in Omie and reconcile in Stark Bank.
`,
onStep: (step) => {
console.log(`[${step.server}] ${step.tool} - ${step.status}`);
},
});
// Output:
// [zoop] codespar_pay - completed (340ms)
// [nuvem-fiscal] codespar_invoice - completed (1.2s)
// [melhor-envio] codespar_ship - completed (890ms)
// [z-api] codespar_notify - completed (210ms)
// [omie] codespar_erp - completed (580ms)
// [stark-bank] codespar_reconcile - completed (440ms)
console.log(loop.summary);
// → {
// steps: 6,
// totalTime: "3.66s",
// payment: { id: "txn_8xKm...", status: "confirmed" },
// invoice: { id: "nfe_Qm3x...", status: "authorized" },
// shipping: { id: "shp_Wn2v...", tracking: "ME123456789BR" },
// notification: { id: "msg_Zk9w...", status: "delivered" },
// erp: { id: "omie_Rk4p...", status: "registered" },
// reconciliation: { id: "rec_Jm8n...", status: "matched" },
// }Six APIs. Six systems. One natural language instruction. Under 4 seconds. Zero human intervention. This is what the Complete Loop looks like in practice.
The session.loop() method is the orchestration layer. Under the hood, it routes through the meta tools in the correct dependency order: payment must confirm before invoicing starts, invoicing must complete before shipping can reference the NF-e access key, and so on. The onStep callback gives you real-time visibility into each step.
What's happening under the hood
When you call session.send() or session.loop(), three layers process the request:
1. Meta tool routing
A full Brazilian commerce preset exposes 99 raw tools across 6 MCP servers. Exposing all 99 to an LLM wastes context window tokens and increases the chance of tool selection errors. Instead, the SDK compresses them into 6 meta tools: codespar_pay, codespar_invoice, codespar_ship, codespar_notify, codespar_erp, and codespar_reconcile.
Each meta tool acts as a router. When the agent calls codespar_pay with method: "pix", the meta tool resolves this to zoop_create_pix_charge on the Zoop MCP server. If you passed method: "boleto", it would route to zoop_create_boleto instead. The agent doesn't need to know which underlying tool to call — the meta tool handles selection.
2. Managed authentication
Each MCP server requires different credentials. Zoop uses marketplace-level API keys. Nuvem Fiscal uses A1 digital certificates. Z-API uses instance tokens. Omie uses app keys and secrets. The SDK stores these credentials per-organization, encrypted at rest, and injects them into the correct server at session creation time. You never pass raw credentials in tool calls.
3. Framework adapters
The SDK works with any LLM framework. The session.tools array returns tool definitions compatible with Claude, OpenAI, and the Vercel AI SDK:
// With Anthropic's Claude SDK
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const session = await codespar.session.create({
userId: "user_abc123",
preset: "brazilian",
});
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
tools: session.tools, // CodeSpar tools in Claude format
messages: [
{ role: "user", content: "Charge R$150 via Pix to Maria" },
],
});
// Handle tool use in the response
for (const block of response.content) {
if (block.type === "tool_use") {
const result = await session.execute(block.name, block.input);
// Feed result back to Claude for next step
}
}Going to production
The sandbox gets you through development. Production requires three additional considerations.
Billing and quotas
CodeSpar meters tool calls per organization. The free tier includes 1,000 calls/month. The Pro tier ($49/month) includes 25,000 calls. Enterprise is usage-based with volume discounts. Each step in a Complete Loop counts as one tool call, so a full 6-step loop counts as 6 calls.
Error handling
Production commerce fails in predictable ways. Pix charges expire. SEFAZ goes down. WhatsApp rate-limits you. The SDK surfaces structured errors for each failure mode:
try {
const loop = await session.loop({
instruction: "...",
});
} catch (error) {
if (error.code === "PAYMENT_EXPIRED") {
// Pix QR code expired before customer scanned
// → Retry with a new charge
}
if (error.code === "SEFAZ_UNAVAILABLE") {
// SEFAZ returned 108 after 3 retries
// → Invoice queued for retry, continue with shipping
}
if (error.code === "WHATSAPP_RATE_LIMITED") {
// Z-API hit Meta's rate limit
// → Message queued, will deliver when window opens
}
}Monitoring
The dashboard provides real-time visibility into every session: tool calls, latencies, error rates, and billing. You can also export events to Datadog, Grafana, or any OpenTelemetry-compatible system via the session.events stream.
// Stream session events to your observability stack
session.on("tool.completed", (event) => {
metrics.histogram("codespar.tool.duration", event.durationMs, {
tool: event.tool,
server: event.server,
status: event.status,
});
});
session.on("tool.failed", (event) => {
alerting.notify({
severity: event.retryable ? "warning" : "critical",
message: `${event.tool} failed: ${event.error.code}`,
sessionId: session.id,
});
});What you've built
In this tutorial, you've built an agent that:
1. Charges via Pix through the Zoop MCP server, with QR code generation and instant settlement.
2. Issues an NF-e through the Nuvem Fiscal MCP server, with automatic tax calculation, XML signing, and SEFAZ validation.
3. Notifies the customer on WhatsApp through the Z-API MCP server, with templated messages and delivery tracking.
4. Runs the Complete Loop with all 6 commerce steps in a single natural language instruction, under 4 seconds.
This is not a demo. These are the same APIs and MCP servers that process real transactions in production. The only difference between your sandbox session and a production session is the CODESPAR_ENV flag.
The gap between "I built a prototype" and "this is running in production" is one environment variable. That's what infrastructure should feel like.
Try the Sandbox: Test tool calls interactively with Claude Opus in the CodeSpar Sandbox.
Read the Complete Loop deep dive: Understand every step, every edge case, and every compliance requirement in our Complete Loop deep dive.
Browse the MCP catalog: See all 50+ servers and 400+ tools on GitHub.
Fabiano Cruz is co-founder of CodeSpar. The SDK is available at npm. All MCP servers referenced in this tutorial are open source on GitHub.