@vexpay/node 0.1.0

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 ADDED
@@ -0,0 +1,770 @@
1
+ // src/errors.ts
2
+ var VexPayError = class extends Error {
3
+ /** HTTP status, when the API answered. */
4
+ status;
5
+ /** Machine-readable API code, e.g. `insufficient_balance`. */
6
+ code;
7
+ /** Parsed response body, when the API answered. */
8
+ body;
9
+ /** Request id from the `x-request-id` response header, when present. */
10
+ requestId;
11
+ constructor(message, details = {}) {
12
+ super(message);
13
+ this.name = new.target.name;
14
+ this.status = details.status;
15
+ this.code = details.code;
16
+ this.body = details.body;
17
+ this.requestId = details.requestId;
18
+ }
19
+ };
20
+ var VexPayConfigurationError = class extends VexPayError {
21
+ };
22
+ var VexPayAuthenticationError = class extends VexPayError {
23
+ };
24
+ var VexPayInvalidRequestError = class extends VexPayError {
25
+ };
26
+ var VexPayNotFoundError = class extends VexPayError {
27
+ };
28
+ var VexPayConflictError = class extends VexPayError {
29
+ };
30
+ var VexPayRateLimitError = class extends VexPayError {
31
+ };
32
+ var VexPayAPIError = class extends VexPayError {
33
+ };
34
+ var VexPayConnectionError = class extends VexPayError {
35
+ };
36
+ var VexPayTimeoutError = class extends VexPayConnectionError {
37
+ };
38
+ var VexPaySignatureVerificationError = class extends VexPayError {
39
+ };
40
+ var MACHINE_CODE = /^[a-z][a-z0-9_]*$/;
41
+ function messageFrom(body, status) {
42
+ if (body && typeof body === "object") {
43
+ const { message, error } = body;
44
+ if (Array.isArray(message)) return message.join("; ");
45
+ if (typeof message === "string" && message) return message;
46
+ if (typeof error === "string" && error) return error;
47
+ }
48
+ if (typeof body === "string" && body) return body;
49
+ return `VEXPay API responded with HTTP ${status}`;
50
+ }
51
+ function errorFromResponse(status, body, requestId) {
52
+ const rawCode = body && typeof body === "object" ? body.error : void 0;
53
+ const code = typeof rawCode === "string" && MACHINE_CODE.test(rawCode) ? rawCode : void 0;
54
+ const details = { status, code, body, requestId };
55
+ const message = messageFrom(body, status);
56
+ if (status === 401) return new VexPayAuthenticationError(message, details);
57
+ if (status === 404) return new VexPayNotFoundError(message, details);
58
+ if (status === 409) return new VexPayConflictError(message, details);
59
+ if (status === 429) return new VexPayRateLimitError(message, details);
60
+ if (status >= 500) return new VexPayAPIError(message, details);
61
+ return new VexPayInvalidRequestError(message, details);
62
+ }
63
+
64
+ // src/version.ts
65
+ var VERSION = "0.1.0";
66
+
67
+ // src/core.ts
68
+ var INITIAL_RETRY_DELAY_MS = 500;
69
+ var MAX_RETRY_DELAY_MS = 8e3;
70
+ var MAX_RETRY_AFTER_MS = 6e4;
71
+ function defaultSleep(ms) {
72
+ return new Promise((resolve) => setTimeout(resolve, ms));
73
+ }
74
+ function userAgent() {
75
+ const runtime = typeof process !== "undefined" && process.version ? ` node/${process.version}` : "";
76
+ return `vexpay-node/${VERSION}${runtime}`;
77
+ }
78
+ function parseRetryAfter(value, now = Date.now()) {
79
+ if (!value) return void 0;
80
+ const seconds = Number(value);
81
+ if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1e3, MAX_RETRY_AFTER_MS);
82
+ const date = Date.parse(value);
83
+ if (Number.isFinite(date)) return Math.min(Math.max(0, date - now), MAX_RETRY_AFTER_MS);
84
+ return void 0;
85
+ }
86
+ function backoffDelay(attempt, random = Math.random) {
87
+ const ceiling = Math.min(INITIAL_RETRY_DELAY_MS * 2 ** attempt, MAX_RETRY_DELAY_MS);
88
+ return Math.round(ceiling / 2 + random() * (ceiling / 2));
89
+ }
90
+ function isRetryable(error) {
91
+ if (error instanceof VexPayConnectionError) return true;
92
+ if (error.status === 429) return true;
93
+ if (error.status !== void 0 && error.status >= 500) return true;
94
+ return error.status === 409 && error.code === "idempotency_request_in_progress";
95
+ }
96
+ function buildUrl(baseUrl, path, query) {
97
+ const url = new URL(path.replace(/^\//, ""), baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
98
+ for (const [key, value] of Object.entries(query ?? {})) {
99
+ if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
100
+ }
101
+ return url.toString();
102
+ }
103
+ async function readBody(response) {
104
+ const text = await response.text();
105
+ if (!text) return void 0;
106
+ try {
107
+ return JSON.parse(text);
108
+ } catch {
109
+ return text;
110
+ }
111
+ }
112
+ var HttpClient = class {
113
+ constructor(config) {
114
+ this.config = config;
115
+ this.sleep = config.sleep ?? defaultSleep;
116
+ }
117
+ sleep;
118
+ async request(method, path, params = {}, options = {}) {
119
+ const maxRetries = options.maxNetworkRetries ?? this.config.maxNetworkRetries;
120
+ const timeoutMs = options.timeoutMs ?? this.config.timeoutMs;
121
+ const url = buildUrl(this.config.baseUrl, path, params.query);
122
+ const headers = {
123
+ accept: "application/json",
124
+ "x-api-key": this.config.apiKey,
125
+ "user-agent": userAgent()
126
+ };
127
+ if (params.body !== void 0) headers["content-type"] = "application/json";
128
+ if (method === "POST") headers["idempotency-key"] = options.idempotencyKey ?? crypto.randomUUID();
129
+ const body = params.body === void 0 ? void 0 : JSON.stringify(params.body);
130
+ for (let attempt = 0; ; attempt += 1) {
131
+ let error;
132
+ let retryAfterMs;
133
+ try {
134
+ const response = await this.send(url, { method, headers, body }, timeoutMs, options.signal);
135
+ const parsed = await readBody(response);
136
+ if (response.ok) return parsed;
137
+ error = errorFromResponse(response.status, parsed, response.headers.get("x-request-id") ?? void 0);
138
+ retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
139
+ } catch (caught) {
140
+ if (!(caught instanceof VexPayError)) throw caught;
141
+ error = caught;
142
+ }
143
+ if (attempt >= maxRetries || !isRetryable(error) || options.signal?.aborted) throw error;
144
+ await this.sleep(retryAfterMs ?? backoffDelay(attempt));
145
+ }
146
+ }
147
+ async send(url, init, timeoutMs, signal) {
148
+ const controller = new AbortController();
149
+ let timedOut = false;
150
+ const timer = setTimeout(() => {
151
+ timedOut = true;
152
+ controller.abort();
153
+ }, timeoutMs);
154
+ const onAbort = () => controller.abort();
155
+ signal?.addEventListener("abort", onAbort, { once: true });
156
+ try {
157
+ return await this.config.fetch(url, { ...init, signal: controller.signal });
158
+ } catch (caught) {
159
+ if (timedOut) {
160
+ throw new VexPayTimeoutError(`Request to VEXPay timed out after ${timeoutMs}ms`);
161
+ }
162
+ if (signal?.aborted) throw caught;
163
+ const reason = caught instanceof Error ? caught.message : String(caught);
164
+ throw new VexPayConnectionError(`Could not reach VEXPay: ${reason}`);
165
+ } finally {
166
+ clearTimeout(timer);
167
+ signal?.removeEventListener("abort", onAbort);
168
+ }
169
+ }
170
+ };
171
+
172
+ // src/pagination.ts
173
+ var PagePromise = class {
174
+ constructor(fetchPage, startCursor) {
175
+ this.fetchPage = fetchPage;
176
+ this.startCursor = startCursor;
177
+ }
178
+ firstPage;
179
+ first() {
180
+ return this.firstPage ??= this.fetchPage(this.startCursor);
181
+ }
182
+ then(onfulfilled, onrejected) {
183
+ return this.first().then(onfulfilled, onrejected);
184
+ }
185
+ catch(onrejected) {
186
+ return this.first().catch(onrejected);
187
+ }
188
+ finally(onfinally) {
189
+ return this.first().finally(onfinally);
190
+ }
191
+ async *[Symbol.asyncIterator]() {
192
+ let page = await this.first();
193
+ let cursor = this.startCursor;
194
+ for (; ; ) {
195
+ for (const item of page.items) yield item;
196
+ const next = page.nextCursor;
197
+ if (!next || next === cursor) return;
198
+ cursor = next;
199
+ page = await this.fetchPage(next);
200
+ }
201
+ }
202
+ /** Collects items across pages, stopping after `limit` (default 10 000). */
203
+ async autoPagingToArray({ limit = 1e4 } = {}) {
204
+ const items = [];
205
+ for await (const item of this) {
206
+ items.push(item);
207
+ if (items.length >= limit) break;
208
+ }
209
+ return items;
210
+ }
211
+ };
212
+
213
+ // src/generated/routes.ts
214
+ var ROUTES = {
215
+ Balance_getBalance: { method: "GET", path: "/v1/balance" },
216
+ CheckoutSessions_create: { method: "POST", path: "/v1/checkout/sessions" },
217
+ CheckoutSessions_retrieve: { method: "GET", path: "/v1/checkout/sessions/{id}" },
218
+ Merchants_addPayoutMethod: { method: "POST", path: "/v1/merchants/{id}/payout-methods" },
219
+ Merchants_confirmVerify: { method: "POST", path: "/v1/merchants/{id}/verify" },
220
+ Merchants_confirmVerifyMethod: { method: "POST", path: "/v1/merchants/{id}/payout-methods/{methodId}/verify" },
221
+ Merchants_create: { method: "POST", path: "/v1/merchants" },
222
+ Merchants_deletePayoutMethod: { method: "DELETE", path: "/v1/merchants/{id}/payout-methods/{methodId}" },
223
+ Merchants_getBalance: { method: "GET", path: "/v1/merchants/{id}/balance" },
224
+ Merchants_getById: { method: "GET", path: "/v1/merchants/{id}" },
225
+ Merchants_listAuditEvents: { method: "GET", path: "/v1/merchants/{id}/audit-events" },
226
+ Merchants_listOrGetByRef: { method: "GET", path: "/v1/merchants" },
227
+ Merchants_listPayoutMethods: { method: "GET", path: "/v1/merchants/{id}/payout-methods" },
228
+ Merchants_remove: { method: "DELETE", path: "/v1/merchants/{id}" },
229
+ Merchants_setDefaultPayoutMethod: { method: "POST", path: "/v1/merchants/{id}/payout-methods/{methodId}/default" },
230
+ Merchants_startVerify: { method: "POST", path: "/v1/merchants/{id}/verify/start" },
231
+ Merchants_startVerifyMethod: { method: "POST", path: "/v1/merchants/{id}/payout-methods/{methodId}/verify/start" },
232
+ Merchants_transfer: { method: "POST", path: "/v1/merchants/{id}/transfers" },
233
+ Merchants_update: { method: "PATCH", path: "/v1/merchants/{id}" },
234
+ Notifications_sendTest: { method: "POST", path: "/v1/notifications/test" },
235
+ PaymentLinks_get: { method: "GET", path: "/v1/payment-links/{id}" },
236
+ PaymentLinks_remove: { method: "DELETE", path: "/v1/payment-links/{id}" },
237
+ PaymentLinks_update: { method: "PATCH", path: "/v1/payment-links/{id}" },
238
+ Payments_executeC2p: { method: "POST", path: "/v1/payments/c2p" },
239
+ Payments_executeVpos: { method: "POST", path: "/v1/payments/vpos" },
240
+ Payments_getBanks: { method: "GET", path: "/v1/banks" },
241
+ Payments_getPayment: { method: "GET", path: "/v1/payments/{id}" },
242
+ Payments_getPaymentByRef: { method: "GET", path: "/v1/payments/by-ref/{externalRef}" },
243
+ Payments_getQuote: { method: "GET", path: "/v1/quote" },
244
+ Payments_requestC2p: { method: "POST", path: "/v1/payments/c2p/request" },
245
+ Payments_reversePayment: { method: "POST", path: "/v1/payments/{id}/reverse" },
246
+ Payments_verifyPagoMovil: { method: "POST", path: "/v1/payments/pago-movil/verify" },
247
+ Payouts_create: { method: "POST", path: "/v1/payouts" },
248
+ Payouts_createBatch: { method: "POST", path: "/v1/payouts/batch" },
249
+ Payouts_createInstant: { method: "POST", path: "/v1/payouts/instant" },
250
+ Payouts_getById: { method: "GET", path: "/v1/payouts/{id}" },
251
+ Payouts_getByRef: { method: "GET", path: "/v1/payouts/by-ref/{externalRef}" },
252
+ Payouts_list: { method: "GET", path: "/v1/payouts" },
253
+ Products_create: { method: "POST", path: "/v1/products" },
254
+ Products_createLink: { method: "POST", path: "/v1/products/{id}/links" },
255
+ Products_get: { method: "GET", path: "/v1/products/{id}" },
256
+ Products_list: { method: "GET", path: "/v1/products" },
257
+ Products_listLinks: { method: "GET", path: "/v1/products/{id}/links" },
258
+ Products_remove: { method: "DELETE", path: "/v1/products/{id}" },
259
+ Products_update: { method: "PATCH", path: "/v1/products/{id}" },
260
+ R4Operations_consultarOperacion: { method: "GET", path: "/v1/payments/operations/{id}" },
261
+ R4Operations_creditoCuentas: { method: "POST", path: "/v1/payments/credit/account" },
262
+ R4Operations_creditoInmediato: { method: "POST", path: "/v1/payments/credit" },
263
+ R4Operations_debitoInmediato: { method: "POST", path: "/v1/payments/debit" },
264
+ R4Operations_dispersarCredito: { method: "POST", path: "/v1/payments/credit/disburse" },
265
+ R4Operations_dispersarPagos: { method: "POST", path: "/v1/payments/payouts" },
266
+ R4Operations_ejecutarVuelto: { method: "POST", path: "/v1/payments/change" },
267
+ R4Operations_generarOtp: { method: "POST", path: "/v1/payments/debit/otp" },
268
+ R4Operations_pollOperation: { method: "POST", path: "/v1/payments/operations/poll" },
269
+ TenantPayoutAccount_confirmVerify: { method: "POST", path: "/v1/tenant/payout-account/verify" },
270
+ TenantPayoutAccount_get: { method: "GET", path: "/v1/tenant/payout-account" },
271
+ TenantPayoutAccount_startVerify: { method: "POST", path: "/v1/tenant/payout-account/verify/start" },
272
+ TenantPayoutAccount_upsert: { method: "PUT", path: "/v1/tenant/payout-account" },
273
+ TenantPayoutAccount_upsertPost: { method: "POST", path: "/v1/tenant/payout-account" },
274
+ Webhooks_createWebhook: { method: "POST", path: "/v1/webhooks" },
275
+ Webhooks_deleteWebhook: { method: "DELETE", path: "/v1/webhooks/{id}" },
276
+ Webhooks_listWebhooks: { method: "GET", path: "/v1/webhooks" },
277
+ Webhooks_updateWebhook: { method: "PATCH", path: "/v1/webhooks/{id}" }
278
+ };
279
+
280
+ // src/resource.ts
281
+ var APIResource = class {
282
+ constructor(http) {
283
+ this.http = http;
284
+ }
285
+ /** Async so a bad argument surfaces as a rejected promise, never a synchronous throw. */
286
+ async call(operationId, args = {}, options) {
287
+ const route = ROUTES[operationId];
288
+ const path = route.path.replace(/\{(\w+)\}/g, (_match, name) => {
289
+ const value = args.path?.[name];
290
+ if (typeof value !== "string" || value === "") {
291
+ throw new VexPayInvalidRequestError(`Missing required path parameter "${name}" for ${operationId}.`);
292
+ }
293
+ return encodeURIComponent(value);
294
+ });
295
+ return this.http.request(
296
+ route.method,
297
+ path,
298
+ { query: args.query, body: args.body },
299
+ options
300
+ );
301
+ }
302
+ };
303
+
304
+ // src/resources/account.ts
305
+ var Banks = class extends APIResource {
306
+ list(options) {
307
+ return this.call("Payments_getBanks", {}, options);
308
+ }
309
+ };
310
+ var Quotes = class extends APIResource {
311
+ retrieve(params, options) {
312
+ return this.call("Payments_getQuote", { query: params }, options);
313
+ }
314
+ };
315
+ var Balance = class extends APIResource {
316
+ retrieve(options) {
317
+ return this.call("Balance_getBalance", {}, options);
318
+ }
319
+ };
320
+ var Payouts = class extends APIResource {
321
+ create(params, options) {
322
+ return this.call("Payouts_create", { body: params }, options);
323
+ }
324
+ /** Pay out immediately (crédito inmediato). */
325
+ createInstant(params, options) {
326
+ return this.call("Payouts_createInstant", { body: params }, options);
327
+ }
328
+ /** Many payouts in one request, with item-level idempotency. */
329
+ createBatch(params, options) {
330
+ return this.call("Payouts_createBatch", { body: params }, options);
331
+ }
332
+ /** Await for one page, or `for await` over every payout. */
333
+ list(params = {}, options) {
334
+ return new PagePromise(
335
+ (cursor) => this.call("Payouts_list", { query: { ...params, cursor } }, options),
336
+ params.cursor
337
+ );
338
+ }
339
+ retrieve(id, options) {
340
+ return this.call("Payouts_getById", { path: { id } }, options);
341
+ }
342
+ retrieveByRef(externalRef, options) {
343
+ return this.call("Payouts_getByRef", { path: { externalRef } }, options);
344
+ }
345
+ };
346
+ var TenantPayoutAccount = class extends APIResource {
347
+ retrieve(options) {
348
+ return this.call("TenantPayoutAccount_get", {}, options);
349
+ }
350
+ /** Create or replace the payout account (PUT). */
351
+ upsert(params, options) {
352
+ return this.call("TenantPayoutAccount_upsert", { body: params }, options);
353
+ }
354
+ /** Same as `upsert`, sent as POST (supports `Idempotency-Key`). */
355
+ create(params, options) {
356
+ return this.call("TenantPayoutAccount_upsertPost", { body: params }, options);
357
+ }
358
+ startVerification(options) {
359
+ return this.call("TenantPayoutAccount_startVerify", {}, options);
360
+ }
361
+ confirmVerification(params, options) {
362
+ return this.call("TenantPayoutAccount_confirmVerify", { body: params }, options);
363
+ }
364
+ };
365
+ var WebhookEndpoints = class extends APIResource {
366
+ /** The response includes the signing `secret` — store it to verify deliveries. */
367
+ create(params, options) {
368
+ return this.call("Webhooks_createWebhook", { body: params }, options);
369
+ }
370
+ list(options) {
371
+ return this.call("Webhooks_listWebhooks", {}, options);
372
+ }
373
+ update(id, params, options) {
374
+ return this.call("Webhooks_updateWebhook", { path: { id }, body: params }, options);
375
+ }
376
+ delete(id, options) {
377
+ return this.call("Webhooks_deleteWebhook", { path: { id } }, options);
378
+ }
379
+ /** Send a `notification.test` delivery to your endpoints. */
380
+ sendTest(options) {
381
+ return this.call("Notifications_sendTest", {}, options);
382
+ }
383
+ };
384
+
385
+ // src/resources/commerce.ts
386
+ var CheckoutSessions = class extends APIResource {
387
+ /** `clientSecret` is returned only here — hand it to the browser, never store or log it. */
388
+ create(params, options) {
389
+ return this.call("CheckoutSessions_create", { body: params }, options);
390
+ }
391
+ retrieve(id, options) {
392
+ return this.call("CheckoutSessions_retrieve", { path: { id } }, options);
393
+ }
394
+ };
395
+ var Checkout = class extends APIResource {
396
+ sessions = new CheckoutSessions(this.http);
397
+ };
398
+ var Products = class extends APIResource {
399
+ create(params, options) {
400
+ return this.call("Products_create", { body: params }, options);
401
+ }
402
+ /** Await for one page, or `for await` over every product. */
403
+ list(params = {}, options) {
404
+ return new PagePromise(
405
+ (cursor) => this.call("Products_list", { query: { ...params, cursor } }, options),
406
+ params.cursor
407
+ );
408
+ }
409
+ retrieve(id, options) {
410
+ return this.call("Products_get", { path: { id } }, options);
411
+ }
412
+ update(id, params, options) {
413
+ return this.call("Products_update", { path: { id }, body: params }, options);
414
+ }
415
+ delete(id, options) {
416
+ return this.call("Products_remove", { path: { id } }, options);
417
+ }
418
+ /** Publish a shareable `/pay/:slug` link for this product. */
419
+ createLink(id, params, options) {
420
+ return this.call("Products_createLink", { path: { id }, body: params }, options);
421
+ }
422
+ listLinks(id, options) {
423
+ return this.call("Products_listLinks", { path: { id } }, options);
424
+ }
425
+ };
426
+ var PaymentLinks = class extends APIResource {
427
+ retrieve(id, options) {
428
+ return this.call("PaymentLinks_get", { path: { id } }, options);
429
+ }
430
+ update(id, params, options) {
431
+ return this.call("PaymentLinks_update", { path: { id }, body: params }, options);
432
+ }
433
+ delete(id, options) {
434
+ return this.call("PaymentLinks_remove", { path: { id } }, options);
435
+ }
436
+ };
437
+
438
+ // src/resources/merchants.ts
439
+ var PayoutMethods = class extends APIResource {
440
+ list(merchantId, options) {
441
+ return this.call("Merchants_listPayoutMethods", { path: { id: merchantId } }, options);
442
+ }
443
+ create(merchantId, params, options) {
444
+ return this.call("Merchants_addPayoutMethod", { path: { id: merchantId }, body: params }, options);
445
+ }
446
+ setDefault(merchantId, methodId, options) {
447
+ return this.call("Merchants_setDefaultPayoutMethod", { path: { id: merchantId, methodId } }, options);
448
+ }
449
+ delete(merchantId, methodId, options) {
450
+ return this.call("Merchants_deletePayoutMethod", { path: { id: merchantId, methodId } }, options);
451
+ }
452
+ /** Send micro-deposits to this payout method. */
453
+ startVerification(merchantId, methodId, options) {
454
+ return this.call("Merchants_startVerifyMethod", { path: { id: merchantId, methodId } }, options);
455
+ }
456
+ /** Confirm the micro-deposit amounts the merchant saw. */
457
+ confirmVerification(merchantId, methodId, params, options) {
458
+ return this.call(
459
+ "Merchants_confirmVerifyMethod",
460
+ { path: { id: merchantId, methodId }, body: params },
461
+ options
462
+ );
463
+ }
464
+ };
465
+ var Merchants = class extends APIResource {
466
+ payoutMethods = new PayoutMethods(this.http);
467
+ /** `externalRef` is required and is the idempotency key for merchant creation. */
468
+ create(params, options) {
469
+ return this.call("Merchants_create", { body: params }, options);
470
+ }
471
+ /** Await for one page (`items`, `nextCursor`), or `for await` over every merchant. */
472
+ list(params = {}, options) {
473
+ return new PagePromise(
474
+ (cursor) => this.call("Merchants_listOrGetByRef", { query: { ...params, cursor } }, options),
475
+ params.cursor
476
+ );
477
+ }
478
+ retrieve(id, options) {
479
+ return this.call("Merchants_getById", { path: { id } }, options);
480
+ }
481
+ /** Look up a merchant by the `externalRef` you created it with. */
482
+ retrieveByRef(externalRef, options) {
483
+ return this.call("Merchants_listOrGetByRef", { query: { externalRef } }, options);
484
+ }
485
+ update(id, params, options) {
486
+ return this.call("Merchants_update", { path: { id }, body: params }, options);
487
+ }
488
+ delete(id, options) {
489
+ return this.call("Merchants_remove", { path: { id } }, options);
490
+ }
491
+ retrieveBalance(id, options) {
492
+ return this.call("Merchants_getBalance", { path: { id } }, options);
493
+ }
494
+ /** Move VES between this merchant's balance and another. */
495
+ transfer(id, params, options) {
496
+ return this.call("Merchants_transfer", { path: { id }, body: params }, options);
497
+ }
498
+ listAuditEvents(id, options) {
499
+ return this.call("Merchants_listAuditEvents", { path: { id } }, options);
500
+ }
501
+ /** Send micro-deposits to the merchant's default payout method. */
502
+ startVerification(id, options) {
503
+ return this.call("Merchants_startVerify", { path: { id } }, options);
504
+ }
505
+ confirmVerification(id, params, options) {
506
+ return this.call("Merchants_confirmVerify", { path: { id }, body: params }, options);
507
+ }
508
+ };
509
+
510
+ // src/resources/payments.ts
511
+ var C2p = class extends APIResource {
512
+ /** Create a C2P intent; the payer's bank sends them a token (R4 also triggers the SMS). */
513
+ request(params, options) {
514
+ return this.call("Payments_requestC2p", { body: params }, options);
515
+ }
516
+ /** Charge with the bank-issued token. Pass `intentId` from `request()`. */
517
+ execute(params, options) {
518
+ return this.call("Payments_executeC2p", { body: params }, options);
519
+ }
520
+ };
521
+ var Vpos = class extends APIResource {
522
+ create(params, options) {
523
+ return this.call("Payments_executeVpos", { body: params }, options);
524
+ }
525
+ };
526
+ var PagoMovil = class extends APIResource {
527
+ verify(params, options) {
528
+ return this.call("Payments_verifyPagoMovil", { body: params }, options);
529
+ }
530
+ };
531
+ var Debit = class extends APIResource {
532
+ requestOtp(params, options) {
533
+ return this.call("R4Operations_generarOtp", { body: params }, options);
534
+ }
535
+ execute(params, options) {
536
+ return this.call("R4Operations_debitoInmediato", { body: params }, options);
537
+ }
538
+ };
539
+ var Credit = class extends APIResource {
540
+ /** Instant credit to a Pago Móvil phone. */
541
+ create(params, options) {
542
+ return this.call("R4Operations_creditoInmediato", { body: params }, options);
543
+ }
544
+ /** Instant credit to a bank account. */
545
+ toAccount(params, options) {
546
+ return this.call("R4Operations_creditoCuentas", { body: params }, options);
547
+ }
548
+ /** Disburse one credit across several recipients. */
549
+ disburse(params, options) {
550
+ return this.call("R4Operations_dispersarCredito", { body: params }, options);
551
+ }
552
+ };
553
+ var Operations = class extends APIResource {
554
+ retrieve(id, options) {
555
+ return this.call("R4Operations_consultarOperacion", { path: { id } }, options);
556
+ }
557
+ /** Re-check a pending operation with the bank now. */
558
+ poll(params, options) {
559
+ return this.call("R4Operations_pollOperation", { body: params }, options);
560
+ }
561
+ };
562
+ var Dispersals = class extends APIResource {
563
+ create(params, options) {
564
+ return this.call("R4Operations_dispersarPagos", { body: params }, options);
565
+ }
566
+ };
567
+ var Change = class extends APIResource {
568
+ create(params, options) {
569
+ return this.call("R4Operations_ejecutarVuelto", { body: params }, options);
570
+ }
571
+ };
572
+ var Payments = class extends APIResource {
573
+ c2p = new C2p(this.http);
574
+ vpos = new Vpos(this.http);
575
+ pagoMovil = new PagoMovil(this.http);
576
+ debit = new Debit(this.http);
577
+ credit = new Credit(this.http);
578
+ operations = new Operations(this.http);
579
+ dispersals = new Dispersals(this.http);
580
+ change = new Change(this.http);
581
+ retrieve(id, options) {
582
+ return this.call("Payments_getPayment", { path: { id } }, options);
583
+ }
584
+ /** Look up a payment by the `externalRef` you sent. */
585
+ retrieveByRef(externalRef, options) {
586
+ return this.call("Payments_getPaymentByRef", { path: { externalRef } }, options);
587
+ }
588
+ /** Reverse a completed C2P payment. */
589
+ reverse(id, options) {
590
+ return this.call("Payments_reversePayment", { path: { id } }, options);
591
+ }
592
+ };
593
+
594
+ // src/webhooks.ts
595
+ import { createHmac, timingSafeEqual } from "crypto";
596
+ var WEBHOOK_EVENT_NAMES = [
597
+ "payment.pending",
598
+ "payment.completed",
599
+ "payment.failed",
600
+ "payment.canceled",
601
+ "payment.reversed",
602
+ "merchant.verified",
603
+ "merchant.rejected",
604
+ "merchant.deactivated",
605
+ "merchant.reactivated",
606
+ "merchant.balance.updated",
607
+ "merchant.created",
608
+ "merchant.activated",
609
+ "merchant.updated",
610
+ "merchant.kyb_required",
611
+ "merchant.restricted",
612
+ "merchant.capability.updated",
613
+ "merchant.wallet_credit",
614
+ "payout.completed",
615
+ "payout.failed",
616
+ "tenant.status_changed",
617
+ "tenant.api_key.created",
618
+ "tenant.api_key.rotated",
619
+ "tenant.api_key.revoked",
620
+ "notification.test"
621
+ ];
622
+ var SIGNATURE_HEADER = "VexPay-Signature";
623
+ var DEFAULT_TOLERANCE_SECONDS = 300;
624
+ var RAW_BODY_HINT = 'Pass the raw request body exactly as received (string or Buffer). Parsing and re-serializing JSON \u2014 e.g. JSON.stringify(req.body) \u2014 changes the bytes and breaks the signature. In Express use express.raw({ type: "application/json" }) on the webhook route.';
625
+ function sign(secret, timestamp, payload) {
626
+ return createHmac("sha256", secret).update(`${timestamp}.${payload}`, "utf8").digest("hex");
627
+ }
628
+ function parseHeader(header) {
629
+ let timestamp;
630
+ const signatures = [];
631
+ for (const part of header.split(",")) {
632
+ const index = part.indexOf("=");
633
+ if (index <= 0) return null;
634
+ const key = part.slice(0, index).trim();
635
+ const value = part.slice(index + 1).trim();
636
+ if (key === "t") timestamp = value;
637
+ else if (key === "v1") signatures.push(value);
638
+ }
639
+ if (!timestamp || !/^\d+$/.test(timestamp)) return null;
640
+ return { timestamp: Number(timestamp), signatures };
641
+ }
642
+ function safeEqualHex(expected, actual) {
643
+ const a = Buffer.from(expected, "utf8");
644
+ const b = Buffer.from(actual, "utf8");
645
+ return a.length === b.length && timingSafeEqual(a, b);
646
+ }
647
+ var Webhooks = class {
648
+ /**
649
+ * Verifies a delivery's `VexPay-Signature` header and returns the parsed event.
650
+ * Throws VexPaySignatureVerificationError on a bad signature or a stale timestamp.
651
+ */
652
+ constructEvent(rawBody, signatureHeader, secret, options = {}) {
653
+ if (typeof rawBody !== "string" && !(rawBody instanceof Uint8Array)) {
654
+ throw new VexPaySignatureVerificationError(`Webhook payload must be the raw body. ${RAW_BODY_HINT}`);
655
+ }
656
+ if (!secret) {
657
+ throw new VexPaySignatureVerificationError("A webhook signing secret is required.");
658
+ }
659
+ const payload = typeof rawBody === "string" ? rawBody : Buffer.from(rawBody).toString("utf8");
660
+ const header = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
661
+ const parsed = header ? parseHeader(header) : null;
662
+ if (!parsed) {
663
+ throw new VexPaySignatureVerificationError(
664
+ `Unable to parse the ${SIGNATURE_HEADER} header. Expected "t=<timestamp>,v1=<signature>".`
665
+ );
666
+ }
667
+ if (parsed.signatures.length === 0) {
668
+ throw new VexPaySignatureVerificationError(`No v1 signatures found in the ${SIGNATURE_HEADER} header.`);
669
+ }
670
+ const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
671
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
672
+ if (tolerance > 0 && Math.abs(now - parsed.timestamp) > tolerance) {
673
+ throw new VexPaySignatureVerificationError(
674
+ `Webhook timestamp is outside the ${tolerance}s tolerance \u2014 possible replay. Check your server clock if this repeats.`
675
+ );
676
+ }
677
+ const expected = sign(secret, parsed.timestamp, payload);
678
+ if (!parsed.signatures.some((candidate) => safeEqualHex(expected, candidate))) {
679
+ throw new VexPaySignatureVerificationError(
680
+ `No signature matches the expected signature for this payload. Check the endpoint secret. ${RAW_BODY_HINT}`
681
+ );
682
+ }
683
+ try {
684
+ return JSON.parse(payload);
685
+ } catch {
686
+ throw new VexPaySignatureVerificationError("Webhook payload is not valid JSON.");
687
+ }
688
+ }
689
+ /** Builds a valid `VexPay-Signature` value — for your own tests. */
690
+ generateTestHeader({
691
+ payload,
692
+ secret,
693
+ timestamp = Math.floor(Date.now() / 1e3)
694
+ }) {
695
+ return `t=${timestamp},v1=${sign(secret, timestamp, payload)}`;
696
+ }
697
+ };
698
+
699
+ // src/client.ts
700
+ var DEFAULT_BASE_URL = "https://api.pay.vexwallet.co";
701
+ var VexPay = class {
702
+ /** @internal */
703
+ http;
704
+ banks;
705
+ quotes;
706
+ balance;
707
+ payments;
708
+ checkout;
709
+ merchants;
710
+ payouts;
711
+ products;
712
+ paymentLinks;
713
+ tenantPayoutAccount;
714
+ webhookEndpoints;
715
+ /** Offline helpers: `constructEvent` verifies deliveries; no API calls. */
716
+ webhooks = new Webhooks();
717
+ constructor(apiKey, options = {}) {
718
+ if (typeof apiKey !== "string" || apiKey.trim() === "") {
719
+ throw new VexPayConfigurationError(
720
+ "A VEXPay API key is required: new VexPay(process.env.VEXPAY_API_KEY)."
721
+ );
722
+ }
723
+ const fetchImpl = options.fetch ?? globalThis.fetch;
724
+ if (typeof fetchImpl !== "function") {
725
+ throw new VexPayConfigurationError("No fetch implementation found. Use Node.js 20+ or pass options.fetch.");
726
+ }
727
+ const config = {
728
+ apiKey: apiKey.trim(),
729
+ baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
730
+ timeoutMs: options.timeoutMs ?? 3e4,
731
+ maxNetworkRetries: options.maxNetworkRetries ?? 2,
732
+ fetch: fetchImpl.bind(globalThis)
733
+ };
734
+ this.http = new HttpClient(config);
735
+ this.banks = new Banks(this.http);
736
+ this.quotes = new Quotes(this.http);
737
+ this.balance = new Balance(this.http);
738
+ this.payments = new Payments(this.http);
739
+ this.checkout = new Checkout(this.http);
740
+ this.merchants = new Merchants(this.http);
741
+ this.payouts = new Payouts(this.http);
742
+ this.products = new Products(this.http);
743
+ this.paymentLinks = new PaymentLinks(this.http);
744
+ this.tenantPayoutAccount = new TenantPayoutAccount(this.http);
745
+ this.webhookEndpoints = new WebhookEndpoints(this.http);
746
+ }
747
+ };
748
+ export {
749
+ DEFAULT_BASE_URL,
750
+ DEFAULT_TOLERANCE_SECONDS,
751
+ PagePromise,
752
+ SIGNATURE_HEADER,
753
+ VERSION,
754
+ VexPay,
755
+ VexPayAPIError,
756
+ VexPayAuthenticationError,
757
+ VexPayConfigurationError,
758
+ VexPayConflictError,
759
+ VexPayConnectionError,
760
+ VexPayError,
761
+ VexPayInvalidRequestError,
762
+ VexPayNotFoundError,
763
+ VexPayRateLimitError,
764
+ VexPaySignatureVerificationError,
765
+ VexPayTimeoutError,
766
+ WEBHOOK_EVENT_NAMES,
767
+ Webhooks,
768
+ VexPay as default
769
+ };
770
+ //# sourceMappingURL=index.js.map