Refunds
A refund sends money back to a customer for a completed checkout session or a paid invoice. Plugipay handles the provider-side refund call (Xendit, Midtrans, PayPal, etc.), posts the corresponding ledger entries, and emits a webhook when the refund settles. This page covers the plugipay.refunds namespace; for the underlying HTTP surface see API: Refunds, and for how refunds appear in the ledger see Concepts → Ledger.
Namespace
plugipay.refunds — every method on this namespace:
plugipay.refunds.create(input)
plugipay.refunds.get(id)
plugipay.refunds.list(params?)
There are no update / cancel methods. Once submitted, a refund is in the provider's hands; if it fails, you create a new refund (or process the return manually and reconcile via the ledger).
Methods
refunds.create
Signature. plugipay.refunds.create(input): Promise<Refund>
Creates a refund against either a checkout session or an invoice. Pass amount (minor units) to refund partially; omit it to refund the full remaining balance. The SDK auto-attaches an Idempotency-Key, so retries don't double-refund.
import { PlugipayClient } from '@forjio/plugipay-node';
const plugipay = new PlugipayClient({
keyId: process.env.PLUGIPAY_KEY_ID!,
secret: process.env.PLUGIPAY_SECRET!,
});
// Full refund of a checkout session:
const fullRefund = await plugipay.refunds.create({
sourceType: 'checkout_session',
sourceId: 'cs_01HX...',
reason: 'Customer requested cancellation within 24 hours',
});
// Partial refund of an invoice:
const partialRefund = await plugipay.refunds.create({
sourceType: 'invoice',
sourceId: 'inv_01HX...',
amount: 50_000_00, // IDR 50,000.00
reason: 'Shipping issue — goodwill credit',
});
console.log(partialRefund.status); // → 'pending' or 'succeeded' depending on adapter
Refunds are irreversible. Once a refund has reached
succeeded, you can't recall the money — the customer must pay again. Partial refunds also can't be "un-partial-ed" upward; create a fresh refund for the additional amount if needed.
refunds.get
Signature. plugipay.refunds.get(id): Promise<Refund>
const refund = await plugipay.refunds.get('rfd_01HX...');
if (refund.status === 'failed') {
console.error(`Refund failed: ${refund.failureReason}`);
}
refunds.list
Signature. plugipay.refunds.list(params?): Promise<{ data: Refund[]; cursor: string | null; hasMore: boolean }>
Filters: status ('pending' | 'succeeded' | 'failed' | 'canceled'), sourceId (the cs_* or inv_* being refunded), limit, cursor. See Pagination.
// All refunds against one source:
const { data } = await plugipay.refunds.list({ sourceId: 'cs_01HX...' });
// All currently-pending refunds (for ops monitoring):
const pending = await plugipay.refunds.list({ status: 'pending', limit: 100 });
Types
type RefundStatus = 'pending' | 'succeeded' | 'failed' | 'canceled';
interface Refund {
id: string; // 'rfd_...'
accountId: string;
amount: number; // minor units
currency: 'IDR' | 'USD';
status: RefundStatus;
reason: string | null;
sourceType: 'checkout_session' | 'invoice';
sourceId: string;
failureReason: string | null;
createdAt: string;
updatedAt: string;
}
For the full state machine (including provider-specific transition rules), see API: Refunds.
Common patterns
Full refund
The simplest case — refund the full remaining balance, no amount argument:
async function refundOrder(checkoutSessionId: string, reason: string) {
return plugipay.refunds.create({
sourceType: 'checkout_session',
sourceId: checkoutSessionId,
reason,
});
}
Partial refund
For partial returns (one of three items refunded, shipping refunded but goods kept, etc.):
async function refundLineItem(checkoutSessionId: string, amountMinorUnits: number) {
return plugipay.refunds.create({
sourceType: 'checkout_session',
sourceId: checkoutSessionId,
amount: amountMinorUnits,
reason: 'Single line item returned',
});
}
The remaining balance can be refunded later in subsequent calls until the full amount has been returned.
Wait for a refund to settle
For card / e-wallet refunds, settlement can take hours or days. Poll get or react to plugipay.refund.succeeded.v1 (when emitted by your event subscriptions):
async function waitForRefund(refundId: string, maxAttempts = 20) {
for (let i = 0; i < maxAttempts; i++) {
const refund = await plugipay.refunds.get(refundId);
if (refund.status === 'succeeded' || refund.status === 'failed') {
return refund;
}
await new Promise((r) => setTimeout(r, 30_000));
}
throw new Error(`Refund ${refundId} still pending after ${maxAttempts} polls`);
}
For production, prefer webhooks — polling burns API calls and your rate limit.
Reconcile refunds against the ledger
Every refund posts a revenue_refund debit and a cash credit to the ledger. To cross-check refunds against the books:
const ledgerEntries = await plugipay.ledger.list({
sourceType: 'refund',
sourceId: 'rfd_01HX...',
});
for (const entry of ledgerEntries.data) {
console.log(`${entry.code} ${entry.direction} ${entry.amount}`);
}
Handle a failed refund
A failed refund is most often an adapter-level problem (closed card, unsupported region for cross-border refund). The failureReason field tells you why; the recovery is usually to process the return manually and either issue a new refund or create a credit-note invoice:
async function recoverFailedRefund(refundId: string) {
const refund = await plugipay.refunds.get(refundId);
if (refund.status === 'failed') {
await notifyOpsTeam(`Refund ${refundId} failed: ${refund.failureReason}`);
// Optionally: manually issue a credit via an invoice
}
}
Errors
| Code | Status | Cause |
|---|---|---|
validation_error |
400 | Bad sourceType, missing sourceId, negative or zero amount. |
not_found |
404 | Source checkout session or invoice doesn't exist. |
state_conflict |
409 | Trying to refund an unpaid session, voided invoice, or over the available balance. |
over_refund |
422 | amount exceeds the remaining refundable balance on the source. |
provider_error |
502 | The downstream payment provider rejected the refund (status failed on the refund object). |
Next
- Checkout sessions — the source of most refunds.
- Invoices — the other refund source.
- Ledger — where refunds post.
- API: Refunds — HTTP reference.