@nile-squad/nylonpay-ts 1.0.8 → 1.0.10

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Server-side SDK for integrating Nylon Pay into merchant applications. Supports TypeScript and JavaScript (ESM and CJS).
4
4
 
5
- This package is the reference implementation of the [Nylon Pay SDK Spec](https://github.com/nile-squad/specs/blob/main/nylon-pay/sdk-spec.md) — the canonical, language-agnostic contract for building Nylon Pay SDKs in any language.
5
+ This package is the reference implementation of the [Nylon Pay SDK Spec](https://github.com/nile-squad/specs/blob/main/nylonpay-sdk-spec/spec.md) — the canonical, language-agnostic contract for building Nylon Pay SDKs in any language.
6
6
 
7
7
  [Full documentation](https://docs.nylonpay.nilesquad.com/docs)
8
8
 
@@ -61,14 +61,16 @@ const payment = await nylonpay.collectPayment({
61
61
  customer: { name: "Jane", phoneNumber: "+256700000000" },
62
62
  description: "Order #1234",
63
63
  method: "mobileMoney",
64
- reference: "ORDER-123",
64
+ reference: "ORDER-2026-001",
65
65
  });
66
66
 
67
67
  payment.on("success", ({ transaction }) => { /* ... */ });
68
68
  payment.on("failed", ({ error }) => { /* ... */ });
69
69
  ```
70
70
 
71
- `reference` is optional and auto-generated if omitted.
71
+ `reference` is optional and auto-generated if omitted. A supplied reference must be
72
+ **13 to 15 characters**; the SDK throws a `validation` error otherwise. A raw UUID is
73
+ 36 characters and will be rejected, so use a short id of your own or omit the field.
72
74
 
73
75
  ### collectPaymentAndResolve
74
76
 
@@ -120,7 +122,7 @@ const result = await nylonpay.makePayoutAndResolve({
120
122
  One-shot status check for a transaction. Does not poll, returns the current server-side state.
121
123
 
122
124
  ```ts
123
- const result = await nylonpay.getStatus({ reference: "ORDER-123" });
125
+ const result = await nylonpay.getStatus({ reference: "ORDER-2026-001" });
124
126
  if (result.isOk) console.log(result.value.status);
125
127
  ```
126
128
 
@@ -129,7 +131,7 @@ if (result.isOk) console.log(result.value.status);
129
131
  Look up a full transaction record by `id` or `reference`. At least one must be provided.
130
132
 
131
133
  ```ts
132
- const result = await nylonpay.getTransaction({ reference: "ORDER-123" });
134
+ const result = await nylonpay.getTransaction({ reference: "ORDER-2026-001" });
133
135
  if (result.isOk) console.log(result.value.failureReason);
134
136
  ```
135
137
 
@@ -216,7 +218,7 @@ All operations return `Result<T, string>` from [slang-ts](https://github.com/nil
216
218
  ```ts
217
219
  import { parseError } from "@nile-squad/nylonpay-ts";
218
220
 
219
- const result = await nylonpay.getStatus({ reference: "ORDER-123" });
221
+ const result = await nylonpay.getStatus({ reference: "ORDER-2026-001" });
220
222
  if (!result.isOk) {
221
223
  const error = parseError(result.error);
222
224
  if (error.retryable) {
package/dist/index.cjs CHANGED
@@ -149,6 +149,7 @@ var KNOWN_CATEGORIES = /* @__PURE__ */ new Set([
149
149
  "rate_limit",
150
150
  "account",
151
151
  "provider",
152
+ "duplicate",
152
153
  "not_found",
153
154
  "internal",
154
155
  "network",
@@ -595,6 +596,15 @@ function createPaymentInstance(initialResponse, deps) {
595
596
  }
596
597
  return paymentInstance;
597
598
  }
599
+
600
+ // src/phone.ts
601
+ function normalizePhone(phone) {
602
+ let normalized = phone.replace(/\s+/g, "").replace(/^\+/, "");
603
+ if (normalized.startsWith("0") && normalized.length === 10) {
604
+ normalized = `256${normalized.slice(1)}`;
605
+ }
606
+ return normalized;
607
+ }
598
608
  var DEFAULT_TOLERANCE_SECONDS = 300;
599
609
  function decodePayload(payload) {
600
610
  return typeof payload === "string" ? payload : Buffer.from(payload).toString("utf8");
@@ -647,6 +657,19 @@ function verifyWebhookSignature(input) {
647
657
  function generateReference() {
648
658
  return crypto.randomBytes(16).toString("hex").slice(0, 15);
649
659
  }
660
+ var REFERENCE_MIN_LENGTH = 13;
661
+ var REFERENCE_MAX_LENGTH = 15;
662
+ function resolveReference(reference) {
663
+ if (reference === void 0) {
664
+ return generateReference();
665
+ }
666
+ if (reference.length < REFERENCE_MIN_LENGTH || reference.length > REFERENCE_MAX_LENGTH) {
667
+ throwValidation(
668
+ `reference must be ${REFERENCE_MIN_LENGTH}\u2013${REFERENCE_MAX_LENGTH} characters`
669
+ );
670
+ }
671
+ return reference;
672
+ }
650
673
  async function runHook(hook, ...args) {
651
674
  if (!hook || hook.enabled === false) return void 0;
652
675
  const result = await slangTs.safeTry(async () => hook.fn(...args));
@@ -657,10 +680,21 @@ async function runHook(hook, ...args) {
657
680
  function throwValidation(message) {
658
681
  throw createSdkError({ category: "validation", message });
659
682
  }
660
- function validateAmount(amount) {
683
+ function validateCollectionAmount(amount) {
684
+ if (!Number.isInteger(amount) || amount <= 0) {
685
+ throwValidation("amount must be a positive integer");
686
+ }
687
+ if (amount < 500) {
688
+ throwValidation("Collection amount must be at least 500 UGX");
689
+ }
690
+ }
691
+ function validatePayoutAmount(amount) {
661
692
  if (!Number.isInteger(amount) || amount <= 0) {
662
693
  throwValidation("amount must be a positive integer");
663
694
  }
695
+ if (amount < 5e3) {
696
+ throwValidation("Payout amount must be at least 5000 UGX");
697
+ }
664
698
  }
665
699
  function validateNonEmpty(value, fieldName) {
666
700
  if (!value || value.trim() === "") {
@@ -690,15 +724,20 @@ function createSdkInstance(config) {
690
724
  maxPollAttempts: config.maxPollAttempts
691
725
  };
692
726
  async function collectPayment(input) {
693
- const reference = input.reference ?? generateReference();
694
- validateAmount(input.amount);
727
+ const reference = resolveReference(input.reference);
728
+ validateCollectionAmount(input.amount);
695
729
  validateNonEmpty(input.customer.name, "customer.name");
696
730
  validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
731
+ const normalizedPhone = normalizePhone(input.customer.phoneNumber);
697
732
  validateNonEmpty(input.description, "description");
698
733
  if (input.method === "bank" && !input.bank) {
699
734
  throwValidation('bank details are required when method is "bank"');
700
735
  }
701
- let payload = { ...input, reference };
736
+ let payload = {
737
+ ...input,
738
+ reference,
739
+ customer: { ...input.customer, phoneNumber: normalizedPhone }
740
+ };
702
741
  const mutated = await runHook(config.hooks?.beforeCollect, payload);
703
742
  if (mutated != null)
704
743
  payload = { ...mutated, reference: mutated.reference ?? reference };
@@ -721,15 +760,20 @@ function createSdkInstance(config) {
721
760
  return createPaymentInstance(result.value, commonDeps);
722
761
  }
723
762
  async function collectPaymentAndResolve(input) {
724
- const reference = input.reference ?? generateReference();
725
- validateAmount(input.amount);
763
+ const reference = resolveReference(input.reference);
764
+ validateCollectionAmount(input.amount);
726
765
  validateNonEmpty(input.customer.name, "customer.name");
727
766
  validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
767
+ const normalizedPhone = normalizePhone(input.customer.phoneNumber);
728
768
  validateNonEmpty(input.description, "description");
729
769
  if (input.method === "bank" && !input.bank) {
730
770
  throwValidation('bank details are required when method is "bank"');
731
771
  }
732
- let payload = { ...input, reference };
772
+ let payload = {
773
+ ...input,
774
+ reference,
775
+ customer: { ...input.customer, phoneNumber: normalizedPhone }
776
+ };
733
777
  const mutated = await runHook(config.hooks?.beforeCollect, payload);
734
778
  if (mutated != null)
735
779
  payload = { ...mutated, reference: mutated.reference ?? reference };
@@ -748,10 +792,11 @@ function createSdkInstance(config) {
748
792
  return slangTs.Err(result.error);
749
793
  }
750
794
  async function makePayout(input) {
751
- const reference = input.reference ?? generateReference();
752
- validateAmount(input.amount);
795
+ const reference = resolveReference(input.reference);
796
+ validatePayoutAmount(input.amount);
753
797
  validateNonEmpty(input.customer.name, "customer.name");
754
798
  validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
799
+ const normalizedPhone = normalizePhone(input.customer.phoneNumber);
755
800
  validateNonEmpty(input.description, "description");
756
801
  validateNonEmpty(
757
802
  input.destination.accountHolderName,
@@ -761,7 +806,11 @@ function createSdkInstance(config) {
761
806
  input.destination.accountNumber,
762
807
  "destination.accountNumber"
763
808
  );
764
- let payload = { ...input, reference };
809
+ let payload = {
810
+ ...input,
811
+ reference,
812
+ customer: { ...input.customer, phoneNumber: normalizedPhone }
813
+ };
765
814
  const mutated = await runHook(config.hooks?.beforePayout, payload);
766
815
  if (mutated != null)
767
816
  payload = { ...mutated, reference: mutated.reference ?? reference };
@@ -784,10 +833,11 @@ function createSdkInstance(config) {
784
833
  return createPaymentInstance(result.value, commonDeps);
785
834
  }
786
835
  async function makePayoutAndResolve(input) {
787
- const reference = input.reference ?? generateReference();
788
- validateAmount(input.amount);
836
+ const reference = resolveReference(input.reference);
837
+ validatePayoutAmount(input.amount);
789
838
  validateNonEmpty(input.customer.name, "customer.name");
790
839
  validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
840
+ const normalizedPhone = normalizePhone(input.customer.phoneNumber);
791
841
  validateNonEmpty(input.description, "description");
792
842
  validateNonEmpty(
793
843
  input.destination.accountHolderName,
@@ -797,7 +847,11 @@ function createSdkInstance(config) {
797
847
  input.destination.accountNumber,
798
848
  "destination.accountNumber"
799
849
  );
800
- let payload = { ...input, reference };
850
+ let payload = {
851
+ ...input,
852
+ reference,
853
+ customer: { ...input.customer, phoneNumber: normalizedPhone }
854
+ };
801
855
  const mutated = await runHook(config.hooks?.beforePayout, payload);
802
856
  if (mutated != null)
803
857
  payload = { ...mutated, reference: mutated.reference ?? reference };
@@ -841,9 +895,10 @@ function createSdkInstance(config) {
841
895
  }
842
896
  async function verifyPhone(input) {
843
897
  validateNonEmpty(input.phoneNumber, "phoneNumber");
898
+ const normalizedPhone = normalizePhone(input.phoneNumber);
844
899
  const result = await transport.send({
845
900
  action: SDK_ACTIONS.verifyPhone,
846
- payload: input
901
+ payload: { ...input, phoneNumber: normalizedPhone }
847
902
  });
848
903
  if (result.isOk) {
849
904
  return slangTs.Ok(result.value);
@@ -851,8 +906,8 @@ function createSdkInstance(config) {
851
906
  return slangTs.Err(result.error);
852
907
  }
853
908
  async function createInvoice(input) {
854
- const reference = input.reference ?? generateReference();
855
- validateAmount(input.amount);
909
+ const reference = resolveReference(input.reference);
910
+ validateCollectionAmount(input.amount);
856
911
  validateNonEmpty(input.description, "description");
857
912
  if (input.items) {
858
913
  if (input.items.length > 50) {