@fanth/payment-router-sdk 0.1.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.js ADDED
@@ -0,0 +1,426 @@
1
+ import { createRemoteJWKSet, customFetch, jwtVerify } from 'jose';
2
+
3
+ // src/defaults.ts
4
+ var DEFAULT_BASE_URL = "https://payment-r.fanth.pl";
5
+
6
+ // src/errors.ts
7
+ var PaymentRouterError = class extends Error {
8
+ constructor(message, options) {
9
+ super(message, options);
10
+ this.name = new.target.name;
11
+ }
12
+ };
13
+ var PaymentRouterApiError = class extends PaymentRouterError {
14
+ status;
15
+ /** The `error` field of the response body, when the router sent one. */
16
+ code;
17
+ /** The parsed (or raw string) response body, for anything the typed fields do not cover. */
18
+ body;
19
+ constructor(message, status, code, body) {
20
+ super(message);
21
+ this.status = status;
22
+ this.code = code;
23
+ this.body = body;
24
+ }
25
+ };
26
+ var RateLimitedError = class extends PaymentRouterApiError {
27
+ };
28
+ var InvalidWebhookUrlError = class extends PaymentRouterApiError {
29
+ /** The router's human-readable reason, e.g. "webhookUrl must use https". */
30
+ get reason() {
31
+ return this.code ?? this.message;
32
+ }
33
+ };
34
+ var PaymentRouterNetworkError = class extends PaymentRouterError {
35
+ };
36
+ var PaymentRouterValidationError = class extends PaymentRouterError {
37
+ };
38
+ var CallbackVerificationError = class extends PaymentRouterError {
39
+ /** Machine-readable failure reason, for logging and metrics. */
40
+ reason;
41
+ constructor(reason, message, options) {
42
+ super(message, options);
43
+ this.reason = reason;
44
+ }
45
+ };
46
+
47
+ // src/http.ts
48
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
49
+ async function requestJson(options, path, init) {
50
+ const url = new URL(path, ensureTrailingSlash(options.baseUrl)).toString();
51
+ let lastError;
52
+ for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
53
+ if (attempt > 0) {
54
+ await sleep(options.retryDelayMs * 2 ** (attempt - 1));
55
+ }
56
+ let response;
57
+ try {
58
+ response = await options.fetch(url, {
59
+ ...init,
60
+ headers: { ...options.headers, ...init.headers },
61
+ signal: init.signal ?? AbortSignal.timeout(options.timeoutMs)
62
+ });
63
+ } catch (error2) {
64
+ lastError = new PaymentRouterNetworkError(`request to ${url} failed: ${describe(error2)}`, { cause: error2 });
65
+ continue;
66
+ }
67
+ if (response.ok) {
68
+ return await response.json();
69
+ }
70
+ const error = await toApiError(response);
71
+ if (!RETRYABLE_STATUSES.has(response.status)) {
72
+ throw error;
73
+ }
74
+ lastError = error;
75
+ }
76
+ throw lastError ?? new PaymentRouterNetworkError(`request to ${url} failed for an unknown reason`);
77
+ }
78
+ async function toApiError(response) {
79
+ const body = await readBody(response);
80
+ const code = extractCode(body);
81
+ const message = `payment-router responded ${response.status}${code ? `: ${code}` : ""}`;
82
+ if (response.status === 429) return new RateLimitedError(message, response.status, code, body);
83
+ if (response.status === 422) return new InvalidWebhookUrlError(message, response.status, code, body);
84
+ return new PaymentRouterApiError(message, response.status, code, body);
85
+ }
86
+ async function readBody(response) {
87
+ const text = await response.text().catch(() => "");
88
+ if (text.length === 0) return null;
89
+ try {
90
+ return JSON.parse(text);
91
+ } catch {
92
+ return text;
93
+ }
94
+ }
95
+ function extractCode(body) {
96
+ if (typeof body !== "object" || body === null) return null;
97
+ const error = body.error;
98
+ if (typeof error === "string") return error;
99
+ const issues = error?.issues;
100
+ const first = issues?.[0]?.message;
101
+ return typeof first === "string" ? first : null;
102
+ }
103
+ function describe(error) {
104
+ if (error instanceof Error) return `${error.name}: ${error.message}`;
105
+ return "unknown error";
106
+ }
107
+ function ensureTrailingSlash(url) {
108
+ return url.endsWith("/") ? url : `${url}/`;
109
+ }
110
+ function sleep(ms) {
111
+ return new Promise((resolve) => setTimeout(resolve, ms));
112
+ }
113
+
114
+ // src/headers.ts
115
+ var SIGNATURE_HEADER = "x-payment-router-signature";
116
+ var ROUTE_ID_HEADER = "x-payment-router-id";
117
+ var GATEWAY_HEADER = "x-payment-router-gateway";
118
+ var EXTERNAL_ID_HEADER = "x-payment-router-external-id";
119
+ var FORWARDED_HOST_HEADER = "x-forwarded-host";
120
+
121
+ // src/verify.ts
122
+ var DEFAULT_MAX_AGE_SECONDS = 300;
123
+ var CallbackVerifier = class {
124
+ issuer;
125
+ maxAgeSeconds;
126
+ clockToleranceSeconds;
127
+ getKey;
128
+ constructor(options = {}) {
129
+ const baseUrl = stripTrailingSlash(options.baseUrl ?? DEFAULT_BASE_URL);
130
+ this.issuer = options.issuer ?? baseUrl;
131
+ this.maxAgeSeconds = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
132
+ this.clockToleranceSeconds = options.clockToleranceSeconds ?? 5;
133
+ this.getKey = options.keyStore ?? createRemoteJWKSet(new URL(options.jwksUrl ?? jwksUrlFor(baseUrl)), {
134
+ ...options.fetch ? { [customFetch]: options.fetch } : {}
135
+ });
136
+ }
137
+ /**
138
+ * Verify a token against the body it is supposed to cover.
139
+ *
140
+ * @throws {CallbackVerificationError} always, when the callback cannot be trusted.
141
+ */
142
+ async verify(input) {
143
+ const token = input.token?.trim();
144
+ if (!token) {
145
+ throw new CallbackVerificationError("missing_signature", `no ${SIGNATURE_HEADER} header on the callback`);
146
+ }
147
+ let payload;
148
+ try {
149
+ ({ payload } = await jwtVerify(token, this.getKey, {
150
+ issuer: this.issuer,
151
+ algorithms: ["EdDSA"],
152
+ maxTokenAge: this.maxAgeSeconds,
153
+ clockTolerance: this.clockToleranceSeconds
154
+ }));
155
+ } catch (error) {
156
+ throw new CallbackVerificationError("invalid_signature", `callback signature is not valid: ${describe2(error)}`, {
157
+ cause: error
158
+ });
159
+ }
160
+ const claims = toClaims(payload);
161
+ const actual = await sha256Hex(input.rawBody);
162
+ if (!timingSafeEqual(actual, claims.bodySha256)) {
163
+ throw new CallbackVerificationError(
164
+ "body_mismatch",
165
+ "callback body does not match the body_sha256 claim - pass the raw bytes you received, not re-serialized JSON"
166
+ );
167
+ }
168
+ return { claims, rawBody: toText(input.rawBody) };
169
+ }
170
+ /**
171
+ * Verify a fetch-API `Request` (Workers, Hono, Next.js route handlers, Deno, Bun).
172
+ *
173
+ * The body is read here, so do not read it yourself first - clone the request if you need it.
174
+ */
175
+ async verifyRequest(request) {
176
+ if (request.bodyUsed) {
177
+ throw new CallbackVerificationError(
178
+ "body_already_consumed",
179
+ "request body was already read - pass a request.clone() or use verify({ token, rawBody })"
180
+ );
181
+ }
182
+ return this.verify({ token: request.headers.get(SIGNATURE_HEADER), rawBody: await request.text() });
183
+ }
184
+ /**
185
+ * The plain version, for any framework: headers and the raw body in, the same raw body back out,
186
+ * or it throws. Handy when all you want is a guard at the top of a webhook handler.
187
+ *
188
+ * The body must be the raw bytes as they arrived - a parsed-and-re-serialized JSON body hashes
189
+ * differently and will be rejected. Use {@link verify} when you also want the claims.
190
+ */
191
+ async verifyWebhook(headers, rawBody) {
192
+ const { rawBody: body } = await this.verify({ token: readHeader(headers, SIGNATURE_HEADER), rawBody });
193
+ return body;
194
+ }
195
+ };
196
+ function jwksUrlFor(baseUrl) {
197
+ return new URL("/.well-known/jwks.json", baseUrl).toString();
198
+ }
199
+ function readHeader(headers, name) {
200
+ if (typeof headers.get === "function") {
201
+ return headers.get(name);
202
+ }
203
+ const bag = headers;
204
+ const value = bag[name] ?? bag[name.toLowerCase()] ?? bag[name.toUpperCase()];
205
+ if (Array.isArray(value)) return value[0] ?? null;
206
+ return value ?? null;
207
+ }
208
+ function toClaims(payload) {
209
+ const routeId = payload.route_id;
210
+ const gateway = payload.gateway;
211
+ const bodySha256 = payload.body_sha256;
212
+ const externalId = payload.external_id;
213
+ if (typeof routeId !== "string" || typeof gateway !== "string" || typeof bodySha256 !== "string") {
214
+ throw new CallbackVerificationError("malformed_claims", "callback token is missing route_id, gateway or body_sha256");
215
+ }
216
+ if (typeof payload.iat !== "number" || typeof payload.exp !== "number") {
217
+ throw new CallbackVerificationError("malformed_claims", "callback token is missing iat or exp");
218
+ }
219
+ return {
220
+ routeId,
221
+ gateway,
222
+ externalId: typeof externalId === "string" ? externalId : null,
223
+ bodySha256,
224
+ issuer: payload.iss ?? "",
225
+ issuedAt: new Date(payload.iat * 1e3),
226
+ expiresAt: new Date(payload.exp * 1e3),
227
+ raw: payload
228
+ };
229
+ }
230
+ async function sha256Hex(data) {
231
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
232
+ const subtle = globalThis.crypto?.subtle;
233
+ if (!subtle) {
234
+ throw new PaymentRouterValidationError("WebCrypto is unavailable - Node 18+, Workers, Deno and Bun all provide it");
235
+ }
236
+ const digest = await subtle.digest("SHA-256", bytes);
237
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
238
+ }
239
+ function timingSafeEqual(a, b) {
240
+ if (a.length !== b.length) return false;
241
+ let diff = 0;
242
+ for (let i = 0; i < a.length; i++) {
243
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
244
+ }
245
+ return diff === 0;
246
+ }
247
+ function toText(body) {
248
+ return typeof body === "string" ? body : new TextDecoder().decode(body);
249
+ }
250
+ function describe2(error) {
251
+ if (error instanceof Error) return error.message;
252
+ return "unknown error";
253
+ }
254
+ function stripTrailingSlash(url) {
255
+ return url.endsWith("/") ? url.slice(0, -1) : url;
256
+ }
257
+
258
+ // src/client.ts
259
+ var DEFAULTS = {
260
+ timeoutMs: 1e4,
261
+ maxRetries: 2,
262
+ retryDelayMs: 200
263
+ };
264
+ var PaymentRouterClient = class {
265
+ baseUrl;
266
+ http;
267
+ verifierOptions;
268
+ cachedVerifier = null;
269
+ constructor(options = {}) {
270
+ this.baseUrl = stripTrailingSlash2(assertAbsoluteUrl(options.baseUrl ?? DEFAULT_BASE_URL, "baseUrl"));
271
+ this.verifierOptions = {
272
+ // The client's own fetch carries whatever a proxy in front of the router needs, and the
273
+ // JWKS lives on that same origin - so it fits the key fetch too, unless overridden.
274
+ ...options.fetch ? { fetch: options.fetch } : {},
275
+ ...options.verifier
276
+ };
277
+ this.http = {
278
+ baseUrl: this.baseUrl,
279
+ fetch: options.fetch ?? defaultFetch(),
280
+ timeoutMs: options.timeoutMs ?? DEFAULTS.timeoutMs,
281
+ maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,
282
+ retryDelayMs: options.retryDelayMs ?? DEFAULTS.retryDelayMs,
283
+ headers: {
284
+ "Content-Type": "application/json",
285
+ Accept: "application/json",
286
+ ...options.headers
287
+ }
288
+ };
289
+ }
290
+ /**
291
+ * Register a webhook URL and get back the route whose `id` you send to the gateway as its
292
+ * external payment id.
293
+ *
294
+ * @throws {InvalidWebhookUrlError} the router refused the URL (not https, or a private host).
295
+ * @throws {RateLimitedError} the router's per-IP limit on route creation was hit.
296
+ */
297
+ async createRoute(input) {
298
+ const payload = {
299
+ webhookUrl: assertAbsoluteUrl(input.webhookUrl, "webhookUrl"),
300
+ ...input.externalId !== void 0 ? { externalId: assertExternalId(input.externalId) } : {},
301
+ ...input.expiresAt !== void 0 ? { expiresAt: toIsoString(input.expiresAt) } : {}
302
+ };
303
+ const route = await requestJson(this.http, "v1/routes", {
304
+ method: "POST",
305
+ body: JSON.stringify(payload)
306
+ });
307
+ return parseRoute(route);
308
+ }
309
+ /**
310
+ * The URL to paste into a gateway's dashboard as its single webhook URL. The same for every
311
+ * route, so it can be read off the client without creating one.
312
+ */
313
+ callbackUrl(gateway) {
314
+ return new URL(`webhooks/${encodeURIComponent(gateway)}`, `${this.baseUrl}/`).toString();
315
+ }
316
+ /** Where the router publishes the public keys that verify forwarded callbacks. */
317
+ get jwksUrl() {
318
+ return jwksUrlFor(this.baseUrl);
319
+ }
320
+ /**
321
+ * Headers and the raw body in, the same raw body back out - or it throws. The short way to guard
322
+ * a webhook handler: verifies the router's EdDSA JWT against its published keys, and checks that
323
+ * the body still hashes to what the token claims.
324
+ *
325
+ * The body must be the raw bytes as they arrived - parsed-and-re-serialized JSON hashes
326
+ * differently and will be rejected. Use {@link verifyCallback} when you also want the claims.
327
+ *
328
+ * @throws {CallbackVerificationError} when the callback cannot be trusted.
329
+ */
330
+ async verifyWebhook(headers, rawBody) {
331
+ return this.verifier().verifyWebhook(headers, rawBody);
332
+ }
333
+ /**
334
+ * Verify a fetch-API `Request` (Workers, Hono, Next.js, Deno, Bun) and get its claims back.
335
+ *
336
+ * The body is read here, so do not read it yourself first - clone the request if you need it.
337
+ */
338
+ async verifyRequest(request) {
339
+ return this.verifier().verifyRequest(request);
340
+ }
341
+ /** Verify a token against the body it covers, and get the claims back. */
342
+ async verifyCallback(input) {
343
+ return this.verifier().verify(input);
344
+ }
345
+ /**
346
+ * Built on first use and kept around: the verifier caches the router's key set, so reusing one
347
+ * client saves a JWKS fetch per callback.
348
+ */
349
+ verifier() {
350
+ this.cachedVerifier ??= new CallbackVerifier({ baseUrl: this.baseUrl, ...this.verifierOptions });
351
+ return this.cachedVerifier;
352
+ }
353
+ };
354
+ function parseRoute(raw) {
355
+ return {
356
+ id: raw.id,
357
+ webhookUrl: raw.webhookUrl,
358
+ externalId: raw.externalId ?? null,
359
+ expiresAt: raw.expiresAt ? new Date(raw.expiresAt) : null,
360
+ createdAt: new Date(raw.createdAt),
361
+ callbackUrls: raw.callbackUrls ?? null
362
+ };
363
+ }
364
+ function isRouteExpired(route, now = /* @__PURE__ */ new Date()) {
365
+ return route.expiresAt !== null && route.expiresAt.getTime() < now.getTime();
366
+ }
367
+ function callbackUrlFor(route, gateway) {
368
+ return route.callbackUrls?.[gateway] ?? null;
369
+ }
370
+ function assertAbsoluteUrl(value, field) {
371
+ let url;
372
+ try {
373
+ url = new URL(value);
374
+ } catch {
375
+ throw new PaymentRouterValidationError(`${field} must be an absolute URL, got "${value}"`);
376
+ }
377
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
378
+ throw new PaymentRouterValidationError(`${field} must use http or https, got "${url.protocol}"`);
379
+ }
380
+ return value;
381
+ }
382
+ function assertExternalId(value) {
383
+ if (value.length === 0 || value.length > 256) {
384
+ throw new PaymentRouterValidationError("externalId must be between 1 and 256 characters");
385
+ }
386
+ return value;
387
+ }
388
+ function toIsoString(value) {
389
+ const date = value instanceof Date ? value : new Date(value);
390
+ if (Number.isNaN(date.getTime())) {
391
+ throw new PaymentRouterValidationError(`expiresAt is not a valid date: ${String(value)}`);
392
+ }
393
+ return date.toISOString();
394
+ }
395
+ function defaultFetch() {
396
+ if (typeof globalThis.fetch !== "function") {
397
+ throw new PaymentRouterValidationError(
398
+ "no global fetch available - pass one via the `fetch` option (Node 18+ has it built in)"
399
+ );
400
+ }
401
+ return globalThis.fetch.bind(globalThis);
402
+ }
403
+ function stripTrailingSlash2(url) {
404
+ return url.endsWith("/") ? url.slice(0, -1) : url;
405
+ }
406
+
407
+ // src/node.ts
408
+ async function readRawBody(request) {
409
+ const chunks = [];
410
+ const encoder = new TextEncoder();
411
+ for await (const chunk of request) {
412
+ chunks.push(typeof chunk === "string" ? encoder.encode(chunk) : chunk);
413
+ }
414
+ const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
415
+ const body = new Uint8Array(total);
416
+ let offset = 0;
417
+ for (const chunk of chunks) {
418
+ body.set(chunk, offset);
419
+ offset += chunk.byteLength;
420
+ }
421
+ return new TextDecoder().decode(body);
422
+ }
423
+
424
+ export { CallbackVerificationError, DEFAULT_BASE_URL, EXTERNAL_ID_HEADER, FORWARDED_HOST_HEADER, GATEWAY_HEADER, InvalidWebhookUrlError, PaymentRouterApiError, PaymentRouterClient, PaymentRouterError, PaymentRouterNetworkError, PaymentRouterValidationError, ROUTE_ID_HEADER, RateLimitedError, SIGNATURE_HEADER, callbackUrlFor, isRouteExpired, jwksUrlFor, parseRoute, readHeader, readRawBody, sha256Hex };
425
+ //# sourceMappingURL=index.js.map
426
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/defaults.ts","../src/errors.ts","../src/http.ts","../src/headers.ts","../src/verify.ts","../src/client.ts","../src/node.ts"],"names":["error","describe","stripTrailingSlash"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * The hosted router, used whenever no `baseUrl` is given. Self-hosting the worker? Pass your own\n * origin to the client and the verifier - the verifier's expected `iss` follows it.\n */\nexport const DEFAULT_BASE_URL = \"https://payment-r.fanth.pl\";\n","/** Base class for everything this SDK throws, so a single `catch` can tell our failures apart. */\nexport class PaymentRouterError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\n/** The router answered, but with a non-2xx status. `code` is the machine-readable `error` field. */\nexport class PaymentRouterApiError extends PaymentRouterError {\n readonly status: number;\n /** The `error` field of the response body, when the router sent one. */\n readonly code: string | null;\n /** The parsed (or raw string) response body, for anything the typed fields do not cover. */\n readonly body: unknown;\n\n constructor(message: string, status: number, code: string | null, body: unknown) {\n super(message);\n this.status = status;\n this.code = code;\n this.body = body;\n }\n}\n\n/** 429 from `POST /v1/routes` - the router's per-IP rate limit kicked in. Safe to retry later. */\nexport class RateLimitedError extends PaymentRouterApiError {}\n\n/**\n * 422 - the router refused the webhook URL (not https, or pointing at a private/loopback host).\n * Never retry this one, the URL itself has to change.\n */\nexport class InvalidWebhookUrlError extends PaymentRouterApiError {\n /** The router's human-readable reason, e.g. \"webhookUrl must use https\". */\n get reason(): string {\n return this.code ?? this.message;\n }\n}\n\n/** The request never produced a response: DNS, TCP, TLS, timeout or abort. */\nexport class PaymentRouterNetworkError extends PaymentRouterError {}\n\n/** The request did not survive the client-side checks, so nothing was sent. */\nexport class PaymentRouterValidationError extends PaymentRouterError {}\n\n/**\n * A forwarded callback failed verification: missing/expired/forged JWT, wrong issuer, or a body that\n * does not match the `body_sha256` claim. Treat it as an unauthenticated request, never as a payment.\n */\nexport class CallbackVerificationError extends PaymentRouterError {\n /** Machine-readable failure reason, for logging and metrics. */\n readonly reason: CallbackVerificationFailure;\n\n constructor(reason: CallbackVerificationFailure, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.reason = reason;\n }\n}\n\nexport type CallbackVerificationFailure =\n | \"missing_signature\"\n | \"invalid_signature\"\n | \"body_mismatch\"\n | \"malformed_claims\"\n | \"body_already_consumed\";\n","import { InvalidWebhookUrlError, PaymentRouterApiError, PaymentRouterNetworkError, RateLimitedError } from \"./errors\";\n\n/** Injectable so tests, proxies and edge runtimes can swap in their own implementation. */\nexport type FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\nexport type HttpOptions = {\n baseUrl: string;\n fetch: FetchLike;\n timeoutMs: number;\n maxRetries: number;\n retryDelayMs: number;\n headers: Record<string, string>;\n};\n\n/** Transient enough that the same request may well succeed a moment later. */\nconst RETRYABLE_STATUSES = new Set([429, 502, 503, 504]);\n\nexport async function requestJson<T>(options: HttpOptions, path: string, init: RequestInit): Promise<T> {\n const url = new URL(path, ensureTrailingSlash(options.baseUrl)).toString();\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= options.maxRetries; attempt++) {\n if (attempt > 0) {\n // Exponential backoff. No jitter: the router rate-limits per IP, not per connection, so\n // spreading retries out buys nothing a plain doubling does not.\n await sleep(options.retryDelayMs * 2 ** (attempt - 1));\n }\n\n let response: Response;\n try {\n response = await options.fetch(url, {\n ...init,\n headers: { ...options.headers, ...(init.headers as Record<string, string> | undefined) },\n signal: init.signal ?? AbortSignal.timeout(options.timeoutMs),\n });\n } catch (error) {\n lastError = new PaymentRouterNetworkError(`request to ${url} failed: ${describe(error)}`, { cause: error });\n continue;\n }\n\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n const error = await toApiError(response);\n if (!RETRYABLE_STATUSES.has(response.status)) {\n throw error;\n }\n lastError = error;\n }\n\n throw lastError ?? new PaymentRouterNetworkError(`request to ${url} failed for an unknown reason`);\n}\n\n/**\n * Map a non-2xx response onto the narrowest error we have. The router answers `{ \"error\": \"...\" }`,\n * except for schema failures, where Hono's zod validator sends its own `{ success, error }` shape.\n */\nasync function toApiError(response: Response): Promise<PaymentRouterApiError> {\n const body = await readBody(response);\n const code = extractCode(body);\n const message = `payment-router responded ${response.status}${code ? `: ${code}` : \"\"}`;\n\n if (response.status === 429) return new RateLimitedError(message, response.status, code, body);\n if (response.status === 422) return new InvalidWebhookUrlError(message, response.status, code, body);\n return new PaymentRouterApiError(message, response.status, code, body);\n}\n\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => \"\");\n if (text.length === 0) return null;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n}\n\nfunction extractCode(body: unknown): string | null {\n if (typeof body !== \"object\" || body === null) return null;\n const error = (body as { error?: unknown }).error;\n if (typeof error === \"string\") return error;\n // Zod validator failures nest the issues under `error`; the first message is the useful bit.\n const issues = (error as { issues?: { message?: unknown }[] } | undefined)?.issues;\n const first = issues?.[0]?.message;\n return typeof first === \"string\" ? first : null;\n}\n\nfunction describe(error: unknown): string {\n if (error instanceof Error) return `${error.name}: ${error.message}`;\n return \"unknown error\";\n}\n\nfunction ensureTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url : `${url}/`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * Headers the router adds to a forwarded callback. All lowercase - Node lowercases incoming header\n * names, and the fetch `Headers` lookup is case-insensitive anyway.\n *\n * Only the signature header is trustworthy: the others are convenience copies of claims that live\n * inside the signed JWT, and anyone who can reach your webhook can set them. Read them for logs,\n * verify the token for decisions.\n */\nexport const SIGNATURE_HEADER = \"x-payment-router-signature\";\nexport const ROUTE_ID_HEADER = \"x-payment-router-id\";\nexport const GATEWAY_HEADER = \"x-payment-router-gateway\";\nexport const EXTERNAL_ID_HEADER = \"x-payment-router-external-id\";\nexport const FORWARDED_HOST_HEADER = \"x-forwarded-host\";\n","import { createRemoteJWKSet, customFetch, jwtVerify, type JWTPayload, type JWTVerifyGetKey } from \"jose\";\nimport { DEFAULT_BASE_URL } from \"./defaults\";\nimport { CallbackVerificationError, PaymentRouterValidationError } from \"./errors\";\nimport { SIGNATURE_HEADER } from \"./headers\";\nimport type { CallbackClaims, VerifiedCallback } from \"./types\";\n\n/** The JWTs the router signs live 5 minutes; anything older is a replay. */\nconst DEFAULT_MAX_AGE_SECONDS = 300;\n\nexport type CallbackVerifierOptions = {\n /**\n * The router's public origin: the JWKS URL and, unless overridden, the expected `iss`. Defaults\n * to {@link DEFAULT_BASE_URL}, the hosted router - set it only when running your own worker.\n */\n baseUrl?: string;\n /** Override the JWKS location, e.g. when it is mirrored somewhere else. */\n jwksUrl?: string;\n /** Expected `iss` claim. Defaults to `baseUrl` - must match the router's `PUBLIC_BASE_URL` exactly. */\n issuer?: string;\n /** Reject tokens issued longer ago than this, in seconds. Default 300 (the router's own TTL). */\n maxAgeSeconds?: number;\n /** Leeway for clock skew between you and the router, in seconds. Default 5. */\n clockToleranceSeconds?: number;\n /** Custom fetch for the JWKS request. */\n fetch?: typeof globalThis.fetch;\n /** Swap the whole key source, e.g. `createLocalJWKSet` in tests or air-gapped setups. */\n keyStore?: JWTVerifyGetKey;\n};\n\n/** The minimum a caller has to hand over: the token and the exact bytes that were delivered. */\nexport type VerifyInput = {\n /** The `X-Payment-Router-Signature` header value. */\n token: string | null | undefined;\n /** The callback body exactly as received - not re-serialized JSON, or the hash will not match. */\n rawBody: string | Uint8Array;\n};\n\n/** Either header shape a runtime hands you: fetch's `Headers` or Node's plain object. */\nexport type HeaderBag = Record<string, string | string[] | undefined> | Headers;\n\n/**\n * Verifies the EdDSA (Ed25519) JWT the router puts on every forwarded callback.\n *\n * Two things are checked, and both matter: the signature (against the router's published JWKS, so a\n * key rotation is transparent) and `body_sha256` (so the signature covers the payload, not just its\n * provenance). Anything else that reaches your webhook is an unauthenticated stranger.\n *\n * ```ts\n * const verifier = new CallbackVerifier({ baseUrl: \"https://router.example.com\" });\n * const { claims, rawBody } = await verifier.verifyRequest(request);\n * const notification = JSON.parse(rawBody);\n * // claims.routeId / claims.externalId tell you which of your payments this is\n * ```\n */\nexport class CallbackVerifier {\n private readonly issuer: string;\n private readonly maxAgeSeconds: number;\n private readonly clockToleranceSeconds: number;\n private readonly getKey: JWTVerifyGetKey;\n\n constructor(options: CallbackVerifierOptions = {}) {\n const baseUrl = stripTrailingSlash(options.baseUrl ?? DEFAULT_BASE_URL);\n this.issuer = options.issuer ?? baseUrl;\n this.maxAgeSeconds = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;\n this.clockToleranceSeconds = options.clockToleranceSeconds ?? 5;\n // createRemoteJWKSet caches the key set and refetches on an unknown `kid`, which is exactly\n // what makes rotation on the router side invisible here.\n this.getKey =\n options.keyStore ??\n createRemoteJWKSet(new URL(options.jwksUrl ?? jwksUrlFor(baseUrl)), {\n ...(options.fetch ? { [customFetch]: options.fetch as never } : {}),\n });\n }\n\n /**\n * Verify a token against the body it is supposed to cover.\n *\n * @throws {CallbackVerificationError} always, when the callback cannot be trusted.\n */\n async verify(input: VerifyInput): Promise<VerifiedCallback> {\n const token = input.token?.trim();\n if (!token) {\n throw new CallbackVerificationError(\"missing_signature\", `no ${SIGNATURE_HEADER} header on the callback`);\n }\n\n let payload: JWTPayload;\n try {\n ({ payload } = await jwtVerify(token, this.getKey, {\n issuer: this.issuer,\n algorithms: [\"EdDSA\"],\n maxTokenAge: this.maxAgeSeconds,\n clockTolerance: this.clockToleranceSeconds,\n }));\n } catch (error) {\n throw new CallbackVerificationError(\"invalid_signature\", `callback signature is not valid: ${describe(error)}`, {\n cause: error,\n });\n }\n\n const claims = toClaims(payload);\n const actual = await sha256Hex(input.rawBody);\n if (!timingSafeEqual(actual, claims.bodySha256)) {\n throw new CallbackVerificationError(\n \"body_mismatch\",\n \"callback body does not match the body_sha256 claim - pass the raw bytes you received, not re-serialized JSON\"\n );\n }\n\n return { claims, rawBody: toText(input.rawBody) };\n }\n\n /**\n * Verify a fetch-API `Request` (Workers, Hono, Next.js route handlers, Deno, Bun).\n *\n * The body is read here, so do not read it yourself first - clone the request if you need it.\n */\n async verifyRequest(request: Request): Promise<VerifiedCallback> {\n if (request.bodyUsed) {\n throw new CallbackVerificationError(\n \"body_already_consumed\",\n \"request body was already read - pass a request.clone() or use verify({ token, rawBody })\"\n );\n }\n return this.verify({ token: request.headers.get(SIGNATURE_HEADER), rawBody: await request.text() });\n }\n\n /**\n * The plain version, for any framework: headers and the raw body in, the same raw body back out,\n * or it throws. Handy when all you want is a guard at the top of a webhook handler.\n *\n * The body must be the raw bytes as they arrived - a parsed-and-re-serialized JSON body hashes\n * differently and will be rejected. Use {@link verify} when you also want the claims.\n */\n async verifyWebhook(headers: HeaderBag, rawBody: string | Uint8Array): Promise<string> {\n const { rawBody: body } = await this.verify({ token: readHeader(headers, SIGNATURE_HEADER), rawBody });\n return body;\n }\n}\n\n/** One-shot verification, for when keeping a verifier around is not worth it. */\nexport async function verifyCallback(options: CallbackVerifierOptions, input: VerifyInput): Promise<VerifiedCallback> {\n return new CallbackVerifier(options).verify(input);\n}\n\nexport function jwksUrlFor(baseUrl: string): string {\n return new URL(\"/.well-known/jwks.json\", baseUrl).toString();\n}\n\n/** Pull a header out of either a fetch `Headers` or Node's plain object bag. */\nexport function readHeader(headers: HeaderBag, name: string): string | null {\n if (typeof (headers as Headers).get === \"function\") {\n return (headers as Headers).get(name);\n }\n const bag = headers as Record<string, string | string[] | undefined>;\n const value = bag[name] ?? bag[name.toLowerCase()] ?? bag[name.toUpperCase()];\n if (Array.isArray(value)) return value[0] ?? null;\n return value ?? null;\n}\n\nfunction toClaims(payload: JWTPayload): CallbackClaims {\n const routeId = payload.route_id;\n const gateway = payload.gateway;\n const bodySha256 = payload.body_sha256;\n const externalId = payload.external_id;\n\n if (typeof routeId !== \"string\" || typeof gateway !== \"string\" || typeof bodySha256 !== \"string\") {\n throw new CallbackVerificationError(\"malformed_claims\", \"callback token is missing route_id, gateway or body_sha256\");\n }\n if (typeof payload.iat !== \"number\" || typeof payload.exp !== \"number\") {\n throw new CallbackVerificationError(\"malformed_claims\", \"callback token is missing iat or exp\");\n }\n\n return {\n routeId,\n gateway,\n externalId: typeof externalId === \"string\" ? externalId : null,\n bodySha256,\n issuer: payload.iss ?? \"\",\n issuedAt: new Date(payload.iat * 1000),\n expiresAt: new Date(payload.exp * 1000),\n raw: payload,\n };\n}\n\n/** Hex SHA-256, matching how the router hashes the forwarded body (UTF-8 bytes). */\nexport async function sha256Hex(data: string | Uint8Array): Promise<string> {\n const bytes = typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new PaymentRouterValidationError(\"WebCrypto is unavailable - Node 18+, Workers, Deno and Bun all provide it\");\n }\n const digest = await subtle.digest(\"SHA-256\", bytes as unknown as ArrayBufferView<ArrayBuffer>);\n return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/** Both values are hex of a fixed length, so a constant-time compare is trivial - do it anyway. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) {\n diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return diff === 0;\n}\n\nfunction toText(body: string | Uint8Array): string {\n return typeof body === \"string\" ? body : new TextDecoder().decode(body);\n}\n\nfunction describe(error: unknown): string {\n if (error instanceof Error) return error.message;\n return \"unknown error\";\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url.slice(0, -1) : url;\n}\n","import { DEFAULT_BASE_URL } from \"./defaults\";\nimport { PaymentRouterValidationError } from \"./errors\";\nimport { requestJson, type FetchLike, type HttpOptions } from \"./http\";\nimport type { CreateRouteInput, GatewayName, PaymentRoute, RawPaymentRoute, VerifiedCallback } from \"./types\";\nimport { CallbackVerifier, jwksUrlFor, type CallbackVerifierOptions, type HeaderBag, type VerifyInput } from \"./verify\";\n\nexport type PaymentRouterClientOptions = {\n /**\n * The router's public origin. Defaults to {@link DEFAULT_BASE_URL}, the hosted one - set it only\n * when running your own worker. A base path is respected.\n */\n baseUrl?: string;\n /** Custom fetch, for proxies, instrumentation or tests. Defaults to the global one. */\n fetch?: FetchLike;\n /** Per-attempt timeout. Default 10s. */\n timeoutMs?: number;\n /**\n * Retries for transient failures (network errors, 429, 502, 503, 504). Default 2.\n *\n * Route creation is not idempotent: if a create succeeded but its response was lost on the way\n * back, the retry creates a second route and the first one is orphaned. That is harmless - an\n * unused route is just a row nobody ever calls back on - but set this to 0 if you would rather\n * see the failure.\n */\n maxRetries?: number;\n /** Base backoff between retries, doubled each attempt. Default 200ms. */\n retryDelayMs?: number;\n /** Extra headers on every request, e.g. one your own reverse proxy requires. */\n headers?: Record<string, string>;\n /**\n * Overrides for the callback verifier hanging off this client (issuer, max age, clock tolerance,\n * ...). `baseUrl` and `fetch` are inherited from the client, so this is usually left alone.\n */\n verifier?: Omit<CallbackVerifierOptions, \"baseUrl\">;\n};\n\nconst DEFAULTS = {\n timeoutMs: 10_000,\n maxRetries: 2,\n retryDelayMs: 200,\n};\n\n/**\n * Talks to the payment-router API.\n *\n * The router is a URL shortener for gateway webhooks: you register the real webhook URL, get a UUID\n * back, and hand that UUID to the gateway as its external payment id. The gateway then calls the\n * router's single webhook URL and the router replays the callback at your URL.\n *\n * The other end of the trip - verifying the callback the router forwards back - hangs off the same\n * object, so one `baseUrl` covers both.\n *\n * ```ts\n * const client = new PaymentRouterClient({ baseUrl: \"https://router.example.com\" });\n * const route = await client.createRoute({ webhookUrl: \"https://shop.example.com/webhooks/payu\" });\n * // route.id -> PayU's extOrderId, route.callbackUrls.payu -> PayU's notifyUrl\n *\n * const rawBody = await client.verifyWebhook(req.headers, req.body);\n * ```\n */\nexport class PaymentRouterClient {\n readonly baseUrl: string;\n private readonly http: HttpOptions;\n private readonly verifierOptions: Omit<CallbackVerifierOptions, \"baseUrl\">;\n private cachedVerifier: CallbackVerifier | null = null;\n\n constructor(options: PaymentRouterClientOptions = {}) {\n this.baseUrl = stripTrailingSlash(assertAbsoluteUrl(options.baseUrl ?? DEFAULT_BASE_URL, \"baseUrl\"));\n this.verifierOptions = {\n // The client's own fetch carries whatever a proxy in front of the router needs, and the\n // JWKS lives on that same origin - so it fits the key fetch too, unless overridden.\n ...(options.fetch ? { fetch: options.fetch as unknown as typeof globalThis.fetch } : {}),\n ...options.verifier,\n };\n this.http = {\n baseUrl: this.baseUrl,\n fetch: options.fetch ?? defaultFetch(),\n timeoutMs: options.timeoutMs ?? DEFAULTS.timeoutMs,\n maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,\n retryDelayMs: options.retryDelayMs ?? DEFAULTS.retryDelayMs,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n },\n };\n }\n\n /**\n * Register a webhook URL and get back the route whose `id` you send to the gateway as its\n * external payment id.\n *\n * @throws {InvalidWebhookUrlError} the router refused the URL (not https, or a private host).\n * @throws {RateLimitedError} the router's per-IP limit on route creation was hit.\n */\n async createRoute(input: CreateRouteInput): Promise<PaymentRoute> {\n const payload = {\n webhookUrl: assertAbsoluteUrl(input.webhookUrl, \"webhookUrl\"),\n ...(input.externalId !== undefined ? { externalId: assertExternalId(input.externalId) } : {}),\n ...(input.expiresAt !== undefined ? { expiresAt: toIsoString(input.expiresAt) } : {}),\n };\n\n const route = await requestJson<RawPaymentRoute>(this.http, \"v1/routes\", {\n method: \"POST\",\n body: JSON.stringify(payload),\n });\n\n return parseRoute(route);\n }\n\n /**\n * The URL to paste into a gateway's dashboard as its single webhook URL. The same for every\n * route, so it can be read off the client without creating one.\n */\n callbackUrl(gateway: GatewayName): string {\n return new URL(`webhooks/${encodeURIComponent(gateway)}`, `${this.baseUrl}/`).toString();\n }\n\n /** Where the router publishes the public keys that verify forwarded callbacks. */\n get jwksUrl(): string {\n return jwksUrlFor(this.baseUrl);\n }\n\n /**\n * Headers and the raw body in, the same raw body back out - or it throws. The short way to guard\n * a webhook handler: verifies the router's EdDSA JWT against its published keys, and checks that\n * the body still hashes to what the token claims.\n *\n * The body must be the raw bytes as they arrived - parsed-and-re-serialized JSON hashes\n * differently and will be rejected. Use {@link verifyCallback} when you also want the claims.\n *\n * @throws {CallbackVerificationError} when the callback cannot be trusted.\n */\n async verifyWebhook(headers: HeaderBag, rawBody: string | Uint8Array): Promise<string> {\n return this.verifier().verifyWebhook(headers, rawBody);\n }\n\n /**\n * Verify a fetch-API `Request` (Workers, Hono, Next.js, Deno, Bun) and get its claims back.\n *\n * The body is read here, so do not read it yourself first - clone the request if you need it.\n */\n async verifyRequest(request: Request): Promise<VerifiedCallback> {\n return this.verifier().verifyRequest(request);\n }\n\n /** Verify a token against the body it covers, and get the claims back. */\n async verifyCallback(input: VerifyInput): Promise<VerifiedCallback> {\n return this.verifier().verify(input);\n }\n\n /**\n * Built on first use and kept around: the verifier caches the router's key set, so reusing one\n * client saves a JWKS fetch per callback.\n */\n private verifier(): CallbackVerifier {\n this.cachedVerifier ??= new CallbackVerifier({ baseUrl: this.baseUrl, ...this.verifierOptions });\n return this.cachedVerifier;\n }\n}\n\n/** Turn the wire shape into the public one, parsing the ISO timestamps. */\nexport function parseRoute(raw: RawPaymentRoute): PaymentRoute {\n return {\n id: raw.id,\n webhookUrl: raw.webhookUrl,\n externalId: raw.externalId ?? null,\n expiresAt: raw.expiresAt ? new Date(raw.expiresAt) : null,\n createdAt: new Date(raw.createdAt),\n callbackUrls: raw.callbackUrls ?? null,\n };\n}\n\n/** Has this route stopped accepting callbacks? Routes without an expiry never do. */\nexport function isRouteExpired(route: PaymentRoute, now: Date = new Date()): boolean {\n return route.expiresAt !== null && route.expiresAt.getTime() < now.getTime();\n}\n\n/** The callback URL for one gateway, or null when the router published none. */\nexport function callbackUrlFor(route: PaymentRoute, gateway: GatewayName): string | null {\n return route.callbackUrls?.[gateway] ?? null;\n}\n\nfunction assertAbsoluteUrl(value: string, field: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new PaymentRouterValidationError(`${field} must be an absolute URL, got \"${value}\"`);\n }\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new PaymentRouterValidationError(`${field} must use http or https, got \"${url.protocol}\"`);\n }\n return value;\n}\n\n/** Mirrors the router's own bounds so an over-long id fails here instead of as an opaque 400. */\nfunction assertExternalId(value: string): string {\n if (value.length === 0 || value.length > 256) {\n throw new PaymentRouterValidationError(\"externalId must be between 1 and 256 characters\");\n }\n return value;\n}\n\nfunction toIsoString(value: Date | string | number): string {\n const date = value instanceof Date ? value : new Date(value);\n if (Number.isNaN(date.getTime())) {\n throw new PaymentRouterValidationError(`expiresAt is not a valid date: ${String(value)}`);\n }\n return date.toISOString();\n}\n\nfunction defaultFetch(): FetchLike {\n if (typeof globalThis.fetch !== \"function\") {\n throw new PaymentRouterValidationError(\n \"no global fetch available - pass one via the `fetch` option (Node 18+ has it built in)\"\n );\n }\n return globalThis.fetch.bind(globalThis) as FetchLike;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url.slice(0, -1) : url;\n}\n","/** The slice of Node's `IncomingMessage` needed to drain a body, kept structural to avoid `@types/node` in the public API. */\nexport type ReadableRequest = AsyncIterable<Uint8Array | string>;\n\n/**\n * Collect the raw request body from a Node stream, for servers with no body parser of their own.\n *\n * The signature covers the bytes as they arrived, so this deliberately returns the untouched text -\n * parse it afterwards, never before.\n */\nexport async function readRawBody(request: ReadableRequest): Promise<string> {\n const chunks: Uint8Array[] = [];\n const encoder = new TextEncoder();\n\n for await (const chunk of request) {\n chunks.push(typeof chunk === \"string\" ? encoder.encode(chunk) : chunk);\n }\n\n const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);\n const body = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n return new TextDecoder().decode(body);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@fanth/payment-router-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Client and callback verifier for payment-router - the URL shortener for payment gateway webhooks.",
5
+ "keywords": [
6
+ "payment-router",
7
+ "webhook",
8
+ "payu",
9
+ "payments",
10
+ "jwt",
11
+ "jwks",
12
+ "sdk"
13
+ ],
14
+ "license": "MIT",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "type": "module",
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js",
26
+ "require": "./dist/index.cjs"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "sideEffects": false,
36
+ "packageManager": "pnpm@11.1.2",
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "type-check": "tsc --noEmit",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "format": "prettier --write .",
46
+ "prepublishOnly": "pnpm run type-check && pnpm run test && pnpm run build"
47
+ },
48
+ "dependencies": {
49
+ "jose": "^6.2.4"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^24.12.2",
53
+ "prettier": "^3.8.3",
54
+ "tsup": "^8.5.1",
55
+ "typescript": "^5.9.3",
56
+ "vitest": "^3.2.7"
57
+ }
58
+ }