Initial Node.js SDK v1.0.0

This commit is contained in:
garfieldheron
2026-02-19 12:07:39 -05:00
commit 6c252b7495
24 changed files with 1837 additions and 0 deletions

143
README.md Normal file
View File

@@ -0,0 +1,143 @@
# FetcherPay Node.js SDK
Official Node.js SDK for the FetcherPay API — One API. Every Rail.
## Installation
```bash
npm install @fetcherpay/node
```
## Quick Start
```typescript
import { FetcherPay } from '@fetcherpay/node';
const client = new FetcherPay({
apiKey: 'fp_test_your_key',
environment: 'sandbox', // or 'production'
});
// Create a payment
const payment = await client.payments.create({
amount: 10000, // $100.00 in cents
currency: 'USD',
source: { payment_method_id: 'pm_123' },
destination: { payment_method_id: 'pm_456' },
rail: 'auto', // Auto-select optimal rail
});
console.log(payment.id, payment.status);
```
## Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `apiKey` | string | required | Your FetcherPay API key |
| `environment` | 'sandbox' \| 'production' | 'sandbox' | API environment |
| `baseUrl` | string | auto | Override base URL |
| `timeout` | number | 30000 | Request timeout (ms) |
## API Reference
### Payments
```typescript
// Create payment
const payment = await client.payments.create({
amount: 10000,
source: { payment_method_id: 'pm_123' },
destination: { payment_method_id: 'pm_456' },
}, 'unique-idempotency-key');
// Retrieve payment
const payment = await client.payments.retrieve('pay_xxx');
// List payments
const payments = await client.payments.list({ limit: 10 });
// Cancel payment
await client.payments.cancel('pay_xxx', 'Customer request');
// Refund payment
await client.payments.refund('pay_xxx', { amount: 5000, reason: 'Partial refund' });
```
### Ledger
```typescript
// List accounts
const accounts = await client.ledger.listAccounts();
// Get account balance
const account = await client.ledger.retrieveAccount('la_xxx');
console.log(account.balance.available);
// List entries
const entries = await client.ledger.listEntries({ account_id: 'la_xxx' });
```
### Payment Methods
```typescript
// Create bank account
const pm = await client.paymentMethods.create({
type: 'bank_account',
bank_account: {
account_number: '000123456789',
routing_number: '011000015',
account_type: 'checking',
},
});
// List payment methods
const methods = await client.paymentMethods.list();
```
### Webhooks
```typescript
// Create webhook endpoint
const webhook = await client.webhooks.create({
url: 'https://your-app.com/webhooks',
events: ['payment.settled', 'payment.failed'],
});
// Verify webhook signature
const isValid = client.verifyWebhookSignature(
payload,
signature, // from X-FetcherPay-Signature header
webhook.secret
);
```
## Error Handling
```typescript
import { FetcherPayError, AuthenticationError, ValidationError } from '@fetcherpay/node';
try {
await client.payments.create({...});
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Invalid API key');
} else if (error instanceof ValidationError) {
console.error('Validation failed:', error.param);
} else if (error instanceof FetcherPayError) {
console.error('API error:', error.type, error.statusCode);
}
}
```
## TypeScript
Full TypeScript support with exported types:
```typescript
import { Payment, CreatePaymentRequest, PaymentStatus } from '@fetcherpay/node';
```
## License
MIT

31
dist/api/ledger.d.ts vendored Normal file
View File

@@ -0,0 +1,31 @@
import { AxiosInstance } from 'axios';
import { LedgerAccount, LedgerEntry, ListResponse, PaginationParams } from '../types';
/**
* Ledger API client
*/
export declare class LedgerClient {
private http;
constructor(http: AxiosInstance);
/**
* List all ledger accounts
*/
listAccounts(params?: PaginationParams & {
type?: string;
}): Promise<ListResponse<LedgerAccount>>;
/**
* Retrieve a ledger account by ID
*/
retrieveAccount(accountId: string): Promise<LedgerAccount>;
/**
* List all ledger entries
*/
listEntries(params?: PaginationParams & {
account_id?: string;
payment_id?: string;
entry_type?: string;
}): Promise<ListResponse<LedgerEntry>>;
/**
* Retrieve a ledger entry by ID
*/
retrieveEntry(entryId: string): Promise<LedgerEntry>;
}

27
dist/api/payment-methods.d.ts vendored Normal file
View File

@@ -0,0 +1,27 @@
import { AxiosInstance } from 'axios';
import { PaymentMethod, CreatePaymentMethodRequest, ListResponse, PaginationParams } from '../types';
/**
* Payment Methods API client
*/
export declare class PaymentMethodsClient {
private http;
constructor(http: AxiosInstance);
/**
* Create a new payment method
*/
create(params: CreatePaymentMethodRequest, idempotencyKey?: string): Promise<PaymentMethod>;
/**
* Retrieve a payment method by ID
*/
retrieve(paymentMethodId: string): Promise<PaymentMethod>;
/**
* List all payment methods
*/
list(params?: PaginationParams & {
type?: string;
}): Promise<ListResponse<PaymentMethod>>;
/**
* Delete a payment method
*/
delete(paymentMethodId: string): Promise<void>;
}

35
dist/api/payments.d.ts vendored Normal file
View File

