From 6c252b7495a9f542f108e1a0f38a98454c0d70ff Mon Sep 17 00:00:00 2001 From: garfieldheron Date: Thu, 19 Feb 2026 12:07:39 -0500 Subject: [PATCH] Initial Node.js SDK v1.0.0 --- README.md | 143 ++++++++++++++++ dist/api/ledger.d.ts | 31 ++++ dist/api/payment-methods.d.ts | 27 +++ dist/api/payments.d.ts | 35 ++++ dist/api/webhooks.d.ts | 29 ++++ dist/client.d.ts | 21 +++ dist/index.d.ts | 27 +++ dist/index.esm.js | 271 +++++++++++++++++++++++++++++ dist/index.esm.js.map | 1 + dist/index.js | 281 +++++++++++++++++++++++++++++++ dist/index.js.map | 1 + dist/types/index.d.ts | 192 +++++++++++++++++++++ examples/basic.ts | 54 ++++++ examples/webhook-verification.ts | 40 +++++ package.json | 35 ++++ rollup.config.js | 27 +++ src/api/ledger.ts | 50 ++++++ src/api/payment-methods.ts | 50 ++++++ src/api/payments.ts | 82 +++++++++ src/api/webhooks.ts | 58 +++++++ src/client.ts | 97 +++++++++++ src/index.ts | 28 +++ src/types/index.ts | 228 +++++++++++++++++++++++++ tsconfig.json | 29 ++++ 24 files changed, 1837 insertions(+) create mode 100644 README.md create mode 100644 dist/api/ledger.d.ts create mode 100644 dist/api/payment-methods.d.ts create mode 100644 dist/api/payments.d.ts create mode 100644 dist/api/webhooks.d.ts create mode 100644 dist/client.d.ts create mode 100644 dist/index.d.ts create mode 100644 dist/index.esm.js create mode 100644 dist/index.esm.js.map create mode 100644 dist/index.js create mode 100644 dist/index.js.map create mode 100644 dist/types/index.d.ts create mode 100644 examples/basic.ts create mode 100644 examples/webhook-verification.ts create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/api/ledger.ts create mode 100644 src/api/payment-methods.ts create mode 100644 src/api/payments.ts create mode 100644 src/api/webhooks.ts create mode 100644 src/client.ts create mode 100644 src/index.ts create mode 100644 src/types/index.ts create mode 100644 tsconfig.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..68a47dc --- /dev/null +++ b/README.md @@ -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 diff --git a/dist/api/ledger.d.ts b/dist/api/ledger.d.ts new file mode 100644 index 0000000..53c9d71 --- /dev/null +++ b/dist/api/ledger.d.ts @@ -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>; + /** + * Retrieve a ledger account by ID + */ + retrieveAccount(accountId: string): Promise; + /** + * List all ledger entries + */ + listEntries(params?: PaginationParams & { + account_id?: string; + payment_id?: string; + entry_type?: string; + }): Promise>; + /** + * Retrieve a ledger entry by ID + */ + retrieveEntry(entryId: string): Promise; +} diff --git a/dist/api/payment-methods.d.ts b/dist/api/payment-methods.d.ts new file mode 100644 index 0000000..1b9ebfe --- /dev/null +++ b/dist/api/payment-methods.d.ts @@ -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; + /** + * Retrieve a payment method by ID + */ + retrieve(paymentMethodId: string): Promise; + /** + * List all payment methods + */ + list(params?: PaginationParams & { + type?: string; + }): Promise>; + /** + * Delete a payment method + */ + delete(paymentMethodId: string): Promise; +} diff --git a/dist/api/payments.d.ts b/dist/api/payments.d.ts new file mode 100644 index 0000000..ce72544 --- /dev/null +++ b/dist/api/payments.d.ts @@ -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; + /** + * Retrieve a payment by ID + */ + retrieve(paymentId: string): Promise; + /** + * List all payments + */ + list(params?: PaginationParams & FilterParams & { + status?: string; + rail?: string; + }): Promise>; + /** + * Cancel a pending payment + */ + cancel(paymentId: string, reason?: string, idempotencyKey?: string): Promise; + /** + * Refund a settled payment + */ + refund(paymentId: string, params?: { + amount?: number; + reason?: string; + }, idempotencyKey?: string): Promise; +} diff --git a/dist/api/webhooks.d.ts b/dist/api/webhooks.d.ts new file mode 100644 index 0000000..ac7c50a --- /dev/null +++ b/dist/api/webhooks.d.ts @@ -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; + /** + * Retrieve a webhook endpoint by ID + */ + retrieve(webhookId: string): Promise; + /** + * List all webhook endpoints + */ + list(params?: PaginationParams): Promise>; + /** + * Update a webhook endpoint + */ + update(webhookId: string, params: Partial): Promise; + /** + * Delete a webhook endpoint + */ + delete(webhookId: string): Promise; +} diff --git a/dist/client.d.ts b/dist/client.d.ts new file mode 100644 index 0000000..b8f0e4d --- /dev/null +++ b/dist/client.d.ts @@ -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; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..17e43b6 --- /dev/null +++ b/dist/index.d.ts @@ -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'; diff --git a/dist/index.esm.js b/dist/index.esm.js new file mode 100644 index 0000000..a42c3b2 --- /dev/null +++ b/dist/index.esm.js @@ -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 diff --git a/dist/index.esm.js.map b/dist/index.esm.js.map new file mode 100644 index 0000000..ef2e29a --- /dev/null +++ b/dist/index.esm.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.esm.js","sources":["../src/types/index.ts","../src/api/payments.ts","../src/api/ledger.ts","../src/api/payment-methods.ts","../src/api/webhooks.ts","../src/client.ts"],"sourcesContent":["/**\n * FetcherPay SDK Types\n */\n\nexport interface FetcherPayConfig {\n apiKey: string;\n environment?: 'sandbox' | 'production';\n baseUrl?: string;\n timeout?: number;\n}\n\nexport interface Payment {\n id: string;\n object: 'payment';\n status: 'pending' | 'authorized' | 'processing' | 'settled' | 'failed' | 'cancelled' | 'refunded' | 'partially_refunded';\n amount: number;\n currency: string;\n rail: string;\n rail_selected: string;\n description?: string;\n source: PaymentSource;\n destination: PaymentDestination;\n fee?: Fee;\n timeline: TimelineEvent[];\n ledger_entry_ids: string[];\n refunds: Refund[];\n idempotency_key?: string;\n metadata: Record;\n created_at: string;\n updated_at: string;\n}\n\nexport interface PaymentSource {\n payment_method_id: string;\n name?: string | null;\n type?: string;\n}\n\nexport interface PaymentDestination {\n payment_method_id: string;\n name?: string | null;\n type?: string;\n}\n\nexport interface Fee {\n amount: number;\n rate: string;\n}\n\nexport interface TimelineEvent {\n status: string;\n timestamp: string;\n detail: string;\n}\n\nexport interface Refund {\n id: string;\n payment_id: string;\n amount: number;\n reason?: string | null;\n status: string;\n created_at: string;\n}\n\nexport interface CreatePaymentRequest {\n amount: number;\n currency?: string;\n rail?: 'auto' | 'ach' | 'rtp' | 'card' | 'crypto';\n rail_fallback_order?: string[];\n description?: string;\n source: PaymentSource;\n destination: PaymentDestination;\n metadata?: Record;\n}\n\nexport interface PaymentMethod {\n id: string;\n object: 'payment_method';\n type: 'bank_account' | 'card' | 'usdc_wallet';\n status: 'active' | 'inactive' | 'verification_required';\n bank_account?: BankAccount;\n card?: Card;\n usdc_wallet?: UsdcWallet;\n metadata: Record;\n created_at: string;\n}\n\nexport interface BankAccount {\n account_type: 'checking' | 'savings';\n bank_name?: string;\n routing_number_last4: string;\n account_number_last4: string;\n}\n\nexport interface Card {\n brand: string;\n last4: string;\n exp_month: number;\n exp_year: number;\n}\n\nexport interface UsdcWallet {\n address: string;\n network: 'ethereum' | 'polygon';\n}\n\nexport interface CreatePaymentMethodRequest {\n type: 'bank_account' | 'card' | 'usdc_wallet';\n bank_account?: {\n account_number: string;\n routing_number: string;\n account_type: 'checking' | 'savings';\n };\n card?: {\n number: string;\n exp_month: number;\n exp_year: number;\n cvc: string;\n };\n usdc_wallet?: {\n address: string;\n network: string;\n };\n metadata?: Record;\n}\n\nexport interface LedgerAccount {\n id: string;\n object: 'ledger_account';\n name: string;\n type: 'asset' | 'liability' | 'revenue' | 'expense' | 'equity';\n currency: string;\n balance: {\n pending: number;\n posted: number;\n available: number;\n };\n metadata: Record;\n created_at: string;\n updated_at: string;\n}\n\nexport interface LedgerEntry {\n id: string;\n object: 'ledger_entry';\n journal_id: string;\n account_id: string;\n payment_id?: string;\n entry_type: 'debit' | 'credit';\n amount: number;\n currency: string;\n status: 'pending' | 'posted';\n description?: string;\n metadata: Record;\n created_at: string;\n}\n\nexport interface WebhookEndpoint {\n id: string;\n object: 'webhook_endpoint';\n url: string;\n events: string[];\n status: 'active' | 'disabled';\n secret: string;\n metadata: Record;\n created_at: string;\n}\n\nexport interface CreateWebhookRequest {\n url: string;\n events?: string[];\n metadata?: Record;\n}\n\nexport interface WebhookEvent {\n id: string;\n object: 'event';\n type: string;\n created_at: string;\n data: any;\n}\n\nexport interface ListResponse {\n data: T[];\n has_more: boolean;\n next_cursor: string | null;\n}\n\nexport interface PaginationParams {\n cursor?: string;\n limit?: number;\n}\n\nexport interface FilterParams {\n created_after?: string;\n created_before?: string;\n}\n\nexport class FetcherPayError extends Error {\n constructor(\n message: string,\n public type: string,\n public statusCode?: number,\n public param?: string | null,\n public code?: string | null\n ) {\n super(message);\n this.name = 'FetcherPayError';\n }\n}\n\nexport class AuthenticationError extends FetcherPayError {\n constructor(message: string) {\n super(message, 'authentication_error', 401);\n }\n}\n\nexport class ValidationError extends FetcherPayError {\n constructor(message: string, param?: string) {\n super(message, 'validation_error', 422, param);\n }\n}\n\nexport class NotFoundError extends FetcherPayError {\n constructor(message: string) {\n super(message, 'not_found', 404);\n }\n}\n","import { AxiosInstance } from 'axios';\nimport {\n Payment,\n CreatePaymentRequest,\n ListResponse,\n PaginationParams,\n FilterParams,\n} from '../types';\n\n/**\n * Payments API client\n */\nexport class PaymentsClient {\n constructor(private http: AxiosInstance) {}\n\n /**\n * Create a new payment\n */\n async create(params: CreatePaymentRequest, idempotencyKey?: string): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post('/payments', params, { headers });\n return response.data;\n }\n\n /**\n * Retrieve a payment by ID\n */\n async retrieve(paymentId: string): Promise {\n const response = await this.http.get(`/payments/${paymentId}`);\n return response.data;\n }\n\n /**\n * List all payments\n */\n async list(params?: PaginationParams & FilterParams & { status?: string; rail?: string }): Promise> {\n const response = await this.http.get('/payments', { params });\n return response.data;\n }\n\n /**\n * Cancel a pending payment\n */\n async cancel(paymentId: string, reason?: string, idempotencyKey?: string): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post(\n `/payments/${paymentId}/cancel`,\n reason ? { reason } : {},\n { headers }\n );\n return response.data;\n }\n\n /**\n * Refund a settled payment\n */\n async refund(\n paymentId: string,\n params?: { amount?: number; reason?: string },\n idempotencyKey?: string\n ): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post(\n `/payments/${paymentId}/refund`,\n params || {},\n { headers }\n );\n return response.data;\n }\n}\n","import { AxiosInstance } from 'axios';\nimport {\n LedgerAccount,\n LedgerEntry,\n ListResponse,\n PaginationParams,\n} from '../types';\n\n/**\n * Ledger API client\n */\nexport class LedgerClient {\n constructor(private http: AxiosInstance) {}\n\n /**\n * List all ledger accounts\n */\n async listAccounts(params?: PaginationParams & { type?: string }): Promise> {\n const response = await this.http.get('/ledger/accounts', { params });\n return response.data;\n }\n\n /**\n * Retrieve a ledger account by ID\n */\n async retrieveAccount(accountId: string): Promise {\n const response = await this.http.get(`/ledger/accounts/${accountId}`);\n return response.data;\n }\n\n /**\n * List all ledger entries\n */\n async listEntries(params?: PaginationParams & {\n account_id?: string;\n payment_id?: string;\n entry_type?: string;\n }): Promise> {\n const response = await this.http.get('/ledger/entries', { params });\n return response.data;\n }\n\n /**\n * Retrieve a ledger entry by ID\n */\n async retrieveEntry(entryId: string): Promise {\n const response = await this.http.get(`/ledger/entries/${entryId}`);\n return response.data;\n }\n}\n","import { AxiosInstance } from 'axios';\nimport {\n PaymentMethod,\n CreatePaymentMethodRequest,\n ListResponse,\n PaginationParams,\n} from '../types';\n\n/**\n * Payment Methods API client\n */\nexport class PaymentMethodsClient {\n constructor(private http: AxiosInstance) {}\n\n /**\n * Create a new payment method\n */\n async create(params: CreatePaymentMethodRequest, idempotencyKey?: string): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post('/payment-methods', params, { headers });\n return response.data;\n }\n\n /**\n * Retrieve a payment method by ID\n */\n async retrieve(paymentMethodId: string): Promise {\n const response = await this.http.get(`/payment-methods/${paymentMethodId}`);\n return response.data;\n }\n\n /**\n * List all payment methods\n */\n async list(params?: PaginationParams & { type?: string }): Promise> {\n const response = await this.http.get('/payment-methods', { params });\n return response.data;\n }\n\n /**\n * Delete a payment method\n */\n async delete(paymentMethodId: string): Promise {\n await this.http.delete(`/payment-methods/${paymentMethodId}`);\n }\n}\n","import { AxiosInstance } from 'axios';\nimport {\n WebhookEndpoint,\n CreateWebhookRequest,\n ListResponse,\n PaginationParams,\n} from '../types';\n\n/**\n * Webhooks API client\n */\nexport class WebhooksClient {\n constructor(private http: AxiosInstance) {}\n\n /**\n * Create a new webhook endpoint\n */\n async create(params: CreateWebhookRequest, idempotencyKey?: string): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post('/webhooks', params, { headers });\n return response.data;\n }\n\n /**\n * Retrieve a webhook endpoint by ID\n */\n async retrieve(webhookId: string): Promise {\n const response = await this.http.get(`/webhooks/${webhookId}`);\n return response.data;\n }\n\n /**\n * List all webhook endpoints\n */\n async list(params?: PaginationParams): Promise> {\n const response = await this.http.get('/webhooks', { params });\n return response.data;\n }\n\n /**\n * Update a webhook endpoint\n */\n async update(webhookId: string, params: Partial): Promise {\n const response = await this.http.put(`/webhooks/${webhookId}`, params);\n return response.data;\n }\n\n /**\n * Delete a webhook endpoint\n */\n async delete(webhookId: string): Promise {\n await this.http.delete(`/webhooks/${webhookId}`);\n }\n}\n","import axios, { AxiosInstance, AxiosError } from 'axios';\nimport {\n FetcherPayConfig,\n FetcherPayError,\n AuthenticationError,\n ValidationError,\n NotFoundError,\n} from './types';\nimport { PaymentsClient } from './api/payments';\nimport { LedgerClient } from './api/ledger';\nimport { PaymentMethodsClient } from './api/payment-methods';\nimport { WebhooksClient } from './api/webhooks';\n\n/**\n * Main FetcherPay client\n */\nexport class FetcherPay {\n private http: AxiosInstance;\n public payments: PaymentsClient;\n public ledger: LedgerClient;\n public paymentMethods: PaymentMethodsClient;\n public webhooks: WebhooksClient;\n\n constructor(config: FetcherPayConfig) {\n const baseUrl = config.baseUrl || (\n config.environment === 'production'\n ? 'https://api.fetcherpay.com/v1'\n : 'https://sandbox.fetcherpay.com/v1'\n );\n\n this.http = axios.create({\n baseURL: baseUrl,\n timeout: config.timeout || 30000,\n headers: {\n 'Authorization': `Bearer ${config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n // Response interceptor for error handling\n this.http.interceptors.response.use(\n (response) => response,\n (error: AxiosError) => {\n if (error.response) {\n const { status, data } = error.response;\n const errorData = (data as any)?.error || {};\n\n switch (status) {\n case 401:\n throw new AuthenticationError(errorData.message || 'Authentication failed');\n case 404:\n throw new NotFoundError(errorData.message || 'Resource not found');\n case 422:\n throw new ValidationError(errorData.message || 'Validation failed', errorData.param);\n default:\n throw new FetcherPayError(\n errorData.message || 'An error occurred',\n errorData.type || 'api_error',\n status,\n errorData.param,\n errorData.code\n );\n }\n }\n throw error;\n }\n );\n\n // Initialize API clients\n this.payments = new PaymentsClient(this.http);\n this.ledger = new LedgerClient(this.http);\n this.paymentMethods = new PaymentMethodsClient(this.http);\n this.webhooks = new WebhooksClient(this.http);\n }\n\n /**\n * Verify webhook signature\n */\n verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {\n const crypto = require('crypto');\n const expected = crypto\n .createHmac('sha256', secret)\n .update(payload)\n .digest('hex');\n \n try {\n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expected)\n );\n } catch {\n return false;\n }\n }\n}\n\nexport default FetcherPay;\n"],"names":[],"mappings":";;AAAA;;AAEG;AAoMG,MAAO,eAAgB,SAAQ,KAAK,CAAA;IACxC,WACE,CAAA,OAAe,EACR,IAAY,EACZ,UAAmB,EACnB,KAAqB,EACrB,IAAoB,EAAA;QAE3B,KAAK,CAAC,OAAO,CAAC,CAAC;QALR,IAAI,CAAA,IAAA,GAAJ,IAAI,CAAQ;QACZ,IAAU,CAAA,UAAA,GAAV,UAAU,CAAS;QACnB,IAAK,CAAA,KAAA,GAAL,KAAK,CAAgB;QACrB,IAAI,CAAA,IAAA,GAAJ,IAAI,CAAgB;AAG3B,QAAA,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;KAC/B;AACF,CAAA;AAEK,MAAO,mBAAoB,SAAQ,eAAe,CAAA;AACtD,IAAA,WAAA,CAAY,OAAe,EAAA;AACzB,QAAA,KAAK,CAAC,OAAO,EAAE,sBAAsB,EAAE,GAAG,CAAC,CAAC;KAC7C;AACF,CAAA;AAEK,MAAO,eAAgB,SAAQ,eAAe,CAAA;IAClD,WAAY,CAAA,OAAe,EAAE,KAAc,EAAA;QACzC,KAAK,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;KAChD;AACF,CAAA;AAEK,MAAO,aAAc,SAAQ,eAAe,CAAA;AAChD,IAAA,WAAA,CAAY,OAAe,EAAA;AACzB,QAAA,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;KAClC;AACF;;AC1ND;;AAEG;MACU,cAAc,CAAA;AACzB,IAAA,WAAA,CAAoB,IAAmB,EAAA;QAAnB,IAAI,CAAA,IAAA,GAAJ,IAAI,CAAe;KAAI;AAE3C;;AAEG;AACH,IAAA,MAAM,MAAM,CAAC,MAA4B,EAAE,cAAuB,EAAA;QAChE,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,IAAI,cAAc,EAAE;AAClB,YAAA,OAAO,CAAC,iBAAiB,CAAC,GAAG,cAAc,CAAC;SAC7C;AAED,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACxE,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,QAAQ,CAAC,SAAiB,EAAA;AAC9B,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA,UAAA,EAAa,SAAS,CAAA,CAAE,CAAC,CAAC;QAC/D,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,IAAI,CAAC,MAA6E,EAAA;AACtF,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9D,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;AACH,IAAA,MAAM,MAAM,CAAC,SAAiB,EAAE,MAAe,EAAE,cAAuB,EAAA;QACtE,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,IAAI,cAAc,EAAE;AAClB,YAAA,OAAO,CAAC,iBAAiB,CAAC,GAAG,cAAc,CAAC;SAC7C;AAED,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CACnC,CAAA,UAAA,EAAa,SAAS,CAAA,OAAA,CAAS,EAC/B,MAAM,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,EACxB,EAAE,OAAO,EAAE,CACZ,CAAC;QACF,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;AACH,IAAA,MAAM,MAAM,CACV,SAAiB,EACjB,MAA6C,EAC7C,cAAuB,EAAA;QAEvB,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,IAAI,cAAc,EAAE;AAClB,YAAA,OAAO,CAAC,iBAAiB,CAAC,GAAG,cAAc,CAAC;SAC7C;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CACnC,CAAA,UAAA,EAAa,SAAS,CAAS,OAAA,CAAA,EAC/B,MAAM,IAAI,EAAE,EACZ,EAAE,OAAO,EAAE,CACZ,CAAC;QACF,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AACF;;ACzED;;AAEG;MACU,YAAY,CAAA;AACvB,IAAA,WAAA,CAAoB,IAAmB,EAAA;QAAnB,IAAI,CAAA,IAAA,GAAJ,IAAI,CAAe;KAAI;AAE3C;;AAEG;IACH,MAAM,YAAY,CAAC,MAA6C,EAAA;AAC9D,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACrE,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,eAAe,CAAC,SAAiB,EAAA;AACrC,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAE,CAAC,CAAC;QACtE,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,WAAW,CAAC,MAIjB,EAAA;AACC,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACpE,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,aAAa,CAAC,OAAe,EAAA;AACjC,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA,gBAAA,EAAmB,OAAO,CAAA,CAAE,CAAC,CAAC;QACnE,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AACF;;ACzCD;;AAEG;MACU,oBAAoB,CAAA;AAC/B,IAAA,WAAA,CAAoB,IAAmB,EAAA;QAAnB,IAAI,CAAA,IAAA,GAAJ,IAAI,CAAe;KAAI;AAE3C;;AAEG;AACH,IAAA,MAAM,MAAM,CAAC,MAAkC,EAAE,cAAuB,EAAA;QACtE,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,IAAI,cAAc,EAAE;AAClB,YAAA,OAAO,CAAC,iBAAiB,CAAC,GAAG,cAAc,CAAC;SAC7C;AAED,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/E,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,QAAQ,CAAC,eAAuB,EAAA;AACpC,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA,iBAAA,EAAoB,eAAe,CAAA,CAAE,CAAC,CAAC;QAC5E,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,IAAI,CAAC,MAA6C,EAAA;AACtD,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACrE,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,MAAM,CAAC,eAAuB,EAAA;QAClC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAoB,iBAAA,EAAA,eAAe,CAAE,CAAA,CAAC,CAAC;KAC/D;AACF;;ACzCD;;AAEG;MACU,cAAc,CAAA;AACzB,IAAA,WAAA,CAAoB,IAAmB,EAAA;QAAnB,IAAI,CAAA,IAAA,GAAJ,IAAI,CAAe;KAAI;AAE3C;;AAEG;AACH,IAAA,MAAM,MAAM,CAAC,MAA4B,EAAE,cAAuB,EAAA;QAChE,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,IAAI,cAAc,EAAE;AAClB,YAAA,OAAO,CAAC,iBAAiB,CAAC,GAAG,cAAc,CAAC;SAC7C;AAED,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACxE,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,QAAQ,CAAC,SAAiB,EAAA;AAC9B,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA,UAAA,EAAa,SAAS,CAAA,CAAE,CAAC,CAAC;QAC/D,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,IAAI,CAAC,MAAyB,EAAA;AAClC,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9D,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;AACH,IAAA,MAAM,MAAM,CAAC,SAAiB,EAAE,MAAqC,EAAA;AACnE,QAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,SAAS,CAAA,CAAE,EAAE,MAAM,CAAC,CAAC;QACvE,OAAO,QAAQ,CAAC,IAAI,CAAC;KACtB;AAED;;AAEG;IACH,MAAM,MAAM,CAAC,SAAiB,EAAA;QAC5B,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAa,UAAA,EAAA,SAAS,CAAE,CAAA,CAAC,CAAC;KAClD;AACF;;AC5CD;;AAEG;MACU,UAAU,CAAA;AAOrB,IAAA,WAAA,CAAY,MAAwB,EAAA;QAClC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,KAC5B,MAAM,CAAC,WAAW,KAAK,YAAY;AACjC,cAAE,+BAA+B;cAC/B,mCAAmC,CACxC,CAAC;AAEF,QAAA,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;AACvB,YAAA,OAAO,EAAE,OAAO;AAChB,YAAA,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,KAAK;AAChC,YAAA,OAAO,EAAE;AACP,gBAAA,eAAe,EAAE,CAAA,OAAA,EAAU,MAAM,CAAC,MAAM,CAAE,CAAA;AAC1C,gBAAA,cAAc,EAAE,kBAAkB;AACnC,aAAA;AACF,SAAA,CAAC,CAAC;;AAGH,QAAA,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CACjC,CAAC,QAAQ,KAAK,QAAQ,EACtB,CAAC,KAAiB,KAAI;AACpB,YAAA,IAAI,KAAK,CAAC,QAAQ,EAAE;gBAClB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC;AACxC,gBAAA,MAAM,SAAS,GAAI,IAAY,EAAE,KAAK,IAAI,EAAE,CAAC;gBAE7C,QAAQ,MAAM;AACZ,oBAAA,KAAK,GAAG;wBACN,MAAM,IAAI,mBAAmB,CAAC,SAAS,CAAC,OAAO,IAAI,uBAAuB,CAAC,CAAC;AAC9E,oBAAA,KAAK,GAAG;wBACN,MAAM,IAAI,aAAa,CAAC,SAAS,CAAC,OAAO,IAAI,oBAAoB,CAAC,CAAC;AACrE,oBAAA,KAAK,GAAG;AACN,wBAAA,MAAM,IAAI,eAAe,CAAC,SAAS,CAAC,OAAO,IAAI,mBAAmB,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;AACvF,oBAAA;wBACE,MAAM,IAAI,eAAe,CACvB,SAAS,CAAC,OAAO,IAAI,mBAAmB,EACxC,SAAS,CAAC,IAAI,IAAI,WAAW,EAC7B,MAAM,EACN,SAAS,CAAC,KAAK,EACf,SAAS,CAAC,IAAI,CACf,CAAC;iBACL;aACF;AACD,YAAA,MAAM,KAAK,CAAC;AACd,SAAC,CACF,CAAC;;QAGF,IAAI,CAAC,QAAQ,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,cAAc,GAAG,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,IAAI,CAAC,QAAQ,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KAC/C;AAED;;AAEG;AACH,IAAA,sBAAsB,CAAC,OAAe,EAAE,SAAiB,EAAE,MAAc,EAAA;AACvE,QAAA,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACjC,MAAM,QAAQ,GAAG,MAAM;AACpB,aAAA,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;aAC5B,MAAM,CAAC,OAAO,CAAC;aACf,MAAM,CAAC,KAAK,CAAC,CAAC;AAEjB,QAAA,IAAI;AACF,YAAA,OAAO,MAAM,CAAC,eAAe,CAC3B,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EACtB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CACtB,CAAC;SACH;AAAC,QAAA,MAAM;AACN,YAAA,OAAO,KAAK,CAAC;SACd;KACF;AACF;;;;"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..aff4859 --- /dev/null +++ b/dist/index.js @@ -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 diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..30246de --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sources":["../src/types/index.ts","../src/api/payments.ts","../src/api/ledger.ts","../src/api/payment-methods.ts","../src/api/webhooks.ts","../src/client.ts"],"sourcesContent":["/**\n * FetcherPay SDK Types\n */\n\nexport interface FetcherPayConfig {\n apiKey: string;\n environment?: 'sandbox' | 'production';\n baseUrl?: string;\n timeout?: number;\n}\n\nexport interface Payment {\n id: string;\n object: 'payment';\n status: 'pending' | 'authorized' | 'processing' | 'settled' | 'failed' | 'cancelled' | 'refunded' | 'partially_refunded';\n amount: number;\n currency: string;\n rail: string;\n rail_selected: string;\n description?: string;\n source: PaymentSource;\n destination: PaymentDestination;\n fee?: Fee;\n timeline: TimelineEvent[];\n ledger_entry_ids: string[];\n refunds: Refund[];\n idempotency_key?: string;\n metadata: Record;\n created_at: string;\n updated_at: string;\n}\n\nexport interface PaymentSource {\n payment_method_id: string;\n name?: string | null;\n type?: string;\n}\n\nexport interface PaymentDestination {\n payment_method_id: string;\n name?: string | null;\n type?: string;\n}\n\nexport interface Fee {\n amount: number;\n rate: string;\n}\n\nexport interface TimelineEvent {\n status: string;\n timestamp: string;\n detail: string;\n}\n\nexport interface Refund {\n id: string;\n payment_id: string;\n amount: number;\n reason?: string | null;\n status: string;\n created_at: string;\n}\n\nexport interface CreatePaymentRequest {\n amount: number;\n currency?: string;\n rail?: 'auto' | 'ach' | 'rtp' | 'card' | 'crypto';\n rail_fallback_order?: string[];\n description?: string;\n source: PaymentSource;\n destination: PaymentDestination;\n metadata?: Record;\n}\n\nexport interface PaymentMethod {\n id: string;\n object: 'payment_method';\n type: 'bank_account' | 'card' | 'usdc_wallet';\n status: 'active' | 'inactive' | 'verification_required';\n bank_account?: BankAccount;\n card?: Card;\n usdc_wallet?: UsdcWallet;\n metadata: Record;\n created_at: string;\n}\n\nexport interface BankAccount {\n account_type: 'checking' | 'savings';\n bank_name?: string;\n routing_number_last4: string;\n account_number_last4: string;\n}\n\nexport interface Card {\n brand: string;\n last4: string;\n exp_month: number;\n exp_year: number;\n}\n\nexport interface UsdcWallet {\n address: string;\n network: 'ethereum' | 'polygon';\n}\n\nexport interface CreatePaymentMethodRequest {\n type: 'bank_account' | 'card' | 'usdc_wallet';\n bank_account?: {\n account_number: string;\n routing_number: string;\n account_type: 'checking' | 'savings';\n };\n card?: {\n number: string;\n exp_month: number;\n exp_year: number;\n cvc: string;\n };\n usdc_wallet?: {\n address: string;\n network: string;\n };\n metadata?: Record;\n}\n\nexport interface LedgerAccount {\n id: string;\n object: 'ledger_account';\n name: string;\n type: 'asset' | 'liability' | 'revenue' | 'expense' | 'equity';\n currency: string;\n balance: {\n pending: number;\n posted: number;\n available: number;\n };\n metadata: Record;\n created_at: string;\n updated_at: string;\n}\n\nexport interface LedgerEntry {\n id: string;\n object: 'ledger_entry';\n journal_id: string;\n account_id: string;\n payment_id?: string;\n entry_type: 'debit' | 'credit';\n amount: number;\n currency: string;\n status: 'pending' | 'posted';\n description?: string;\n metadata: Record;\n created_at: string;\n}\n\nexport interface WebhookEndpoint {\n id: string;\n object: 'webhook_endpoint';\n url: string;\n events: string[];\n status: 'active' | 'disabled';\n secret: string;\n metadata: Record;\n created_at: string;\n}\n\nexport interface CreateWebhookRequest {\n url: string;\n events?: string[];\n metadata?: Record;\n}\n\nexport interface WebhookEvent {\n id: string;\n object: 'event';\n type: string;\n created_at: string;\n data: any;\n}\n\nexport interface ListResponse {\n data: T[];\n has_more: boolean;\n next_cursor: string | null;\n}\n\nexport interface PaginationParams {\n cursor?: string;\n limit?: number;\n}\n\nexport interface FilterParams {\n created_after?: string;\n created_before?: string;\n}\n\nexport class FetcherPayError extends Error {\n constructor(\n message: string,\n public type: string,\n public statusCode?: number,\n public param?: string | null,\n public code?: string | null\n ) {\n super(message);\n this.name = 'FetcherPayError';\n }\n}\n\nexport class AuthenticationError extends FetcherPayError {\n constructor(message: string) {\n super(message, 'authentication_error', 401);\n }\n}\n\nexport class ValidationError extends FetcherPayError {\n constructor(message: string, param?: string) {\n super(message, 'validation_error', 422, param);\n }\n}\n\nexport class NotFoundError extends FetcherPayError {\n constructor(message: string) {\n super(message, 'not_found', 404);\n }\n}\n","import { AxiosInstance } from 'axios';\nimport {\n Payment,\n CreatePaymentRequest,\n ListResponse,\n PaginationParams,\n FilterParams,\n} from '../types';\n\n/**\n * Payments API client\n */\nexport class PaymentsClient {\n constructor(private http: AxiosInstance) {}\n\n /**\n * Create a new payment\n */\n async create(params: CreatePaymentRequest, idempotencyKey?: string): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post('/payments', params, { headers });\n return response.data;\n }\n\n /**\n * Retrieve a payment by ID\n */\n async retrieve(paymentId: string): Promise {\n const response = await this.http.get(`/payments/${paymentId}`);\n return response.data;\n }\n\n /**\n * List all payments\n */\n async list(params?: PaginationParams & FilterParams & { status?: string; rail?: string }): Promise> {\n const response = await this.http.get('/payments', { params });\n return response.data;\n }\n\n /**\n * Cancel a pending payment\n */\n async cancel(paymentId: string, reason?: string, idempotencyKey?: string): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post(\n `/payments/${paymentId}/cancel`,\n reason ? { reason } : {},\n { headers }\n );\n return response.data;\n }\n\n /**\n * Refund a settled payment\n */\n async refund(\n paymentId: string,\n params?: { amount?: number; reason?: string },\n idempotencyKey?: string\n ): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post(\n `/payments/${paymentId}/refund`,\n params || {},\n { headers }\n );\n return response.data;\n }\n}\n","import { AxiosInstance } from 'axios';\nimport {\n LedgerAccount,\n LedgerEntry,\n ListResponse,\n PaginationParams,\n} from '../types';\n\n/**\n * Ledger API client\n */\nexport class LedgerClient {\n constructor(private http: AxiosInstance) {}\n\n /**\n * List all ledger accounts\n */\n async listAccounts(params?: PaginationParams & { type?: string }): Promise> {\n const response = await this.http.get('/ledger/accounts', { params });\n return response.data;\n }\n\n /**\n * Retrieve a ledger account by ID\n */\n async retrieveAccount(accountId: string): Promise {\n const response = await this.http.get(`/ledger/accounts/${accountId}`);\n return response.data;\n }\n\n /**\n * List all ledger entries\n */\n async listEntries(params?: PaginationParams & {\n account_id?: string;\n payment_id?: string;\n entry_type?: string;\n }): Promise> {\n const response = await this.http.get('/ledger/entries', { params });\n return response.data;\n }\n\n /**\n * Retrieve a ledger entry by ID\n */\n async retrieveEntry(entryId: string): Promise {\n const response = await this.http.get(`/ledger/entries/${entryId}`);\n return response.data;\n }\n}\n","import { AxiosInstance } from 'axios';\nimport {\n PaymentMethod,\n CreatePaymentMethodRequest,\n ListResponse,\n PaginationParams,\n} from '../types';\n\n/**\n * Payment Methods API client\n */\nexport class PaymentMethodsClient {\n constructor(private http: AxiosInstance) {}\n\n /**\n * Create a new payment method\n */\n async create(params: CreatePaymentMethodRequest, idempotencyKey?: string): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post('/payment-methods', params, { headers });\n return response.data;\n }\n\n /**\n * Retrieve a payment method by ID\n */\n async retrieve(paymentMethodId: string): Promise {\n const response = await this.http.get(`/payment-methods/${paymentMethodId}`);\n return response.data;\n }\n\n /**\n * List all payment methods\n */\n async list(params?: PaginationParams & { type?: string }): Promise> {\n const response = await this.http.get('/payment-methods', { params });\n return response.data;\n }\n\n /**\n * Delete a payment method\n */\n async delete(paymentMethodId: string): Promise {\n await this.http.delete(`/payment-methods/${paymentMethodId}`);\n }\n}\n","import { AxiosInstance } from 'axios';\nimport {\n WebhookEndpoint,\n CreateWebhookRequest,\n ListResponse,\n PaginationParams,\n} from '../types';\n\n/**\n * Webhooks API client\n */\nexport class WebhooksClient {\n constructor(private http: AxiosInstance) {}\n\n /**\n * Create a new webhook endpoint\n */\n async create(params: CreateWebhookRequest, idempotencyKey?: string): Promise {\n const headers: Record = {};\n if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const response = await this.http.post('/webhooks', params, { headers });\n return response.data;\n }\n\n /**\n * Retrieve a webhook endpoint by ID\n */\n async retrieve(webhookId: string): Promise {\n const response = await this.http.get(`/webhooks/${webhookId}`);\n return response.data;\n }\n\n /**\n * List all webhook endpoints\n */\n async list(params?: PaginationParams): Promise> {\n const response = await this.http.get('/webhooks', { params });\n return response.data;\n }\n\n /**\n * Update a webhook endpoint\n */\n async update(webhookId: string, params: Partial): Promise {\n const response = await this.http.put(`/webhooks/${webhookId}`, params);\n return response.data;\n }\n\n /**\n * Delete a webhook endpoint\n */\n async delete(webhookId: string): Promise {\n await this.http.delete(`/webhooks/${webhookId}`);\n }\n}\n","import axios, { AxiosInstance, AxiosError } from 'axios';\nimport {\n FetcherPayConfig,\n FetcherPayError,\n AuthenticationError,\n ValidationError,\n NotFoundError,\n} from './types';\nimport { PaymentsClient } from './api/payments';\nimport { LedgerClient } from './api/ledger';\nimport { PaymentMethodsClient } from './api/payment-methods';\nimport { WebhooksClient } from './api/webhooks';\n\n/**\n * Main FetcherPay client\n */\nexport class FetcherPay {\n private http: AxiosInstance;\n public payments: PaymentsClient;\n public ledger: LedgerClient;\n public paymentMethods: PaymentMethodsClient;\n public webhooks: WebhooksClient;\n\n constructor(config: FetcherPayConfig) {\n const baseUrl = config.baseUrl || (\n config.environment === 'production'\n ? 'https://api.fetcherpay.com/v1'\n : 'https://sandbox.fetcherpay.com/v1'\n );\n\n this.http = axios.create({\n baseURL: baseUrl,\n timeout: config.timeout || 30000,\n headers: {\n 'Authorization': `Bearer ${config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n\n // Response interceptor for error handling\n this.http.interceptors.response.use(\n (response) => response,\n (error: AxiosError) => {\n if (error.response) {\n const { status, data } = error.response;\n const errorData = (data as any)?.error || {};\n\n switch (status) {\n case 401:\n throw new AuthenticationError(errorData.message || 'Authentication failed');\n case 404:\n throw new NotFoundError(errorData.message || 'Resource not found');\n case 422:\n throw new ValidationError(errorData.message || 'Validation failed', errorData.param);\n default:\n throw new FetcherPayError(\n errorData.message || 'An error occurred',\n errorData.type || 'api_error',\n status,\n errorData.param,\n errorData.code\n );\n }\n }\n throw error;\n }\n );\n\n // Initialize API clients\n this.payments = new PaymentsClient(this.http);\n this.ledger = new LedgerClient(this.http);\n this.paymentMethods = new PaymentMethodsClient(this.http);\n this.webhooks = new WebhooksClient(this.http);\n }\n\n /**\n * Verify webhook signature\n */\n verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {\n const crypto = require('crypto');\n const expected = crypto\n .createHmac('sha256', secret)\n .update(payload)\n .digest('hex');\n \n try {\n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expected)\n );\n } catch {\n return false;\n }\n }\n}\n\nexport default FetcherPay;\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/types/index.d.ts b/dist/types/index.d.ts new file mode 100644 index 0000000..fb7252d --- /dev/null +++ b/dist/types/index.d.ts @@ -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; + 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; +} +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; + 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; +} +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; + 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; + created_at: string; +} +export interface WebhookEndpoint { + id: string; + object: 'webhook_endpoint'; + url: string; + events: string[]; + status: 'active' | 'disabled'; + secret: string; + metadata: Record; + created_at: string; +} +export interface CreateWebhookRequest { + url: string; + events?: string[]; + metadata?: Record; +} +export interface WebhookEvent { + id: string; + object: 'event'; + type: string; + created_at: string; + data: any; +} +export interface ListResponse { + 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); +} diff --git a/examples/basic.ts b/examples/basic.ts new file mode 100644 index 0000000..2d8a28f --- /dev/null +++ b/examples/basic.ts @@ -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(); diff --git a/examples/webhook-verification.ts b/examples/webhook-verification.ts new file mode 100644 index 0000000..5121cbc --- /dev/null +++ b/examples/webhook-verification.ts @@ -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'); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..621fd32 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..0d0c2ee --- /dev/null +++ b/rollup.config.js @@ -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'], + }, +]; diff --git a/src/api/ledger.ts b/src/api/ledger.ts new file mode 100644 index 0000000..75131c5 --- /dev/null +++ b/src/api/ledger.ts @@ -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> { + const response = await this.http.get('/ledger/accounts', { params }); + return response.data; + } + + /** + * Retrieve a ledger account by ID + */ + async retrieveAccount(accountId: string): Promise { + 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> { + const response = await this.http.get('/ledger/entries', { params }); + return response.data; + } + + /** + * Retrieve a ledger entry by ID + */ + async retrieveEntry(entryId: string): Promise { + const response = await this.http.get(`/ledger/entries/${entryId}`); + return response.data; + } +} diff --git a/src/api/payment-methods.ts b/src/api/payment-methods.ts new file mode 100644 index 0000000..deee21c --- /dev/null +++ b/src/api/payment-methods.ts @@ -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 { + const headers: Record = {}; + 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 { + const response = await this.http.get(`/payment-methods/${paymentMethodId}`); + return response.data; + } + + /** + * List all payment methods + */ + async list(params?: PaginationParams & { type?: string }): Promise> { + const response = await this.http.get('/payment-methods', { params }); + return response.data; + } + + /** + * Delete a payment method + */ + async delete(paymentMethodId: string): Promise { + await this.http.delete(`/payment-methods/${paymentMethodId}`); + } +} diff --git a/src/api/payments.ts b/src/api/payments.ts new file mode 100644 index 0000000..8623545 --- /dev/null +++ b/src/api/payments.ts @@ -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 { + const headers: Record = {}; + 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 { + const response = await this.http.get(`/payments/${paymentId}`); + return response.data; + } + + /** + * List all payments + */ + async list(params?: PaginationParams & FilterParams & { status?: string; rail?: string }): Promise> { + const response = await this.http.get('/payments', { params }); + return response.data; + } + + /** + * Cancel a pending payment + */ + async cancel(paymentId: string, reason?: string, idempotencyKey?: string): Promise { + const headers: Record = {}; + 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 { + const headers: Record = {}; + if (idempotencyKey) { + headers['Idempotency-Key'] = idempotencyKey; + } + + const response = await this.http.post( + `/payments/${paymentId}/refund`, + params || {}, + { headers } + ); + return response.data; + } +} diff --git a/src/api/webhooks.ts b/src/api/webhooks.ts new file mode 100644 index 0000000..7c893c3 --- /dev/null +++ b/src/api/webhooks.ts @@ -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 { + const headers: Record = {}; + 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 { + const response = await this.http.get(`/webhooks/${webhookId}`); + return response.data; + } + + /** + * List all webhook endpoints + */ + async list(params?: PaginationParams): Promise> { + const response = await this.http.get('/webhooks', { params }); + return response.data; + } + + /** + * Update a webhook endpoint + */ + async update(webhookId: string, params: Partial): Promise { + const response = await this.http.put(`/webhooks/${webhookId}`, params); + return response.data; + } + + /** + * Delete a webhook endpoint + */ + async delete(webhookId: string): Promise { + await this.http.delete(`/webhooks/${webhookId}`); + } +} diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..35d4a8a --- /dev/null +++ b/src/client.ts @@ -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; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..cbddbd7 --- /dev/null +++ b/src/index.ts @@ -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'; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..c643736 --- /dev/null +++ b/src/types/index.ts @@ -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; + 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; +} + +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; + 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; +} + +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; + 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; + created_at: string; +} + +export interface WebhookEndpoint { + id: string; + object: 'webhook_endpoint'; + url: string; + events: string[]; + status: 'active' | 'disabled'; + secret: string; + metadata: Record; + created_at: string; +} + +export interface CreateWebhookRequest { + url: string; + events?: string[]; + metadata?: Record; +} + +export interface WebhookEvent { + id: string; + object: 'event'; + type: string; + created_at: string; + data: any; +} + +export interface ListResponse { + 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); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1d5cab0 --- /dev/null +++ b/tsconfig.json @@ -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"] +}