@paymos/sdk 1.0.0 → 2.0.3

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.js CHANGED
@@ -1,6 +1,37 @@
1
1
  // src/index.ts
2
2
  import { randomUUID } from "crypto";
3
3
 
4
+ // src/wire.ts
5
+ function toWire(value) {
6
+ return mapKeys(value, (key) => key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`));
7
+ }
8
+ function fromWire(value) {
9
+ return mapKeys(value, (key) => key.replace(/_([a-z0-9])/g, (_, letter) => letter.toUpperCase()));
10
+ }
11
+ function mapKeys(value, rename) {
12
+ if (Array.isArray(value)) return value.map((item) => mapKeys(item, rename));
13
+ if (!isPlainObject(value)) return value;
14
+ const mapped = {};
15
+ for (const [key, item] of Object.entries(value)) {
16
+ const mappedKey = rename(key);
17
+ if (Object.hasOwn(mapped, mappedKey)) {
18
+ throw new TypeError(`Duplicate Paymos field after case conversion: ${mappedKey}`);
19
+ }
20
+ Object.defineProperty(mapped, mappedKey, {
21
+ value: mapKeys(item, rename),
22
+ enumerable: true,
23
+ configurable: true,
24
+ writable: true
25
+ });
26
+ }
27
+ return mapped;
28
+ }
29
+ function isPlainObject(value) {
30
+ if (value === null || typeof value !== "object") return false;
31
+ const prototype = Object.getPrototypeOf(value);
32
+ return prototype === Object.prototype || prototype === null;
33
+ }
34
+
4
35
  // src/errors.ts
5
36
  var PaymosError = class extends Error {
6
37
  constructor(message, options) {
@@ -20,8 +51,8 @@ var ApiError = class extends PaymosError {
20
51
  headers;
21
52
  problem;
22
53
  constructor(status, body, headers) {
23
- const problem = parseProblem(body);
24
- const detail = problem?.detail ?? problem?.errors?.[0]?.message ?? problem?.code ?? problem?.title;
54
+ const problem = parseProblem(status, body);
55
+ const detail = problem?.detail || problem?.code || problem?.title;
25
56
  super(`Paymos API ${status}: ${detail || body || "empty response"}`);
26
57
  this.status = status;
27
58
  this.body = body;
@@ -29,10 +60,10 @@ var ApiError = class extends PaymosError {
29
60
  this.problem = problem;
30
61
  }
31
62
  get code() {
32
- return this.problem?.errors?.[0]?.code ?? this.problem?.code ?? "";
63
+ return this.problem?.code ?? "";
33
64
  }
34
65
  get field() {
35
- return this.problem?.errors?.[0]?.field ?? this.problem?.field ?? null;
66
+ return this.problem?.field ?? null;
36
67
  }
37
68
  get errors() {
38
69
  return this.problem?.errors ?? [];
@@ -72,11 +103,13 @@ function apiErrorFromResponse(status, body, headers) {
72
103
  if (status >= 500) return new ServerError(status, body, headers);
73
104
  return new ApiError(status, body, headers);
74
105
  }
75
- function parseProblem(body) {
106
+ function parseProblem(status, body) {
76
107
  if (!body) return null;
77
108
  try {
78
109
  const parsed = JSON.parse(body);
79
- return parsed !== null && typeof parsed === "object" ? parsed : null;
110
+ if (parsed === null || typeof parsed !== "object") return null;
111
+ const problem = fromWire(parsed);
112
+ return typeof problem.type === "string" && typeof problem.title === "string" && typeof problem.status === "number" && Number.isInteger(problem.status) && problem.status === status && typeof problem.detail === "string" && typeof problem.code === "string" ? problem : null;
80
113
  } catch {
81
114
  return null;
82
115
  }
@@ -127,7 +160,7 @@ function rfc3986(value) {
127
160
  }
128
161
 
129
162
  // src/version.ts
130
- var SDK_VERSION = "1.0.0";
163
+ var SDK_VERSION = "2.0.3";
131
164
 
132
165
  // src/http.ts
133
166
  var HttpClient = class {
@@ -136,7 +169,7 @@ var HttpClient = class {
136
169
  this.options = normalizeOptions(options);
137
170
  }
138
171
  async request(method, path, payload, query = "") {
139
- const body = payload === void 0 ? "" : JSON.stringify(payload);
172
+ const body = payload === void 0 ? "" : JSON.stringify(toWire(payload));
140
173
  const url = `${this.options.baseUrl}${path}${query}`;
141
174
  let attempt = 0;
142
175
  while (true) {
@@ -178,6 +211,7 @@ var HttpClient = class {
178
211
  }
179
212
  if (shouldRetry(method, response.status) && attempt < this.options.maxRetries) {
180
213
  const retryAfter = retryAfterMs(response.headers.get("retry-after"));
214
+ await response.body?.cancel();
181
215
  await sleep(Math.max(backoff(this.options.baseDelayMs, ++attempt), retryAfter ?? 0));
182
216
  continue;
183
217
  }
@@ -185,7 +219,7 @@ var HttpClient = class {
185
219
  if (!response.ok) throw apiErrorFromResponse(response.status, responseBody, response.headers);
186
220
  if (responseBody === "") return {};
187
221
  try {
188
- return JSON.parse(responseBody);
222
+ return fromWire(JSON.parse(responseBody));
189
223
  } catch (error) {
190
224
  throw new PaymosError("Paymos API returned invalid JSON.", { cause: error });
191
225
  }
@@ -263,7 +297,7 @@ var InvoicesResource = class {
263
297
  return this.http.request("GET", `/v1/invoices/${encodePathSegment(invoiceId)}`);
264
298
  }
265
299
  list(params = {}) {
266
- return this.http.request("GET", "/v1/invoices", void 0, buildQuery(params));
300
+ return this.http.request("GET", "/v1/invoices", void 0, buildQuery(toWire(params)));
267
301
  }
268
302
  async *iterate(params = {}, maxPages = 100) {
269
303
  assertMaxPages(maxPages);
@@ -271,7 +305,7 @@ var InvoicesResource = class {
271
305
  for (let pageNumber = 0; pageNumber < maxPages; pageNumber += 1) {
272
306
  const page = await this.list(cursor === void 0 ? params : { ...params, cursor });
273
307
  yield* page.items;
274
- const next = page.next_cursor || void 0;
308
+ const next = page.nextCursor || void 0;
275
309
  if (next === void 0) return;
276
310
  if (next === cursor) throw new Error("Paymos API returned the same pagination cursor twice.");
277
311
  cursor = next;
@@ -302,7 +336,7 @@ var WithdrawalsResource = class {
302
336
  return this.http.request("GET", `/v1/withdrawals/${encodePathSegment(withdrawalId)}`);
303
337
  }
304
338
  list(params = {}) {
305
- return this.http.request("GET", "/v1/withdrawals", void 0, buildQuery(params));
339
+ return this.http.request("GET", "/v1/withdrawals", void 0, buildQuery(toWire(params)));
306
340
  }
307
341
  async *iterate(params = {}, maxPages = 100) {
308
342
  assertMaxPages(maxPages);
@@ -310,7 +344,7 @@ var WithdrawalsResource = class {
310
344
  for (let pageNumber = 0; pageNumber < maxPages; pageNumber += 1) {
311
345
  const page = await this.list(cursor === void 0 ? params : { ...params, cursor });
312
346
  yield* page.items;
313
- const next = page.next_cursor || void 0;
347
+ const next = page.nextCursor || void 0;
314
348
  if (next === void 0) return;
315
349
  if (next === cursor) throw new Error("Paymos API returned the same pagination cursor twice.");
316
350
  cursor = next;
@@ -396,8 +430,11 @@ var WebhookVerifier = class {
396
430
  constructEvent(signatureHeader, rawBody, now = Math.floor(Date.now() / 1e3)) {
397
431
  this.assertValid(signatureHeader, rawBody, now);
398
432
  try {
399
- return JSON.parse(rawBody.toString());
433
+ const event = fromWire(JSON.parse(rawBody.toString()));
434
+ assertEventEnvelope(event);
435
+ return event;
400
436
  } catch (error) {
437
+ if (error instanceof TypeError) throw error;
401
438
  throw new TypeError("Webhook payload is not valid JSON.", { cause: error });
402
439
  }
403
440
  }
@@ -405,16 +442,39 @@ var WebhookVerifier = class {
405
442
  function parseHeader(header) {
406
443
  if (typeof header !== "string") return null;
407
444
  let timestamp;
445
+ let timestampCount = 0;
408
446
  const signatures = [];
409
447
  for (const part of header.split(",")) {
410
448
  const separator = part.indexOf("=");
411
449
  if (separator < 1) continue;
412
450
  const key = part.slice(0, separator).trim();
413
451
  const value = part.slice(separator + 1).trim();
414
- if (key === "t" && /^\d+$/.test(value)) timestamp = Number(value);
452
+ if (key === "t" && /^\d+$/.test(value)) {
453
+ timestamp = Number(value);
454
+ timestampCount += 1;
455
+ }
415
456
  if (key === "v1" && value !== "") signatures.push(value);
416
457
  }
417
- return timestamp === void 0 || signatures.length === 0 ? null : { timestamp, signatures };
458
+ return timestamp === void 0 || timestampCount !== 1 || !Number.isSafeInteger(timestamp) || signatures.length === 0 ? null : { timestamp, signatures };
459
+ }
460
+ function assertEventEnvelope(value) {
461
+ if (value === null || typeof value !== "object") throw new TypeError("Webhook payload must be an object.");
462
+ const event = value;
463
+ if (typeof event.eventId !== "string" || event.eventId.trim() === "") {
464
+ throw new TypeError("Webhook eventId must be a non-empty string.");
465
+ }
466
+ if (typeof event.eventType !== "string" || event.eventType.trim() === "") {
467
+ throw new TypeError("Webhook eventType must be a non-empty string.");
468
+ }
469
+ if (!Number.isSafeInteger(event.version) || event.version < 1) {
470
+ throw new TypeError("Webhook version must be a positive integer.");
471
+ }
472
+ if (!Number.isSafeInteger(event.occurredAt) || event.occurredAt < 0) {
473
+ throw new TypeError("Webhook occurredAt must be a non-negative Unix timestamp.");
474
+ }
475
+ if (event.data === null || typeof event.data !== "object") {
476
+ throw new TypeError("Webhook data must be an object.");
477
+ }
418
478
  }
419
479
 
420
480
  // src/index.ts
package/package.json CHANGED
@@ -1,64 +1,69 @@
1
- {
2
- "name": "@paymos/sdk",
3
- "version": "1.0.0",
4
- "description": "Official TypeScript and JavaScript SDK for the Paymos Merchant API",
5
- "keywords": [
6
- "paymos",
7
- "payments",
8
- "stablecoins",
9
- "usdt",
10
- "usdc",
11
- "crypto",
12
- "sdk"
13
- ],
14
- "homepage": "https://paymos.io/docs/quick-start",
15
- "bugs": "https://github.com/Paymos-labs/typescript-sdk/issues",
16
- "repository": {
17
- "type": "git",
18
- "url": "git+https://github.com/Paymos-labs/typescript-sdk.git"
19
- },
20
- "license": "MIT",
21
- "author": "Paymos Labs <support@paymos.io>",
22
- "type": "module",
23
- "main": "./dist/index.cjs",
24
- "module": "./dist/index.js",
25
- "types": "./dist/index.d.ts",
26
- "exports": {
27
- ".": {
28
- "types": "./dist/index.d.ts",
29
- "import": "./dist/index.js",
30
- "require": "./dist/index.cjs"
31
- }
32
- },
33
- "files": [
34
- "dist",
35
- "README.md",
36
- "LICENSE",
37
- "CHANGELOG.md"
38
- ],
39
- "sideEffects": false,
40
- "engines": {
41
- "node": ">=22.12"
42
- },
43
- "scripts": {
44
- "build": "tsup src/index.ts --format esm,cjs --dts --clean",
45
- "check": "tsc --noEmit",
46
- "test": "vitest run",
47
- "test:coverage": "vitest run --coverage",
48
- "prepack": "npm run check && npm test && npm run build"
49
- },
50
- "devDependencies": {
51
- "@types/node": "^22.10.0",
52
- "@vitest/coverage-v8": "^3.2.0",
53
- "tsup": "^8.5.0",
54
- "typescript": "^5.8.0",
55
- "vitest": "^3.2.0"
56
- },
57
- "overrides": {
58
- "esbuild": "0.28.1"
59
- },
60
- "publishConfig": {
61
- "access": "public",
62
- "provenance": true
63
- }
64
- }
1
+ {
2
+ "name": "@paymos/sdk",
3
+ "version": "2.0.3",
4
+ "description": "Official TypeScript and JavaScript SDK for the Paymos Merchant API",
5
+ "keywords": [
6
+ "paymos",
7
+ "payments",
8
+ "stablecoins",
9
+ "usdt",
10
+ "usdc",
11
+ "crypto",
12
+ "sdk"
13
+ ],
14
+ "homepage": "https://paymos.io/docs/server-sdks",
15
+ "bugs": "https://github.com/Paymos-labs/typescript-sdk/issues",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/Paymos-labs/typescript-sdk.git"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Paymos Labs <support@paymos.io>",
22
+ "type": "module",
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "import": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "require": {
33
+ "types": "./dist/index.d.cts",
34
+ "default": "./dist/index.cjs"
35
+ }
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE",
42
+ "CHANGELOG.md"
43
+ ],
44
+ "sideEffects": false,
45
+ "engines": {
46
+ "node": ">=22.12"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
50
+ "check": "tsc --noEmit",
51
+ "test": "vitest run",
52
+ "test:coverage": "vitest run --coverage",
53
+ "prepack": "npm run check && npm test && npm run build"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.10.0",
57
+ "@vitest/coverage-v8": "^3.2.0",
58
+ "tsup": "^8.5.0",
59
+ "typescript": "^5.8.0",
60
+ "vitest": "^3.2.0"
61
+ },
62
+ "overrides": {
63
+ "esbuild": "0.28.1"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public",
67
+ "provenance": true
68
+ }
69
+ }