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