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 +1 -0
- package/dist/crypto/moonpay.js +23 -13
- package/dist/crypto/yellowcard.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/providers/flutterwave.js +2 -1
- package/dist/providers/ozow.js +2 -5
- package/dist/providers/payfast.js +2 -5
- package/dist/providers/paystack.js +2 -1
- package/dist/providers/peach.js +2 -5
- package/dist/providers/softycomp.js +3 -10
- package/dist/providers/stripe.js +2 -5
- package/dist/providers/yoco.js +4 -15
- package/dist/router.js +53 -18
- package/dist/utils/fetch.d.ts +24 -0
- package/dist/utils/fetch.js +74 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
> **One API. Every payment provider. 🌍**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/paybridge)
|
|
6
|
+
[](https://github.com/kobie3717/paybridge/actions/workflows/test.yml)
|
|
6
7
|
[](LICENSE)
|
|
7
8
|
[](https://www.typescriptlang.org/)
|
|
8
9
|
[](https://discord.gg/Y2jCXNGgE)
|
package/dist/crypto/moonpay.js
CHANGED
|
@@ -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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
57
|
+
const response = await (0, fetch_1.timedFetch)(url, {
|
|
57
58
|
method,
|
|
58
59
|
headers: {
|
|
59
60
|
Authorization: `Bearer ${this.apiKey}`,
|
package/dist/providers/ozow.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
58
|
+
const response = await (0, fetch_1.timedFetch)(url, {
|
|
58
59
|
method,
|
|
59
60
|
headers: {
|
|
60
61
|
Authorization: `Bearer ${this.apiKey}`,
|
package/dist/providers/peach.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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());
|
package/dist/providers/stripe.js
CHANGED
|
@@ -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
|
|
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) {
|
package/dist/providers/yoco.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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.
|
|
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
|
|
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
|
},
|