@nile-squad/nylonpay-ts 1.2.0 → 1.3.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/README.md +7 -10
- package/dist/index.cjs +58 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -1
- package/dist/index.d.ts +97 -1
- package/dist/index.js +59 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -89,6 +89,13 @@ type CollectPaymentInput = {
|
|
|
89
89
|
method?: PaymentMethod;
|
|
90
90
|
bank?: BankDetails;
|
|
91
91
|
metadata?: Record<string, string>;
|
|
92
|
+
/**
|
|
93
|
+
* Business labels to attach to the transaction. Normalized to lowercase;
|
|
94
|
+
* reserved tags (`"live"`, `"test"`) are managed automatically. Max 10 tags,
|
|
95
|
+
* each up to 50 characters. Useful for grouping payments by campaign, product,
|
|
96
|
+
* team, or any merchant-defined dimension.
|
|
97
|
+
*/
|
|
98
|
+
tags?: string[];
|
|
92
99
|
};
|
|
93
100
|
/**
|
|
94
101
|
* Input for initiating a payout. Use this to disburse funds to a
|
|
@@ -102,6 +109,8 @@ type MakePayoutInput = {
|
|
|
102
109
|
description: string;
|
|
103
110
|
reference?: string;
|
|
104
111
|
metadata?: Record<string, string>;
|
|
112
|
+
/** Business labels — same normalization rules as {@link CollectPaymentInput.tags}. */
|
|
113
|
+
tags?: string[];
|
|
105
114
|
};
|
|
106
115
|
/**
|
|
107
116
|
* Input for a one-shot status check. Does not start polling; returns
|
|
@@ -140,6 +149,8 @@ type CreateInvoiceInput = {
|
|
|
140
149
|
redirectUrl?: string;
|
|
141
150
|
reference?: string;
|
|
142
151
|
metadata?: Record<string, string>;
|
|
152
|
+
/** Business labels — same normalization rules as {@link CollectPaymentInput.tags}. */
|
|
153
|
+
tags?: string[];
|
|
143
154
|
};
|
|
144
155
|
/**
|
|
145
156
|
* Input for verifying a webhook signature. Operates on raw payload bytes
|
|
@@ -414,6 +425,59 @@ type EventData = {
|
|
|
414
425
|
* data (if available), and timestamp.
|
|
415
426
|
*/
|
|
416
427
|
type PaymentEventHandler = (data: EventData) => void;
|
|
428
|
+
/**
|
|
429
|
+
* Lightweight transaction summary returned by `listTransactions`. Carries the
|
|
430
|
+
* fields needed for listing and aggregation without the full provider detail
|
|
431
|
+
* that `getTransaction` exposes. `tags` is included so merchants can see which
|
|
432
|
+
* labels a row carries while paginating.
|
|
433
|
+
*/
|
|
434
|
+
type TransactionSummary = {
|
|
435
|
+
id: string;
|
|
436
|
+
reference: string;
|
|
437
|
+
amount: number;
|
|
438
|
+
currency: Currency;
|
|
439
|
+
status: TransactionStatus;
|
|
440
|
+
type: TransactionType;
|
|
441
|
+
method: string | null;
|
|
442
|
+
mode: TransactionMode;
|
|
443
|
+
/** Smart tags on the transaction (system + developer). */
|
|
444
|
+
tags: string[];
|
|
445
|
+
createdAt: string;
|
|
446
|
+
updatedAt: string;
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Input for listing and filtering transactions. All fields are optional —
|
|
450
|
+
* omitting filters returns the most recent transactions for the calling
|
|
451
|
+
* key's account and mode.
|
|
452
|
+
*/
|
|
453
|
+
type ListTransactionsInput = {
|
|
454
|
+
/** Filter to transactions carrying ALL of these tags (AND semantics). */
|
|
455
|
+
tags?: string[];
|
|
456
|
+
status?: TransactionStatus;
|
|
457
|
+
type?: TransactionType;
|
|
458
|
+
/** Maximum number of results (1–100, default 20). */
|
|
459
|
+
limit?: number;
|
|
460
|
+
/** Zero-based offset for pagination (default 0). */
|
|
461
|
+
offset?: number;
|
|
462
|
+
/** ISO 8601 datetime — only transactions created at or after this time. */
|
|
463
|
+
createdAfter?: string;
|
|
464
|
+
/** ISO 8601 datetime — only transactions created at or before this time. */
|
|
465
|
+
createdBefore?: string;
|
|
466
|
+
};
|
|
467
|
+
/**
|
|
468
|
+
* Response from `listTransactions`. Includes the page of summaries, the count
|
|
469
|
+
* returned, and the effective pagination/filter parameters so callers can build
|
|
470
|
+
* pagination UIs without round-tripping for metadata.
|
|
471
|
+
*/
|
|
472
|
+
type ListTransactionsResponse = {
|
|
473
|
+
transactions: TransactionSummary[];
|
|
474
|
+
/** Number of transactions in this page (≤ limit). */
|
|
475
|
+
count: number;
|
|
476
|
+
limit: number;
|
|
477
|
+
offset: number;
|
|
478
|
+
/** The normalized tags that were applied as filters (empty if no tag filter). */
|
|
479
|
+
tags: string[];
|
|
480
|
+
};
|
|
417
481
|
/**
|
|
418
482
|
* SDK instance returned by the factory. Provides all payment operations
|
|
419
483
|
* and the webhook verification utility.
|
|
@@ -569,6 +633,38 @@ interface NylonPaySdk {
|
|
|
569
633
|
* ```
|
|
570
634
|
*/
|
|
571
635
|
createInvoice(input: CreateInvoiceInput): Promise<Result<InvoiceResponse, string>>;
|
|
636
|
+
/**
|
|
637
|
+
* List transactions for the calling key's account, with optional filtering.
|
|
638
|
+
* Returns lightweight summaries — use `getTransaction` for full detail.
|
|
639
|
+
*
|
|
640
|
+
* Tag filters use AND semantics: `tags: ["vip", "promo"]` returns only
|
|
641
|
+
* transactions tagged with BOTH labels. Tags are normalized (lowercase) before
|
|
642
|
+
* matching, so `"VIP"` and `"vip"` match the same rows.
|
|
643
|
+
*
|
|
644
|
+
* @example
|
|
645
|
+
* ```ts
|
|
646
|
+
* // All transactions tagged "vip" from the last week
|
|
647
|
+
* const result = await nylonpay.listTransactions({
|
|
648
|
+
* tags: ["vip"],
|
|
649
|
+
* createdAfter: new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString(),
|
|
650
|
+
* });
|
|
651
|
+
* if (result.isOk) console.log(result.value.transactions);
|
|
652
|
+
* ```
|
|
653
|
+
*/
|
|
654
|
+
listTransactions(input?: ListTransactionsInput): Promise<Result<ListTransactionsResponse, string>>;
|
|
655
|
+
/**
|
|
656
|
+
* Convenience wrapper around `listTransactions` that filters by a single tag.
|
|
657
|
+
* Equivalent to `listTransactions({ tags: [tag], ...options })`.
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* ```ts
|
|
661
|
+
* const result = await nylonpay.getTransactionsByTag("campaign-q2");
|
|
662
|
+
* if (result.isOk) {
|
|
663
|
+
* const total = result.value.transactions.reduce((s, t) => s + t.amount, 0);
|
|
664
|
+
* }
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
getTransactionsByTag(tag: string, options?: Omit<ListTransactionsInput, "tags">): Promise<Result<ListTransactionsResponse, string>>;
|
|
572
668
|
/**
|
|
573
669
|
* Verify that an incoming webhook payload was signed by Nylon Pay.
|
|
574
670
|
* Operates on raw payload bytes (string or Uint8Array), not parsed JSON,
|
|
@@ -735,7 +831,7 @@ declare function parseError(error: string): SdkError;
|
|
|
735
831
|
* retries hours later, is re-stamped and re-signed, so this never rejects
|
|
736
832
|
* legitimate traffic. Pass `toleranceSeconds: 0` to skip this check.
|
|
737
833
|
*
|
|
738
|
-
* @returns True when the signature is valid and (when enforced) the webhook is fresh
|
|
834
|
+
* @returns True when the signature is valid and (when enforced) the webhook is fresh. Never throws — returns false on any error.
|
|
739
835
|
*/
|
|
740
836
|
declare function verifyWebhookSignature(input: VerifyWebhookInput): boolean;
|
|
741
837
|
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash, createHmac, timingSafeEqual, randomBytes } from 'crypto';
|
|
1
|
+
import { createHash, createHmac, timingSafeEqual, randomUUID, randomBytes } from 'crypto';
|
|
2
2
|
import { Ok, Err, safeTry } from 'slang-ts';
|
|
3
3
|
import { type, platform, arch, release, hostname } from 'os';
|
|
4
4
|
|
|
@@ -70,10 +70,12 @@ var SDK_ACTIONS = {
|
|
|
70
70
|
makePayoutAndResolve: "sdk-make-payout-and-resolve",
|
|
71
71
|
getStatus: "sdk-get-status",
|
|
72
72
|
getTransaction: "sdk-get-transaction",
|
|
73
|
+
listTransactions: "sdk-list-transactions",
|
|
73
74
|
verifyPhone: "sdk-verify-phone",
|
|
74
75
|
createInvoice: "sdk-create-invoice"
|
|
75
76
|
};
|
|
76
77
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
78
|
+
var MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
77
79
|
function generateFingerprint() {
|
|
78
80
|
const components = [
|
|
79
81
|
`type:${type()}`,
|
|
@@ -268,6 +270,17 @@ function createTransport({
|
|
|
268
270
|
body: bodyString,
|
|
269
271
|
signal: controller.signal
|
|
270
272
|
});
|
|
273
|
+
const contentLength = response.headers?.get("content-length");
|
|
274
|
+
if (contentLength && Number(contentLength) > MAX_RESPONSE_BYTES) {
|
|
275
|
+
cleanup();
|
|
276
|
+
return Err(
|
|
277
|
+
JSON.stringify({
|
|
278
|
+
category: "internal",
|
|
279
|
+
message: "Received an invalid response from the server",
|
|
280
|
+
retryable: false
|
|
281
|
+
})
|
|
282
|
+
);
|
|
283
|
+
}
|
|
271
284
|
if (!response.ok) {
|
|
272
285
|
const statusCode = response.status;
|
|
273
286
|
const retryable = RETRYABLE_STATUS_CODES.has(statusCode);
|
|
@@ -655,43 +668,44 @@ function extractSignedTimestampMs(payloadString) {
|
|
|
655
668
|
return null;
|
|
656
669
|
}
|
|
657
670
|
function verifyWebhookSignature(input) {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
671
|
+
try {
|
|
672
|
+
const payloadString = decodePayload(input.payload);
|
|
673
|
+
const payloadBytes = Buffer.from(payloadString, "utf8");
|
|
674
|
+
const expectedSignature = createHmac("sha256", input.secret).update(payloadBytes).digest("hex");
|
|
675
|
+
const providedBuffer = Buffer.from(input.signature, "hex");
|
|
676
|
+
const expectedBuffer = Buffer.from(expectedSignature, "hex");
|
|
677
|
+
if (providedBuffer.length !== expectedBuffer.length) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
if (!timingSafeEqual(providedBuffer, expectedBuffer)) {
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
const toleranceSeconds = input.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
684
|
+
if (toleranceSeconds === 0) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
if (toleranceSeconds < 0) {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
const timestampMs = extractSignedTimestampMs(payloadString);
|
|
691
|
+
if (timestampMs === null) {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
const ageMs = Math.abs(Date.now() - timestampMs);
|
|
695
|
+
return ageMs <= toleranceSeconds * 1e3;
|
|
696
|
+
} catch {
|
|
675
697
|
return false;
|
|
676
698
|
}
|
|
677
|
-
const ageMs = Math.abs(Date.now() - timestampMs);
|
|
678
|
-
return ageMs <= toleranceSeconds * 1e3;
|
|
679
699
|
}
|
|
680
700
|
|
|
681
701
|
// src/sdk.ts
|
|
682
|
-
|
|
683
|
-
return randomBytes(16).toString("hex").slice(0, 15);
|
|
684
|
-
}
|
|
685
|
-
var REFERENCE_MIN_LENGTH = 13;
|
|
686
|
-
var REFERENCE_MAX_LENGTH = 15;
|
|
702
|
+
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
687
703
|
function resolveReference(reference) {
|
|
688
704
|
if (reference === void 0) {
|
|
689
|
-
return
|
|
705
|
+
return randomUUID();
|
|
690
706
|
}
|
|
691
|
-
if (
|
|
692
|
-
throwValidation(
|
|
693
|
-
`reference must be ${REFERENCE_MIN_LENGTH}\u2013${REFERENCE_MAX_LENGTH} characters`
|
|
694
|
-
);
|
|
707
|
+
if (!UUID_REGEX.test(reference)) {
|
|
708
|
+
throwValidation("reference must be a valid UUID");
|
|
695
709
|
}
|
|
696
710
|
return reference;
|
|
697
711
|
}
|
|
@@ -958,6 +972,19 @@ function createSdkInstance(config) {
|
|
|
958
972
|
}
|
|
959
973
|
return Err(result.error);
|
|
960
974
|
}
|
|
975
|
+
async function listTransactions(input) {
|
|
976
|
+
const result = await transport.send({
|
|
977
|
+
action: SDK_ACTIONS.listTransactions,
|
|
978
|
+
payload: input ?? {}
|
|
979
|
+
});
|
|
980
|
+
if (result.isOk) {
|
|
981
|
+
return Ok(result.value);
|
|
982
|
+
}
|
|
983
|
+
return Err(result.error);
|
|
984
|
+
}
|
|
985
|
+
async function getTransactionsByTag(tag, options) {
|
|
986
|
+
return listTransactions({ ...options, tags: [tag] });
|
|
987
|
+
}
|
|
961
988
|
function verifyWebhook(input) {
|
|
962
989
|
return verifyWebhookSignature(input);
|
|
963
990
|
}
|
|
@@ -968,6 +995,8 @@ function createSdkInstance(config) {
|
|
|
968
995
|
makePayoutAndResolve,
|
|
969
996
|
getStatus,
|
|
970
997
|
getTransaction,
|
|
998
|
+
listTransactions,
|
|
999
|
+
getTransactionsByTag,
|
|
971
1000
|
verifyPhone,
|
|
972
1001
|
createInvoice,
|
|
973
1002
|
verifyWebhookSignature: verifyWebhook
|