paybridge 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  > **One API. Every payment provider. 🌍**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/paybridge.svg)](https://www.npmjs.com/package/paybridge)
6
+ [![CI](https://github.com/kobie3717/paybridge/actions/workflows/test.yml/badge.svg)](https://github.com/kobie3717/paybridge/actions/workflows/test.yml)
6
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue)](https://www.typescriptlang.org/)
8
9
  [![Discord](https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white)](https://discord.gg/Y2jCXNGgE)
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.MoonPayProvider = void 0;
11
11
  const crypto_1 = __importDefault(require("crypto"));
12
12
  const base_1 = require("./base");
13
+ const fetch_1 = require("../utils/fetch");
13
14
  class MoonPayProvider extends base_1.CryptoRampProvider {
14
15
  constructor(config) {
15
16
  super();
@@ -25,14 +26,26 @@ class MoonPayProvider extends base_1.CryptoRampProvider {
25
26
  : 'https://buy.moonpay.com';
26
27
  }
27
28
  async getQuote(direction, fiatAmount, fiatCurrency, cryptoAsset, _network) {
28
- const params = new URLSearchParams({
29
- apiKey: this.apiKey,
30
- baseCurrencyCode: fiatCurrency.toLowerCase(),
31
- quoteCurrencyCode: cryptoAsset.toLowerCase(),
32
- baseCurrencyAmount: fiatAmount.toString(),
33
- });
34
- const url = `${this.baseUrl}/v3/currencies/${cryptoAsset.toLowerCase()}/quote?${params}`;
35
- const response = await fetch(url);
29
+ const endpoint = direction === 'on' ? 'quote' : 'sell_quote';
30
+ let params;
31
+ if (direction === 'on') {
32
+ params = new URLSearchParams({
33
+ apiKey: this.apiKey,
34
+ baseCurrencyCode: fiatCurrency.toLowerCase(),
35
+ quoteCurrencyCode: cryptoAsset.toLowerCase(),
36
+ baseCurrencyAmount: fiatAmount.toString(),
37
+ });
38
+ }
39
+ else {
40
+ params = new URLSearchParams({
41
+ apiKey: this.apiKey,
42
+ baseCurrencyCode: cryptoAsset.toLowerCase(),
43
+ quoteCurrencyCode: fiatCurrency.toLowerCase(),
44
+ baseCurrencyAmount: fiatAmount.toString(),
45
+ });
46
+ }
47
+ const url = `${this.baseUrl}/v3/currencies/${cryptoAsset.toLowerCase()}/${endpoint}?${params}`;
48
+ const response = await (0, fetch_1.timedFetch)(url);
36
49
  if (!response.ok) {
37
50
  throw new Error(`MoonPay quote failed: ${response.status}`);
38
51
  }
@@ -80,10 +93,7 @@ class MoonPayProvider extends base_1.CryptoRampProvider {
80
93
  if (params.sourceWallet) {
81
94
  (0, base_1.validateWalletAddress)(params.sourceWallet, params.network);
82
95
  }
83
- // TODO: verify endpoint - use /v3/currencies/{code}/sell_quote?baseCurrencyAmount=...
84
- // For now, use getQuote with 'off' direction
85
- const quote = await this.getQuote('off', 0, // Will be calculated from crypto amount
86
- params.fiatCurrency, params.asset, params.network);
96
+ const quote = await this.getQuote('off', params.cryptoAmount, params.fiatCurrency, params.asset, params.network);
87
97
  const sellWidgetUrl = this.sandbox
88
98
  ? 'https://sell-sandbox.moonpay.com'
89
99
  : 'https://sell.moonpay.com';
@@ -110,7 +120,7 @@ class MoonPayProvider extends base_1.CryptoRampProvider {
110
120
  };
111
121
  }
112
122
  async getRamp(id) {
113
- const response = await fetch(`${this.baseUrl}/v3/transactions/${id}`, {
123
+ const response = await (0, fetch_1.timedFetch)(`${this.baseUrl}/v3/transactions/${id}`, {
114
124
  headers: {
115
125
  Authorization: `Bearer ${this.secretKey}`,
116
126
  },
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.YellowCardProvider = void 0;
25
25
  const crypto_1 = __importDefault(require("crypto"));
26
26
  const base_1 = require("./base");
27
+ const fetch_1 = require("../utils/fetch");
27
28
  /**
28
29
  * Yellow Card crypto on/off-ramp provider.
29
30
  *
@@ -263,7 +264,7 @@ class YellowCardProvider extends base_1.CryptoRampProvider {
263
264
  // Current: X-API-Key, X-Timestamp, X-Signature
264
265
  // Common alternatives: X-YC-API-Key, X-YC-Timestamp, X-YC-Signature
265
266
  // Or: Authorization header + separate X-Signature
266
- const response = await fetch(url, {
267
+ const response = await (0, fetch_1.timedFetch)(url, {
267
268
  method,
268
269
  headers: {
269
270
  'Content-Type': 'application/json',
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ import { PaymentProvider } from './providers/base';
8
8
  import { PayBridgeConfig, CreatePaymentParams, PaymentResult, CreateSubscriptionParams, SubscriptionResult, RefundParams, RefundResult, WebhookEvent } from './types';
9
9
  export * from './types';
10
10
  export * from './utils/currency';
11
+ export * from './utils/fetch';
11
12
  export * from './routing-types';
12
13
  export * from './circuit-breaker';
13
14
  export * from './circuit-breaker-store';
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ const paystack_1 = require("./providers/paystack");
31
31
  const flutterwave_1 = require("./providers/flutterwave");
32
32
  __exportStar(require("./types"), exports);
33
33
  __exportStar(require("./utils/currency"), exports);
34
+ __exportStar(require("./utils/fetch"), exports);
34
35
  __exportStar(require("./routing-types"), exports);
35
36
  __exportStar(require("./circuit-breaker"), exports);
36
37
  __exportStar(require("./circuit-breaker-store"), exports);
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.FlutterwaveProvider = void 0;
42
42
  const crypto = __importStar(require("crypto"));
43
43
  const base_1 = require("./base");
44
+ const fetch_1 = require("../utils/fetch");
44
45
  class FlutterwaveProvider extends base_1.PaymentProvider {
45
46
  constructor(config) {
46
47
  super();
@@ -53,7 +54,7 @@ class FlutterwaveProvider extends base_1.PaymentProvider {
53
54
  }
54
55
  async apiRequest(method, path, data) {
55
56
  const url = `${this.baseUrl}${path}`;
56
- const response = await fetch(url, {
57
+ const response = await (0, fetch_1.timedFetch)(url, {
57
58
  method,
58
59
  headers: {
59
60
  Authorization: `Bearer ${this.apiKey}`,
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.OzowProvider = void 0;
12
12
  const crypto_1 = __importDefault(require("crypto"));
13
13
  const base_1 = require("./base");
14
+ const fetch_1 = require("../utils/fetch");
14
15
  class OzowProvider extends base_1.PaymentProvider {
15
16
  constructor(config) {
16
17
  super();
@@ -83,16 +84,12 @@ class OzowProvider extends base_1.PaymentProvider {
83
84
  }
84
85
  async getPayment(id) {
85
86
  const url = `${this.baseUrl}/GetTransactionByReference?siteCode=${this.siteCode}&transactionReference=${encodeURIComponent(id)}`;
86
- const response = await fetch(url, {
87
+ const response = await (0, fetch_1.timedFetchOrThrow)(url, {
87
88
  method: 'GET',
88
89
  headers: {
89
90
  'ApiKey': this.apiKey,
90
91
  },
91
92
  });
92
- if (!response.ok) {
93
- const errorText = await response.text();
94
- throw new Error(`Ozow getPayment failed: ${response.status} - ${errorText}`);
95
- }
96
93
  const data = (await response.json());
97
94
  if (!Array.isArray(data) || data.length === 0) {
98
95
  throw new Error('Transaction not found');
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.PayFastProvider = void 0;
12
12
  const crypto_1 = __importDefault(require("crypto"));
13
13
  const base_1 = require("./base");
14
+ const fetch_1 = require("../utils/fetch");
14
15
  class PayFastProvider extends base_1.PaymentProvider {
15
16
  constructor(config) {
16
17
  super();
@@ -143,7 +144,7 @@ class PayFastProvider extends base_1.PaymentProvider {
143
144
  headers['signature'] = this.generateApiSignature(headers);
144
145
  const testingParam = this.sandbox ? '?testing=true' : '';
145
146
  const url = `${this.apiBaseUrl}/query/fetch${testingParam}`;
146
- const response = await fetch(url, {
147
+ const response = await (0, fetch_1.timedFetchOrThrow)(url, {
147
148
  method: 'POST',
148
149
  headers: {
149
150
  ...headers,
@@ -153,10 +154,6 @@ class PayFastProvider extends base_1.PaymentProvider {
153
154
  m_payment_id: id,
154
155
  }),
155
156
  });
156
- if (!response.ok) {
157
- const errorText = await response.text();
158
- throw new Error(`PayFast getPayment failed: ${response.status} - ${errorText}`);
159
- }
160
157
  const data = (await response.json());
161
158
  const status = this.mapPayFastStatus(data.status);
162
159
  return {
@@ -42,6 +42,7 @@ exports.PayStackProvider = void 0;
42
42
  const crypto = __importStar(require("crypto"));
43
43
  const base_1 = require("./base");
44
44
  const currency_1 = require("../utils/currency");
45
+ const fetch_1 = require("../utils/fetch");
45
46
  class PayStackProvider extends base_1.PaymentProvider {
46
47
  constructor(config) {
47
48
  super();
@@ -54,7 +55,7 @@ class PayStackProvider extends base_1.PaymentProvider {
54
55
  }
55
56
  async apiRequest(method, path, data) {
56
57
  const url = `${this.baseUrl}${path}`;
57
- const response = await fetch(url, {
58
+ const response = await (0, fetch_1.timedFetch)(url, {
58
59
  method,
59
60
  headers: {
60
61
  Authorization: `Bearer ${this.apiKey}`,
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.PeachProvider = void 0;
42
42
  const crypto = __importStar(require("crypto"));
43
43
  const base_1 = require("./base");
44
+ const fetch_1 = require("../utils/fetch");
44
45
  class PeachProvider extends base_1.PaymentProvider {
45
46
  constructor(config) {
46
47
  super();
@@ -76,7 +77,7 @@ class PeachProvider extends base_1.PaymentProvider {
76
77
  const bodyData = { entityId: this.entityId, ...data };
77
78
  body = this.buildFormData(bodyData);
78
79
  }
79
- const response = await fetch(finalUrl, {
80
+ const response = await (0, fetch_1.timedFetchOrThrow)(finalUrl, {
80
81
  method,
81
82
  headers: {
82
83
  Authorization: `Bearer ${this.accessToken}`,
@@ -84,10 +85,6 @@ class PeachProvider extends base_1.PaymentProvider {
84
85
  },
85
86
  body,
86
87
  });
87
- if (!response.ok) {
88
- const errorText = await response.text();
89
- throw new Error(`Peach Payments API error (${method} ${path}): ${response.status} - ${errorText}`);
90
- }
91
88
  return (await response.json());
92
89
  }
93
90
  mapPeachStatus(code) {
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.SoftyCompProvider = void 0;
11
11
  const crypto_1 = __importDefault(require("crypto"));
12
12
  const base_1 = require("./base");
13
+ const fetch_1 = require("../utils/fetch");
13
14
  class SoftyCompProvider extends base_1.PaymentProvider {
14
15
  constructor(config) {
15
16
  super();
@@ -36,7 +37,7 @@ class SoftyCompProvider extends base_1.PaymentProvider {
36
37
  if (this.token && Date.now() < this.tokenExpiry - 60000) {
37
38
  return this.token;
38
39
  }
39
- const response = await fetch(`${this.baseUrl}/api/auth/generatetoken`, {
40
+ const response = await (0, fetch_1.timedFetchOrThrow)(`${this.baseUrl}/api/auth/generatetoken`, {
40
41
  method: 'POST',
41
42
  headers: { 'Content-Type': 'application/json' },
42
43
  body: JSON.stringify({
@@ -44,10 +45,6 @@ class SoftyCompProvider extends base_1.PaymentProvider {
44
45
  apiSecret: this.secretKey,
45
46
  }),
46
47
  });
47
- if (!response.ok) {
48
- const errorText = await response.text();
49
- throw new Error(`SoftyComp authentication failed: ${response.status} - ${errorText}`);
50
- }
51
48
  const data = (await response.json());
52
49
  this.token = data.token;
53
50
  this.tokenExpiry = new Date(data.expiration).getTime();
@@ -56,7 +53,7 @@ class SoftyCompProvider extends base_1.PaymentProvider {
56
53
  async apiRequest(method, path, data) {
57
54
  const token = await this.authenticate();
58
55
  const url = `${this.baseUrl}${path}`;
59
- const response = await fetch(url, {
56
+ const response = await (0, fetch_1.timedFetchOrThrow)(url, {
60
57
  method,
61
58
  headers: {
62
59
  Authorization: `Bearer ${token}`,
@@ -64,10 +61,6 @@ class SoftyCompProvider extends base_1.PaymentProvider {
64
61
  },
65
62
  body: data ? JSON.stringify(data) : undefined,
66
63
  });
67
- if (!response.ok) {
68
- const errorText = await response.text();
69
- throw new Error(`SoftyComp API error (${method} ${path}): ${response.status} - ${errorText}`);
70
- }
71
64
  const contentType = response.headers.get('content-type');
72
65
  if (contentType && contentType.includes('application/json')) {
73
66
  return (await response.json());
@@ -42,6 +42,7 @@ exports.StripeProvider = void 0;
42
42
  const crypto = __importStar(require("crypto"));
43
43
  const base_1 = require("./base");
44
44
  const currency_1 = require("../utils/currency");
45
+ const fetch_1 = require("../utils/fetch");
45
46
  class StripeProvider extends base_1.PaymentProvider {
46
47
  constructor(config) {
47
48
  super();
@@ -70,7 +71,7 @@ class StripeProvider extends base_1.PaymentProvider {
70
71
  async apiRequest(method, path, data) {
71
72
  const url = `${this.baseUrl}${path}`;
72
73
  const body = data ? this.buildFormData(data) : undefined;
73
- const response = await fetch(url, {
74
+ const response = await (0, fetch_1.timedFetchOrThrow)(url, {
74
75
  method,
75
76
  headers: {
76
77
  Authorization: `Bearer ${this.apiKey}`,
@@ -78,10 +79,6 @@ class StripeProvider extends base_1.PaymentProvider {
78
79
  },
79
80
  body,
80
81
  });
81
- if (!response.ok) {
82
- const errorText = await response.text();
83
- throw new Error(`Stripe API error (${method} ${path}): ${response.status} - ${errorText}`);
84
- }
85
82
  return (await response.json());
86
83
  }
87
84
  async createPayment(params) {
@@ -12,6 +12,7 @@ exports.YocoProvider = void 0;
12
12
  const crypto_1 = __importDefault(require("crypto"));
13
13
  const base_1 = require("./base");
14
14
  const currency_1 = require("../utils/currency");
15
+ const fetch_1 = require("../utils/fetch");
15
16
  class YocoProvider extends base_1.PaymentProvider {
16
17
  constructor(config) {
17
18
  super();
@@ -41,7 +42,7 @@ class YocoProvider extends base_1.PaymentProvider {
41
42
  ...params.metadata,
42
43
  },
43
44
  };
44
- const response = await fetch(`${this.baseUrl}/checkouts`, {
45
+ const response = await (0, fetch_1.timedFetchOrThrow)(`${this.baseUrl}/checkouts`, {
45
46
  method: 'POST',
46
47
  headers: {
47
48
  'Authorization': `Bearer ${this.apiKey}`,
@@ -50,10 +51,6 @@ class YocoProvider extends base_1.PaymentProvider {
50
51
  },
51
52
  body: JSON.stringify(requestBody),
52
53
  });
53
- if (!response.ok) {
54
- const errorText = await response.text();
55
- throw new Error(`Yoco API error (POST /checkouts): ${response.status} - ${errorText}`);
56
- }
57
54
  const data = await response.json();
58
55
  return {
59
56
  id: data.id,
@@ -76,17 +73,13 @@ class YocoProvider extends base_1.PaymentProvider {
76
73
  throw new Error('Yoco does not support subscriptions. Use the Yoco Recurring Billing API directly or choose another provider.');
77
74
  }
78
75
  async getPayment(id) {
79
- const response = await fetch(`${this.baseUrl}/checkouts/${id}`, {
76
+ const response = await (0, fetch_1.timedFetchOrThrow)(`${this.baseUrl}/checkouts/${id}`, {
80
77
  method: 'GET',
81
78
  headers: {
82
79
  'Authorization': `Bearer ${this.apiKey}`,
83
80
  'Content-Type': 'application/json',
84
81
  },
85
82
  });
86
- if (!response.ok) {
87
- const errorText = await response.text();
88
- throw new Error(`Yoco API error (GET /checkouts/${id}): ${response.status} - ${errorText}`);
89
- }
90
83
  const data = await response.json();
91
84
  return {
92
85
  id: data.id,
@@ -110,7 +103,7 @@ class YocoProvider extends base_1.PaymentProvider {
110
103
  if (params.reason) {
111
104
  requestBody.metadata = { reason: params.reason };
112
105
  }
113
- const response = await fetch(`${this.baseUrl}/refunds`, {
106
+ const response = await (0, fetch_1.timedFetchOrThrow)(`${this.baseUrl}/refunds`, {
114
107
  method: 'POST',
115
108
  headers: {
116
109
  'Authorization': `Bearer ${this.apiKey}`,
@@ -119,10 +112,6 @@ class YocoProvider extends base_1.PaymentProvider {
119
112
  },
120
113
  body: JSON.stringify(requestBody),
121
114
  });
122
- if (!response.ok) {
123
- const errorText = await response.text();
124
- throw new Error(`Yoco API error (POST /refunds): ${response.status} - ${errorText}`);
125
- }
126
115
  const data = await response.json();
127
116
  const status = this.mapYocoStatus(data.status);
128
117
  const refundStatus = status === 'completed' ? 'completed' :
package/dist/router.js CHANGED
@@ -7,6 +7,7 @@ exports.PayBridgeRouter = void 0;
7
7
  const routing_types_1 = require("./routing-types");
8
8
  const strategies_1 = require("./strategies");
9
9
  const circuit_breaker_1 = require("./circuit-breaker");
10
+ const fetch_1 = require("./utils/fetch");
10
11
  function sanitizeErrorMessage(msg) {
11
12
  if (!msg)
12
13
  return 'unknown error';
@@ -94,15 +95,32 @@ class PayBridgeRouter {
94
95
  catch (error) {
95
96
  const latencyMs = Date.now() - startTime;
96
97
  lastError = error;
97
- if (breaker)
98
- await breaker.recordFailure();
99
- attempts.push({
100
- provider: providerName,
101
- status: 'failed',
102
- errorCode: error.code,
103
- errorMessage: sanitizeErrorMessage(error.message),
104
- latencyMs,
105
- });
98
+ const isRateLimited = error instanceof fetch_1.HttpError &&
99
+ (error.status === 429 || (error.status === 503 && error.retryAfterMs !== undefined));
100
+ if (isRateLimited) {
101
+ attempts.push({
102
+ provider: providerName,
103
+ status: 'failed',
104
+ errorCode: 'RATE_LIMITED',
105
+ errorMessage: sanitizeErrorMessage(error.message),
106
+ latencyMs,
107
+ });
108
+ }
109
+ else {
110
+ if (breaker)
111
+ await breaker.recordFailure();
112
+ let errorCode = error.code;
113
+ if (error instanceof fetch_1.FetchTimeoutError) {
114
+ errorCode = 'TIMEOUT';
115
+ }
116
+ attempts.push({
117
+ provider: providerName,
118
+ status: 'failed',
119
+ errorCode,
120
+ errorMessage: sanitizeErrorMessage(error.message),
121
+ latencyMs,
122
+ });
123
+ }
106
124
  if (!this.fallback.enabled || attempts.length >= (this.fallback.maxAttempts ?? 3)) {
107
125
  break;
108
126
  }
@@ -156,15 +174,32 @@ class PayBridgeRouter {
156
174
  catch (error) {
157
175
  const latencyMs = Date.now() - startTime;
158
176
  lastError = error;
159
- if (breaker)
160
- await breaker.recordFailure();
161
- attempts.push({
162
- provider: providerName,
163
- status: 'failed',
164
- errorCode: error.code,
165
- errorMessage: sanitizeErrorMessage(error.message),
166
- latencyMs,
167
- });
177
+ const isRateLimited = error instanceof fetch_1.HttpError &&
178
+ (error.status === 429 || (error.status === 503 && error.retryAfterMs !== undefined));
179
+ if (isRateLimited) {
180
+ attempts.push({
181
+ provider: providerName,
182
+ status: 'failed',
183
+ errorCode: 'RATE_LIMITED',
184
+ errorMessage: sanitizeErrorMessage(error.message),
185
+ latencyMs,
186
+ });
187
+ }
188
+ else {
189
+ if (breaker)
190
+ await breaker.recordFailure();
191
+ let errorCode = error.code;
192
+ if (error instanceof fetch_1.FetchTimeoutError) {
193
+ errorCode = 'TIMEOUT';
194
+ }
195
+ attempts.push({
196
+ provider: providerName,
197
+ status: 'failed',
198
+ errorCode,
199
+ errorMessage: sanitizeErrorMessage(error.message),
200
+ latencyMs,
201
+ });
202
+ }
168
203
  if (!this.fallback.enabled || attempts.length >= (this.fallback.maxAttempts ?? 3)) {
169
204
  break;
170
205
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Fetch utilities with timeout and HTTP error handling
3
+ */
4
+ export interface FetchOptions extends RequestInit {
5
+ timeoutMs?: number;
6
+ }
7
+ export declare class FetchTimeoutError extends Error {
8
+ readonly name = "FetchTimeoutError";
9
+ readonly url: string;
10
+ readonly timeoutMs: number;
11
+ constructor(url: string, timeoutMs: number);
12
+ }
13
+ export declare class HttpError extends Error {
14
+ readonly name = "HttpError";
15
+ readonly status: number;
16
+ readonly retryAfterMs?: number;
17
+ readonly body?: string;
18
+ constructor(status: number, message: string, opts?: {
19
+ retryAfterMs?: number;
20
+ body?: string;
21
+ });
22
+ }
23
+ export declare function timedFetch(url: string, opts?: FetchOptions): Promise<Response>;
24
+ export declare function timedFetchOrThrow(url: string, opts?: FetchOptions): Promise<Response>;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * Fetch utilities with timeout and HTTP error handling
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HttpError = exports.FetchTimeoutError = void 0;
7
+ exports.timedFetch = timedFetch;
8
+ exports.timedFetchOrThrow = timedFetchOrThrow;
9
+ class FetchTimeoutError extends Error {
10
+ constructor(url, timeoutMs) {
11
+ super(`Request to ${url} timed out after ${timeoutMs}ms`);
12
+ this.name = 'FetchTimeoutError';
13
+ this.url = url;
14
+ this.timeoutMs = timeoutMs;
15
+ }
16
+ }
17
+ exports.FetchTimeoutError = FetchTimeoutError;
18
+ class HttpError extends Error {
19
+ constructor(status, message, opts = {}) {
20
+ super(message);
21
+ this.name = 'HttpError';
22
+ this.status = status;
23
+ this.retryAfterMs = opts.retryAfterMs;
24
+ this.body = opts.body;
25
+ }
26
+ }
27
+ exports.HttpError = HttpError;
28
+ const DEFAULT_TIMEOUT_MS = 30000;
29
+ async function timedFetch(url, opts = {}) {
30
+ const { timeoutMs = DEFAULT_TIMEOUT_MS, signal: callerSignal, ...rest } = opts;
31
+ const controller = new AbortController();
32
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
33
+ if (callerSignal) {
34
+ if (callerSignal.aborted)
35
+ controller.abort();
36
+ else
37
+ callerSignal.addEventListener('abort', () => controller.abort(), { once: true });
38
+ }
39
+ try {
40
+ return await fetch(url, { ...rest, signal: controller.signal });
41
+ }
42
+ catch (err) {
43
+ if (err?.name === 'AbortError') {
44
+ throw new FetchTimeoutError(url, timeoutMs);
45
+ }
46
+ throw err;
47
+ }
48
+ finally {
49
+ clearTimeout(timeoutId);
50
+ }
51
+ }
52
+ async function timedFetchOrThrow(url, opts = {}) {
53
+ const response = await timedFetch(url, opts);
54
+ if (!response.ok) {
55
+ let body = '';
56
+ try {
57
+ body = await response.text();
58
+ }
59
+ catch { }
60
+ const retryAfterHeader = response.headers.get('retry-after');
61
+ const retryAfterMs = retryAfterHeader ? parseRetryAfter(retryAfterHeader) : undefined;
62
+ throw new HttpError(response.status, `HTTP ${response.status}: ${body.slice(0, 200) || response.statusText}`, { retryAfterMs, body });
63
+ }
64
+ return response;
65
+ }
66
+ function parseRetryAfter(value) {
67
+ const seconds = parseInt(value, 10);
68
+ if (!isNaN(seconds))
69
+ return seconds * 1000;
70
+ const dateMs = Date.parse(value);
71
+ if (!isNaN(dateMs))
72
+ return Math.max(0, dateMs - Date.now());
73
+ return undefined;
74
+ }
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "paybridge",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "One API for fiat + crypto payments. Multi-provider routing, automatic failover, MoonPay on/off-ramp. SA-first, global-ready.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "clean": "rm -rf dist",
10
+ "prepack": "npm run clean && npm run build",
10
11
  "prepublishOnly": "npm run clean && npm run build",
11
- "test": "tsc && node --test dist/tests/*.test.js",
12
+ "test": "tsc && tsc --project tsconfig.test.json && node --test 'dist-test/**/*.test.js'",
12
13
  "test:e2e:moonpay": "tsx tests/e2e/moonpay-sandbox.ts",
13
14
  "test:e2e:yellowcard": "tsx tests/e2e/yellowcard-sandbox.ts"
14
15
  },