@nile-squad/nylonpay-ts 1.1.0 → 1.2.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 CHANGED
@@ -255,13 +255,13 @@ function createTransport({
255
255
  async function send(request) {
256
256
  const envelope = buildEnvelope(request);
257
257
  const signedPayload = envelope.payload;
258
- const headers = buildAuthHeaders({
259
- apiKey,
260
- apiSecret,
261
- payload: signedPayload
262
- });
263
258
  const bodyString = JSON.stringify(envelope);
264
259
  async function attempt(currentAttempt) {
260
+ const headers = buildAuthHeaders({
261
+ apiKey,
262
+ apiSecret,
263
+ payload: signedPayload
264
+ });
265
265
  const { controller, cleanup } = withTimeout(timeoutMs);
266
266
  try {
267
267
  const response = await fetchImpl(baseUrl, {
@@ -300,7 +300,7 @@ function createTransport({
300
300
  return slangTs.Err(
301
301
  JSON.stringify({
302
302
  category: "internal",
303
- message: "Response missing status field",
303
+ message: "Received an invalid response from the server",
304
304
  retryable: false
305
305
  })
306
306
  );
@@ -313,7 +313,7 @@ function createTransport({
313
313
  return slangTs.Err(
314
314
  JSON.stringify({
315
315
  category: "internal",
316
- message: "Response signature missing",
316
+ message: "Could not verify the server response",
317
317
  retryable: false
318
318
  })
319
319
  );
@@ -328,7 +328,7 @@ function createTransport({
328
328
  return slangTs.Err(
329
329
  JSON.stringify({
330
330
  category: "internal",
331
- message: "Response signature verification failed",
331
+ message: "Could not verify the server response",
332
332
  retryable: false
333
333
  })
334
334
  );
@@ -344,7 +344,7 @@ function createTransport({
344
344
  const isAbort = error instanceof DOMException && error.name === "AbortError";
345
345
  const sdkError = {
346
346
  category: isAbort ? "timeout" : "network",
347
- message: isAbort ? `Request timed out after ${timeoutMs}ms` : String(error),
347
+ message: isAbort ? "The request timed out" : "Could not reach the server, check your network connection and try again",
348
348
  retryable: true
349
349
  };
350
350
  if (currentAttempt < maxRetries) {
@@ -410,10 +410,16 @@ function createPaymentInstance(initialResponse, deps) {
410
410
  maxPollDuration: deps.maxPollDuration ?? 3e5,
411
411
  maxPollAttempts: deps.maxPollAttempts ?? 150
412
412
  };
413
- function resolveWithError(error) {
413
+ function resolveWithError(error, category, retryable) {
414
414
  state.resolved = true;
415
415
  stopUpdates();
416
- emitEvent("error", parseError(error).message);
416
+ const parsed = parseError(error);
417
+ emitEvent(
418
+ "error",
419
+ parsed.message,
420
+ category ?? parsed.category,
421
+ parsed.retryable
422
+ );
417
423
  }
418
424
  function emitEvent(event, error, category, retryable) {
419
425
  const data = {
@@ -450,7 +456,8 @@ function createPaymentInstance(initialResponse, deps) {
450
456
  }
451
457
  if (response.reference !== state.reference) {
452
458
  resolveWithError(
453
- `Reference mismatch: expected ${state.reference} but got ${response.reference}`
459
+ "Received a status update for a different transaction",
460
+ "internal"
454
461
  );
455
462
  return;
456
463
  }
@@ -472,7 +479,7 @@ function createPaymentInstance(initialResponse, deps) {
472
479
  if (parsed.category === "not_found") {
473
480
  return;
474
481
  }
475
- emitEvent("error", parsed.message);
482
+ emitEvent("error", parsed.message, parsed.category, parsed.retryable);
476
483
  state.resolved = true;
477
484
  stopUpdates();
478
485
  }
@@ -492,11 +499,17 @@ function createPaymentInstance(initialResponse, deps) {
492
499
  return;
493
500
  }
494
501
  if (state.pollAttempts >= state.maxPollAttempts) {
495
- resolveWithError("Polling timeout: exceeded maximum attempts");
502
+ resolveWithError(
503
+ "Timed out waiting for the transaction status to update",
504
+ "timeout"
505
+ );
496
506
  return;
497
507
  }
498
508
  if (Date.now() - state.pollStartTime >= state.maxPollDuration) {
499
- resolveWithError("Polling timeout: exceeded maximum duration");
509
+ resolveWithError(
510
+ "Timed out waiting for the transaction status to update",
511
+ "timeout"
512
+ );
500
513
  return;
501
514
  }
502
515
  state.pollAttempts += 1;
@@ -616,6 +629,9 @@ function normalizePhone(phone) {
616
629
  }
617
630
  return normalized;
618
631
  }
632
+ function isValidPhoneFormat(normalizedPhone) {
633
+ return /^\d{9,15}$/.test(normalizedPhone);
634
+ }
619
635
  var DEFAULT_TOLERANCE_SECONDS = 300;
620
636
  function decodePayload(payload) {
621
637
  return typeof payload === "string" ? payload : Buffer.from(payload).toString("utf8");
@@ -712,6 +728,56 @@ function validateNonEmpty(value, fieldName) {
712
728
  throwValidation(`${fieldName} is required`);
713
729
  }
714
730
  }
731
+ function validatePhoneFormat(normalizedPhone, fieldName) {
732
+ if (!isValidPhoneFormat(normalizedPhone)) {
733
+ throwValidation(`${fieldName} must be a valid phone number`);
734
+ }
735
+ }
736
+ function prepareCollectPayload(input) {
737
+ const reference = resolveReference(input.reference);
738
+ validateCollectionAmount(input.amount);
739
+ validateNonEmpty(input.customer.name, "customer.name");
740
+ validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
741
+ const normalizedPhone = normalizePhone(input.customer.phoneNumber);
742
+ validatePhoneFormat(normalizedPhone, "customer.phoneNumber");
743
+ validateNonEmpty(input.description, "description");
744
+ if (input.method === "bank" && !input.bank) {
745
+ throwValidation('bank details are required when method is "bank"');
746
+ }
747
+ return {
748
+ ...input,
749
+ reference,
750
+ customer: { ...input.customer, phoneNumber: normalizedPhone }
751
+ };
752
+ }
753
+ function preparePayoutPayload(input) {
754
+ const reference = resolveReference(input.reference);
755
+ validatePayoutAmount(input.amount);
756
+ validateNonEmpty(input.customer.name, "customer.name");
757
+ validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
758
+ const normalizedPhone = normalizePhone(input.customer.phoneNumber);
759
+ validatePhoneFormat(normalizedPhone, "customer.phoneNumber");
760
+ validateNonEmpty(input.description, "description");
761
+ validateNonEmpty(
762
+ input.destination.accountHolderName,
763
+ "destination.accountHolderName"
764
+ );
765
+ validateNonEmpty(
766
+ input.destination.accountNumber,
767
+ "destination.accountNumber"
768
+ );
769
+ return {
770
+ ...input,
771
+ reference,
772
+ customer: { ...input.customer, phoneNumber: normalizedPhone }
773
+ };
774
+ }
775
+ function applyBeforeHookMutation(mutated, current, prepare) {
776
+ return prepare({
777
+ ...mutated,
778
+ reference: mutated.reference ?? current.reference
779
+ });
780
+ }
715
781
  function createSdkInstance(config) {
716
782
  const transport = createTransport({
717
783
  apiKey: config.apiKey,
@@ -735,23 +801,15 @@ function createSdkInstance(config) {
735
801
  maxPollAttempts: config.maxPollAttempts
736
802
  };
737
803
  async function collectPayment(input) {
738
- const reference = resolveReference(input.reference);
739
- validateCollectionAmount(input.amount);
740
- validateNonEmpty(input.customer.name, "customer.name");
741
- validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
742
- const normalizedPhone = normalizePhone(input.customer.phoneNumber);
743
- validateNonEmpty(input.description, "description");
744
- if (input.method === "bank" && !input.bank) {
745
- throwValidation('bank details are required when method is "bank"');
746
- }
747
- let payload = {
748
- ...input,
749
- reference,
750
- customer: { ...input.customer, phoneNumber: normalizedPhone }
751
- };
804
+ let payload = prepareCollectPayload(input);
752
805
  const mutated = await runHook(config.hooks?.beforeCollect, payload);
753
- if (mutated != null)
754
- payload = { ...mutated, reference: mutated.reference ?? reference };
806
+ if (mutated != null) {
807
+ payload = applyBeforeHookMutation(
808
+ mutated,
809
+ payload,
810
+ prepareCollectPayload
811
+ );
812
+ }
755
813
  const result = await transport.send({
756
814
  action: SDK_ACTIONS.collectPayment,
757
815
  payload
@@ -759,35 +817,27 @@ function createSdkInstance(config) {
759
817
  await runHook(
760
818
  config.hooks?.afterCollect,
761
819
  result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
762
- payload
820
+ { ...payload, raw: input }
763
821
  );
764
822
  if (result.isErr) {
765
823
  const sdkErr = parseError(result.error);
766
824
  return createPaymentInstance(
767
- { reference, status: "pending" },
825
+ { reference: payload.reference, status: "pending" },
768
826
  { ...commonDeps, initialError: sdkErr }
769
827
  );
770
828
  }
771
829
  return createPaymentInstance(result.value, commonDeps);
772
830
  }
773
831
  async function collectPaymentAndResolve(input) {
774
- const reference = resolveReference(input.reference);
775
- validateCollectionAmount(input.amount);
776
- validateNonEmpty(input.customer.name, "customer.name");
777
- validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
778
- const normalizedPhone = normalizePhone(input.customer.phoneNumber);
779
- validateNonEmpty(input.description, "description");
780
- if (input.method === "bank" && !input.bank) {
781
- throwValidation('bank details are required when method is "bank"');
782
- }
783
- let payload = {
784
- ...input,
785
- reference,
786
- customer: { ...input.customer, phoneNumber: normalizedPhone }
787
- };
832
+ let payload = prepareCollectPayload(input);
788
833
  const mutated = await runHook(config.hooks?.beforeCollect, payload);
789
- if (mutated != null)
790
- payload = { ...mutated, reference: mutated.reference ?? reference };
834
+ if (mutated != null) {
835
+ payload = applyBeforeHookMutation(
836
+ mutated,
837
+ payload,
838
+ prepareCollectPayload
839
+ );
840
+ }
791
841
  const result = await transport.send({
792
842
  action: SDK_ACTIONS.collectPaymentAndResolve,
793
843
  payload
@@ -795,7 +845,7 @@ function createSdkInstance(config) {
795
845
  await runHook(
796
846
  config.hooks?.afterCollect,
797
847
  result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
798
- payload
848
+ { ...payload, raw: input }
799
849
  );
800
850
  if (result.isOk) {
801
851
  return slangTs.Ok(result.value);
@@ -803,28 +853,11 @@ function createSdkInstance(config) {
803
853
  return slangTs.Err(result.error);
804
854
  }
805
855
  async function makePayout(input) {
806
- const reference = resolveReference(input.reference);
807
- validatePayoutAmount(input.amount);
808
- validateNonEmpty(input.customer.name, "customer.name");
809
- validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
810
- const normalizedPhone = normalizePhone(input.customer.phoneNumber);
811
- validateNonEmpty(input.description, "description");
812
- validateNonEmpty(
813
- input.destination.accountHolderName,
814
- "destination.accountHolderName"
815
- );
816
- validateNonEmpty(
817
- input.destination.accountNumber,
818
- "destination.accountNumber"
819
- );
820
- let payload = {
821
- ...input,
822
- reference,
823
- customer: { ...input.customer, phoneNumber: normalizedPhone }
824
- };
856
+ let payload = preparePayoutPayload(input);
825
857
  const mutated = await runHook(config.hooks?.beforePayout, payload);
826
- if (mutated != null)
827
- payload = { ...mutated, reference: mutated.reference ?? reference };
858
+ if (mutated != null) {
859
+ payload = applyBeforeHookMutation(mutated, payload, preparePayoutPayload);
860
+ }
828
861
  const result = await transport.send({
829
862
  action: SDK_ACTIONS.makePayout,
830
863
  payload
@@ -832,40 +865,23 @@ function createSdkInstance(config) {
832
865
  await runHook(
833
866
  config.hooks?.afterPayout,
834
867
  result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
835
- payload
868
+ { ...payload, raw: input }
836
869
  );
837
870
  if (result.isErr) {
838
871
  const sdkErr = parseError(result.error);
839
872
  return createPaymentInstance(
840
- { reference, status: "pending" },
873
+ { reference: payload.reference, status: "pending" },
841
874
  { ...commonDeps, initialError: sdkErr }
842
875
  );
843
876
  }
844
877
  return createPaymentInstance(result.value, commonDeps);
845
878
  }
846
879
  async function makePayoutAndResolve(input) {
847
- const reference = resolveReference(input.reference);
848
- validatePayoutAmount(input.amount);
849
- validateNonEmpty(input.customer.name, "customer.name");
850
- validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
851
- const normalizedPhone = normalizePhone(input.customer.phoneNumber);
852
- validateNonEmpty(input.description, "description");
853
- validateNonEmpty(
854
- input.destination.accountHolderName,
855
- "destination.accountHolderName"
856
- );
857
- validateNonEmpty(
858
- input.destination.accountNumber,
859
- "destination.accountNumber"
860
- );
861
- let payload = {
862
- ...input,
863
- reference,
864
- customer: { ...input.customer, phoneNumber: normalizedPhone }
865
- };
880
+ let payload = preparePayoutPayload(input);
866
881
  const mutated = await runHook(config.hooks?.beforePayout, payload);
867
- if (mutated != null)
868
- payload = { ...mutated, reference: mutated.reference ?? reference };
882
+ if (mutated != null) {
883
+ payload = applyBeforeHookMutation(mutated, payload, preparePayoutPayload);
884
+ }
869
885
  const result = await transport.send({
870
886
  action: SDK_ACTIONS.makePayoutAndResolve,
871
887
  payload
@@ -873,7 +889,7 @@ function createSdkInstance(config) {
873
889
  await runHook(
874
890
  config.hooks?.afterPayout,
875
891
  result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
876
- payload
892
+ { ...payload, raw: input }
877
893
  );
878
894
  if (result.isOk) {
879
895
  return slangTs.Ok(result.value);
@@ -907,6 +923,7 @@ function createSdkInstance(config) {
907
923
  async function verifyPhone(input) {
908
924
  validateNonEmpty(input.phoneNumber, "phoneNumber");
909
925
  const normalizedPhone = normalizePhone(input.phoneNumber);
926
+ validatePhoneFormat(normalizedPhone, "phoneNumber");
910
927
  const result = await transport.send({
911
928
  action: SDK_ACTIONS.verifyPhone,
912
929
  payload: { ...input, phoneNumber: normalizedPhone }