@nile-squad/nylonpay-ts 1.0.6 → 1.0.8
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 +145 -262
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -23
- package/dist/index.d.ts +54 -23
- package/dist/index.js +145 -262
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ var crypto = require('crypto');
|
|
|
4
4
|
var slangTs = require('slang-ts');
|
|
5
5
|
var os = require('os');
|
|
6
6
|
|
|
7
|
-
// src/
|
|
7
|
+
// src/create-nylon-pay.ts
|
|
8
8
|
|
|
9
9
|
// src/pubsub.ts
|
|
10
10
|
function createEmitter() {
|
|
@@ -63,9 +63,7 @@ var DEFAULT_MAX_RETRIES = 3;
|
|
|
63
63
|
var DEFAULT_MAX_POLL_INTERVAL_MS = 2e3;
|
|
64
64
|
var DEFAULT_MAX_POLL_DURATION_MS = 3e5;
|
|
65
65
|
var DEFAULT_MAX_POLL_ATTEMPTS = 150;
|
|
66
|
-
var
|
|
67
|
-
var STREAM_PATH = "/sse/transaction";
|
|
68
|
-
var MAX_STREAM_RECONNECTS = 2;
|
|
66
|
+
var POLL_JITTER_MS = 250;
|
|
69
67
|
var SDK_SERVICE = "sdk";
|
|
70
68
|
var SDK_ACTIONS = {
|
|
71
69
|
collectPayment: "sdk-collect-payment",
|
|
@@ -93,13 +91,22 @@ function generateFingerprint() {
|
|
|
93
91
|
function generateNonce(length = 16) {
|
|
94
92
|
return crypto.randomBytes(length).toString("hex");
|
|
95
93
|
}
|
|
94
|
+
function compareByCodePoint(first, second) {
|
|
95
|
+
if (first < second) {
|
|
96
|
+
return -1;
|
|
97
|
+
}
|
|
98
|
+
if (first > second) {
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
96
103
|
function sortValue(value) {
|
|
97
104
|
if (Array.isArray(value)) {
|
|
98
105
|
return value.map((entry) => sortValue(entry));
|
|
99
106
|
}
|
|
100
107
|
if (value && typeof value === "object") {
|
|
101
108
|
const sortedEntries = Object.entries(value).sort(
|
|
102
|
-
([firstKey], [secondKey]) => firstKey
|
|
109
|
+
([firstKey], [secondKey]) => compareByCodePoint(firstKey, secondKey)
|
|
103
110
|
);
|
|
104
111
|
return Object.fromEntries(
|
|
105
112
|
sortedEntries.map(([entryKey, entryValue]) => [
|
|
@@ -123,42 +130,6 @@ function createSignature(input) {
|
|
|
123
130
|
function createTimestamp() {
|
|
124
131
|
return Date.now().toString();
|
|
125
132
|
}
|
|
126
|
-
|
|
127
|
-
// src/sse-parse.ts
|
|
128
|
-
function parseBlock(block) {
|
|
129
|
-
let event = "message";
|
|
130
|
-
const dataLines = [];
|
|
131
|
-
for (const rawLine of block.split("\n")) {
|
|
132
|
-
const line = rawLine.replace(/\r$/, "");
|
|
133
|
-
if (line.startsWith(":")) {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
if (line.startsWith("event:")) {
|
|
137
|
-
event = line.slice("event:".length).trim();
|
|
138
|
-
} else if (line.startsWith("data:")) {
|
|
139
|
-
dataLines.push(line.slice("data:".length).trim());
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (dataLines.length === 0) {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
return { event, data: dataLines.join("\n") };
|
|
146
|
-
}
|
|
147
|
-
function parseSseBuffer(buffer) {
|
|
148
|
-
const messages = [];
|
|
149
|
-
let rest = buffer;
|
|
150
|
-
let separator = rest.indexOf("\n\n");
|
|
151
|
-
while (separator !== -1) {
|
|
152
|
-
const block = rest.slice(0, separator);
|
|
153
|
-
rest = rest.slice(separator + 2);
|
|
154
|
-
const message = parseBlock(block);
|
|
155
|
-
if (message) {
|
|
156
|
-
messages.push(message);
|
|
157
|
-
}
|
|
158
|
-
separator = rest.indexOf("\n\n");
|
|
159
|
-
}
|
|
160
|
-
return { messages, rest };
|
|
161
|
-
}
|
|
162
133
|
function verifyResponseSignature(data, signature, secret) {
|
|
163
134
|
const expectedSignature = crypto.createHmac("sha256", secret).update(createCanonicalPayload(data)).digest("hex");
|
|
164
135
|
const providedBuffer = Buffer.from(signature, "hex");
|
|
@@ -171,13 +142,6 @@ function verifyResponseSignature(data, signature, secret) {
|
|
|
171
142
|
|
|
172
143
|
// src/transport.ts
|
|
173
144
|
var CACHED_FINGERPRINT = generateFingerprint();
|
|
174
|
-
function streamUrl(baseUrl) {
|
|
175
|
-
try {
|
|
176
|
-
return new URL(baseUrl).origin + STREAM_PATH;
|
|
177
|
-
} catch {
|
|
178
|
-
return baseUrl.replace(/\/api\/services\/?$/u, "") + STREAM_PATH;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
145
|
var KNOWN_CATEGORIES = /* @__PURE__ */ new Set([
|
|
182
146
|
"auth",
|
|
183
147
|
"validation",
|
|
@@ -343,22 +307,30 @@ function createTransport({
|
|
|
343
307
|
const { status, message, data } = responseBody;
|
|
344
308
|
if (status === true) {
|
|
345
309
|
const { data: strippedData, responseSignature } = stripResponseSignature(data);
|
|
346
|
-
if (responseSignature) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
310
|
+
if (!responseSignature) {
|
|
311
|
+
cleanup();
|
|
312
|
+
return slangTs.Err(
|
|
313
|
+
JSON.stringify({
|
|
314
|
+
category: "internal",
|
|
315
|
+
message: "Response signature missing",
|
|
316
|
+
retryable: false
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const isValid = verifyResponseSignature(
|
|
321
|
+
strippedData,
|
|
322
|
+
responseSignature,
|
|
323
|
+
apiSecret
|
|
324
|
+
);
|
|
325
|
+
if (!isValid) {
|
|
326
|
+
cleanup();
|
|
327
|
+
return slangTs.Err(
|
|
328
|
+
JSON.stringify({
|
|
329
|
+
category: "internal",
|
|
330
|
+
message: "Response signature verification failed",
|
|
331
|
+
retryable: false
|
|
332
|
+
})
|
|
351
333
|
);
|
|
352
|
-
if (!isValid) {
|
|
353
|
-
cleanup();
|
|
354
|
-
return slangTs.Err(
|
|
355
|
-
JSON.stringify({
|
|
356
|
-
category: "internal",
|
|
357
|
-
message: "Response signature verification failed",
|
|
358
|
-
retryable: false
|
|
359
|
-
})
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
334
|
}
|
|
363
335
|
cleanup();
|
|
364
336
|
return slangTs.Ok(strippedData);
|
|
@@ -383,91 +355,7 @@ function createTransport({
|
|
|
383
355
|
}
|
|
384
356
|
return attempt(0);
|
|
385
357
|
}
|
|
386
|
-
|
|
387
|
-
const controller = new AbortController();
|
|
388
|
-
let closed = false;
|
|
389
|
-
const close = () => {
|
|
390
|
-
if (closed) {
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
closed = true;
|
|
394
|
-
controller.abort();
|
|
395
|
-
};
|
|
396
|
-
const payload = {
|
|
397
|
-
reference: input.reference,
|
|
398
|
-
_fingerprint: CACHED_FINGERPRINT
|
|
399
|
-
};
|
|
400
|
-
const headers = buildAuthHeaders({ apiKey, apiSecret, payload });
|
|
401
|
-
const url = streamUrl(baseUrl);
|
|
402
|
-
const run = async () => {
|
|
403
|
-
const fetchResult = await slangTs.safeTry(
|
|
404
|
-
() => fetchImpl(url, {
|
|
405
|
-
method: "POST",
|
|
406
|
-
headers,
|
|
407
|
-
body: JSON.stringify(payload),
|
|
408
|
-
signal: controller.signal
|
|
409
|
-
})
|
|
410
|
-
);
|
|
411
|
-
if (fetchResult.isErr) {
|
|
412
|
-
if (!closed) {
|
|
413
|
-
input.onError(fetchResult.error);
|
|
414
|
-
}
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
const response = fetchResult.value;
|
|
418
|
-
if (!response.ok || !response.body) {
|
|
419
|
-
const textResult = await slangTs.safeTry(() => response.text());
|
|
420
|
-
const message = textResult.isOk ? textResult.value : `HTTP ${response.status}`;
|
|
421
|
-
if (!closed) {
|
|
422
|
-
input.onError(message || `HTTP ${response.status}`);
|
|
423
|
-
}
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
const reader = response.body.getReader();
|
|
427
|
-
const decoder = new TextDecoder();
|
|
428
|
-
let buffer = "";
|
|
429
|
-
while (!closed) {
|
|
430
|
-
const chunkResult = await slangTs.safeTry(() => reader.read());
|
|
431
|
-
if (chunkResult.isErr) {
|
|
432
|
-
if (!closed) {
|
|
433
|
-
input.onError(chunkResult.error);
|
|
434
|
-
}
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
const chunk = chunkResult.value;
|
|
438
|
-
if (chunk.done) {
|
|
439
|
-
break;
|
|
440
|
-
}
|
|
441
|
-
buffer += decoder.decode(chunk.value, { stream: true });
|
|
442
|
-
const { messages, rest } = parseSseBuffer(buffer);
|
|
443
|
-
buffer = rest;
|
|
444
|
-
for (const message of messages) {
|
|
445
|
-
if (message.event === "status") {
|
|
446
|
-
const statusResult = await slangTs.safeTry(
|
|
447
|
-
() => JSON.parse(message.data)
|
|
448
|
-
);
|
|
449
|
-
if (statusResult.isOk) {
|
|
450
|
-
input.onStatus(statusResult.value);
|
|
451
|
-
}
|
|
452
|
-
} else if (message.event === "error") {
|
|
453
|
-
const errDataResult = await slangTs.safeTry(
|
|
454
|
-
() => JSON.parse(message.data)
|
|
455
|
-
);
|
|
456
|
-
const text = errDataResult.isOk ? errDataResult.value.message ?? message.data : message.data;
|
|
457
|
-
close();
|
|
458
|
-
input.onError(text);
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
if (!closed) {
|
|
464
|
-
input.onClose();
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
void run();
|
|
468
|
-
return { close };
|
|
469
|
-
}
|
|
470
|
-
return { send, openStream, parseError };
|
|
358
|
+
return { send, parseError };
|
|
471
359
|
}
|
|
472
360
|
function parseError(error) {
|
|
473
361
|
try {
|
|
@@ -517,22 +405,20 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
517
405
|
fetchTransaction: deps.fetchTransaction,
|
|
518
406
|
pollIntervalMs: deps.pollIntervalMs ?? 2e3,
|
|
519
407
|
maxPollDuration: deps.maxPollDuration ?? 3e5,
|
|
520
|
-
maxPollAttempts: deps.maxPollAttempts ?? 150
|
|
521
|
-
streaming: deps.streaming ?? false,
|
|
522
|
-
openStream: deps.openStream,
|
|
523
|
-
streamHandle: null,
|
|
524
|
-
streamReconnects: 0
|
|
408
|
+
maxPollAttempts: deps.maxPollAttempts ?? 150
|
|
525
409
|
};
|
|
526
410
|
function resolveWithError(error) {
|
|
527
411
|
state.resolved = true;
|
|
528
412
|
stopUpdates();
|
|
529
413
|
emitEvent("error", parseError(error).message);
|
|
530
414
|
}
|
|
531
|
-
function emitEvent(event, error) {
|
|
415
|
+
function emitEvent(event, error, category, retryable) {
|
|
532
416
|
const data = {
|
|
533
417
|
event,
|
|
534
418
|
transaction: state.transaction ?? void 0,
|
|
535
419
|
error,
|
|
420
|
+
category,
|
|
421
|
+
retryable,
|
|
536
422
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
537
423
|
};
|
|
538
424
|
state.emitter.emit(event, data);
|
|
@@ -555,6 +441,9 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
555
441
|
stopUpdates();
|
|
556
442
|
}
|
|
557
443
|
async function handleStatusUpdate(response) {
|
|
444
|
+
if (state.resolved) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
558
447
|
if (response.reference !== state.reference) {
|
|
559
448
|
resolveWithError(
|
|
560
449
|
`Reference mismatch: expected ${state.reference} but got ${response.reference}`
|
|
@@ -588,10 +477,11 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
588
477
|
if (state.resolved || state.pollingTimer) {
|
|
589
478
|
return;
|
|
590
479
|
}
|
|
480
|
+
const delay2 = state.pollIntervalMs + Math.random() * POLL_JITTER_MS;
|
|
591
481
|
state.pollingTimer = setTimeout(() => {
|
|
592
482
|
state.pollingTimer = null;
|
|
593
483
|
void pollStatus();
|
|
594
|
-
},
|
|
484
|
+
}, delay2);
|
|
595
485
|
}
|
|
596
486
|
async function pollStatus() {
|
|
597
487
|
if (state.resolved) {
|
|
@@ -619,42 +509,6 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
619
509
|
}
|
|
620
510
|
scheduleNextPoll();
|
|
621
511
|
}
|
|
622
|
-
function closeStream() {
|
|
623
|
-
if (state.streamHandle) {
|
|
624
|
-
state.streamHandle.close();
|
|
625
|
-
state.streamHandle = null;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
function startStream() {
|
|
629
|
-
if (state.resolved || !state.openStream) {
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
state.streamHandle = state.openStream({
|
|
633
|
-
reference: state.reference,
|
|
634
|
-
onStatus: (status) => {
|
|
635
|
-
void handleStatusUpdate(status);
|
|
636
|
-
},
|
|
637
|
-
onError: () => handleStreamFailure(),
|
|
638
|
-
onClose: () => handleStreamFailure()
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
function handleStreamFailure() {
|
|
642
|
-
closeStream();
|
|
643
|
-
if (state.resolved) {
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
if (state.streamReconnects < MAX_STREAM_RECONNECTS) {
|
|
647
|
-
state.streamReconnects += 1;
|
|
648
|
-
const backoff = 500 * 2 ** (state.streamReconnects - 1) + Math.random() * 250;
|
|
649
|
-
setTimeout(() => {
|
|
650
|
-
if (!state.resolved) {
|
|
651
|
-
startStream();
|
|
652
|
-
}
|
|
653
|
-
}, backoff);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
scheduleNextPoll();
|
|
657
|
-
}
|
|
658
512
|
function startUpdates() {
|
|
659
513
|
if (TERMINAL_STATES.has(state.status)) {
|
|
660
514
|
setTimeout(() => {
|
|
@@ -662,10 +516,6 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
662
516
|
}, 0);
|
|
663
517
|
return;
|
|
664
518
|
}
|
|
665
|
-
if (state.streaming && state.openStream) {
|
|
666
|
-
startStream();
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
519
|
scheduleNextPoll();
|
|
670
520
|
}
|
|
671
521
|
function stopUpdates() {
|
|
@@ -673,7 +523,6 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
673
523
|
clearTimeout(state.pollingTimer);
|
|
674
524
|
state.pollingTimer = null;
|
|
675
525
|
}
|
|
676
|
-
closeStream();
|
|
677
526
|
}
|
|
678
527
|
function on(event, handler) {
|
|
679
528
|
state.emitter.on(event, handler);
|
|
@@ -735,24 +584,76 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
735
584
|
off,
|
|
736
585
|
wait
|
|
737
586
|
};
|
|
738
|
-
|
|
587
|
+
if (deps.initialError) {
|
|
588
|
+
state.resolved = true;
|
|
589
|
+
const err = deps.initialError;
|
|
590
|
+
setTimeout(() => {
|
|
591
|
+
emitEvent("error", err.message, err.category, err.retryable);
|
|
592
|
+
}, 0);
|
|
593
|
+
} else {
|
|
594
|
+
startUpdates();
|
|
595
|
+
}
|
|
739
596
|
return paymentInstance;
|
|
740
597
|
}
|
|
598
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
599
|
+
function decodePayload(payload) {
|
|
600
|
+
return typeof payload === "string" ? payload : Buffer.from(payload).toString("utf8");
|
|
601
|
+
}
|
|
602
|
+
function extractSignedTimestampMs(payloadString) {
|
|
603
|
+
let parsed;
|
|
604
|
+
try {
|
|
605
|
+
parsed = JSON.parse(payloadString);
|
|
606
|
+
} catch {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
if (!parsed || typeof parsed !== "object") {
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
const raw = parsed.timestamp;
|
|
613
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
614
|
+
return raw < 1e12 ? raw * 1e3 : raw;
|
|
615
|
+
}
|
|
616
|
+
if (typeof raw === "string") {
|
|
617
|
+
const ms = Date.parse(raw);
|
|
618
|
+
return Number.isNaN(ms) ? null : ms;
|
|
619
|
+
}
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
741
622
|
function verifyWebhookSignature(input) {
|
|
742
|
-
const
|
|
623
|
+
const payloadString = decodePayload(input.payload);
|
|
624
|
+
const payloadBytes = Buffer.from(payloadString, "utf8");
|
|
743
625
|
const expectedSignature = crypto.createHmac("sha256", input.secret).update(payloadBytes).digest("hex");
|
|
744
626
|
const providedBuffer = Buffer.from(input.signature, "hex");
|
|
745
627
|
const expectedBuffer = Buffer.from(expectedSignature, "hex");
|
|
746
628
|
if (providedBuffer.length !== expectedBuffer.length) {
|
|
747
629
|
return false;
|
|
748
630
|
}
|
|
749
|
-
|
|
631
|
+
if (!crypto.timingSafeEqual(providedBuffer, expectedBuffer)) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
const toleranceSeconds = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
635
|
+
if (toleranceSeconds <= 0) {
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
const timestampMs = extractSignedTimestampMs(payloadString);
|
|
639
|
+
if (timestampMs === null) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
const ageMs = Math.abs(Date.now() - timestampMs);
|
|
643
|
+
return ageMs <= toleranceSeconds * 1e3;
|
|
750
644
|
}
|
|
751
645
|
|
|
752
646
|
// src/sdk.ts
|
|
753
647
|
function generateReference() {
|
|
754
648
|
return crypto.randomBytes(16).toString("hex").slice(0, 15);
|
|
755
649
|
}
|
|
650
|
+
async function runHook(hook, ...args) {
|
|
651
|
+
if (!hook || hook.enabled === false) return void 0;
|
|
652
|
+
const result = await slangTs.safeTry(async () => hook.fn(...args));
|
|
653
|
+
if (result.isOk) return result.value;
|
|
654
|
+
await slangTs.safeTry(async () => hook.onError(result.error));
|
|
655
|
+
return void 0;
|
|
656
|
+
}
|
|
756
657
|
function throwValidation(message) {
|
|
757
658
|
throw createSdkError({ category: "validation", message });
|
|
758
659
|
}
|
|
@@ -786,9 +687,7 @@ function createSdkInstance(config) {
|
|
|
786
687
|
}),
|
|
787
688
|
pollIntervalMs: config.maxPollIntervalMs,
|
|
788
689
|
maxPollDuration: config.maxPollDurationMs,
|
|
789
|
-
maxPollAttempts: config.maxPollAttempts
|
|
790
|
-
streaming: config.streaming,
|
|
791
|
-
openStream: config.streaming ? transport.openStream : void 0
|
|
690
|
+
maxPollAttempts: config.maxPollAttempts
|
|
792
691
|
};
|
|
793
692
|
async function collectPayment(input) {
|
|
794
693
|
const reference = input.reference ?? generateReference();
|
|
@@ -800,26 +699,24 @@ function createSdkInstance(config) {
|
|
|
800
699
|
throwValidation('bank details are required when method is "bank"');
|
|
801
700
|
}
|
|
802
701
|
let payload = { ...input, reference };
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
807
|
-
}
|
|
702
|
+
const mutated = await runHook(config.hooks?.beforeCollect, payload);
|
|
703
|
+
if (mutated != null)
|
|
704
|
+
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
808
705
|
const result = await transport.send({
|
|
809
706
|
action: SDK_ACTIONS.collectPayment,
|
|
810
707
|
payload
|
|
811
708
|
});
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
}) : slangTs.Err(result.error),
|
|
818
|
-
payload
|
|
819
|
-
);
|
|
820
|
-
}
|
|
709
|
+
await runHook(
|
|
710
|
+
config.hooks?.afterCollect,
|
|
711
|
+
result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
|
|
712
|
+
payload
|
|
713
|
+
);
|
|
821
714
|
if (result.isErr) {
|
|
822
|
-
|
|
715
|
+
const sdkErr = parseError(result.error);
|
|
716
|
+
return createPaymentInstance(
|
|
717
|
+
{ reference, status: "pending" },
|
|
718
|
+
{ ...commonDeps, initialError: sdkErr }
|
|
719
|
+
);
|
|
823
720
|
}
|
|
824
721
|
return createPaymentInstance(result.value, commonDeps);
|
|
825
722
|
}
|
|
@@ -833,24 +730,18 @@ function createSdkInstance(config) {
|
|
|
833
730
|
throwValidation('bank details are required when method is "bank"');
|
|
834
731
|
}
|
|
835
732
|
let payload = { ...input, reference };
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
840
|
-
}
|
|
733
|
+
const mutated = await runHook(config.hooks?.beforeCollect, payload);
|
|
734
|
+
if (mutated != null)
|
|
735
|
+
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
841
736
|
const result = await transport.send({
|
|
842
737
|
action: SDK_ACTIONS.collectPaymentAndResolve,
|
|
843
738
|
payload
|
|
844
739
|
});
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
}) : slangTs.Err(result.error),
|
|
851
|
-
payload
|
|
852
|
-
);
|
|
853
|
-
}
|
|
740
|
+
await runHook(
|
|
741
|
+
config.hooks?.afterCollect,
|
|
742
|
+
result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
|
|
743
|
+
payload
|
|
744
|
+
);
|
|
854
745
|
if (result.isOk) {
|
|
855
746
|
return slangTs.Ok(result.value);
|
|
856
747
|
}
|
|
@@ -871,26 +762,24 @@ function createSdkInstance(config) {
|
|
|
871
762
|
"destination.accountNumber"
|
|
872
763
|
);
|
|
873
764
|
let payload = { ...input, reference };
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
878
|
-
}
|
|
765
|
+
const mutated = await runHook(config.hooks?.beforePayout, payload);
|
|
766
|
+
if (mutated != null)
|
|
767
|
+
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
879
768
|
const result = await transport.send({
|
|
880
769
|
action: SDK_ACTIONS.makePayout,
|
|
881
770
|
payload
|
|
882
771
|
});
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
}) : slangTs.Err(result.error),
|
|
889
|
-
payload
|
|
890
|
-
);
|
|
891
|
-
}
|
|
772
|
+
await runHook(
|
|
773
|
+
config.hooks?.afterPayout,
|
|
774
|
+
result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
|
|
775
|
+
payload
|
|
776
|
+
);
|
|
892
777
|
if (result.isErr) {
|
|
893
|
-
|
|
778
|
+
const sdkErr = parseError(result.error);
|
|
779
|
+
return createPaymentInstance(
|
|
780
|
+
{ reference, status: "pending" },
|
|
781
|
+
{ ...commonDeps, initialError: sdkErr }
|
|
782
|
+
);
|
|
894
783
|
}
|
|
895
784
|
return createPaymentInstance(result.value, commonDeps);
|
|
896
785
|
}
|
|
@@ -909,24 +798,18 @@ function createSdkInstance(config) {
|
|
|
909
798
|
"destination.accountNumber"
|
|
910
799
|
);
|
|
911
800
|
let payload = { ...input, reference };
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
916
|
-
}
|
|
801
|
+
const mutated = await runHook(config.hooks?.beforePayout, payload);
|
|
802
|
+
if (mutated != null)
|
|
803
|
+
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
917
804
|
const result = await transport.send({
|
|
918
805
|
action: SDK_ACTIONS.makePayoutAndResolve,
|
|
919
806
|
payload
|
|
920
807
|
});
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
}) : slangTs.Err(result.error),
|
|
927
|
-
payload
|
|
928
|
-
);
|
|
929
|
-
}
|
|
808
|
+
await runHook(
|
|
809
|
+
config.hooks?.afterPayout,
|
|
810
|
+
result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
|
|
811
|
+
payload
|
|
812
|
+
);
|
|
930
813
|
if (result.isOk) {
|
|
931
814
|
return slangTs.Ok(result.value);
|
|
932
815
|
}
|
|
@@ -1026,7 +909,8 @@ function createNylonPay(config) {
|
|
|
1026
909
|
throw new Error('apiSecret must start with "nps_"');
|
|
1027
910
|
}
|
|
1028
911
|
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
1029
|
-
const
|
|
912
|
+
const secretHash = crypto.createHash("sha256").update(config.apiSecret).digest("hex").slice(0, 16);
|
|
913
|
+
const instanceKey = `${config.apiKey}:${baseUrl}:${secretHash}`;
|
|
1030
914
|
if (!config.force) {
|
|
1031
915
|
const existing = instances.get(instanceKey);
|
|
1032
916
|
if (existing) return existing;
|
|
@@ -1040,7 +924,6 @@ function createNylonPay(config) {
|
|
|
1040
924
|
maxPollIntervalMs: config.maxPollIntervalMs ?? DEFAULT_MAX_POLL_INTERVAL_MS,
|
|
1041
925
|
maxPollDurationMs: config.maxPollDurationMs ?? DEFAULT_MAX_POLL_DURATION_MS,
|
|
1042
926
|
maxPollAttempts: config.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS,
|
|
1043
|
-
streaming: config.streaming ?? DEFAULT_STREAMING,
|
|
1044
927
|
fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
|
|
1045
928
|
hooks: config.hooks
|
|
1046
929
|
};
|