Initial Node.js SDK v1.0.0
This commit is contained in:
143
README.md
Normal file
143
README.md
Normal 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
31
dist/api/ledger.d.ts
vendored
Normal 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
27
dist/api/payment-methods.d.ts
vendored
Normal 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
35
dist/api/payments.d.ts
vendored
Normal 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
29
dist/api/webhooks.d.ts
vendored
Normal 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
21
dist/client.d.ts
vendored
Normal 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
27
dist/index.d.ts
vendored
Normal 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
271
dist/index.esm.js
vendored
Normal 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
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
281
dist/index.js
vendored
Normal 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
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
192
dist/types/index.d.ts
vendored
Normal 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
54
examples/basic.ts
Normal 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();
|
||||
40
examples/webhook-verification.ts
Normal file
40
examples/webhook-verification.ts
Normal 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
35
package.json
Normal 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
27
rollup.config.js
Normal 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
50
src/api/ledger.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
50
src/api/payment-methods.ts
Normal file
50
src/api/payment-methods.ts
Normal 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
82
src/api/payments.ts
Normal 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
58
src/api/webhooks.ts
Normal 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
97
src/client.ts
Normal 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
28
src/index.ts
Normal 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
228
src/types/index.ts
Normal 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
29
tsconfig.json
Normal 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"]
|
||||
}
|
||||
Reference in New Issue
Block a user