@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.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(JSON.stringify(sdkError));
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
- code: "INVALID_RESPONSE",
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
- code: "RESPONSE_TAMPERED",
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
- code: isAbort ? "TIMEOUT" : "NETWORK_ERROR",
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
- return { send, parseError };
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" && "code" in parsed && "message" in parsed && typeof parsed.code === "string" && typeof parsed.message === "string") {
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
- return { code: "UNKNOWN", message: error };
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
- stopPolling();
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
- emitEvent(event);
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
- stopPolling();
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 isNotFound = error.includes("not found") || error.includes("NOT_FOUND");
395
- if (isNotFound) {
577
+ const parsed = parseError(error);
578
+ if (parsed.category === "not_found") {
396
579
  return;
397
580
  }
398
- emitEvent("error", parseError(error).message);
581
+ emitEvent("error", parsed.message);
399
582
  state.resolved = true;
400
- stopPolling();
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
- stopPolling();
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
- stopPolling();
615
+ stopUpdates();
433
616
  return;
434
617
  }
435
618
  scheduleNextPoll();
436
619
  }
437
- function startPolling() {
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 stopPolling() {
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, reject) => {
689
+ return new Promise((resolve) => {
460
690
  if (state.resolved) {
461
- if (state.status === "successful" && state.transaction) {
462
- resolve(state.transaction);
463
- } else {
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
- if (state.transaction) {
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
- reject(new Error("Payment failed"));
702
+ resolve(null);
481
703
  }
482
704
  function onCancelled() {
483
705
  cleanup();
484
- reject(new Error("Payment cancelled"));
706
+ resolve(null);
485
707
  }
486
- function onError(data) {
708
+ function onError() {
487
709
  cleanup();
488
- const eventData = data;
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
- startPolling();
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
- throw new Error("amount must be a positive integer");
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
- throw new Error(`${fieldName} is required`);
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
- throw new Error('bank details are required when method is "bank"');
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.isOk) {
594
- return createPaymentInstance(result.value, commonDeps);
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
- throw new Error('bank details are required when method is "bank"');
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.isOk) {
674
- return createPaymentInstance(result.value, commonDeps);
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
- throw new Error("id or reference is required");
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
- throw new Error("items must not exceed 50");
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
- throw new Error("item quantity must be a positive integer");
978
+ throwValidation("item quantity must be a positive integer");
771
979
  }
772
980
  if (!Number.isInteger(item.unitPrice) || item.unitPrice <= 0) {
773
- throw new Error("item unitPrice must be a positive integer");
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