@@ -0,0 +1,35 @@
import { AxiosInstance } from 'axios';
import { Payment, CreatePaymentRequest, ListResponse, PaginationParams, FilterParams } from '../types';
/**
* Payments API client
*/
export declare class PaymentsClient {
private http;
constructor(http: AxiosInstance);
/**
* Create a new payment
*/
create(params: CreatePaymentRequest, idempotencyKey?: string): Promise<Payment>;
/**
* Retrieve a payment by ID
*/
retrieve(paymentId: string): Promise<Payment>;
/**
* List all payments
*/
list(params?: PaginationParams & FilterParams & {
status?: string;
rail?: string;
}): Promise<ListResponse<Payment>>;
/**
* Cancel a pending payment
*/
cancel(paymentId: string, reason?: string, idempotencyKey?: string): Promise<Payment>;
/**
* Refund a settled payment
*/
refund(paymentId: string, params?: {
amount?: number;
reason?: string;
}, idempotencyKey?: string): Promise<Payment>;
}

29
dist/api/webhooks.d.ts vendored Normal file
View File

@@ -0,0 +1,29 @@
import { AxiosInstance } from 'axios';
import { WebhookEndpoint, CreateWebhookRequest, ListResponse, PaginationParams } from '../types';
/**
* Webhooks API client
*/
export declare class WebhooksClient {
private http;
constructor(http: AxiosInstance);
/**
* Create a new webhook endpoint
*/
create(params: CreateWebhookRequest, idempotencyKey?: string): Promise<WebhookEndpoint>;
/**
* Retrieve a webhook endpoint by ID
*/
retrieve(webhookId: string): Promise<WebhookEndpoint>;
/**
* List all webhook endpoints
*/
list(params?: PaginationParams): Promise<ListResponse<WebhookEndpoint>>;
/**
* Update a webhook endpoint
*/
update(webhookId: string, params: Partial<CreateWebhookRequest>): Promise<WebhookEndpoint>;
/**
* Delete a webhook endpoint
*/
delete(webhookId: string): Promise<void>;
}

21
dist/client.d.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
import { FetcherPayConfig } from './types';
import { PaymentsClient } from './api/payments';
import { LedgerClient } from './api/ledger';
import { PaymentMethodsClient } from './api/payment-methods';
import { WebhooksClient } from './api/webhooks';
/**
* Main FetcherPay client
*/
export declare class FetcherPay {
private http;
payments: PaymentsClient;
ledger: LedgerClient;
paymentMethods: PaymentMethodsClient;
webhooks: WebhooksClient;
constructor(config: FetcherPayConfig);
/**
* Verify webhook signature
*/
verifyWebhookSignature(payload: string, signature: string, secret: string): boolean;
}
export default FetcherPay;

27
dist/index.d.ts vendored Normal file
View File

@@ -0,0 +1,27 @@
/**
* FetcherPay Node.js SDK
* One API. Every Rail.
*
* @example
* ```typescript
* import { FetcherPay } from '@fetcherpay/node';
*
* const client = new FetcherPay({
* apiKey: 'fp_test_your_key',
* environment: 'sandbox'
* });
*
* const payment = await client.payments.create({
* amount: 10000,
* currency: 'USD',
* source: { payment_method_id: 'pm_123' },
* destination: { payment_method_id: 'pm_456' }
* });
* ```
*/
export { FetcherPay } from './client';
export { PaymentsClient } from './api/payments';
export { LedgerClient } from './api/ledger';
export { PaymentMethodsClient } from './api/payment-methods';
export { WebhooksClient } from './api/webhooks';
export * from './types';

271
dist/index.esm.js vendored Normal file
View File

