Bulk Refund
Refund a batch of payments in parallel using session.loop(). Handle partial failures without aborting the whole run.
Refund a batch of payments in one server-side job. Use session.loop() with abortOnError: false so one provider error does not kill the whole run. Good fit for chargeback reconciliation, marketplace seller disputes, and end-of-day cleanup workers.
Prerequisites
npm install @codespar/sdkEach settled refund is one transaction — $0.10 + 0.5% cross-border FX if applicable. See Billing.
The loop
session.loop() runs N steps in order, but the steps are independent — each is a codespar_pay refund against a specific payment. abortOnError: false lets the successful ones settle even if some fail.
import { CodeSpar } from "@codespar/sdk";
const codespar = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY! });
interface RefundJob {
payment_id: string;
amount: number; // centavos
reason: string;
}
async function bulkRefund(jobs: RefundJob[]) {
const session = await codespar.create("refund-worker", {
servers: ["stripe", "asaas", "mercadopago"],
metadata: { source: "cron:bulk-refund" },
});
try {
const result = await session.loop({
steps: jobs.map((job) => ({
tool: "codespar_pay",
params: {
action: "refund",
payment_id: job.payment_id,
amount: job.amount,
reason: job.reason,
},
})),
abortOnError: false,
onStepError: (step, error, index) => {
console.error(`Refund ${index} failed`, step.params, error);
},
});
return {
total: jobs.length,
succeeded: result.results.filter((r) => r.success).length,
failed: result.results.filter((r) => !r.success),
};
} finally {
await session.close();
}
}Handling the failures
result.results is a parallel array to the input jobs. Each entry has success, data, error, and the provider that handled it — so you can retry, alert, or write to a dead-letter queue per-job:
const summary = await bulkRefund(todaysChargebacks);
for (const failure of summary.failed) {
await deadLetterQueue.add({
tool_call_id: failure.tool_call_id,
error: failure.error,
payload: failure.data,
});
}
metrics.increment("refund.bulk.succeeded", summary.succeeded);
metrics.increment("refund.bulk.failed", summary.failed.length);Idempotency
Provider APIs return the same refund if you call them twice with the same payment_id (at least for Stripe, Asaas, Pagar.me, and Mercado Pago). CodeSpar forwards the call verbatim — so re-running the same bulk job is safe. If you need stricter guarantees, use your own idempotency table keyed on payment_id + date.
Variations
Pre-flight check with codespar_discover
Call codespar_discover once before the loop to confirm which providers are connected for refunds and avoid N wasted attempts when a provider is down:
const capabilities = await session.execute("codespar_discover", {
domain: "payments",
capability: "refund",
});
// Only proceed if capabilities.servers.length > 0Parallel vs sequential
session.loop() runs steps sequentially by design — it is predictable and easy to debug. For large batches (1000+), chunk into groups of 50 and run chunks in parallel with Promise.all, reopening a session per chunk if you hit the 30-min inactivity timeout.
Next steps
Last updated on
Cross-Border Fintech
Accept a USD card charge, settle to a BRL account, issue the fiscal document. One transaction, explicit FX surcharge, LGPD-ready audit trail.
Multi-Provider Agent
Route payments across Stripe, Asaas, and Mercado Pago automatically. The agent calls codespar_pay — CodeSpar picks the best provider for each transaction.