swapped-commerce-sdk 1.0.0 → 1.0.1
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/dist/index.js +340 -0
- package/package.json +10 -7
package/dist/index.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// src/utils/errors.ts
|
|
2
|
+
class SwappedError extends Error {
|
|
3
|
+
statusCode;
|
|
4
|
+
code;
|
|
5
|
+
details;
|
|
6
|
+
constructor(message, statusCode, code, details) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SwappedError";
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = details;
|
|
12
|
+
if (Error.captureStackTrace) {
|
|
13
|
+
Error.captureStackTrace(this, SwappedError);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class SwappedAuthenticationError extends SwappedError {
|
|
19
|
+
constructor(message = "Authentication failed") {
|
|
20
|
+
super(message, 401, "AUTHENTICATION_ERROR");
|
|
21
|
+
this.name = "SwappedAuthenticationError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class SwappedValidationError extends SwappedError {
|
|
26
|
+
constructor(message, details) {
|
|
27
|
+
super(message, 400, "VALIDATION_ERROR", details);
|
|
28
|
+
this.name = "SwappedValidationError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class SwappedRateLimitError extends SwappedError {
|
|
33
|
+
constructor(message = "Rate limit exceeded") {
|
|
34
|
+
super(message, 429, "RATE_LIMIT_ERROR");
|
|
35
|
+
this.name = "SwappedRateLimitError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class SwappedNotFoundError extends SwappedError {
|
|
40
|
+
constructor(resource) {
|
|
41
|
+
super(`${resource} not found`, 404, "NOT_FOUND_ERROR");
|
|
42
|
+
this.name = "SwappedNotFoundError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function createAuthenticationError(message) {
|
|
46
|
+
return new SwappedAuthenticationError(message);
|
|
47
|
+
}
|
|
48
|
+
function createValidationError(message, details) {
|
|
49
|
+
return new SwappedValidationError(message, details);
|
|
50
|
+
}
|
|
51
|
+
function createRateLimitError(message) {
|
|
52
|
+
return new SwappedRateLimitError(message);
|
|
53
|
+
}
|
|
54
|
+
function createNotFoundError(resource) {
|
|
55
|
+
return new SwappedNotFoundError(resource);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/utils/retry.ts
|
|
59
|
+
async function withRetry(fn, config) {
|
|
60
|
+
let lastError;
|
|
61
|
+
for (let attempt = 0;attempt <= config.maxRetries; attempt++) {
|
|
62
|
+
try {
|
|
63
|
+
return await fn();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
lastError = error;
|
|
66
|
+
if (error instanceof SwappedError && error.statusCode !== undefined && error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
if (attempt < config.maxRetries) {
|
|
70
|
+
const delayMs = Math.pow(2, attempt) * 1000;
|
|
71
|
+
await delay(delayMs);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
throw lastError ?? new Error("Request failed after retries");
|
|
76
|
+
}
|
|
77
|
+
function delay(ms) {
|
|
78
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/utils/http.ts
|
|
82
|
+
var BASE_URL = "https://pay-api.swapped.com";
|
|
83
|
+
function buildUrl(baseUrl, path, params) {
|
|
84
|
+
const url = new URL(path, baseUrl);
|
|
85
|
+
if (params) {
|
|
86
|
+
for (const [key, value] of Object.entries(params)) {
|
|
87
|
+
if (value !== undefined && value !== null) {
|
|
88
|
+
url.searchParams.append(key, String(value));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return url.toString();
|
|
93
|
+
}
|
|
94
|
+
function createRequestConfig(config, method, path, body) {
|
|
95
|
+
const headers = {
|
|
96
|
+
"X-API-Key": config.apiKey,
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
Accept: "application/json"
|
|
99
|
+
};
|
|
100
|
+
const requestInit = {
|
|
101
|
+
method,
|
|
102
|
+
headers
|
|
103
|
+
};
|
|
104
|
+
if (body !== undefined) {
|
|
105
|
+
requestInit.body = JSON.stringify(body);
|
|
106
|
+
}
|
|
107
|
+
return requestInit;
|
|
108
|
+
}
|
|
109
|
+
async function handleErrorResponse(response) {
|
|
110
|
+
let errorData = {};
|
|
111
|
+
try {
|
|
112
|
+
const text = await response.text();
|
|
113
|
+
if (text) {
|
|
114
|
+
errorData = JSON.parse(text);
|
|
115
|
+
}
|
|
116
|
+
} catch {}
|
|
117
|
+
const message = errorData.message ?? response.statusText;
|
|
118
|
+
switch (response.status) {
|
|
119
|
+
case 401:
|
|
120
|
+
return new SwappedAuthenticationError(message);
|
|
121
|
+
case 400:
|
|
122
|
+
return new SwappedValidationError(message, errorData.details);
|
|
123
|
+
case 404:
|
|
124
|
+
return new SwappedNotFoundError(message);
|
|
125
|
+
case 429:
|
|
126
|
+
return new SwappedRateLimitError(message);
|
|
127
|
+
default:
|
|
128
|
+
return new SwappedError(message, response.status, errorData.code, errorData.details);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function request(config, method, path, options) {
|
|
132
|
+
const url = buildUrl(BASE_URL, path, options?.params);
|
|
133
|
+
const requestConfig = createRequestConfig(config, method, path, options?.body);
|
|
134
|
+
const controller = new AbortController;
|
|
135
|
+
const timeoutId = setTimeout(() => {
|
|
136
|
+
controller.abort();
|
|
137
|
+
}, config.timeout);
|
|
138
|
+
try {
|
|
139
|
+
const response = await withRetry(async () => {
|
|
140
|
+
const res = await fetch(url, {
|
|
141
|
+
...requestConfig,
|
|
142
|
+
signal: controller.signal
|
|
143
|
+
});
|
|
144
|
+
if (!res.ok) {
|
|
145
|
+
throw await handleErrorResponse(res);
|
|
146
|
+
}
|
|
147
|
+
return res;
|
|
148
|
+
}, {
|
|
149
|
+
maxRetries: config.retries,
|
|
150
|
+
timeout: config.timeout
|
|
151
|
+
});
|
|
152
|
+
const data = await response.json();
|
|
153
|
+
return data;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof SwappedError) {
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
159
|
+
throw new SwappedError(`Request timeout after ${config.timeout}ms`, 408, "TIMEOUT_ERROR");
|
|
160
|
+
}
|
|
161
|
+
throw new SwappedError(error instanceof Error ? error.message : "Network error occurred", 0, "NETWORK_ERROR");
|
|
162
|
+
} finally {
|
|
163
|
+
clearTimeout(timeoutId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/resources/orders.ts
|
|
168
|
+
async function listOrders(httpConfig, params) {
|
|
169
|
+
return request(httpConfig.config, "GET", "/v1/merchants/orders", {
|
|
170
|
+
params
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async function getOrder(httpConfig, orderId) {
|
|
174
|
+
return request(httpConfig.config, "GET", `/v1/merchants/orders/${encodeURIComponent(orderId)}`);
|
|
175
|
+
}
|
|
176
|
+
async function refundOrder(httpConfig, orderId, params) {
|
|
177
|
+
return request(httpConfig.config, "POST", `/v1/merchants/orders/${encodeURIComponent(orderId)}/refund`, {
|
|
178
|
+
body: params
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/resources/paymentLinks.ts
|
|
183
|
+
async function createPaymentLink(httpConfig, params) {
|
|
184
|
+
return request(httpConfig.config, "POST", "/v1/orders", {
|
|
185
|
+
body: params
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/resources/paymentRoutes.ts
|
|
190
|
+
async function createPaymentRoute(httpConfig, params) {
|
|
191
|
+
return request(httpConfig.config, "POST", "/v1/merchants/payment-routes", {
|
|
192
|
+
body: params
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/resources/payments.ts
|
|
197
|
+
async function getPayment(httpConfig, paymentId) {
|
|
198
|
+
return request(httpConfig.config, "GET", `/v1/merchants/payments/${encodeURIComponent(paymentId)}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/resources/balances.ts
|
|
202
|
+
async function listBalances(httpConfig, params) {
|
|
203
|
+
return request(httpConfig.config, "GET", "/v1/merchants/balances", {
|
|
204
|
+
params
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async function getBalance(httpConfig, currencyId) {
|
|
208
|
+
return request(httpConfig.config, "GET", `/v1/merchants/balances/${encodeURIComponent(currencyId)}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/resources/quotes.ts
|
|
212
|
+
async function getQuote(httpConfig, params) {
|
|
213
|
+
return request(httpConfig.config, "POST", "/v1/merchants/quotes", {
|
|
214
|
+
body: params
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/resources/payouts.ts
|
|
219
|
+
async function createPayout(httpConfig, params) {
|
|
220
|
+
return request(httpConfig.config, "POST", "/v1/merchants/payouts", {
|
|
221
|
+
body: params
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async function listPayouts(httpConfig, params) {
|
|
225
|
+
return request(httpConfig.config, "GET", "/v1/merchants/payouts", {
|
|
226
|
+
params
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async function getPayout(httpConfig, payoutId) {
|
|
230
|
+
return request(httpConfig.config, "GET", `/v1/merchants/payouts/${encodeURIComponent(payoutId)}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/resources/kyc.ts
|
|
234
|
+
async function getKYCStatus(httpConfig, customerId) {
|
|
235
|
+
return request(httpConfig.config, "GET", `/v1/merchants/kyc/${encodeURIComponent(customerId)}`);
|
|
236
|
+
}
|
|
237
|
+
async function submitKYC(httpConfig, params) {
|
|
238
|
+
return request(httpConfig.config, "POST", "/v1/merchants/kyc", {
|
|
239
|
+
body: params
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/utils/webhooks.ts
|
|
244
|
+
async function verifyWebhookSignature(payload, signature, secret) {
|
|
245
|
+
try {
|
|
246
|
+
const encoder = new TextEncoder;
|
|
247
|
+
const keyData = encoder.encode(secret);
|
|
248
|
+
const key = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
249
|
+
const payloadData = encoder.encode(payload);
|
|
250
|
+
const signatureBuffer = await crypto.subtle.sign("HMAC", key, payloadData);
|
|
251
|
+
const signatureArray = Array.from(new Uint8Array(signatureBuffer));
|
|
252
|
+
const expectedSignature = signatureArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
253
|
+
return constantTimeEqual(expectedSignature, signature);
|
|
254
|
+
} catch {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function constantTimeEqual(a, b) {
|
|
259
|
+
if (a.length !== b.length) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
let result = 0;
|
|
263
|
+
for (let i = 0;i < a.length; i++) {
|
|
264
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
265
|
+
}
|
|
266
|
+
return result === 0;
|
|
267
|
+
}
|
|
268
|
+
function parseWebhookEvent(payload) {
|
|
269
|
+
try {
|
|
270
|
+
return JSON.parse(payload);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
throw new Error(`Failed to parse webhook payload: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/client/createClient.ts
|
|
277
|
+
var DEFAULT_CONFIG = {
|
|
278
|
+
environment: "production",
|
|
279
|
+
timeout: 30000,
|
|
280
|
+
retries: 3
|
|
281
|
+
};
|
|
282
|
+
var BASE_URL2 = "https://pay-api.swapped.com";
|
|
283
|
+
function createClient(config) {
|
|
284
|
+
const requiredConfig = {
|
|
285
|
+
...DEFAULT_CONFIG,
|
|
286
|
+
...config
|
|
287
|
+
};
|
|
288
|
+
const httpConfig = {
|
|
289
|
+
config: requiredConfig,
|
|
290
|
+
baseUrl: BASE_URL2
|
|
291
|
+
};
|
|
292
|
+
return {
|
|
293
|
+
orders: {
|
|
294
|
+
list: (params) => listOrders(httpConfig, params),
|
|
295
|
+
get: (orderId) => getOrder(httpConfig, orderId),
|
|
296
|
+
refund: (orderId, params) => refundOrder(httpConfig, orderId, params)
|
|
297
|
+
},
|
|
298
|
+
paymentLinks: {
|
|
299
|
+
create: (params) => createPaymentLink(httpConfig, params)
|
|
300
|
+
},
|
|
301
|
+
paymentRoutes: {
|
|
302
|
+
create: (params) => createPaymentRoute(httpConfig, params)
|
|
303
|
+
},
|
|
304
|
+
payments: {
|
|
305
|
+
get: (paymentId) => getPayment(httpConfig, paymentId)
|
|
306
|
+
},
|
|
307
|
+
balances: {
|
|
308
|
+
list: (params) => listBalances(httpConfig, params),
|
|
309
|
+
get: (currencyId) => getBalance(httpConfig, currencyId)
|
|
310
|
+
},
|
|
311
|
+
quotes: {
|
|
312
|
+
get: (params) => getQuote(httpConfig, params)
|
|
313
|
+
},
|
|
314
|
+
payouts: {
|
|
315
|
+
create: (params) => createPayout(httpConfig, params),
|
|
316
|
+
list: (params) => listPayouts(httpConfig, params),
|
|
317
|
+
get: (payoutId) => getPayout(httpConfig, payoutId)
|
|
318
|
+
},
|
|
319
|
+
kyc: {
|
|
320
|
+
getStatus: (customerId) => getKYCStatus(httpConfig, customerId),
|
|
321
|
+
submit: (params) => submitKYC(httpConfig, params)
|
|
322
|
+
},
|
|
323
|
+
verifyWebhookSignature,
|
|
324
|
+
parseWebhookEvent
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
export {
|
|
328
|
+
verifyWebhookSignature,
|
|
329
|
+
parseWebhookEvent,
|
|
330
|
+
createValidationError,
|
|
331
|
+
createRateLimitError,
|
|
332
|
+
createNotFoundError,
|
|
333
|
+
createClient,
|
|
334
|
+
createAuthenticationError,
|
|
335
|
+
SwappedValidationError,
|
|
336
|
+
SwappedRateLimitError,
|
|
337
|
+
SwappedNotFoundError,
|
|
338
|
+
SwappedError,
|
|
339
|
+
SwappedAuthenticationError
|
|
340
|
+
};
|
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swapped-commerce-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "TypeScript SDK for Swapped Commerce Integration API - Functional, performant, and fully typed",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
7
|
-
"module": "
|
|
8
|
-
"types": "src/index.ts",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./src/index.ts",
|
|
12
|
-
"import": "./
|
|
13
|
-
"default": "./
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "bun build src/index.ts --outdir dist --target
|
|
17
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm",
|
|
18
|
+
"build:dev": "bun build src/index.ts --outdir dist --target node --format esm",
|
|
19
|
+
"prepublishOnly": "bun run build",
|
|
18
20
|
"test": "bun test",
|
|
19
21
|
"test:watch": "bun test --watch",
|
|
20
22
|
"test:coverage": "bun test --coverage",
|
|
@@ -50,6 +52,7 @@
|
|
|
50
52
|
"typescript": "^5"
|
|
51
53
|
},
|
|
52
54
|
"files": [
|
|
55
|
+
"dist",
|
|
53
56
|
"src",
|
|
54
57
|
"README.md",
|
|
55
58
|
"LICENSE"
|