@@ -0,0 +1,271 @@
import axios from 'axios';
/**
* FetcherPay SDK Types
*/
class FetcherPayError extends Error {
constructor(message, type, statusCode, param, code) {
super(message);
this.type = type;
this.statusCode = statusCode;
this.param = param;
this.code = code;
this.name = 'FetcherPayError';
}
}
class AuthenticationError extends FetcherPayError {
constructor(message) {
super(message, 'authentication_error', 401);
}
}
class ValidationError extends FetcherPayError {
constructor(message, param) {
super(message, 'validation_error', 422, param);
}
}
class NotFoundError extends FetcherPayError {
constructor(message) {
super(message, 'not_found', 404);
}
}
/**
* Payments API client
*/
class PaymentsClient {
constructor(http) {
this.http = http;
}
/**
* Create a new payment
*/
async create(params, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post('/payments', params, { headers });
return response.data;
}
/**
* Retrieve a payment by ID
*/
async retrieve(paymentId) {
const response = await this.http.get(`/payments/${paymentId}`);
return response.data;
}
/**
* List all payments
*/
async list(params) {
const response = await this.http.get('/payments', { params });
return response.data;
}
/**
* Cancel a pending payment
*/
async cancel(paymentId, reason, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post(`/payments/${paymentId}/cancel`, reason ? { reason } : {}, { headers });
return response.data;
}
/**
* Refund a settled payment
*/
async refund(paymentId, params, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post(`/payments/${paymentId}/refund`, params || {}, { headers });
return response.data;
}
}
/**
* Ledger API client
*/
class LedgerClient {
constructor(http) {
this.http = http;
}
/**
* List all ledger accounts
*/
async listAccounts(params) {
const response = await this.http.get('/ledger/accounts', { params });
return response.data;
}
/**
* Retrieve a ledger account by ID
*/
async retrieveAccount(accountId) {
const response = await this.http.get(`/ledger/accounts/${accountId}`);
return response.data;
}
/**
* List all ledger entries
*/
async listEntries(params) {
const response = await this.http.get('/ledger/entries', { params });
return response.data;
}
/**
* Retrieve a ledger entry by ID
*/
async retrieveEntry(entryId) {
const response = await this.http.get(`/ledger/entries/${entryId}`);
return response.data;
}
}
/**
* Payment Methods API client
*/
class PaymentMethodsClient {
constructor(http) {
this.http = http;
}
/**
* Create a new payment method
*/
async create(params, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post('/payment-methods', params, { headers });
return response.data;
}
/**
* Retrieve a payment method by ID
*/
async retrieve(paymentMethodId) {
const response = await this.http.get(`/payment-methods/${paymentMethodId}`);
return response.data;
}
/**
* List all payment methods
*/
async list(params) {
const response = await this.http.get('/payment-methods', { params });
return response.data;
}
/**
* Delete a payment method
*/
async delete(paymentMethodId) {
await this.http.delete(`/payment-methods/${paymentMethodId}`);
}
}
/**
* Webhooks API client
*/
class WebhooksClient {
constructor(http) {
this.http = http;
}
/**
* Create a new webhook endpoint
*/
async create(params, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post('/webhooks', params, { headers });
return response.data;
}
/**
* Retrieve a webhook endpoint by ID
*/
async retrieve(webhookId) {
const response = await this.http.get(`/webhooks/${webhookId}`);
return response.data;
}
/**
* List all webhook endpoints
*/
async list(params) {
const response = await this.http.get('/webhooks', { params });
return response.data;
}
/**
* Update a webhook endpoint
*/
async update(webhookId, params) {
const response = await this.http.put(`/webhooks/${webhookId}`, params);
return response.data;
}
/**
* Delete a webhook endpoint
*/
async delete(webhookId) {
await this.http.delete(`/webhooks/${webhookId}`);
}
}
/**
* Main FetcherPay client
*/
class FetcherPay {
constructor(config) {
const baseUrl = config.baseUrl || (config.environment === 'production'
? 'https://api.fetcherpay.com/v1'
: 'https://sandbox.fetcherpay.com/v1');
this.http = axios.create({
baseURL: baseUrl,
timeout: config.timeout || 30000,
headers: {
'Authorization': `Bearer ${config.apiKey}`,
'Content-Type': 'application/json',
},
});
// Response interceptor for error handling
this.http.interceptors.response.use((response) => response, (error) => {
if (error.response) {
const { status, data } = error.response;
const errorData = data?.error || {};
switch (status) {
case 401:
throw new AuthenticationError(errorData.message || 'Authentication failed');
case 404:
throw new NotFoundError(errorData.message || 'Resource not found');
case 422:
throw new ValidationError(errorData.message || 'Validation failed', errorData.param);
default:
throw new FetcherPayError(errorData.message || 'An error occurred', errorData.type || 'api_error', status, errorData.param, errorData.code);
}
}
throw error;
});
// Initialize API clients
this.payments = new PaymentsClient(this.http);
this.ledger = new LedgerClient(this.http);
this.paymentMethods = new PaymentMethodsClient(this.http);
this.webhooks = new WebhooksClient(this.http);
}
/**
* Verify webhook signature
*/
verifyWebhookSignature(payload, signature, secret) {
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
catch {
return false;
}
}
}
export { AuthenticationError, FetcherPay, FetcherPayError, LedgerClient, NotFoundError, PaymentMethodsClient, PaymentsClient, ValidationError, WebhooksClient };
//# sourceMappingURL=index.esm.js.map

1
dist/index.esm.js.map vendored Normal file

File diff suppressed because one or more lines are too long

281
dist/index.js vendored Normal file
View File

