@nile-squad/nylonpay-ts 1.0.3 → 1.0.5
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 +306 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -17
- package/dist/index.d.ts +47 -17
- package/dist/index.js +307 -98
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, createHmac, timingSafeEqual, randomBytes } from 'crypto';
|
|
2
|
-
import { Ok, Err } from 'slang-ts';
|
|
2
|
+
import { Ok, Err, safeTry } from 'slang-ts';
|
|
3
3
|
import { type, platform, arch, release, hostname } from 'os';
|
|
4
4
|
|
|
5
5
|
// src/sdk.ts
|
|
@@ -53,21 +53,6 @@ function createEmitter() {
|
|
|
53
53
|
const emitter = { on, once, off, emit, clear, listenerCount };
|
|
54
54
|
return emitter;
|
|
55
55
|
}
|
|
56
|
-
function generateFingerprint() {
|
|
57
|
-
const components = [
|
|
58
|
-
`type:${type()}`,
|
|
59
|
-
`platform:${platform()}`,
|
|
60
|
-
`arch:${arch()}`,
|
|
61
|
-
`release:${release()}`,
|
|
62
|
-
`hostname:${hostname()}`,
|
|
63
|
-
`node:${process.versions.node}`,
|
|
64
|
-
`v8:${process.versions.v8}`
|
|
65
|
-
].join("|");
|
|
66
|
-
return createHash("sha256").update(components).digest("hex");
|
|
67
|
-
}
|
|
68
|
-
function generateNonce(length = 16) {
|
|
69
|
-
return randomBytes(length).toString("hex");
|
|
70
|
-
}
|
|
71
56
|
|
|
72
57
|
// src/sdk.config.ts
|
|
73
58
|
var DEFAULT_BASE_URL = "https://api.nylonpay.nilesquad.com/api/services";
|
|
@@ -76,6 +61,9 @@ var DEFAULT_MAX_RETRIES = 3;
|
|
|
76
61
|
var DEFAULT_MAX_POLL_INTERVAL_MS = 2e3;
|
|
77
62
|
var DEFAULT_MAX_POLL_DURATION_MS = 3e5;
|
|
78
63
|
var DEFAULT_MAX_POLL_ATTEMPTS = 150;
|
|
64
|
+
var DEFAULT_STREAMING = true;
|
|
65
|
+
var STREAM_PATH = "/sse/transaction";
|
|
66
|
+
var MAX_STREAM_RECONNECTS = 2;
|
|
79
67
|
var SDK_SERVICE = "sdk";
|
|
80
68
|
var SDK_ACTIONS = {
|
|
81
69
|
collectPayment: "sdk-collect-payment",
|
|
@@ -88,6 +76,21 @@ var SDK_ACTIONS = {
|
|
|
88
76
|
createInvoice: "sdk-create-invoice"
|
|
89
77
|
};
|
|
90
78
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
79
|
+
function generateFingerprint() {
|
|
80
|
+
const components = [
|
|
81
|
+
`type:${type()}`,
|
|
82
|
+
`platform:${platform()}`,
|
|
83
|
+
`arch:${arch()}`,
|
|
84
|
+
`release:${release()}`,
|
|
85
|
+
`hostname:${hostname()}`,
|
|
86
|
+
`node:${process.versions.node}`,
|
|
87
|
+
`v8:${process.versions.v8}`
|
|
88
|
+
].join("|");
|
|
89
|
+
return createHash("sha256").update(components).digest("hex");
|
|
90
|
+
}
|
|
91
|
+
function generateNonce(length = 16) {
|
|
92
|
+
return randomBytes(length).toString("hex");
|
|
93
|
+
}
|
|
91
94
|
function sortValue(value) {
|
|
92
95
|
if (Array.isArray(value)) {
|
|
93
96
|
return value.map((entry) => sortValue(entry));
|
|
@@ -118,6 +121,42 @@ function createSignature(input) {
|
|
|
118
121
|
function createTimestamp() {
|
|
119
122
|
return Date.now().toString();
|
|
120
123
|
}
|
|
124
|
+
|
|
125
|
+
// src/sse-parse.ts
|
|
126
|
+
function parseBlock(block) {
|
|
127
|
+
let event = "message";
|
|
128
|
+
const dataLines = [];
|
|
129
|
+
for (const rawLine of block.split("\n")) {
|
|
130
|
+
const line = rawLine.replace(/\r$/, "");
|
|
131
|
+
if (line.startsWith(":")) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (line.startsWith("event:")) {
|
|
135
|
+
event = line.slice("event:".length).trim();
|
|
136
|
+
} else if (line.startsWith("data:")) {
|
|
137
|
+
dataLines.push(line.slice("data:".length).trim());
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (dataLines.length === 0) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return { event, data: dataLines.join("\n") };
|
|
144
|
+
}
|
|
145
|
+
function parseSseBuffer(buffer) {
|
|
146
|
+
const messages = [];
|
|
147
|
+
let rest = buffer;
|
|
148
|
+
let separator = rest.indexOf("\n\n");
|
|
149
|
+
while (separator !== -1) {
|
|
150
|
+
const block = rest.slice(0, separator);
|
|
151
|
+
rest = rest.slice(separator + 2);
|
|
152
|
+
const message = parseBlock(block);
|
|
153
|
+
if (message) {
|
|
154
|
+
messages.push(message);
|
|
155
|
+
}
|
|
156
|
+
separator = rest.indexOf("\n\n");
|
|
157
|
+
}
|
|
158
|
+
return { messages, rest };
|
|
159
|
+
}
|
|
121
160
|
function verifyResponseSignature(data, signature, secret) {
|
|
122
161
|
const expectedSignature = createHmac("sha256", secret).update(createCanonicalPayload(data)).digest("hex");
|
|
123
162
|
const providedBuffer = Buffer.from(signature, "hex");
|
|
@@ -130,6 +169,55 @@ function verifyResponseSignature(data, signature, secret) {
|
|
|
130
169
|
|
|
131
170
|
// src/transport.ts
|
|
132
171
|
var CACHED_FINGERPRINT = generateFingerprint();
|
|
172
|
+
function streamUrl(baseUrl) {
|
|
173
|
+
try {
|
|
174
|
+
return new URL(baseUrl).origin + STREAM_PATH;
|
|
175
|
+
} catch {
|
|
176
|
+
return baseUrl.replace(/\/api\/services\/?$/u, "") + STREAM_PATH;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
var KNOWN_CATEGORIES = /* @__PURE__ */ new Set([
|
|
180
|
+
"auth",
|
|
181
|
+
"validation",
|
|
182
|
+
"limit",
|
|
183
|
+
"rate_limit",
|
|
184
|
+
"account",
|
|
185
|
+
"provider",
|
|
186
|
+
"not_found",
|
|
187
|
+
"internal",
|
|
188
|
+
"network",
|
|
189
|
+
"timeout"
|
|
190
|
+
]);
|
|
191
|
+
var STATUS_CATEGORY = {
|
|
192
|
+
408: "timeout",
|
|
193
|
+
429: "rate_limit"
|
|
194
|
+
};
|
|
195
|
+
var ERROR_TYPE_SUFFIX = /^(.*?)\s*--\s*error-type:\s*([a-z_]+)\s*$/is;
|
|
196
|
+
function parseCategoryFromMessage(message) {
|
|
197
|
+
const match = ERROR_TYPE_SUFFIX.exec(message);
|
|
198
|
+
if (match?.[2] && KNOWN_CATEGORIES.has(match[2])) {
|
|
199
|
+
return {
|
|
200
|
+
category: match[2],
|
|
201
|
+
message: match[1] ?? message
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return { category: null, message };
|
|
205
|
+
}
|
|
206
|
+
function buildHttpError(params) {
|
|
207
|
+
const parsed = parseCategoryFromMessage(params.message);
|
|
208
|
+
const category = parsed.category ?? STATUS_CATEGORY[params.statusCode] ?? (params.statusCode >= 500 ? "internal" : "validation");
|
|
209
|
+
return {
|
|
210
|
+
category,
|
|
211
|
+
message: parsed.message,
|
|
212
|
+
retryable: RETRYABLE_STATUS_CODES.has(params.statusCode)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function createSdkError(error) {
|
|
216
|
+
return Object.assign(new Error(error.message), {
|
|
217
|
+
category: error.category,
|
|
218
|
+
retryable: error.retryable
|
|
219
|
+
});
|
|
220
|
+
}
|
|
133
221
|
function calculateBackoff(attempt) {
|
|
134
222
|
const base = 2 ** attempt * 1e3;
|
|
135
223
|
const jitter = Math.random() * 500;
|
|
@@ -232,21 +320,19 @@ function createTransport({
|
|
|
232
320
|
await delay(calculateBackoff(currentAttempt));
|
|
233
321
|
return attempt(currentAttempt + 1);
|
|
234
322
|
}
|
|
235
|
-
const sdkError = {
|
|
236
|
-
code: `HTTP_${statusCode}`,
|
|
237
|
-
message: errorMessage,
|
|
238
|
-
statusCode,
|
|
239
|
-
retryable
|
|
240
|
-
};
|
|
241
323
|
cleanup();
|
|
242
|
-
return Err(
|
|
324
|
+
return Err(
|
|
325
|
+
JSON.stringify(
|
|
326
|
+
buildHttpError({ message: errorMessage, statusCode })
|
|
327
|
+
)
|
|
328
|
+
);
|
|
243
329
|
}
|
|
244
330
|
const responseBody = await response.json();
|
|
245
331
|
if (!responseBody || typeof responseBody !== "object" || !("status" in responseBody)) {
|
|
246
332
|
cleanup();
|
|
247
333
|
return Err(
|
|
248
334
|
JSON.stringify({
|
|
249
|
-
|
|
335
|
+
category: "internal",
|
|
250
336
|
message: "Response missing status field",
|
|
251
337
|
retryable: false
|
|
252
338
|
})
|
|
@@ -265,7 +351,7 @@ function createTransport({
|
|
|
265
351
|
cleanup();
|
|
266
352
|
return Err(
|
|
267
353
|
JSON.stringify({
|
|
268
|
-
|
|
354
|
+
category: "internal",
|
|
269
355
|
message: "Response signature verification failed",
|
|
270
356
|
retryable: false
|
|
271
357
|
})
|
|
@@ -282,7 +368,7 @@ function createTransport({
|
|
|
282
368
|
cleanup();
|
|
283
369
|
const isAbort = error instanceof DOMException && error.name === "AbortError";
|
|
284
370
|
const sdkError = {
|
|
285
|
-
|
|
371
|
+
category: isAbort ? "timeout" : "network",
|
|
286
372
|
message: isAbort ? `Request timed out after ${timeoutMs}ms` : String(error),
|
|
287
373
|
retryable: true
|
|
288
374
|
};
|
|
@@ -295,17 +381,105 @@ function createTransport({
|
|
|
295
381
|
}
|
|
296
382
|
return attempt(0);
|
|
297
383
|
}
|
|
298
|
-
|
|
384
|
+
function openStream(input) {
|
|
385
|
+
const controller = new AbortController();
|
|
386
|
+
let closed = false;
|
|
387
|
+
const close = () => {
|
|
388
|
+
if (closed) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
closed = true;
|
|
392
|
+
controller.abort();
|
|
393
|
+
};
|
|
394
|
+
const payload = {
|
|
395
|
+
reference: input.reference,
|
|
396
|
+
_fingerprint: CACHED_FINGERPRINT
|
|
397
|
+
};
|
|
398
|
+
const headers = buildAuthHeaders({ apiKey, apiSecret, payload });
|
|
399
|
+
const url = streamUrl(baseUrl);
|
|
400
|
+
const run = async () => {
|
|
401
|
+
const fetchResult = await safeTry(
|
|
402
|
+
() => fetchImpl(url, {
|
|
403
|
+
method: "POST",
|
|
404
|
+
headers,
|
|
405
|
+
body: JSON.stringify(payload),
|
|
406
|
+
signal: controller.signal
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
if (fetchResult.isErr) {
|
|
410
|
+
if (!closed) {
|
|
411
|
+
input.onError(fetchResult.error);
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const response = fetchResult.value;
|
|
416
|
+
if (!response.ok || !response.body) {
|
|
417
|
+
const textResult = await safeTry(() => response.text());
|
|
418
|
+
const message = textResult.isOk ? textResult.value : `HTTP ${response.status}`;
|
|
419
|
+
if (!closed) {
|
|
420
|
+
input.onError(message || `HTTP ${response.status}`);
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const reader = response.body.getReader();
|
|
425
|
+
const decoder = new TextDecoder();
|
|
426
|
+
let buffer = "";
|
|
427
|
+
while (!closed) {
|
|
428
|
+
const chunkResult = await safeTry(() => reader.read());
|
|
429
|
+
if (chunkResult.isErr) {
|
|
430
|
+
if (!closed) {
|
|
431
|
+
input.onError(chunkResult.error);
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const chunk = chunkResult.value;
|
|
436
|
+
if (chunk.done) {
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
440
|
+
const { messages, rest } = parseSseBuffer(buffer);
|
|
441
|
+
buffer = rest;
|
|
442
|
+
for (const message of messages) {
|
|
443
|
+
if (message.event === "status") {
|
|
444
|
+
const statusResult = await safeTry(
|
|
445
|
+
() => JSON.parse(message.data)
|
|
446
|
+
);
|
|
447
|
+
if (statusResult.isOk) {
|
|
448
|
+
input.onStatus(statusResult.value);
|
|
449
|
+
}
|
|
450
|
+
} else if (message.event === "error") {
|
|
451
|
+
const errDataResult = await safeTry(
|
|
452
|
+
() => JSON.parse(message.data)
|
|
453
|
+
);
|
|
454
|
+
const text = errDataResult.isOk ? errDataResult.value.message ?? message.data : message.data;
|
|
455
|
+
close();
|
|
456
|
+
input.onError(text);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (!closed) {
|
|
462
|
+
input.onClose();
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
void run();
|
|
466
|
+
return { close };
|
|
467
|
+
}
|
|
468
|
+
return { send, openStream, parseError };
|
|
299
469
|
}
|
|
300
470
|
function parseError(error) {
|
|
301
471
|
try {
|
|
302
472
|
const parsed = JSON.parse(error);
|
|
303
|
-
if (parsed && typeof parsed === "object" && "
|
|
473
|
+
if (parsed && typeof parsed === "object" && "category" in parsed && "message" in parsed && typeof parsed.category === "string" && typeof parsed.message === "string") {
|
|
304
474
|
return parsed;
|
|
305
475
|
}
|
|
306
476
|
} catch {
|
|
307
477
|
}
|
|
308
|
-
|
|
478
|
+
const fromSuffix = parseCategoryFromMessage(error);
|
|
479
|
+
return {
|
|
480
|
+
category: fromSuffix.category ?? "internal",
|
|
481
|
+
message: fromSuffix.message
|
|
482
|
+
};
|
|
309
483
|
}
|
|
310
484
|
|
|
311
485
|
// src/payment.ts
|
|
@@ -323,10 +497,14 @@ var TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
|
323
497
|
"failed",
|
|
324
498
|
"cancelled"
|
|
325
499
|
]);
|
|
500
|
+
function normalizeStatus(raw) {
|
|
501
|
+
if (raw === "completed") return "successful";
|
|
502
|
+
return raw;
|
|
503
|
+
}
|
|
326
504
|
function createPaymentInstance(initialResponse, deps) {
|
|
327
505
|
const state = {
|
|
328
506
|
reference: initialResponse.reference,
|
|
329
|
-
status: initialResponse.status,
|
|
507
|
+
status: normalizeStatus(initialResponse.status),
|
|
330
508
|
transaction: null,
|
|
331
509
|
pollingTimer: null,
|
|
332
510
|
resolved: false,
|
|
@@ -337,11 +515,15 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
337
515
|
fetchTransaction: deps.fetchTransaction,
|
|
338
516
|
pollIntervalMs: deps.pollIntervalMs ?? 2e3,
|
|
339
517
|
maxPollDuration: deps.maxPollDuration ?? 3e5,
|
|
340
|
-
maxPollAttempts: deps.maxPollAttempts ?? 150
|
|
518
|
+
maxPollAttempts: deps.maxPollAttempts ?? 150,
|
|
519
|
+
streaming: deps.streaming ?? false,
|
|
520
|
+
openStream: deps.openStream,
|
|
521
|
+
streamHandle: null,
|
|
522
|
+
streamReconnects: 0
|
|
341
523
|
};
|
|
342
524
|
function resolveWithError(error) {
|
|
343
525
|
state.resolved = true;
|
|
344
|
-
|
|
526
|
+
stopUpdates();
|
|
345
527
|
emitEvent("error", parseError(error).message);
|
|
346
528
|
}
|
|
347
529
|
function emitEvent(event, error) {
|
|
@@ -361,13 +543,14 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
361
543
|
state.transaction = txResult.value;
|
|
362
544
|
const event = statusToEvent(status);
|
|
363
545
|
if (event) {
|
|
364
|
-
|
|
546
|
+
const error = status === "failed" ? state.transaction.failureReason ?? void 0 : void 0;
|
|
547
|
+
emitEvent(event, error);
|
|
365
548
|
}
|
|
366
549
|
} else {
|
|
367
550
|
emitEvent("error", `Failed to fetch transaction: ${txResult.error}`);
|
|
368
551
|
}
|
|
369
552
|
state.resolved = true;
|
|
370
|
-
|
|
553
|
+
stopUpdates();
|
|
371
554
|
}
|
|
372
555
|
async function handleStatusUpdate(response) {
|
|
373
556
|
if (response.reference !== state.reference) {
|
|
@@ -376,7 +559,7 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
376
559
|
);
|
|
377
560
|
return;
|
|
378
561
|
}
|
|
379
|
-
const newStatus = response.status;
|
|
562
|
+
const newStatus = normalizeStatus(response.status);
|
|
380
563
|
const oldStatus = state.status;
|
|
381
564
|
state.status = newStatus;
|
|
382
565
|
if (newStatus !== oldStatus) {
|
|
@@ -391,13 +574,13 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
391
574
|
}
|
|
392
575
|
}
|
|
393
576
|
function handlePollError(error) {
|
|
394
|
-
const
|
|
395
|
-
if (
|
|
577
|
+
const parsed = parseError(error);
|
|
578
|
+
if (parsed.category === "not_found") {
|
|
396
579
|
return;
|
|
397
580
|
}
|
|
398
|
-
emitEvent("error",
|
|
581
|
+
emitEvent("error", parsed.message);
|
|
399
582
|
state.resolved = true;
|
|
400
|
-
|
|
583
|
+
stopUpdates();
|
|
401
584
|
}
|
|
402
585
|
function scheduleNextPoll() {
|
|
403
586
|
if (state.resolved || state.pollingTimer) {
|
|
@@ -410,7 +593,7 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
410
593
|
}
|
|
411
594
|
async function pollStatus() {
|
|
412
595
|
if (state.resolved) {
|
|
413
|
-
|
|
596
|
+
stopUpdates();
|
|
414
597
|
return;
|
|
415
598
|
}
|
|
416
599
|
if (state.pollAttempts >= state.maxPollAttempts) {
|
|
@@ -429,19 +612,66 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
429
612
|
handlePollError(result.error);
|
|
430
613
|
}
|
|
431
614
|
if (state.resolved) {
|
|
432
|
-
|
|
615
|
+
stopUpdates();
|
|
433
616
|
return;
|
|
434
617
|
}
|
|
435
618
|
scheduleNextPoll();
|
|
436
619
|
}
|
|
437
|
-
function
|
|
620
|
+
function closeStream() {
|
|
621
|
+
if (state.streamHandle) {
|
|
622
|
+
state.streamHandle.close();
|
|
623
|
+
state.streamHandle = null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function startStream() {
|
|
627
|
+
if (state.resolved || !state.openStream) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
state.streamHandle = state.openStream({
|
|
631
|
+
reference: state.reference,
|
|
632
|
+
onStatus: (status) => {
|
|
633
|
+
void handleStatusUpdate(status);
|
|
634
|
+
},
|
|
635
|
+
onError: () => handleStreamFailure(),
|
|
636
|
+
onClose: () => handleStreamFailure()
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
function handleStreamFailure() {
|
|
640
|
+
closeStream();
|
|
641
|
+
if (state.resolved) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (state.streamReconnects < MAX_STREAM_RECONNECTS) {
|
|
645
|
+
state.streamReconnects += 1;
|
|
646
|
+
const backoff = 500 * 2 ** (state.streamReconnects - 1) + Math.random() * 250;
|
|
647
|
+
setTimeout(() => {
|
|
648
|
+
if (!state.resolved) {
|
|
649
|
+
startStream();
|
|
650
|
+
}
|
|
651
|
+
}, backoff);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
438
654
|
scheduleNextPoll();
|
|
439
655
|
}
|
|
440
|
-
function
|
|
656
|
+
function startUpdates() {
|
|
657
|
+
if (TERMINAL_STATES.has(state.status)) {
|
|
658
|
+
setTimeout(() => {
|
|
659
|
+
void handleTerminalState(state.status);
|
|
660
|
+
}, 0);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (state.streaming && state.openStream) {
|
|
664
|
+
startStream();
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
scheduleNextPoll();
|
|
668
|
+
}
|
|
669
|
+
function stopUpdates() {
|
|
441
670
|
if (state.pollingTimer) {
|
|
442
671
|
clearTimeout(state.pollingTimer);
|
|
443
672
|
state.pollingTimer = null;
|
|
444
673
|
}
|
|
674
|
+
closeStream();
|
|
445
675
|
}
|
|
446
676
|
function on(event, handler) {
|
|
447
677
|
state.emitter.on(event, handler);
|
|
@@ -456,37 +686,28 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
456
686
|
return paymentInstance;
|
|
457
687
|
}
|
|
458
688
|
function wait() {
|
|
459
|
-
return new Promise((resolve
|
|
689
|
+
return new Promise((resolve) => {
|
|
460
690
|
if (state.resolved) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
reject(new Error(`Payment ${state.status ?? "error"}`));
|
|
465
|
-
}
|
|
691
|
+
resolve(
|
|
692
|
+
state.status === "successful" && state.transaction ? state.transaction : null
|
|
693
|
+
);
|
|
466
694
|
return;
|
|
467
695
|
}
|
|
468
696
|
function onSuccess() {
|
|
469
697
|
cleanup();
|
|
470
|
-
|
|
471
|
-
resolve(state.transaction);
|
|
472
|
-
} else {
|
|
473
|
-
reject(
|
|
474
|
-
new Error("Payment successful but transaction data unavailable")
|
|
475
|
-
);
|
|
476
|
-
}
|
|
698
|
+
resolve(state.transaction ?? null);
|
|
477
699
|
}
|
|
478
700
|
function onFailed() {
|
|
479
701
|
cleanup();
|
|
480
|
-
|
|
702
|
+
resolve(null);
|
|
481
703
|
}
|
|
482
704
|
function onCancelled() {
|
|
483
705
|
cleanup();
|
|
484
|
-
|
|
706
|
+
resolve(null);
|
|
485
707
|
}
|
|
486
|
-
function onError(
|
|
708
|
+
function onError() {
|
|
487
709
|
cleanup();
|
|
488
|
-
|
|
489
|
-
reject(new Error(eventData.error ?? "Payment error"));
|
|
710
|
+
resolve(null);
|
|
490
711
|
}
|
|
491
712
|
function cleanup() {
|
|
492
713
|
state.emitter.off("success", onSuccess);
|
|
@@ -512,7 +733,7 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
512
733
|
off,
|
|
513
734
|
wait
|
|
514
735
|
};
|
|
515
|
-
|
|
736
|
+
startUpdates();
|
|
516
737
|
return paymentInstance;
|
|
517
738
|
}
|
|
518
739
|
function verifyWebhookSignature(input) {
|
|
@@ -530,14 +751,17 @@ function verifyWebhookSignature(input) {
|
|
|
530
751
|
function generateReference() {
|
|
531
752
|
return randomBytes(16).toString("hex").slice(0, 15);
|
|
532
753
|
}
|
|
754
|
+
function throwValidation(message) {
|
|
755
|
+
throw createSdkError({ category: "validation", message });
|
|
756
|
+
}
|
|
533
757
|
function validateAmount(amount) {
|
|
534
758
|
if (!Number.isInteger(amount) || amount <= 0) {
|
|
535
|
-
|
|
759
|
+
throwValidation("amount must be a positive integer");
|
|
536
760
|
}
|
|
537
761
|
}
|
|
538
762
|
function validateNonEmpty(value, fieldName) {
|
|
539
763
|
if (!value || value.trim() === "") {
|
|
540
|
-
|
|
764
|
+
throwValidation(`${fieldName} is required`);
|
|
541
765
|
}
|
|
542
766
|
}
|
|
543
767
|
function createSdkInstance(config) {
|
|
@@ -560,7 +784,9 @@ function createSdkInstance(config) {
|
|
|
560
784
|
}),
|
|
561
785
|
pollIntervalMs: config.maxPollIntervalMs,
|
|
562
786
|
maxPollDuration: config.maxPollDurationMs,
|
|
563
|
-
maxPollAttempts: config.maxPollAttempts
|
|
787
|
+
maxPollAttempts: config.maxPollAttempts,
|
|
788
|
+
streaming: config.streaming,
|
|
789
|
+
openStream: config.streaming ? transport.openStream : void 0
|
|
564
790
|
};
|
|
565
791
|
async function collectPayment(input) {
|
|
566
792
|
const reference = input.reference ?? generateReference();
|
|
@@ -569,7 +795,7 @@ function createSdkInstance(config) {
|
|
|
569
795
|
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
570
796
|
validateNonEmpty(input.description, "description");
|
|
571
797
|
if (input.method === "bank" && !input.bank) {
|
|
572
|
-
|
|
798
|
+
throwValidation('bank details are required when method is "bank"');
|
|
573
799
|
}
|
|
574
800
|
let payload = { ...input, reference };
|
|
575
801
|
if (config.hooks?.beforeCollect) {
|
|
@@ -590,19 +816,10 @@ function createSdkInstance(config) {
|
|
|
590
816
|
payload
|
|
591
817
|
);
|
|
592
818
|
}
|
|
593
|
-
if (result.
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
return createPaymentInstance(
|
|
597
|
-
{ reference, status: "pending" },
|
|
598
|
-
{
|
|
599
|
-
fetchStatus: async () => Err(result.error),
|
|
600
|
-
fetchTransaction: async () => Err(result.error),
|
|
601
|
-
pollIntervalMs: 0,
|
|
602
|
-
maxPollAttempts: 1,
|
|
603
|
-
maxPollDuration: Number.MAX_SAFE_INTEGER
|
|
604
|
-
}
|
|
605
|
-
);
|
|
819
|
+
if (result.isErr) {
|
|
820
|
+
throw createSdkError(parseError(result.error));
|
|
821
|
+
}
|
|
822
|
+
return createPaymentInstance(result.value, commonDeps);
|
|
606
823
|
}
|
|
607
824
|
async function collectPaymentAndResolve(input) {
|
|
608
825
|
const reference = input.reference ?? generateReference();
|
|
@@ -611,7 +828,7 @@ function createSdkInstance(config) {
|
|
|
611
828
|
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
612
829
|
validateNonEmpty(input.description, "description");
|
|
613
830
|
if (input.method === "bank" && !input.bank) {
|
|
614
|
-
|
|
831
|
+
throwValidation('bank details are required when method is "bank"');
|
|
615
832
|
}
|
|
616
833
|
let payload = { ...input, reference };
|
|
617
834
|
if (config.hooks?.beforeCollect) {
|
|
@@ -670,19 +887,10 @@ function createSdkInstance(config) {
|
|
|
670
887
|
payload
|
|
671
888
|
);
|
|
672
889
|
}
|
|
673
|
-
if (result.
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
return createPaymentInstance(
|
|
677
|
-
{ reference, status: "pending" },
|
|
678
|
-
{
|
|
679
|
-
fetchStatus: async () => Err(result.error),
|
|
680
|
-
fetchTransaction: async () => Err(result.error),
|
|
681
|
-
pollIntervalMs: 0,
|
|
682
|
-
maxPollAttempts: 1,
|
|
683
|
-
maxPollDuration: Number.MAX_SAFE_INTEGER
|
|
684
|
-
}
|
|
685
|
-
);
|
|
890
|
+
if (result.isErr) {
|
|
891
|
+
throw createSdkError(parseError(result.error));
|
|
892
|
+
}
|
|
893
|
+
return createPaymentInstance(result.value, commonDeps);
|
|
686
894
|
}
|
|
687
895
|
async function makePayoutAndResolve(input) {
|
|
688
896
|
const reference = input.reference ?? generateReference();
|
|
@@ -735,7 +943,7 @@ function createSdkInstance(config) {
|
|
|
735
943
|
}
|
|
736
944
|
async function getTransaction(input) {
|
|
737
945
|
if (!input.id && !input.reference) {
|
|
738
|
-
|
|
946
|
+
throwValidation("id or reference is required");
|
|
739
947
|
}
|
|
740
948
|
const result = await transport.send({
|
|
741
949
|
action: SDK_ACTIONS.getTransaction,
|
|
@@ -763,14 +971,14 @@ function createSdkInstance(config) {
|
|
|
763
971
|
validateNonEmpty(input.description, "description");
|
|
764
972
|
if (input.items) {
|
|
765
973
|
if (input.items.length > 50) {
|
|
766
|
-
|
|
974
|
+
throwValidation("items must not exceed 50");
|
|
767
975
|
}
|
|
768
976
|
for (const item of input.items) {
|
|
769
977
|
if (!Number.isInteger(item.quantity) || item.quantity <= 0) {
|
|
770
|
-
|
|
978
|
+
throwValidation("item quantity must be a positive integer");
|
|
771
979
|
}
|
|
772
980
|
if (!Number.isInteger(item.unitPrice) || item.unitPrice <= 0) {
|
|
773
|
-
|
|
981
|
+
throwValidation("item unitPrice must be a positive integer");
|
|
774
982
|
}
|
|
775
983
|
}
|
|
776
984
|
}
|
|
@@ -830,6 +1038,7 @@ function createNylonPay(config) {
|
|
|
830
1038
|
maxPollIntervalMs: config.maxPollIntervalMs ?? DEFAULT_MAX_POLL_INTERVAL_MS,
|
|
831
1039
|
maxPollDurationMs: config.maxPollDurationMs ?? DEFAULT_MAX_POLL_DURATION_MS,
|
|
832
1040
|
maxPollAttempts: config.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS,
|
|
1041
|
+
streaming: config.streaming ?? DEFAULT_STREAMING,
|
|
833
1042
|
fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
|
|
834
1043
|
hooks: config.hooks
|
|
835
1044
|
};
|
|
@@ -838,6 +1047,6 @@ function createNylonPay(config) {
|
|
|
838
1047
|
return instance;
|
|
839
1048
|
}
|
|
840
1049
|
|
|
841
|
-
export { createNylonPay, parseError, verifyWebhookSignature };
|
|
1050
|
+
export { createNylonPay, createSdkError, parseError, verifyWebhookSignature };
|
|
842
1051
|
//# sourceMappingURL=index.js.map
|
|
843
1052
|
//# sourceMappingURL=index.js.map
|