@nile-squad/nylonpay-ts 1.0.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.cjs ADDED
@@ -0,0 +1,781 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var slangTs = require('slang-ts');
5
+ var os = require('os');
6
+
7
+ // src/sdk.ts
8
+
9
+ // src/pubsub.ts
10
+ function createEmitter() {
11
+ const state = {
12
+ listeners: /* @__PURE__ */ new Map()
13
+ };
14
+ function on(event, handler) {
15
+ if (!state.listeners.has(event)) {
16
+ state.listeners.set(event, /* @__PURE__ */ new Set());
17
+ }
18
+ state.listeners.get(event).add(handler);
19
+ return () => off(event, handler);
20
+ }
21
+ function once(event, handler) {
22
+ const wrapper = (data) => {
23
+ off(event, wrapper);
24
+ handler(data);
25
+ };
26
+ on(event, wrapper);
27
+ return emitter;
28
+ }
29
+ function off(event, handler) {
30
+ const handlers = state.listeners.get(event);
31
+ if (handlers) {
32
+ handlers.delete(handler);
33
+ }
34
+ }
35
+ function emit(event, data) {
36
+ const handlers = state.listeners.get(event);
37
+ if (!handlers || handlers.size === 0) return;
38
+ for (const handler of handlers) {
39
+ try {
40
+ handler(data);
41
+ } catch {
42
+ }
43
+ }
44
+ }
45
+ function clear(event) {
46
+ if (event) {
47
+ state.listeners.delete(event);
48
+ } else {
49
+ state.listeners.clear();
50
+ }
51
+ }
52
+ function listenerCount(event) {
53
+ return state.listeners.get(event)?.size ?? 0;
54
+ }
55
+ const emitter = { on, once, off, emit, clear, listenerCount };
56
+ return emitter;
57
+ }
58
+
59
+ // src/payment.ts
60
+ var STATUS_TO_EVENT = {
61
+ successful: "success",
62
+ failed: "failed",
63
+ processing: "processing",
64
+ cancelled: "cancelled"
65
+ };
66
+ function statusToEvent(status) {
67
+ return STATUS_TO_EVENT[status] ?? null;
68
+ }
69
+ var TERMINAL_STATES = /* @__PURE__ */ new Set([
70
+ "successful",
71
+ "failed",
72
+ "cancelled"
73
+ ]);
74
+ function createPaymentInstance(initialResponse, deps) {
75
+ const state = {
76
+ reference: initialResponse.reference,
77
+ status: initialResponse.status,
78
+ transaction: null,
79
+ pollingTimer: null,
80
+ resolved: false,
81
+ pollAttempts: 0,
82
+ pollStartTime: Date.now(),
83
+ emitter: createEmitter(),
84
+ fetchStatus: deps.fetchStatus,
85
+ fetchTransaction: deps.fetchTransaction,
86
+ pollIntervalMs: deps.pollIntervalMs ?? 2e3,
87
+ maxPollDuration: deps.maxPollDuration ?? 3e5,
88
+ maxPollAttempts: deps.maxPollAttempts ?? 150
89
+ };
90
+ function resolveWithError(error) {
91
+ state.resolved = true;
92
+ stopPolling();
93
+ emitEvent("error", error);
94
+ }
95
+ function emitEvent(event, error) {
96
+ const data = {
97
+ event,
98
+ transaction: state.transaction ?? void 0,
99
+ error,
100
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
101
+ };
102
+ state.emitter.emit(event, data);
103
+ }
104
+ async function handleTerminalState(status) {
105
+ const txResult = await state.fetchTransaction({
106
+ reference: state.reference
107
+ });
108
+ if (txResult.isOk) {
109
+ state.transaction = txResult.value;
110
+ const event = statusToEvent(status);
111
+ if (event) {
112
+ emitEvent(event);
113
+ }
114
+ } else {
115
+ emitEvent("error", `Failed to fetch transaction: ${txResult.error}`);
116
+ }
117
+ state.resolved = true;
118
+ stopPolling();
119
+ }
120
+ async function handleStatusUpdate(response) {
121
+ if (response.reference !== state.reference) {
122
+ resolveWithError(
123
+ `Reference mismatch: expected ${state.reference} but got ${response.reference}`
124
+ );
125
+ return;
126
+ }
127
+ const newStatus = response.status;
128
+ const oldStatus = state.status;
129
+ state.status = newStatus;
130
+ if (newStatus !== oldStatus) {
131
+ const event = statusToEvent(newStatus);
132
+ if (event) {
133
+ if (TERMINAL_STATES.has(newStatus)) {
134
+ await handleTerminalState(newStatus);
135
+ return;
136
+ }
137
+ emitEvent(event);
138
+ }
139
+ }
140
+ }
141
+ function handlePollError(error) {
142
+ const isNotFound = error.includes("not found") || error.includes("NOT_FOUND");
143
+ if (isNotFound) {
144
+ return;
145
+ }
146
+ emitEvent("error", error);
147
+ state.resolved = true;
148
+ stopPolling();
149
+ }
150
+ function scheduleNextPoll() {
151
+ if (state.resolved || state.pollingTimer) {
152
+ return;
153
+ }
154
+ state.pollingTimer = setTimeout(() => {
155
+ state.pollingTimer = null;
156
+ void pollStatus();
157
+ }, state.pollIntervalMs);
158
+ }
159
+ async function pollStatus() {
160
+ if (state.resolved) {
161
+ stopPolling();
162
+ return;
163
+ }
164
+ if (state.pollAttempts >= state.maxPollAttempts) {
165
+ resolveWithError("Polling timeout: exceeded maximum attempts");
166
+ return;
167
+ }
168
+ if (Date.now() - state.pollStartTime >= state.maxPollDuration) {
169
+ resolveWithError("Polling timeout: exceeded maximum duration");
170
+ return;
171
+ }
172
+ state.pollAttempts += 1;
173
+ const result = await state.fetchStatus({ reference: state.reference });
174
+ if (result.isOk) {
175
+ await handleStatusUpdate(result.value);
176
+ } else {
177
+ handlePollError(result.error);
178
+ }
179
+ if (state.resolved) {
180
+ stopPolling();
181
+ return;
182
+ }
183
+ scheduleNextPoll();
184
+ }
185
+ function startPolling() {
186
+ scheduleNextPoll();
187
+ }
188
+ function stopPolling() {
189
+ if (state.pollingTimer) {
190
+ clearTimeout(state.pollingTimer);
191
+ state.pollingTimer = null;
192
+ }
193
+ }
194
+ function on(event, handler) {
195
+ state.emitter.on(event, handler);
196
+ return paymentInstance;
197
+ }
198
+ function off(event, handler) {
199
+ state.emitter.off(event, handler);
200
+ return paymentInstance;
201
+ }
202
+ function once(event, handler) {
203
+ state.emitter.once(event, handler);
204
+ return paymentInstance;
205
+ }
206
+ function wait() {
207
+ return new Promise((resolve, reject) => {
208
+ if (state.resolved) {
209
+ if (state.status === "successful" && state.transaction) {
210
+ resolve(state.transaction);
211
+ } else {
212
+ reject(new Error(`Payment ${state.status ?? "error"}`));
213
+ }
214
+ return;
215
+ }
216
+ function onSuccess() {
217
+ cleanup();
218
+ if (state.transaction) {
219
+ resolve(state.transaction);
220
+ } else {
221
+ reject(
222
+ new Error("Payment successful but transaction data unavailable")
223
+ );
224
+ }
225
+ }
226
+ function onFailed() {
227
+ cleanup();
228
+ reject(new Error("Payment failed"));
229
+ }
230
+ function onCancelled() {
231
+ cleanup();
232
+ reject(new Error("Payment cancelled"));
233
+ }
234
+ function onError(data) {
235
+ cleanup();
236
+ const eventData = data;
237
+ reject(new Error(eventData.error ?? "Payment error"));
238
+ }
239
+ function cleanup() {
240
+ state.emitter.off("success", onSuccess);
241
+ state.emitter.off("failed", onFailed);
242
+ state.emitter.off("cancelled", onCancelled);
243
+ state.emitter.off("error", onError);
244
+ }
245
+ state.emitter.on("success", onSuccess);
246
+ state.emitter.on("failed", onFailed);
247
+ state.emitter.on("cancelled", onCancelled);
248
+ state.emitter.on("error", onError);
249
+ });
250
+ }
251
+ const paymentInstance = {
252
+ get reference() {
253
+ return state.reference;
254
+ },
255
+ get status() {
256
+ return state.status;
257
+ },
258
+ on,
259
+ once,
260
+ off,
261
+ wait
262
+ };
263
+ startPolling();
264
+ return paymentInstance;
265
+ }
266
+
267
+ // src/sdk.config.ts
268
+ var DEFAULT_BASE_URL = "https://api.nylonpay.io/api/services";
269
+ var DEFAULT_TIMEOUT_MS = 3e4;
270
+ var DEFAULT_MAX_RETRIES = 3;
271
+ var DEFAULT_MAX_POLL_INTERVAL_MS = 2e3;
272
+ var DEFAULT_MAX_POLL_DURATION_MS = 3e5;
273
+ var DEFAULT_MAX_POLL_ATTEMPTS = 150;
274
+ var SDK_SERVICE = "sdk";
275
+ var SDK_ACTIONS = {
276
+ collectPayment: "sdk-collect-payment",
277
+ collectPaymentAndResolve: "sdk-collect-payment-and-resolve",
278
+ makePayout: "sdk-make-payout",
279
+ makePayoutAndResolve: "sdk-make-payout-and-resolve",
280
+ getStatus: "sdk-get-status",
281
+ getTransaction: "sdk-get-transaction",
282
+ verifyPhone: "sdk-verify-phone",
283
+ createInvoice: "sdk-create-invoice"
284
+ };
285
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
286
+ function generateFingerprint() {
287
+ const components = [
288
+ `type:${os.type()}`,
289
+ `platform:${os.platform()}`,
290
+ `arch:${os.arch()}`,
291
+ `release:${os.release()}`,
292
+ `hostname:${os.hostname()}`,
293
+ `node:${process.versions.node}`,
294
+ `v8:${process.versions.v8}`
295
+ ].join("|");
296
+ return crypto.createHash("sha256").update(components).digest("hex");
297
+ }
298
+ function generateNonce(length = 16) {
299
+ return crypto.randomBytes(length).toString("hex");
300
+ }
301
+ function sortValue(value) {
302
+ if (Array.isArray(value)) {
303
+ return value.map((entry) => sortValue(entry));
304
+ }
305
+ if (value && typeof value === "object") {
306
+ const sortedEntries = Object.entries(value).sort(
307
+ ([firstKey], [secondKey]) => firstKey.localeCompare(secondKey)
308
+ );
309
+ return Object.fromEntries(
310
+ sortedEntries.map(([entryKey, entryValue]) => [
311
+ entryKey,
312
+ sortValue(entryValue)
313
+ ])
314
+ );
315
+ }
316
+ return value;
317
+ }
318
+ function createCanonicalPayload(payload) {
319
+ return JSON.stringify(sortValue(payload));
320
+ }
321
+ function createSignaturePayload(input) {
322
+ return `${input.fingerprint}.${input.nonce}.${input.timestamp}.${createCanonicalPayload(input.payload)}`;
323
+ }
324
+ function createSignature(input) {
325
+ const payload = createSignaturePayload(input);
326
+ return crypto.createHmac("sha256", input.secret).update(payload).digest("hex");
327
+ }
328
+ function createTimestamp() {
329
+ return Date.now().toString();
330
+ }
331
+ function verifyResponseSignature(data, signature, secret) {
332
+ const expectedSignature = crypto.createHmac("sha256", secret).update(createCanonicalPayload(data)).digest("hex");
333
+ const providedBuffer = Buffer.from(signature, "hex");
334
+ const expectedBuffer = Buffer.from(expectedSignature, "hex");
335
+ if (providedBuffer.length !== expectedBuffer.length) {
336
+ return false;
337
+ }
338
+ return crypto.timingSafeEqual(providedBuffer, expectedBuffer);
339
+ }
340
+
341
+ // src/transport.ts
342
+ var CACHED_FINGERPRINT = generateFingerprint();
343
+ function calculateBackoff(attempt) {
344
+ const base = 2 ** attempt * 1e3;
345
+ const jitter = Math.random() * 500;
346
+ return base + jitter;
347
+ }
348
+ function delay(ms) {
349
+ return new Promise((resolve) => setTimeout(resolve, ms));
350
+ }
351
+ function stripResponseSignature(payload) {
352
+ if (!payload || typeof payload !== "object" || !("_responseSignature" in payload)) {
353
+ return { data: payload, responseSignature: null };
354
+ }
355
+ const { _responseSignature, ...rest } = payload;
356
+ return {
357
+ data: rest,
358
+ responseSignature: typeof _responseSignature === "string" ? _responseSignature : null
359
+ };
360
+ }
361
+ function buildEnvelope({
362
+ action,
363
+ payload
364
+ }) {
365
+ return {
366
+ intent: "execute",
367
+ service: SDK_SERVICE,
368
+ action,
369
+ payload: {
370
+ ...payload,
371
+ _fingerprint: CACHED_FINGERPRINT
372
+ }
373
+ };
374
+ }
375
+ function buildAuthHeaders({
376
+ apiKey,
377
+ apiSecret,
378
+ payload
379
+ }) {
380
+ const nonce = generateNonce();
381
+ const timestamp = createTimestamp();
382
+ const signature = createSignature({
383
+ fingerprint: CACHED_FINGERPRINT,
384
+ nonce,
385
+ timestamp,
386
+ payload,
387
+ secret: apiSecret
388
+ });
389
+ return {
390
+ "content-type": "application/json",
391
+ "x-nylon-key": apiKey,
392
+ "x-nylon-nonce": nonce,
393
+ "x-nylon-signature": signature,
394
+ "x-nylon-timestamp": timestamp
395
+ };
396
+ }
397
+ function withTimeout(timeoutMs) {
398
+ const controller = new AbortController();
399
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
400
+ return { controller, cleanup: () => clearTimeout(timeoutId) };
401
+ }
402
+ function createTransport({
403
+ apiKey,
404
+ apiSecret,
405
+ baseUrl = DEFAULT_BASE_URL,
406
+ timeoutMs = DEFAULT_TIMEOUT_MS,
407
+ maxRetries = DEFAULT_MAX_RETRIES,
408
+ fetch: fetchImpl
409
+ }) {
410
+ async function send(request) {
411
+ const envelope = buildEnvelope(request);
412
+ const signedPayload = envelope.payload;
413
+ const headers = buildAuthHeaders({
414
+ apiKey,
415
+ apiSecret,
416
+ payload: signedPayload
417
+ });
418
+ const bodyString = JSON.stringify(envelope);
419
+ async function attempt(currentAttempt) {
420
+ const { controller, cleanup } = withTimeout(timeoutMs);
421
+ try {
422
+ const response = await fetchImpl(baseUrl, {
423
+ method: "POST",
424
+ headers,
425
+ body: bodyString,
426
+ signal: controller.signal
427
+ });
428
+ if (!response.ok) {
429
+ const statusCode = response.status;
430
+ const retryable = RETRYABLE_STATUS_CODES.has(statusCode);
431
+ let errorMessage = `HTTP ${statusCode}`;
432
+ try {
433
+ const errorBody = await response.json();
434
+ if (errorBody && typeof errorBody === "object" && "message" in errorBody) {
435
+ errorMessage = String(errorBody.message);
436
+ }
437
+ } catch {
438
+ errorMessage = response.statusText || errorMessage;
439
+ }
440
+ if (retryable && currentAttempt < maxRetries) {
441
+ cleanup();
442
+ await delay(calculateBackoff(currentAttempt));
443
+ return attempt(currentAttempt + 1);
444
+ }
445
+ const sdkError = {
446
+ code: `HTTP_${statusCode}`,
447
+ message: errorMessage,
448
+ statusCode,
449
+ retryable
450
+ };
451
+ cleanup();
452
+ return slangTs.Err(JSON.stringify(sdkError));
453
+ }
454
+ const responseBody = await response.json();
455
+ if (!responseBody || typeof responseBody !== "object" || !("status" in responseBody)) {
456
+ cleanup();
457
+ return slangTs.Err(
458
+ JSON.stringify({
459
+ code: "INVALID_RESPONSE",
460
+ message: "Response missing status field",
461
+ retryable: false
462
+ })
463
+ );
464
+ }
465
+ const { status, message, data } = responseBody;
466
+ if (status === true) {
467
+ const { data: strippedData, responseSignature } = stripResponseSignature(data);
468
+ if (responseSignature) {
469
+ const isValid = verifyResponseSignature(
470
+ strippedData,
471
+ responseSignature,
472
+ apiSecret
473
+ );
474
+ if (!isValid) {
475
+ cleanup();
476
+ return slangTs.Err(
477
+ JSON.stringify({
478
+ code: "RESPONSE_TAMPERED",
479
+ message: "Response signature verification failed",
480
+ retryable: false
481
+ })
482
+ );
483
+ }
484
+ }
485
+ cleanup();
486
+ return slangTs.Ok(strippedData);
487
+ }
488
+ const parsedError = parseError(message);
489
+ cleanup();
490
+ return slangTs.Err(JSON.stringify(parsedError));
491
+ } catch (error) {
492
+ cleanup();
493
+ const isAbort = error instanceof DOMException && error.name === "AbortError";
494
+ const sdkError = {
495
+ code: isAbort ? "TIMEOUT" : "NETWORK_ERROR",
496
+ message: isAbort ? `Request timed out after ${timeoutMs}ms` : String(error),
497
+ retryable: true
498
+ };
499
+ if (currentAttempt < maxRetries) {
500
+ await delay(calculateBackoff(currentAttempt));
501
+ return attempt(currentAttempt + 1);
502
+ }
503
+ return slangTs.Err(JSON.stringify(sdkError));
504
+ }
505
+ }
506
+ return attempt(0);
507
+ }
508
+ return { send, parseError };
509
+ }
510
+ function parseError(error) {
511
+ try {
512
+ const parsed = JSON.parse(error);
513
+ if (parsed && typeof parsed === "object" && "code" in parsed && "message" in parsed && typeof parsed.code === "string" && typeof parsed.message === "string") {
514
+ return parsed;
515
+ }
516
+ } catch {
517
+ }
518
+ return { code: "UNKNOWN", message: error };
519
+ }
520
+ function verifyWebhookSignature(input) {
521
+ const payloadBytes = typeof input.payload === "string" ? Buffer.from(input.payload, "utf8") : Buffer.from(input.payload);
522
+ const expectedSignature = crypto.createHmac("sha256", input.secret).update(payloadBytes).digest("hex");
523
+ const providedBuffer = Buffer.from(input.signature, "hex");
524
+ const expectedBuffer = Buffer.from(expectedSignature, "hex");
525
+ if (providedBuffer.length !== expectedBuffer.length) {
526
+ return false;
527
+ }
528
+ return crypto.timingSafeEqual(providedBuffer, expectedBuffer);
529
+ }
530
+
531
+ // src/sdk.ts
532
+ function generateReference() {
533
+ return crypto.randomBytes(16).toString("hex").slice(0, 15);
534
+ }
535
+ function validateAmount(amount) {
536
+ if (!Number.isInteger(amount) || amount <= 0) {
537
+ throw new Error("amount must be a positive integer");
538
+ }
539
+ }
540
+ function validateNonEmpty(value, fieldName) {
541
+ if (!value || value.trim() === "") {
542
+ throw new Error(`${fieldName} is required`);
543
+ }
544
+ }
545
+ function createSdkInstance(config) {
546
+ const transport = createTransport({
547
+ apiKey: config.apiKey,
548
+ apiSecret: config.apiSecret,
549
+ baseUrl: config.baseUrl,
550
+ timeoutMs: config.timeoutMs,
551
+ maxRetries: config.maxRetries,
552
+ fetch: config.fetch
553
+ });
554
+ const commonDeps = {
555
+ fetchStatus: (input) => transport.send({
556
+ action: SDK_ACTIONS.getStatus,
557
+ payload: input
558
+ }),
559
+ fetchTransaction: (input) => transport.send({
560
+ action: SDK_ACTIONS.getTransaction,
561
+ payload: input
562
+ }),
563
+ pollIntervalMs: config.maxPollIntervalMs,
564
+ maxPollDuration: config.maxPollDurationMs,
565
+ maxPollAttempts: config.maxPollAttempts
566
+ };
567
+ async function collectPayment(input) {
568
+ const reference = input.reference ?? generateReference();
569
+ validateAmount(input.amount);
570
+ validateNonEmpty(input.customer.name, "customer.name");
571
+ validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
572
+ validateNonEmpty(input.description, "description");
573
+ if (input.method === "bank" && !input.bank) {
574
+ throw new Error('bank details are required when method is "bank"');
575
+ }
576
+ const payload = { ...input, reference };
577
+ const result = await transport.send({
578
+ action: SDK_ACTIONS.collectPayment,
579
+ payload
580
+ });
581
+ if (result.isOk) {
582
+ return createPaymentInstance(result.value, commonDeps);
583
+ }
584
+ return createPaymentInstance(
585
+ { reference, status: "pending" },
586
+ {
587
+ fetchStatus: async () => slangTs.Err(result.error),
588
+ fetchTransaction: async () => slangTs.Err(result.error),
589
+ pollIntervalMs: 0,
590
+ maxPollAttempts: 1,
591
+ maxPollDuration: Number.MAX_SAFE_INTEGER
592
+ }
593
+ );
594
+ }
595
+ async function collectPaymentAndResolve(input) {
596
+ const reference = input.reference ?? generateReference();
597
+ validateAmount(input.amount);
598
+ validateNonEmpty(input.customer.name, "customer.name");
599
+ validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
600
+ validateNonEmpty(input.description, "description");
601
+ if (input.method === "bank" && !input.bank) {
602
+ throw new Error('bank details are required when method is "bank"');
603
+ }
604
+ const payload = { ...input, reference };
605
+ const result = await transport.send({
606
+ action: SDK_ACTIONS.collectPaymentAndResolve,
607
+ payload
608
+ });
609
+ if (result.isOk) {
610
+ return slangTs.Ok(result.value);
611
+ }
612
+ return slangTs.Err(result.error);
613
+ }
614
+ async function makePayout(input) {
615
+ const reference = input.reference ?? generateReference();
616
+ validateAmount(input.amount);
617
+ validateNonEmpty(input.customer.name, "customer.name");
618
+ validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
619
+ validateNonEmpty(input.description, "description");
620
+ validateNonEmpty(
621
+ input.destination.accountHolderName,
622
+ "destination.accountHolderName"
623
+ );
624
+ validateNonEmpty(
625
+ input.destination.accountNumber,
626
+ "destination.accountNumber"
627
+ );
628
+ const payload = { ...input, reference };
629
+ const result = await transport.send({
630
+ action: SDK_ACTIONS.makePayout,
631
+ payload
632
+ });
633
+ if (result.isOk) {
634
+ return createPaymentInstance(result.value, commonDeps);
635
+ }
636
+ return createPaymentInstance(
637
+ { reference, status: "pending" },
638
+ {
639
+ fetchStatus: async () => slangTs.Err(result.error),
640
+ fetchTransaction: async () => slangTs.Err(result.error),
641
+ pollIntervalMs: 0,
642
+ maxPollAttempts: 1,
643
+ maxPollDuration: Number.MAX_SAFE_INTEGER
644
+ }
645
+ );
646
+ }
647
+ async function makePayoutAndResolve(input) {
648
+ const reference = input.reference ?? generateReference();
649
+ validateAmount(input.amount);
650
+ validateNonEmpty(input.customer.name, "customer.name");
651
+ validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
652
+ validateNonEmpty(input.description, "description");
653
+ validateNonEmpty(
654
+ input.destination.accountHolderName,
655
+ "destination.accountHolderName"
656
+ );
657
+ validateNonEmpty(
658
+ input.destination.accountNumber,
659
+ "destination.accountNumber"
660
+ );
661
+ const payload = { ...input, reference };
662
+ const result = await transport.send({
663
+ action: SDK_ACTIONS.makePayoutAndResolve,
664
+ payload
665
+ });
666
+ if (result.isOk) {
667
+ return slangTs.Ok(result.value);
668
+ }
669
+ return slangTs.Err(result.error);
670
+ }
671
+ async function getStatus(input) {
672
+ validateNonEmpty(input.reference, "reference");
673
+ const result = await transport.send({
674
+ action: SDK_ACTIONS.getStatus,
675
+ payload: input
676
+ });
677
+ if (result.isOk) {
678
+ return slangTs.Ok(result.value);
679
+ }
680
+ return slangTs.Err(result.error);
681
+ }
682
+ async function getTransaction(input) {
683
+ if (!input.id && !input.reference) {
684
+ throw new Error("id or reference is required");
685
+ }
686
+ const result = await transport.send({
687
+ action: SDK_ACTIONS.getTransaction,
688
+ payload: input
689
+ });
690
+ if (result.isOk) {
691
+ return slangTs.Ok(result.value);
692
+ }
693
+ return slangTs.Err(result.error);
694
+ }
695
+ async function verifyPhone(input) {
696
+ validateNonEmpty(input.phoneNumber, "phoneNumber");
697
+ const result = await transport.send({
698
+ action: SDK_ACTIONS.verifyPhone,
699
+ payload: input
700
+ });
701
+ if (result.isOk) {
702
+ return slangTs.Ok(result.value);
703
+ }
704
+ return slangTs.Err(result.error);
705
+ }
706
+ async function createInvoice(input) {
707
+ const reference = input.reference ?? generateReference();
708
+ validateAmount(input.amount);
709
+ validateNonEmpty(input.description, "description");
710
+ if (input.items) {
711
+ if (input.items.length > 50) {
712
+ throw new Error("items must not exceed 50");
713
+ }
714
+ for (const item of input.items) {
715
+ if (!Number.isInteger(item.quantity) || item.quantity <= 0) {
716
+ throw new Error("item quantity must be a positive integer");
717
+ }
718
+ if (!Number.isInteger(item.unitPrice) || item.unitPrice <= 0) {
719
+ throw new Error("item unitPrice must be a positive integer");
720
+ }
721
+ }
722
+ }
723
+ const payload = { ...input, reference };
724
+ const result = await transport.send({
725
+ action: SDK_ACTIONS.createInvoice,
726
+ payload
727
+ });
728
+ if (result.isOk) {
729
+ return slangTs.Ok(result.value);
730
+ }
731
+ return slangTs.Err(result.error);
732
+ }
733
+ function verifyWebhook(input) {
734
+ return verifyWebhookSignature(input);
735
+ }
736
+ return {
737
+ collectPayment,
738
+ collectPaymentAndResolve,
739
+ makePayout,
740
+ makePayoutAndResolve,
741
+ getStatus,
742
+ getTransaction,
743
+ verifyPhone,
744
+ createInvoice,
745
+ verifyWebhookSignature: verifyWebhook
746
+ };
747
+ }
748
+
749
+ // src/create-nylon-pay.ts
750
+ function createNylonPay(config) {
751
+ if (!config.apiKey) {
752
+ throw new Error("apiKey is required");
753
+ }
754
+ if (!config.apiKey.startsWith("npk_")) {
755
+ throw new Error('apiKey must start with "npk_"');
756
+ }
757
+ if (!config.apiSecret) {
758
+ throw new Error("apiSecret is required");
759
+ }
760
+ if (!config.apiSecret.startsWith("nps_")) {
761
+ throw new Error('apiSecret must start with "nps_"');
762
+ }
763
+ const resolvedConfig = {
764
+ apiKey: config.apiKey,
765
+ apiSecret: config.apiSecret,
766
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
767
+ timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
768
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
769
+ maxPollIntervalMs: config.maxPollIntervalMs ?? DEFAULT_MAX_POLL_INTERVAL_MS,
770
+ maxPollDurationMs: config.maxPollDurationMs ?? DEFAULT_MAX_POLL_DURATION_MS,
771
+ maxPollAttempts: config.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS,
772
+ fetch: config.fetch ?? globalThis.fetch.bind(globalThis)
773
+ };
774
+ return createSdkInstance(resolvedConfig);
775
+ }
776
+
777
+ exports.createNylonPay = createNylonPay;
778
+ exports.parseError = parseError;
779
+ exports.verifyWebhookSignature = verifyWebhookSignature;
780
+ //# sourceMappingURL=index.cjs.map
781
+ //# sourceMappingURL=index.cjs.map