@@ -0,0 +1,281 @@
'use strict';
var axios = require('axios');
/**
* FetcherPay SDK Types
*/
class FetcherPayError extends Error {
constructor(message, type, statusCode, param, code) {
super(message);
this.type = type;
this.statusCode = statusCode;
this.param = param;
this.code = code;
this.name = 'FetcherPayError';
}
}
class AuthenticationError extends FetcherPayError {
constructor(message) {
super(message, 'authentication_error', 401);
}
}
class ValidationError extends FetcherPayError {
constructor(message, param) {
super(message, 'validation_error', 422, param);
}
}
class NotFoundError extends FetcherPayError {
constructor(message) {
super(message, 'not_found', 404);
}
}
/**
* Payments API client
*/
class PaymentsClient {
constructor(http) {
this.http = http;
}
/**
* Create a new payment
*/
async create(params, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post('/payments', params, { headers });
return response.data;
}
/**
* Retrieve a payment by ID
*/
async retrieve(paymentId) {
const response = await this.http.get(`/payments/${paymentId}`);
return response.data;
}
/**
* List all payments
*/
async list(params) {
const response = await this.http.get('/payments', { params });
return response.data;
}
/**
* Cancel a pending payment
*/
async cancel(paymentId, reason, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post(`/payments/${paymentId}/cancel`, reason ? { reason } : {}, { headers });
return response.data;
}
/**
* Refund a settled payment
*/
async refund(paymentId, params, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post(`/payments/${paymentId}/refund`, params || {}, { headers });
return response.data;
}
}
/**
* Ledger API client
*/
class LedgerClient {
constructor(http) {
this.http = http;
}
/**
* List all ledger accounts
*/
async listAccounts(params) {
const response = await this.http.get('/ledger/accounts', { params });
return response.data;
}
/**
* Retrieve a ledger account by ID
*/
async retrieveAccount(accountId) {
const response = await this.http.get(`/ledger/accounts/${accountId}`);
return response.data;
}
/**
* List all ledger entries
*/
async listEntries(params) {
const response = await this.http.get('/ledger/entries', { params });
return response.data;
}
/**
* Retrieve a ledger entry by ID
*/
async retrieveEntry(entryId) {
const response = await this.http.get(`/ledger/entries/${entryId}`);
return response.data;
}
}
/**
* Payment Methods API client
*/
class PaymentMethodsClient {
constructor(http) {
this.http = http;
}
/**
* Create a new payment method
*/
async create(params, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post('/payment-methods', params, { headers });
return response.data;
}
/**
* Retrieve a payment method by ID
*/
async retrieve(paymentMethodId) {
const response = await this.http.get(`/payment-methods/${paymentMethodId}`);
return response.data;
}
/**
* List all payment methods
*/
async list(params) {
const response = await this.http.get('/payment-methods', { params });
return response.data;
}
/**
* Delete a payment method
*/
async delete(paymentMethodId) {
await this.http.delete(`/payment-methods/${paymentMethodId}`);
}
}
/**
* Webhooks API client
*/
class WebhooksClient {
constructor(http) {
this.http = http;
}
/**
* Create a new webhook endpoint
*/
async create(params, idempotencyKey) {
const headers = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post('/webhooks', params, { headers });
return response.data;
}
/**
* Retrieve a webhook endpoint by ID
*/
async retrieve(webhookId) {
const response = await this.http.get(`/webhooks/${webhookId}`);
return response.data;
}
/**
* List all webhook endpoints
*/
async list(params) {
const response = await this.http.get('/webhooks', { params });
return response.data;
}
/**
* Update a webhook endpoint
*/
async update(webhookId, params) {
const response = await this.http.put(`/webhooks/${webhookId}`, params);
return response.data;
}
/**
* Delete a webhook endpoint
*/
async delete(webhookId) {
await this.http.delete(`/webhooks/${webhookId}`);
}
}
/**
* Main FetcherPay client
*/
class FetcherPay {
constructor(config) {
const baseUrl = config.baseUrl || (config.environment === 'production'
? 'https://api.fetcherpay.com/v1'
: 'https://sandbox.fetcherpay.com/v1');
this.http = axios.create({
baseURL: baseUrl,
timeout: config.timeout || 30000,
headers: {
'Authorization': `Bearer ${config.apiKey}`,
'Content-Type': 'application/json',
},
});
// Response interceptor for error handling
this.http.interceptors.response.use((response) => response, (error) => {
if (error.response) {
const { status, data } = error.response;
const errorData = data?.error || {};
switch (status) {
case 401:
throw new AuthenticationError(errorData.message || 'Authentication failed');
case 404:
throw new NotFoundError(errorData.message || 'Resource not found');
case 422:
throw new ValidationError(errorData.message || 'Validation failed', errorData.param);
default:
throw new FetcherPayError(errorData.message || 'An error occurred', errorData.type || 'api_error', status, errorData.param, errorData.code);
}
}
throw error;
});
// Initialize API clients
this.payments = new PaymentsClient(this.http);
this.ledger = new LedgerClient(this.http);
this.paymentMethods = new PaymentMethodsClient(this.http);
this.webhooks = new WebhooksClient(this.http);
}
/**
* Verify webhook signature
*/
verifyWebhookSignature(payload, signature, secret) {
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
catch {
return false;
}
}
}
exports.AuthenticationError = AuthenticationError;
exports.FetcherPay = FetcherPay;
exports.FetcherPayError = FetcherPayError;
exports.LedgerClient = LedgerClient;
exports.NotFoundError = NotFoundError;
exports.PaymentMethodsClient = PaymentMethodsClient;
exports.PaymentsClient = PaymentsClient;
exports.ValidationError = ValidationError;
exports.WebhooksClient = WebhooksClient;
//# sourceMappingURL=index.js.map

1
dist/index.js.map vendored Normal file

File diff suppressed because one or more lines are too long

