@nile-squad/nylonpay-ts 1.0.10 → 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 +133 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -9
- package/dist/index.d.ts +35 -9
- package/dist/index.js +133 -105
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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: "
|
|
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: "
|
|
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: "
|
|
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 ?
|
|
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) {
|
|
@@ -375,6 +375,7 @@ function parseError(error) {
|
|
|
375
375
|
|
|
376
376
|
// src/payment.ts
|
|
377
377
|
var STATUS_TO_EVENT = {
|
|
378
|
+
pending: "processing",
|
|
378
379
|
successful: "success",
|
|
379
380
|
failed: "failed",
|
|
380
381
|
processing: "processing",
|
|
@@ -398,6 +399,7 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
398
399
|
status: normalizeStatus(initialResponse.status),
|
|
399
400
|
transaction: null,
|
|
400
401
|
pollingTimer: null,
|
|
402
|
+
lastStatusEvent: null,
|
|
401
403
|
resolved: false,
|
|
402
404
|
pollAttempts: 0,
|
|
403
405
|
pollStartTime: Date.now(),
|
|
@@ -408,14 +410,21 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
408
410
|
maxPollDuration: deps.maxPollDuration ?? 3e5,
|
|
409
411
|
maxPollAttempts: deps.maxPollAttempts ?? 150
|
|
410
412
|
};
|
|
411
|
-
function resolveWithError(error) {
|
|
413
|
+
function resolveWithError(error, category, retryable) {
|
|
412
414
|
state.resolved = true;
|
|
413
415
|
stopUpdates();
|
|
414
|
-
|
|
416
|
+
const parsed = parseError(error);
|
|
417
|
+
emitEvent(
|
|
418
|
+
"error",
|
|
419
|
+
parsed.message,
|
|
420
|
+
category ?? parsed.category,
|
|
421
|
+
parsed.retryable
|
|
422
|
+
);
|
|
415
423
|
}
|
|
416
424
|
function emitEvent(event, error, category, retryable) {
|
|
417
425
|
const data = {
|
|
418
426
|
event,
|
|
427
|
+
reference: state.reference,
|
|
419
428
|
transaction: state.transaction ?? void 0,
|
|
420
429
|
error,
|
|
421
430
|
category,
|
|
@@ -447,30 +456,30 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
447
456
|
}
|
|
448
457
|
if (response.reference !== state.reference) {
|
|
449
458
|
resolveWithError(
|
|
450
|
-
|
|
459
|
+
"Received a status update for a different transaction",
|
|
460
|
+
"internal"
|
|
451
461
|
);
|
|
452
462
|
return;
|
|
453
463
|
}
|
|
454
464
|
const newStatus = normalizeStatus(response.status);
|
|
455
|
-
const oldStatus = state.status;
|
|
456
465
|
state.status = newStatus;
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (TERMINAL_STATES.has(newStatus)) {
|
|
461
|
-
await handleTerminalState(newStatus);
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
emitEvent(event);
|
|
465
|
-
}
|
|
466
|
+
const event = statusToEvent(newStatus);
|
|
467
|
+
if (!event || event === state.lastStatusEvent) {
|
|
468
|
+
return;
|
|
466
469
|
}
|
|
470
|
+
state.lastStatusEvent = event;
|
|
471
|
+
if (TERMINAL_STATES.has(newStatus)) {
|
|
472
|
+
await handleTerminalState(newStatus);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
emitEvent(event);
|
|
467
476
|
}
|
|
468
477
|
function handlePollError(error) {
|
|
469
478
|
const parsed = parseError(error);
|
|
470
479
|
if (parsed.category === "not_found") {
|
|
471
480
|
return;
|
|
472
481
|
}
|
|
473
|
-
emitEvent("error", parsed.message);
|
|
482
|
+
emitEvent("error", parsed.message, parsed.category, parsed.retryable);
|
|
474
483
|
state.resolved = true;
|
|
475
484
|
stopUpdates();
|
|
476
485
|
}
|
|
@@ -490,11 +499,17 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
490
499
|
return;
|
|
491
500
|
}
|
|
492
501
|
if (state.pollAttempts >= state.maxPollAttempts) {
|
|
493
|
-
resolveWithError(
|
|
502
|
+
resolveWithError(
|
|
503
|
+
"Timed out waiting for the transaction status to update",
|
|
504
|
+
"timeout"
|
|
505
|
+
);
|
|
494
506
|
return;
|
|
495
507
|
}
|
|
496
508
|
if (Date.now() - state.pollStartTime >= state.maxPollDuration) {
|
|
497
|
-
resolveWithError(
|
|
509
|
+
resolveWithError(
|
|
510
|
+
"Timed out waiting for the transaction status to update",
|
|
511
|
+
"timeout"
|
|
512
|
+
);
|
|
498
513
|
return;
|
|
499
514
|
}
|
|
500
515
|
state.pollAttempts += 1;
|
|
@@ -517,6 +532,15 @@ function createPaymentInstance(initialResponse, deps) {
|
|
|
517
532
|
}, 0);
|
|
518
533
|
return;
|
|
519
534
|
}
|
|
535
|
+
const initialEvent = statusToEvent(state.status);
|
|
536
|
+
if (initialEvent) {
|
|
537
|
+
state.lastStatusEvent = initialEvent;
|
|
538
|
+
setTimeout(() => {
|
|
539
|
+
if (!state.resolved) {
|
|
540
|
+
emitEvent(initialEvent);
|
|
541
|
+
}
|
|
542
|
+
}, 0);
|
|
543
|
+
}
|
|
520
544
|
scheduleNextPoll();
|
|
521
545
|
}
|
|
522
546
|
function stopUpdates() {
|
|
@@ -605,6 +629,9 @@ function normalizePhone(phone) {
|
|
|
605
629
|
}
|
|
606
630
|
return normalized;
|
|
607
631
|
}
|
|
632
|
+
function isValidPhoneFormat(normalizedPhone) {
|
|
633
|
+
return /^\d{9,15}$/.test(normalizedPhone);
|
|
634
|
+
}
|
|
608
635
|
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
609
636
|
function decodePayload(payload) {
|
|
610
637
|
return typeof payload === "string" ? payload : Buffer.from(payload).toString("utf8");
|
|
@@ -701,6 +728,56 @@ function validateNonEmpty(value, fieldName) {
|
|
|
701
728
|
throwValidation(`${fieldName} is required`);
|
|
702
729
|
}
|
|
703
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
|
+
}
|
|
704
781
|
function createSdkInstance(config) {
|
|
705
782
|
const transport = createTransport({
|
|
706
783
|
apiKey: config.apiKey,
|
|
@@ -724,23 +801,15 @@ function createSdkInstance(config) {
|
|
|
724
801
|
maxPollAttempts: config.maxPollAttempts
|
|
725
802
|
};
|
|
726
803
|
async function collectPayment(input) {
|
|
727
|
-
|
|
728
|
-
validateCollectionAmount(input.amount);
|
|
729
|
-
validateNonEmpty(input.customer.name, "customer.name");
|
|
730
|
-
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
731
|
-
const normalizedPhone = normalizePhone(input.customer.phoneNumber);
|
|
732
|
-
validateNonEmpty(input.description, "description");
|
|
733
|
-
if (input.method === "bank" && !input.bank) {
|
|
734
|
-
throwValidation('bank details are required when method is "bank"');
|
|
735
|
-
}
|
|
736
|
-
let payload = {
|
|
737
|
-
...input,
|
|
738
|
-
reference,
|
|
739
|
-
customer: { ...input.customer, phoneNumber: normalizedPhone }
|
|
740
|
-
};
|
|
804
|
+
let payload = prepareCollectPayload(input);
|
|
741
805
|
const mutated = await runHook(config.hooks?.beforeCollect, payload);
|
|
742
|
-
if (mutated != null)
|
|
743
|
-
payload =
|
|
806
|
+
if (mutated != null) {
|
|
807
|
+
payload = applyBeforeHookMutation(
|
|
808
|
+
mutated,
|
|
809
|
+
payload,
|
|
810
|
+
prepareCollectPayload
|
|
811
|
+
);
|
|
812
|
+
}
|
|
744
813
|
const result = await transport.send({
|
|
745
814
|
action: SDK_ACTIONS.collectPayment,
|
|
746
815
|
payload
|
|
@@ -748,35 +817,27 @@ function createSdkInstance(config) {
|
|
|
748
817
|
await runHook(
|
|
749
818
|
config.hooks?.afterCollect,
|
|
750
819
|
result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
|
|
751
|
-
payload
|
|
820
|
+
{ ...payload, raw: input }
|
|
752
821
|
);
|
|
753
822
|
if (result.isErr) {
|
|
754
823
|
const sdkErr = parseError(result.error);
|
|
755
824
|
return createPaymentInstance(
|
|
756
|
-
{ reference, status: "pending" },
|
|
825
|
+
{ reference: payload.reference, status: "pending" },
|
|
757
826
|
{ ...commonDeps, initialError: sdkErr }
|
|
758
827
|
);
|
|
759
828
|
}
|
|
760
829
|
return createPaymentInstance(result.value, commonDeps);
|
|
761
830
|
}
|
|
762
831
|
async function collectPaymentAndResolve(input) {
|
|
763
|
-
|
|
764
|
-
validateCollectionAmount(input.amount);
|
|
765
|
-
validateNonEmpty(input.customer.name, "customer.name");
|
|
766
|
-
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
767
|
-
const normalizedPhone = normalizePhone(input.customer.phoneNumber);
|
|
768
|
-
validateNonEmpty(input.description, "description");
|
|
769
|
-
if (input.method === "bank" && !input.bank) {
|
|
770
|
-
throwValidation('bank details are required when method is "bank"');
|
|
771
|
-
}
|
|
772
|
-
let payload = {
|
|
773
|
-
...input,
|
|
774
|
-
reference,
|
|
775
|
-
customer: { ...input.customer, phoneNumber: normalizedPhone }
|
|
776
|
-
};
|
|
832
|
+
let payload = prepareCollectPayload(input);
|
|
777
833
|
const mutated = await runHook(config.hooks?.beforeCollect, payload);
|
|
778
|
-
if (mutated != null)
|
|
779
|
-
payload =
|
|
834
|
+
if (mutated != null) {
|
|
835
|
+
payload = applyBeforeHookMutation(
|
|
836
|
+
mutated,
|
|
837
|
+
payload,
|
|
838
|
+
prepareCollectPayload
|
|
839
|
+
);
|
|
840
|
+
}
|
|
780
841
|
const result = await transport.send({
|
|
781
842
|
action: SDK_ACTIONS.collectPaymentAndResolve,
|
|
782
843
|
payload
|
|
@@ -784,7 +845,7 @@ function createSdkInstance(config) {
|
|
|
784
845
|
await runHook(
|
|
785
846
|
config.hooks?.afterCollect,
|
|
786
847
|
result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
|
|
787
|
-
payload
|
|
848
|
+
{ ...payload, raw: input }
|
|
788
849
|
);
|
|
789
850
|
if (result.isOk) {
|
|
790
851
|
return slangTs.Ok(result.value);
|
|
@@ -792,28 +853,11 @@ function createSdkInstance(config) {
|
|
|
792
853
|
return slangTs.Err(result.error);
|
|
793
854
|
}
|
|
794
855
|
async function makePayout(input) {
|
|
795
|
-
|
|
796
|
-
validatePayoutAmount(input.amount);
|
|
797
|
-
validateNonEmpty(input.customer.name, "customer.name");
|
|
798
|
-
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
799
|
-
const normalizedPhone = normalizePhone(input.customer.phoneNumber);
|
|
800
|
-
validateNonEmpty(input.description, "description");
|
|
801
|
-
validateNonEmpty(
|
|
802
|
-
input.destination.accountHolderName,
|
|
803
|
-
"destination.accountHolderName"
|
|
804
|
-
);
|
|
805
|
-
validateNonEmpty(
|
|
806
|
-
input.destination.accountNumber,
|
|
807
|
-
"destination.accountNumber"
|
|
808
|
-
);
|
|
809
|
-
let payload = {
|
|
810
|
-
...input,
|
|
811
|
-
reference,
|
|
812
|
-
customer: { ...input.customer, phoneNumber: normalizedPhone }
|
|
813
|
-
};
|
|
856
|
+
let payload = preparePayoutPayload(input);
|
|
814
857
|
const mutated = await runHook(config.hooks?.beforePayout, payload);
|
|
815
|
-
if (mutated != null)
|
|
816
|
-
payload =
|
|
858
|
+
if (mutated != null) {
|
|
859
|
+
payload = applyBeforeHookMutation(mutated, payload, preparePayoutPayload);
|
|
860
|
+
}
|
|
817
861
|
const result = await transport.send({
|
|
818
862
|
action: SDK_ACTIONS.makePayout,
|
|
819
863
|
payload
|
|
@@ -821,40 +865,23 @@ function createSdkInstance(config) {
|
|
|
821
865
|
await runHook(
|
|
822
866
|
config.hooks?.afterPayout,
|
|
823
867
|
result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
|
|
824
|
-
payload
|
|
868
|
+
{ ...payload, raw: input }
|
|
825
869
|
);
|
|
826
870
|
if (result.isErr) {
|
|
827
871
|
const sdkErr = parseError(result.error);
|
|
828
872
|
return createPaymentInstance(
|
|
829
|
-
{ reference, status: "pending" },
|
|
873
|
+
{ reference: payload.reference, status: "pending" },
|
|
830
874
|
{ ...commonDeps, initialError: sdkErr }
|
|
831
875
|
);
|
|
832
876
|
}
|
|
833
877
|
return createPaymentInstance(result.value, commonDeps);
|
|
834
878
|
}
|
|
835
879
|
async function makePayoutAndResolve(input) {
|
|
836
|
-
|
|
837
|
-
validatePayoutAmount(input.amount);
|
|
838
|
-
validateNonEmpty(input.customer.name, "customer.name");
|
|
839
|
-
validateNonEmpty(input.customer.phoneNumber, "customer.phoneNumber");
|
|
840
|
-
const normalizedPhone = normalizePhone(input.customer.phoneNumber);
|
|
841
|
-
validateNonEmpty(input.description, "description");
|
|
842
|
-
validateNonEmpty(
|
|
843
|
-
input.destination.accountHolderName,
|
|
844
|
-
"destination.accountHolderName"
|
|
845
|
-
);
|
|
846
|
-
validateNonEmpty(
|
|
847
|
-
input.destination.accountNumber,
|
|
848
|
-
"destination.accountNumber"
|
|
849
|
-
);
|
|
850
|
-
let payload = {
|
|
851
|
-
...input,
|
|
852
|
-
reference,
|
|
853
|
-
customer: { ...input.customer, phoneNumber: normalizedPhone }
|
|
854
|
-
};
|
|
880
|
+
let payload = preparePayoutPayload(input);
|
|
855
881
|
const mutated = await runHook(config.hooks?.beforePayout, payload);
|
|
856
|
-
if (mutated != null)
|
|
857
|
-
payload =
|
|
882
|
+
if (mutated != null) {
|
|
883
|
+
payload = applyBeforeHookMutation(mutated, payload, preparePayoutPayload);
|
|
884
|
+
}
|
|
858
885
|
const result = await transport.send({
|
|
859
886
|
action: SDK_ACTIONS.makePayoutAndResolve,
|
|
860
887
|
payload
|
|
@@ -862,7 +889,7 @@ function createSdkInstance(config) {
|
|
|
862
889
|
await runHook(
|
|
863
890
|
config.hooks?.afterPayout,
|
|
864
891
|
result.isOk ? slangTs.Ok({ reference: result.value.reference, status: result.value.status }) : slangTs.Err(result.error),
|
|
865
|
-
payload
|
|
892
|
+
{ ...payload, raw: input }
|
|
866
893
|
);
|
|
867
894
|
if (result.isOk) {
|
|
868
895
|
return slangTs.Ok(result.value);
|
|
@@ -896,6 +923,7 @@ function createSdkInstance(config) {
|
|
|
896
923
|
async function verifyPhone(input) {
|
|
897
924
|
validateNonEmpty(input.phoneNumber, "phoneNumber");
|
|
898
925
|
const normalizedPhone = normalizePhone(input.phoneNumber);
|
|
926
|
+
validatePhoneFormat(normalizedPhone, "phoneNumber");
|
|
899
927
|
const result = await transport.send({
|
|
900
928
|
action: SDK_ACTIONS.verifyPhone,
|
|
901
929
|
payload: { ...input, phoneNumber: normalizedPhone }
|