@nile-squad/nylonpay-ts 1.0.9 → 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/dist/index.cjs +53 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +53 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -172,6 +172,12 @@ type Transaction = {
|
|
|
172
172
|
type: TransactionType;
|
|
173
173
|
method: PaymentMethod;
|
|
174
174
|
description: string;
|
|
175
|
+
/**
|
|
176
|
+
* Present (true) when this response replayed an existing transaction because
|
|
177
|
+
* the reference was already used — same reference, same transaction. No new
|
|
178
|
+
* payment was initiated; use a fresh reference to start a new one.
|
|
179
|
+
*/
|
|
180
|
+
duplicate?: boolean;
|
|
175
181
|
phone: string;
|
|
176
182
|
email: string | null;
|
|
177
183
|
failureReason: string | null;
|
|
@@ -321,12 +327,15 @@ type NylonPayConfig = {
|
|
|
321
327
|
* - `rate_limit` — too many requests.
|
|
322
328
|
* - `account` — merchant account missing or not active.
|
|
323
329
|
* - `provider` — payment provider/engine rejected the operation.
|
|
330
|
+
* - `duplicate` — the reference was already used for another transaction.
|
|
331
|
+
* Retrying with a NEW reference will pass; reusing the same reference
|
|
332
|
+
* replays the existing transaction instead of charging again.
|
|
324
333
|
* - `not_found` — referenced transaction does not exist.
|
|
325
334
|
* - `internal` — unexpected server-side failure.
|
|
326
335
|
* - `network` — request never reached the server (DNS, TLS, connection).
|
|
327
336
|
* - `timeout` — request exceeded the configured timeout.
|
|
328
337
|
*/
|
|
329
|
-
type SdkErrorCategory = "auth" | "validation" | "limit" | "rate_limit" | "account" | "provider" | "not_found" | "internal" | "network" | "timeout";
|
|
338
|
+
type SdkErrorCategory = "auth" | "validation" | "limit" | "rate_limit" | "account" | "provider" | "duplicate" | "not_found" | "internal" | "network" | "timeout";
|
|
330
339
|
/**
|
|
331
340
|
* Structured error returned by SDK operations. `category` is machine-readable
|
|
332
341
|
* for branching logic; `message` is human-readable for logs and alerts.
|
package/dist/index.d.ts
CHANGED
|
@@ -172,6 +172,12 @@ type Transaction = {
|
|
|
172
172
|
type: TransactionType;
|
|
173
173
|
method: PaymentMethod;
|
|
174
174
|
description: string;
|
|
175
|
+
/**
|
|
176
|
+
* Present (true) when this response replayed an existing transaction because
|
|
177
|
+
* the reference was already used — same reference, same transaction. No new
|
|
178
|
+
* payment was initiated; use a fresh reference to start a new one.
|
|
179
|
+
*/
|
|
180
|
+
duplicate?: boolean;
|
|
175
181
|
phone: string;
|
|
176
182
|
email: string | null;
|
|
177
183
|
failureReason: string | null;
|
|
@@ -321,12 +327,15 @@ type NylonPayConfig = {
|
|
|
321
327
|
* - `rate_limit` — too many requests.
|
|
322
328
|
* - `account` — merchant account missing or not active.
|
|
323
329
|
* - `provider` — payment provider/engine rejected the operation.
|
|
330
|
+
* - `duplicate` — the reference was already used for another transaction.
|
|
331
|
+
* Retrying with a NEW reference will pass; reusing the same reference
|
|
332
|
+
* replays the existing transaction instead of charging again.
|
|
324
333
|
* - `not_found` — referenced transaction does not exist.
|
|
325
334
|
* - `internal` — unexpected server-side failure.
|
|
326
335
|
* - `network` — request never reached the server (DNS, TLS, connection).
|
|
327
336
|
* - `timeout` — request exceeded the configured timeout.
|
|
328
337
|
*/
|
|
329
|
-
type SdkErrorCategory = "auth" | "validation" | "limit" | "rate_limit" | "account" | "provider" | "not_found" | "internal" | "network" | "timeout";
|
|
338
|
+
type SdkErrorCategory = "auth" | "validation" | "limit" | "rate_limit" | "account" | "provider" | "duplicate" | "not_found" | "internal" | "network" | "timeout";
|
|
330
339
|
/**
|
|
331
340
|
* Structured error returned by SDK operations. `category` is machine-readable
|
|
332
341
|
* for branching logic; `message` is human-readable for logs and alerts.
|
package/dist/index.js
CHANGED
|
@@ -147,6 +147,7 @@ var KNOWN_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
147
147
|
"rate_limit",
|
|
148
148
|
"account",
|
|
149
149
|
"provider",
|
|
150
|
+
"duplicate",
|
|
150
151
|
"not_found",
|
|
151
152
|
"internal",
|
|
152
153
|
"network",
|
|
@@ -593,6 +594,15 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
593
594
|
}
|
|
594
595
|
return paymentInstance;
|
|
595
596
|
}
|
|
597
|
+
|
|
598
|
+
// src/phone.ts
|
|
599
|
+
function normalizePhone(phone) {
|
|
600
|
+
let normalized = phone.replace(/\s+/g, "").replace(/^\+/, "");
|
|
601
|
+
if (normalized.startsWith("0") && normalized.length === 10) {
|
|
602
|
+
normalized = `256${normalized.slice(1)}`;
|
|
603
|
+
}
|
|
604
|
+
return normalized;
|
|
605
|
+
}
|
|
596
606
|
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
597
607
|
function decodePayload(payload) {
|
|
598
608
|
return typeof payload === "string" ? payload : Buffer.from(payload).toString("utf8");
|
|
@@ -668,10 +678,21 @@ async function runHook(hook, ...args) {
|
|
|
668
678
|
function throwValidation(message) {
|
|
669
679
|
throw createSdkError({ category: "validation", message });
|
|
670
680
|
}
|
|
671
|
-
function
|
|
681
|
+
function validateCollectionAmount(amount) {
|
|
682
|
+
if (!Number.isInteger(amount) || amount <= 0) {
|
|
683
|
+
throwValidation("amount must be a positive integer");
|
|
684
|
+
}
|
|
685
|
+
if (amount < 500) {
|
|
686
|
+
throwValidation("Collection amount must be at least 500 UGX");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function validatePayoutAmount(amount) {
|
|
672
690
|
if (!Number.isInteger(amount) || amount <= 0) {
|
|
673
691
|
throwValidation("amount must be a positive integer");
|
|
674
692
|
}
|
|
693
|
+
if (amount < 5e3) {
|
|
694
|
+
throwValidation("Payout amount must be at least 5000 UGX");
|
|
695
|
+
}
|
|
675
696
|
}
|
|
676
697
|
function validateNonEmpty(value, fieldName) {
|
|
677
698
|
if (!value || value.trim() === "") {
|
|
@@ -702,14 +723,19 @@ function createSdkInstance(config) {
|
|
|
702
723
|
};
|
|
703
724
|
async function collectPayment(input) {
|
|
704
725
|
const reference = resolveReference(input.reference);
|
|
705
|
-
|
|
726
|
+
validateCollectionAmount(input.amount);
|
|
706
727
|
validateNonEmpty(input.customer.name, "customer.name");
|
|
707
728
|
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
729
|
+
const normalizedPhone = normalizePhone(input.customer.phoneNumber);
|
|
708
730
|
validateNonEmpty(input.description, "description");
|
|
709
731
|
if (input.method === "bank" && !input.bank) {
|
|
710
732
|
throwValidation('bank details are required when method is "bank"');
|
|
711
733
|
}
|
|
712
|
-
let payload = {
|
|
734
|
+
let payload = {
|
|
735
|
+
...input,
|
|
736
|
+
reference,
|
|
737
|
+
customer: { ...input.customer, phoneNumber: normalizedPhone }
|
|
738
|
+
};
|
|
713
739
|
const mutated = await runHook(config.hooks?.beforeCollect, payload);
|
|
714
740
|
if (mutated != null)
|
|
715
741
|
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
@@ -733,14 +759,19 @@ function createSdkInstance(config) {
|
|
|
733
759
|
}
|
|
734
760
|
async function collectPaymentAndResolve(input) {
|
|
735
761
|
const reference = resolveReference(input.reference);
|
|
736
|
-
|
|
762
|
+
validateCollectionAmount(input.amount);
|
|
737
763
|
validateNonEmpty(input.customer.name, "customer.name");
|
|
738
764
|
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
765
|
+
const normalizedPhone = normalizePhone(input.customer.phoneNumber);
|
|
739
766
|
validateNonEmpty(input.description, "description");
|
|
740
767
|
if (input.method === "bank" && !input.bank) {
|
|
741
768
|
throwValidation('bank details are required when method is "bank"');
|
|
742
769
|
}
|
|
743
|
-
let payload = {
|
|
770
|
+
let payload = {
|
|
771
|
+
...input,
|
|
772
|
+
reference,
|
|
773
|
+
customer: { ...input.customer, phoneNumber: normalizedPhone }
|
|
774
|
+
};
|
|
744
775
|
const mutated = await runHook(config.hooks?.beforeCollect, payload);
|
|
745
776
|
if (mutated != null)
|
|
746
777
|
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
@@ -760,9 +791,10 @@ function createSdkInstance(config) {
|
|
|
760
791
|
}
|
|
761
792
|
async function makePayout(input) {
|
|
762
793
|
const reference = resolveReference(input.reference);
|
|
763
|
-
|
|
794
|
+
validatePayoutAmount(input.amount);
|
|
764
795
|
validateNonEmpty(input.customer.name, "customer.name");
|
|
765
796
|
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
797
|
+
const normalizedPhone = normalizePhone(input.customer.phoneNumber);
|
|
766
798
|
validateNonEmpty(input.description, "description");
|
|
767
799
|
validateNonEmpty(
|
|
768
800
|
input.destination.accountHolderName,
|
|
@@ -772,7 +804,11 @@ function createSdkInstance(config) {
|
|
|
772
804
|
input.destination.accountNumber,
|
|
773
805
|
"destination.accountNumber"
|
|
774
806
|
);
|
|
775
|
-
let payload = {
|
|
807
|
+
let payload = {
|
|
808
|
+
...input,
|
|
809
|
+
reference,
|
|
810
|
+
customer: { ...input.customer, phoneNumber: normalizedPhone }
|
|
811
|
+
};
|
|
776
812
|
const mutated = await runHook(config.hooks?.beforePayout, payload);
|
|
777
813
|
if (mutated != null)
|
|
778
814
|
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
@@ -796,9 +832,10 @@ function createSdkInstance(config) {
|
|
|
796
832
|
}
|
|
797
833
|
async function makePayoutAndResolve(input) {
|
|
798
834
|
const reference = resolveReference(input.reference);
|
|
799
|
-
|
|
835
|
+
validatePayoutAmount(input.amount);
|
|
800
836
|
validateNonEmpty(input.customer.name, "customer.name");
|
|
801
837
|
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
838
|
+
const normalizedPhone = normalizePhone(input.customer.phoneNumber);
|
|
802
839
|
validateNonEmpty(input.description, "description");
|
|
803
840
|
validateNonEmpty(
|
|
804
841
|
input.destination.accountHolderName,
|
|
@@ -808,7 +845,11 @@ function createSdkInstance(config) {
|
|
|
808
845
|
input.destination.accountNumber,
|
|
809
846
|
"destination.accountNumber"
|
|
810
847
|
);
|
|
811
|
-
let payload = {
|
|
848
|
+
let payload = {
|
|
849
|
+
...input,
|
|
850
|
+
reference,
|
|
851
|
+
customer: { ...input.customer, phoneNumber: normalizedPhone }
|
|
852
|
+
};
|
|
812
853
|
const mutated = await runHook(config.hooks?.beforePayout, payload);
|
|
813
854
|
if (mutated != null)
|
|
814
855
|
payload = { ...mutated, reference: mutated.reference ?? reference };
|
|
@@ -852,9 +893,10 @@ function createSdkInstance(config) {
|
|
|
852
893
|
}
|
|
853
894
|
async function verifyPhone(input) {
|
|
854
895
|
validateNonEmpty(input.phoneNumber, "phoneNumber");
|
|
896
|
+
const normalizedPhone = normalizePhone(input.phoneNumber);
|
|
855
897
|
const result = await transport.send({
|
|
856
898
|
action: SDK_ACTIONS.verifyPhone,
|
|
857
|
-
payload: input
|
|
899
|
+
payload: { ...input, phoneNumber: normalizedPhone }
|
|
858
900
|
});
|
|
859
901
|
if (result.isOk) {
|
|
860
902
|
return Ok(result.value);
|
|
@@ -863,7 +905,7 @@ function createSdkInstance(config) {
|
|
|
863
905
|
}
|
|
864
906
|
async function createInvoice(input) {
|
|
865
907
|
const reference = resolveReference(input.reference);
|
|
866
|
-
|
|
908
|
+
validateCollectionAmount(input.amount);
|
|
867
909
|
validateNonEmpty(input.description, "description");
|
|
868
910
|
if (input.items) {
|
|
869
911
|
if (input.items.length > 50) {
|