192
dist/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,192 @@
/**
* FetcherPay SDK Types
*/
export interface FetcherPayConfig {
apiKey: string;
environment?: 'sandbox' | 'production';
baseUrl?: string;
timeout?: number;
}
export interface Payment {
id: string;
object: 'payment';
status: 'pending' | 'authorized' | 'processing' | 'settled' | 'failed' | 'cancelled' | 'refunded' | 'partially_refunded';
amount: number;
currency: string;
rail: string;
rail_selected: string;
description?: string;
source: PaymentSource;
destination: PaymentDestination;
fee?: Fee;
timeline: TimelineEvent[];
ledger_entry_ids: string[];
refunds: Refund[];
idempotency_key?: string;
metadata: Record<string, any>;
created_at: string;
updated_at: string;
}
export interface PaymentSource {
payment_method_id: string;
name?: string | null;
type?: string;
}
export interface PaymentDestination {
payment_method_id: string;
name?: string | null;
type?: string;
}
export interface Fee {
amount: number;
rate: string;
}
export interface TimelineEvent {
status: string;
timestamp: string;
detail: string;
}
export interface Refund {
id: string;
payment_id: string;
amount: number;
reason?: string | null;
status: string;
created_at: string;
}
export interface CreatePaymentRequest {
amount: number;
currency?: string;
rail?: 'auto' | 'ach' | 'rtp' | 'card' | 'crypto';
rail_fallback_order?: string[];
description?: string;
source: PaymentSource;
destination: PaymentDestination;
metadata?: Record<string, any>;
}
export interface PaymentMethod {
id: string;
object: 'payment_method';
type: 'bank_account' | 'card' | 'usdc_wallet';
status: 'active' | 'inactive' | 'verification_required';
bank_account?: BankAccount;
card?: Card;
usdc_wallet?: UsdcWallet;
metadata: Record<string, any>;
created_at: string;
}
export interface BankAccount {
account_type: 'checking' | 'savings';
bank_name?: string;
routing_number_last4: string;
account_number_last4: string;
}
export interface Card {
brand: string;
last4: string;
exp_month: number;
exp_year: number;
}
export interface UsdcWallet {
address: string;
network: 'ethereum' | 'polygon';
}
export interface CreatePaymentMethodRequest {
type: 'bank_account' | 'card' | 'usdc_wallet';
bank_account?: {
account_number: string;
routing_number: string;
account_type: 'checking' | 'savings';
};
card?: {
number: string;
exp_month: number;
exp_year: number;
cvc: string;
};
usdc_wallet?: {
address: string;
network: string;
};
metadata?: Record<string, any>;
}
export interface LedgerAccount {
id: string;
object: 'ledger_account';
name: string;
type: 'asset' | 'liability' | 'revenue' | 'expense' | 'equity';
currency: string;
balance: {
pending: number;
posted: number;
available: number;
};
metadata: Record<string, any>;
created_at: string;
updated_at: string;
}
export interface LedgerEntry {
id: string;
object: 'ledger_entry';
journal_id: string;
account_id: string;
payment_id?: string;
entry_type: 'debit' | 'credit';
amount: number;
currency: string;
status: 'pending' | 'posted';
description?: string;
metadata: Record<string, any>;
created_at: string;
}
export interface WebhookEndpoint {
id: string;
object: 'webhook_endpoint';
url: string;
events: string[];
status: 'active' | 'disabled';
secret: string;
metadata: Record<string, any>;
created_at: string;
}
export interface CreateWebhookRequest {
url: string;
events?: string[];
metadata?: Record<string, any>;
}
export interface WebhookEvent {
id: string;
object: 'event';
type: string;
created_at: string;
data: any;
}
export interface ListResponse<T> {
data: T[];
has_more: boolean;
next_cursor: string | null;
}
export interface PaginationParams {
cursor?: string;
limit?: number;
}
export interface FilterParams {
created_after?: string;
created_before?: string;
}
export declare class FetcherPayError extends Error {
type: string;
statusCode?: number | undefined;
param?: string | null | undefined;
code?: string | null | undefined;
constructor(message: string, type: string, statusCode?: number | undefined, param?: string | null | undefined, code?: string | null | undefined);
}
export declare class AuthenticationError extends FetcherPayError {
constructor(message: string);
}
export declare class ValidationError extends FetcherPayError {
constructor(message: string, param?: string);
}
export declare class NotFoundError extends FetcherPayError {
constructor(message: string);
}

54
examples/basic.ts Normal file
View File

@@ -0,0 +1,54 @@
/**
* FetcherPay Node.js SDK - Basic Example
*/
import { FetcherPay } from '../src';
// Initialize client
const client = new FetcherPay({
apiKey: process.env.FETCHERPAY_API_KEY || 'sandbox',
environment: 'sandbox',
});
async function main() {
try {
// Create a payment
console.log('Creating payment...');
const payment = await client.payments.create({
amount: 10000, // $100.00 in cents
currency: 'USD',
source: {
payment_method_id: 'pm_bank_123',
},
destination: {
payment_method_id: 'pm_merchant_456',
},
rail: 'auto', // Let FetcherPay choose optimal rail
});
console.log('Payment created:', payment.id, 'Status:', payment.status);
// Retrieve the payment
console.log('\nRetrieving payment...');
const retrieved = await client.payments.retrieve(payment.id);
console.log('Retrieved:', retrieved.id, 'Timeline:', retrieved.timeline.length, 'events');
// List payments
console.log('\nListing payments...');
const payments = await client.payments.list({ limit: 5 });
console.log(`Found ${payments.data.length} payments`);
// List ledger accounts
console.log('\nListing ledger accounts...');
const accounts = await client.ledger.listAccounts();
console.log(`Found ${accounts.data.length} accounts`);
accounts.data.forEach(acc => {
console.log(` - ${acc.name}: $${acc.balance.available / 100} available`);
});
} catch (error) {
console.error('Error:', error);
process.exit(1);
}
}
main();

