@epay/sdk
v1.0.2
Published
Official TypeScript SDK for the Epay cryptocurrency payment processing API
Readme
@epay/sdk
The official TypeScript SDK for the Epay cryptocurrency payment processing API.
Installation
pnpm add @epay/sdk
# npm
npm install @epay/sdkQuick Start
import { Epay } from '@epay/sdk';
const epay = new Epay({ apiKey: 'sk_live_...' });
const invoice = await epay.invoices.create({
invoice: {
amount: 100,
destinationCurrency: 'usdt_polygon',
},
customerEmail: '[email protected]',
customerName: 'Jane Doe',
redirectUrl: 'https://example.com/payment/complete',
});
console.log(invoice.id); // "clx9abc..."
console.log(invoice.status); // "PREPARED"
console.log(invoice.paymentUrl); // "https://pay.epay.com/..."Configuration
import { Epay } from '@epay/sdk';
const epay = new Epay({
apiKey: 'sk_live_...',
// 'sandbox' | 'production' (default: 'production')
environment: 'sandbox',
// Or override with a custom base URL
baseUrl: 'https://custom.api.example.com/api/business/v1',
// Request timeout in milliseconds (default: 30000)
timeout: 30_000,
// Maximum automatic retries on 5xx/network errors (default: 2)
maxRetries: 2,
// Cap total wall time across all retries (optional)
totalTimeout: 90_000,
// Enable debug logging to console.debug (default: false)
debug: false,
});Resources
Invoices
// Create
const invoice = await epay.invoices.create({
invoice: { amount: 250_000, destinationCurrency: 'usdt_polygon' },
customerEmail: '[email protected]',
customerName: 'Alice',
reference: { orderId: 'ORD-9821' },
webhookUrl: 'https://merchant.com/webhooks/epay',
});
// List (paginated)
const page = await epay.invoices.list({ page: 1, limit: 20, statuses: ['ACTIVE', 'COMPLETED'] });
for (const inv of page.data) {
console.log(inv.id, inv.number, inv.destinationAmount);
}
// Retrieve
const inv = await epay.invoices.retrieve('clx9abc123...');
// Update
const updated = await epay.invoices.update('clx9abc123...', {
customerEmail: '[email protected]',
});
// Activate (PREPARED -> ACTIVE)
const active = await epay.invoices.activate('clx9abc123...', {
sourceCurrencyAlias: 'usdt_polygon',
expiredAt: '2026-04-15T23:59:59.000Z',
});
// Cancel
const cancelled = await epay.invoices.cancel('clx9abc123...', {
reason: 'Customer requested cancellation',
});Payouts
// Create (always use idempotency key)
const payout = await epay.payouts.create({
amount: 500_000,
payoutType: 'CRYPTO_FIAT',
networkSlug: 'polygon',
sourceCurrencyAlias: 'usdt_polygon',
destinationCurrencyAlias: 'idr',
payoutProviderName: 'xendit',
recipientName: 'Bob Smith',
recipientBankId: 'bca',
recipientBankAccountNumber: '1234567890',
}, { idempotencyKey: '550e8400-e29b-41d4-a716-446655440000' });
// List
const payouts = await epay.payouts.list({ status: 'COMPLETED', sortBy: 'createdAt' });
// Retrieve
const p = await epay.payouts.retrieve('clx9payout123...');
// Status history
const history = await epay.payouts.history('clx9payout123...');
// Validate bank account
const account = await epay.payouts.inquireAccount({
bankCode: 'bca',
accountNumber: '1234567890',
destinationCurrencyAlias: 'idr',
sourceCurrencyAlias: 'usdt_polygon',
networkSlug: 'polygon',
payoutProviderName: 'xendit',
});
// Get fee info before creating
const info = await epay.payouts.payoutInfo({
amount: 500_000,
payoutType: 'CRYPTO_FIAT',
networkSlug: 'polygon',
sourceCurrencyAlias: 'usdt_polygon',
destinationCurrencyAlias: 'idr',
payoutProviderName: 'xendit',
});Balances
// List
const balances = await epay.balances.list({ currencyAlias: 'usdt_polygon' });
// Retrieve
const bal = await epay.balances.retrieve('clx9balance123...');
// History
const history = await epay.balances.history('clx9balance123...', {
type: 'INVOICE',
startDate: '2026-01-01',
endDate: '2026-03-31',
});
// Export as CSV (returns raw Response)
const response = await epay.balances.exportHistory('clx9balance123...');
const csv = await response.text();Exchange Rates
const rates = await epay.exchangeRates.list({ currencies: ['IDR', 'USD'] });
const btcRates = await epay.exchangeRates.get('BTC', { currencies: ['IDR'] });Networks
const networks = await epay.networks.list({ isActive: true, blockchainType: 'EVM' });
const polygon = await epay.networks.get('polygon');Currencies
const currencies = await epay.currencies.list({ networkSlug: 'polygon' });
const usdt = await epay.currencies.get('usdt_polygon');Pagination
let page = await epay.invoices.list({ page: 1, limit: 100 });
while (true) {
for (const invoice of page.data) {
await processInvoice(invoice);
}
if (!page.hasNextPage()) break;
page = await page.nextPage();
}Error Handling
import { Epay, ApiError, NetworkError } from '@epay/sdk';
try {
const invoice = await epay.invoices.retrieve('invalid_id');
} catch (error) {
if (error instanceof ApiError) {
console.error('Status:', error.status); // 404
console.error('Message:', error.message); // "Invoice not found"
console.error('Request ID:', error.requestId); // for support tickets
if (error.isAuthentication) { /* 401 - invalid API key */ }
if (error.isNotFound) { /* 404 */ }
if (error.isRateLimit) { /* 429 - slow down */ }
}
if (error instanceof NetworkError) {
console.error('Network failure:', error.cause);
}
}Webhook Verification
Webhooks are verified using a separate import to keep the main entry browser/edge-safe.
# The webhook module uses Node.js crypto
import { verify } from '@epay/sdk/webhooks';Express
import express from 'express';
import { verify } from '@epay/sdk/webhooks';
const app = express();
const WEBHOOK_SECRET = 'whsec_...';
app.post('/webhooks/epay', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = verify(req.body, req.headers['x-signature'] as string, WEBHOOK_SECRET);
switch (event.type) {
case 'invoice.payment.received':
console.log(`Invoice ${event.data.number} paid: ${event.data.paidAmount}`);
break;
case 'invoice.lifecycle.expired':
console.log(`Invoice ${event.data.number} expired`);
break;
case 'invoice.status.changed':
console.log(`Invoice ${event.data.id}: ${event.data.previousStatus} -> ${event.data.status}`);
break;
}
res.json({ received: true });
} catch (err) {
console.error('Webhook verification failed:', err);
res.status(400).json({ error: 'Invalid signature' });
}
});Fastify
import Fastify from 'fastify';
import { verify } from '@epay/sdk/webhooks';
const fastify = Fastify();
fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, (_req, body, done) => {
(req as any).rawBody = body;
try { done(null, JSON.parse(body.toString())); }
catch (e) { done(e as Error, undefined); }
});
fastify.post('/webhooks/epay', (req, reply) => {
try {
const event = verify((req as any).rawBody, req.headers['x-signature'] as string, 'whsec_...');
// Handle event...
reply.send({ received: true });
} catch (err) {
reply.status(400).send({ error: 'Invalid signature' });
}
});Hono
import { Hono } from 'hono';
import { verify } from '@epay/sdk/webhooks';
const app = new Hono();
app.post('/webhooks/epay', async (c) => {
const rawBody = await c.req.text();
try {
const event = verify(rawBody, c.req.header('x-signature')!, 'whsec_...');
// Handle event...
return c.json({ received: true });
} catch {
return c.json({ error: 'Invalid signature' }, 400);
}
});Debug Mode
const epay = new Epay({ apiKey: 'sk_test_...', debug: true });
// Logs request/response details to console.debug
// API key is redacted in all outputAbortSignal
const controller = new AbortController();
const invoice = await epay.invoices.retrieve('clx9abc123...', {
signal: controller.signal,
});
// Cancel in-flight request
controller.abort();Idempotency
import { randomUUID } from 'node:crypto';
const key = randomUUID();
// Safe to retry -- same key returns same result
const payout = await epay.payouts.create(
{ amount: 500_000, /* ... */ },
{ idempotencyKey: key },
);Security
- HTTPS enforced --
baseUrlmust usehttps://. Onlyhttp://localhostis allowed for local development. - API key protection -- the key is stored in a private field, invisible to
JSON.stringify,Object.keys, andconsole.log. Always redacted in errors and debug output. - Path parameter validation -- resource IDs are validated to prevent path traversal attacks. Only
[a-zA-Z0-9_-]characters are allowed. - Idempotency warnings --
invoices.create()andpayouts.create()log a warning if called without anidempotencyKey. Always provide one for financial mutations to enable safe retries. - Webhook replay protection -- the SDK validates
event.timestampfreshness (default 5-minute tolerance). You must track processedevent.eventIdvalues to prevent replay attacks within the tolerance window. Store processed event IDs in your database and reject duplicates. - baseUrl must not be user-controllable -- if your application allows end-users to configure the API endpoint, it could enable SSRF attacks targeting internal services. Hardcode the URL or use the built-in environment presets.
- Request interceptor warning -- tools like Sentry or DataDog that instrument
fetchmay capture theX-API-Keyheader in telemetry logs. Be aware of this when using request monitoring in production.
Requirements
- Node.js >= 18.0.0
- TypeScript >= 5.0 (for type-checked usage)
- Webhook verification requires Node.js (
cryptomodule)
License
MIT
