@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 +8 -6
- package/dist/index.cjs +71 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -5
- package/dist/index.d.ts +14 -5
- package/dist/index.js +71 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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/
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
694
|
-
|
|
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 = {
|
|
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
|
|
725
|
-
|
|
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 = {
|
|
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
|
|
752
|
-
|
|
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 = {
|
|
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
|
|
788
|
-
|
|
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 = {
|
|
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
|
|
855
|
-
|
|
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) {
|