View File

@@ -0,0 +1,40 @@
/**
* FetcherPay Node.js SDK - Webhook Verification Example
*/
import { FetcherPay } from '../src';
const client = new FetcherPay({
apiKey: 'sandbox',
environment: 'sandbox',
});
// Example webhook payload
const payload = JSON.stringify({
id: 'evt_123',
type: 'payment.settled',
created_at: '2026-02-18T20:00:00Z',
data: {
id: 'pay_456',
status: 'settled',
amount: 10000,
},
});
// Your webhook secret from the dashboard
const webhookSecret = 'whsec_your_secret_here';
// Simulate receiving a webhook
const signature = 'sha256=...'; // From X-FetcherPay-Signature header
// Verify the signature
const isValid = client.verifyWebhookSignature(payload, signature, webhookSecret);
if (isValid) {
console.log('✅ Webhook signature verified');
// Process the webhook event
const event = JSON.parse(payload);
console.log('Event type:', event.type);
} else {
console.log('❌ Invalid webhook signature');
}

35
package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "@fetcherpay/node",
"version": "1.0.0",
"description": "FetcherPay Node.js SDK",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src/**/*.ts",
"prepublishOnly": "npm run build"
},
"keywords": ["payments", "fintech", "api", "fetcherpay"],
"author": "FetcherPay",
"license": "MIT",
"dependencies": {
"axios": "^1.5.0",
"crypto": "^1.0.1"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.0",
"@types/jest": "^29.5.0",
"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.0.0",
"jest": "^29.5.0",
"rollup": "^3.29.0",
"ts-jest": "^29.1.0",
"typescript": "^5.0.0"
}
}

27
rollup.config.js Normal file
View File

@@ -0,0 +1,27 @@
import typescript from '@rollup/plugin-typescript';
export default [
// ES Module build
{
input: 'src/index.ts',
output: {
file: 'dist/index.esm.js',
format: 'es',
sourcemap: true,
},
plugins: [typescript({ tsconfig: './tsconfig.json' })],
external: ['axios', 'crypto'],
},
// CommonJS build
{
input: 'src/index.ts',
output: {
file: 'dist/index.js',
format: 'cjs',
sourcemap: true,
exports: 'named',
},
plugins: [typescript({ tsconfig: './tsconfig.json' })],
external: ['axios', 'crypto'],
},
];

50
src/api/ledger.ts Normal file
View File

@@ -0,0 +1,50 @@
import { AxiosInstance } from 'axios';
import {
LedgerAccount,
LedgerEntry,
ListResponse,
PaginationParams,
} from '../types';
/**
* Ledger API client
*/
export class LedgerClient {
constructor(private http: AxiosInstance) {}
/**
* List all ledger accounts
*/
async listAccounts(params?: PaginationParams & { type?: string }): Promise<ListResponse<LedgerAccount>> {
const response = await this.http.get('/ledger/accounts', { params });
return response.data;
}
/**
* Retrieve a ledger account by ID
*/
async retrieveAccount(accountId: string): Promise<LedgerAccount> {
const response = await this.http.get(`/ledger/accounts/${accountId}`);
return response.data;
}
/**
* List all ledger entries
*/
async listEntries(params?: PaginationParams & {
account_id?: string;
payment_id?: string;
entry_type?: string;
}): Promise<ListResponse<LedgerEntry>> {
const response = await this.http.get('/ledger/entries', { params });
return response.data;
}
/**
* Retrieve a ledger entry by ID
*/
async retrieveEntry(entryId: string): Promise<LedgerEntry> {
const response = await this.http.get(`/ledger/entries/${entryId}`);
return response.data;
}
}

View File

@@ -0,0 +1,50 @@
import { AxiosInstance } from 'axios';
import {
PaymentMethod,
CreatePaymentMethodRequest,
ListResponse,
PaginationParams,
} from '../types';
/**
* Payment Methods API client
*/
export class PaymentMethodsClient {
constructor(private http: AxiosInstance) {}
/**
* Create a new payment method
*/
async create(params: CreatePaymentMethodRequest, idempotencyKey?: string): Promise<PaymentMethod> {
const headers: Record<string, string> = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post('/payment-methods', params, { headers });
return response.data;
}
/**
* Retrieve a payment method by ID
*/
async retrieve(paymentMethodId: string): Promise<PaymentMethod> {
const response = await this.http.get(`/payment-methods/${paymentMethodId}`);
return response.data;
}
/**
* List all payment methods
*/
async list(params?: PaginationParams & { type?: string }): Promise<ListResponse<PaymentMethod>> {
const response = await this.http.get('/payment-methods', { params });
return response.data;
}
/**
* Delete a payment method
*/
async delete(paymentMethodId: string): Promise<void> {
await this.http.delete(`/payment-methods/${paymentMethodId}`);
}
}

82
src/api/payments.ts Normal file
View File

@@ -0,0 +1,82 @@
import { AxiosInstance } from 'axios';
import {
Payment,
CreatePaymentRequest,
ListResponse,
PaginationParams,
FilterParams,
} from '../types';
/**
* Payments API client
*/
export class PaymentsClient {
constructor(private http: AxiosInstance) {}
/**
* Create a new payment
*/
async create(params: CreatePaymentRequest, idempotencyKey?: string): Promise<Payment> {
const headers: Record<string, string> = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post('/payments', params, { headers });
return response.data;
}
/**
* Retrieve a payment by ID
*/
async retrieve(paymentId: string): Promise<Payment> {
const response = await this.http.get(`/payments/${paymentId}`);
return response.data;
}
/**
* List all payments
*/
async list(params?: PaginationParams & FilterParams & { status?: string; rail?: string }): Promise<ListResponse<Payment>> {
const response = await this.http.get('/payments', { params });
return response.data;
}
/**
* Cancel a pending payment
*/
async cancel(paymentId: string, reason?: string, idempotencyKey?: string): Promise<Payment> {
const headers: Record<string, string> = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post(
`/payments/${paymentId}/cancel`,
reason ? { reason } : {},
{ headers }
);
return response.data;
}
/**
* Refund a settled payment
*/
async refund(
paymentId: string,
params?: { amount?: number; reason?: string },
idempotencyKey?: string
): Promise<Payment> {
const headers: Record<string, string> = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post(
`/payments/${paymentId}/refund`,
params || {},
{ headers }
);
return response.data;
}
}

58
src/api/webhooks.ts Normal file
View File

@@ -0,0 +1,58 @@
import { AxiosInstance } from 'axios';
import {
WebhookEndpoint,
CreateWebhookRequest,
ListResponse,
PaginationParams,
} from '../types';
/**
* Webhooks API client
*/
export class WebhooksClient {
constructor(private http: AxiosInstance) {}
/**
* Create a new webhook endpoint
*/
async create(params: CreateWebhookRequest, idempotencyKey?: string): Promise<WebhookEndpoint> {
const headers: Record<string, string> = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.http.post('/webhooks', params, { headers });
return response.data;
}
/**
* Retrieve a webhook endpoint by ID
*/
async retrieve(webhookId: string): Promise<WebhookEndpoint> {
const response = await this.http.get(`/webhooks/${webhookId}`);
return response.data;
}
/**
* List all webhook endpoints
*/
async list(params?: PaginationParams): Promise<ListResponse<WebhookEndpoint>> {
const response = await this.http.get('/webhooks', { params });
return response.data;
}
/**
* Update a webhook endpoint
*/
async update(webhookId: string, params: Partial<CreateWebhookRequest>): Promise<WebhookEndpoint> {
const response = await this.http.put(`/webhooks/${webhookId}`, params);
return response.data;
}
/**
* Delete a webhook endpoint
*/
async delete(webhookId: string): Promise<void> {
await this.http.delete(`/webhooks/${webhookId}`);
}
}

97
src/client.ts Normal file
View File

@@ -0,0 +1,97 @@
import axios, { AxiosInstance, AxiosError } from 'axios';
import {
FetcherPayConfig,
FetcherPayError,
AuthenticationError,
ValidationError,
NotFoundError,
} from './types';
import { PaymentsClient } from './api/payments';
import { LedgerClient } from './api/ledger';
import { PaymentMethodsClient } from './api/payment-methods';
import { WebhooksClient } from './api/webhooks';
/**
* Main FetcherPay client
*/
export class FetcherPay {
private http: AxiosInstance;
public payments: PaymentsClient;
public ledger: LedgerClient;
public paymentMethods: PaymentMethodsClient;
public webhooks: WebhooksClient;
constructor(config: FetcherPayConfig) {
const baseUrl = config.baseUrl || (
config.environment === 'production'
? 'https://api.fetcherpay.com/v1'
: 'https://sandbox.fetcherpay.com/v1'
);
this.http = axios.create({
baseURL: baseUrl,
timeout: config.timeout || 30000,
headers: {
'Authorization': `Bearer ${config.apiKey}`,
'Content-Type': 'application/json',
},
});
// Response interceptor for error handling
this.http.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response) {
const { status, data } = error.response;
const errorData = (data as any)?.error || {};
switch (status) {
case 401:
throw new AuthenticationError(errorData.message || 'Authentication failed');
case 404:
throw new NotFoundError(errorData.message || 'Resource not found');
case 422:
throw new ValidationError(errorData.message || 'Validation failed', errorData.param);
default:
throw new FetcherPayError(
errorData.message || 'An error occurred',
errorData.type || 'api_error',
status,
errorData.param,
errorData.code
);
}
}
throw error;
}
);
// Initialize API clients
this.payments = new PaymentsClient(this.http);
this.ledger = new LedgerClient(this.http);
this.paymentMethods = new PaymentMethodsClient(this.http);
this.webhooks = new WebhooksClient(this.http);
}
/**
* Verify webhook signature
*/
verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
} catch {
return false;
}
}
}
export default FetcherPay;

28
src/index.ts Normal file
View File

@@ -0,0 +1,28 @@
/**
* FetcherPay Node.js SDK
* One API. Every Rail.
*
* @example
* ```typescript
* import { FetcherPay } from '@fetcherpay/node';
*
* const client = new FetcherPay({
* apiKey: 'fp_test_your_key',
* environment: 'sandbox'
* });
*
* const payment = await client.payments.create({
* amount: 10000,
* currency: 'USD',
* source: { payment_method_id: 'pm_123' },
* destination: { payment_method_id: 'pm_456' }
* });
* ```
*/
export { FetcherPay } from './client';
export { PaymentsClient } from './api/payments';
export { LedgerClient } from './api/ledger';
export { PaymentMethodsClient } from './api/payment-methods';
export { WebhooksClient } from './api/webhooks';
export * from './types';

228
src/types/index.ts Normal file
View File

@@ -0,0 +1,228 @@
/**
* FetcherPay SDK Types
*/
export interface FetcherPayConfig {
apiKey: string;
environment?: 'sandbox' | 'production';
baseUrl?: string;
timeout?: number;
}
export interface Payment {
id: string;
object: 'payment';
status: 'pending' | 'authorized' | 'processing' | 'settled' | 'failed' | 'cancelled' | 'refunded' | 'partially_refunded';
amount: number;
currency: string;
rail: string;
rail_selected: string;
description?: string;
source: PaymentSource;
destination: PaymentDestination;
fee?: Fee;
timeline: TimelineEvent[];
ledger_entry_ids: string[];
refunds: Refund[];
idempotency_key?: string;
metadata: Record<string, any>;
created_at: string;
updated_at: string;
}
export interface PaymentSource {
payment_method_id: string;
name?: string | null;
type?: string;
}
export interface PaymentDestination {
payment_method_id: string;
name?: string | null;
type?: string;
}
export interface Fee {
amount: number;
rate: string;
}
export interface TimelineEvent {
status: string;
timestamp: string;
detail: string;
}
export interface Refund {
id: string;
payment_id: string;
amount: number;
reason?: string | null;
status: string;
created_at: string;
}
export interface CreatePaymentRequest {
amount: number;
currency?: string;
rail?: 'auto' | 'ach' | 'rtp' | 'card' | 'crypto';
rail_fallback_order?: string[];
description?: string;
source: PaymentSource;
destination: PaymentDestination;
metadata?: Record<string, any>;
}
export interface PaymentMethod {
id: string;
object: 'payment_method';
type: 'bank_account' | 'card' | 'usdc_wallet';
status: 'active' | 'inactive' | 'verification_required';
bank_account?: BankAccount;
card?: Card;
usdc_wallet?: UsdcWallet;
metadata: Record<string, any>;
created_at: string;
}
export interface BankAccount {
account_type: 'checking' | 'savings';
bank_name?: string;
routing_number_last4: string;
account_number_last4: string;
}
export interface Card {
brand: string;
last4: string;
exp_month: number;
exp_year: number;
}
export interface UsdcWallet {
address: string;
network: 'ethereum' | 'polygon';
}
export interface CreatePaymentMethodRequest {
type: 'bank_account' | 'card' | 'usdc_wallet';
bank_account?: {
account_number: string;
routing_number: string;
account_type: 'checking' | 'savings';
};
card?: {
number: string;
exp_month: number;
exp_year: number;
cvc: string;
};
usdc_wallet?: {
address: string;
network: string;
};
metadata?: Record<string, any>;
}
export interface LedgerAccount {
id: string;
object: 'ledger_account';
name: string;
type: 'asset' | 'liability' | 'revenue' | 'expense' | 'equity';
currency: string;
balance: {
pending: number;
posted: number;
available: number;
};
metadata: Record<string, any>;
created_at: string;
updated_at: string;
}
export interface LedgerEntry {
id: string;
object: 'ledger_entry';
journal_id: string;
account_id: string;
payment_id?: string;
entry_type: 'debit' | 'credit';
amount: number;
currency: string;
status: 'pending' | 'posted';
description?: string;
metadata: Record<string, any>;
created_at: string;
}
export interface WebhookEndpoint {
id: string;
object: 'webhook_endpoint';
url: string;
events: string[];
status: 'active' | 'disabled';
secret: string;
metadata: Record<string, any>;
created_at: string;
}
export interface CreateWebhookRequest {
url: string;
events?: string[];
metadata?: Record<string, any>;
}
export interface WebhookEvent {
id: string;
object: 'event';
type: string;
created_at: string;
data: any;
}
export interface ListResponse<T> {
data: T[];
has_more: boolean;
next_cursor: string | null;
}
export interface PaginationParams {
cursor?: string;
limit?: number;
}
export interface FilterParams {
created_after?: string;
created_before?: string;
}
export class FetcherPayError extends Error {
constructor(
message: string,
public type: string,
public statusCode?: number,
public param?: string | null,
public code?: string | null
) {
super(message);
this.name = 'FetcherPayError';
}
}
export class AuthenticationError extends FetcherPayError {
constructor(message: string) {
super(message, 'authentication_error', 401);
}
}
export class ValidationError extends FetcherPayError {
constructor(message: string, param?: string) {
super(message, 'validation_error', 422, param);
}
}
export class NotFoundError extends FetcherPayError {
constructor(message: string) {
super(message, 'not_found', 404);
}
}

29
tsconfig.json Normal file
View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"outDir": "./dist",
"rootDir": "./src",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}