@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/LICENSE +9 -0
- package/README.md +201 -0
- package/dist/index.cjs +448 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +275 -0
- package/dist/index.d.ts +275 -0
- package/dist/index.js +426 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
|
@@ -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","createRemoteJWKSet","customFetch","jwtVerify","describe","stripTrailingSlash"],"mappings":"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","file":"index.cjs","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/dist/index.d.cts
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { JWTPayload, JWTVerifyGetKey } from 'jose';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The hosted router, used whenever no `baseUrl` is given. Self-hosting the worker? Pass your own
|
|
5
|
+
* origin to the client and the verifier - the verifier's expected `iss` follows it.
|
|
6
|
+
*/
|
|
7
|
+
declare const DEFAULT_BASE_URL = "https://payment-r.fanth.pl";
|
|
8
|
+
|
|
9
|
+
/** Injectable so tests, proxies and edge runtimes can swap in their own implementation. */
|
|
10
|
+
type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
|
|
11
|
+
|
|
12
|
+
/** Gateways the router ships an adapter for. Any other string is accepted too - adapters are added server-side. */
|
|
13
|
+
type KnownGateway = "payu";
|
|
14
|
+
type GatewayName = KnownGateway | (string & {});
|
|
15
|
+
type CreateRouteInput = {
|
|
16
|
+
/** The real webhook URL the gateway callback should end up at. Must be https and publicly resolvable. */
|
|
17
|
+
webhookUrl: string;
|
|
18
|
+
/** Your own id for this payment, echoed back on the forwarded callback (header + `external_id` claim). */
|
|
19
|
+
externalId?: string;
|
|
20
|
+
/** When callbacks carrying this route id stop being accepted. Anything `Date`-ish is fine. */
|
|
21
|
+
expiresAt?: Date | string | number;
|
|
22
|
+
};
|
|
23
|
+
/** A route as it comes back from `POST /v1/routes`, with timestamps parsed. */
|
|
24
|
+
type PaymentRoute = {
|
|
25
|
+
/**
|
|
26
|
+
* The id to hand the gateway as its external payment id (PayU: `extOrderId`). This is the whole
|
|
27
|
+
* point of the router - the callback carries it back and that is how the real URL is found.
|
|
28
|
+
*/
|
|
29
|
+
id: string;
|
|
30
|
+
webhookUrl: string;
|
|
31
|
+
externalId: string | null;
|
|
32
|
+
expiresAt: Date | null;
|
|
33
|
+
createdAt: Date;
|
|
34
|
+
/**
|
|
35
|
+
* The fixed URL to paste into each gateway's dashboard, per gateway. The same for every route by
|
|
36
|
+
* design. `null` when the router has no `PUBLIC_BASE_URL` configured.
|
|
37
|
+
*/
|
|
38
|
+
callbackUrls: Record<GatewayName, string> | null;
|
|
39
|
+
};
|
|
40
|
+
/** The wire shape of a route, before timestamps are parsed. */
|
|
41
|
+
type RawPaymentRoute = {
|
|
42
|
+
id: string;
|
|
43
|
+
webhookUrl: string;
|
|
44
|
+
externalId: string | null;
|
|
45
|
+
expiresAt: string | null;
|
|
46
|
+
createdAt: string;
|
|
47
|
+
callbackUrls: Record<string, string> | null;
|
|
48
|
+
};
|
|
49
|
+
/** The verified claims of the EdDSA JWT the router puts on every forwarded callback. */
|
|
50
|
+
type CallbackClaims = {
|
|
51
|
+
/** The route id that matched - the id you handed the gateway. */
|
|
52
|
+
routeId: string;
|
|
53
|
+
/** The adapter that handled the callback, e.g. "payu". */
|
|
54
|
+
gateway: GatewayName;
|
|
55
|
+
/** Your own id, if you set one when creating the route. */
|
|
56
|
+
externalId: string | null;
|
|
57
|
+
/** Hex SHA-256 of the callback body, already checked against the body you passed in. */
|
|
58
|
+
bodySha256: string;
|
|
59
|
+
/** The router's public origin (`iss`). */
|
|
60
|
+
issuer: string;
|
|
61
|
+
issuedAt: Date;
|
|
62
|
+
expiresAt: Date;
|
|
63
|
+
/** The raw JWT payload, for claims this type does not model. */
|
|
64
|
+
raw: JWTPayload;
|
|
65
|
+
};
|
|
66
|
+
type VerifiedCallback = {
|
|
67
|
+
claims: CallbackClaims;
|
|
68
|
+
/** The callback body, byte for byte as the gateway sent it - parse it with the gateway's own schema. */
|
|
69
|
+
rawBody: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type CallbackVerifierOptions = {
|
|
73
|
+
/**
|
|
74
|
+
* The router's public origin: the JWKS URL and, unless overridden, the expected `iss`. Defaults
|
|
75
|
+
* to {@link DEFAULT_BASE_URL}, the hosted router - set it only when running your own worker.
|
|
76
|
+
*/
|
|
77
|
+
baseUrl?: string;
|
|
78
|
+
/** Override the JWKS location, e.g. when it is mirrored somewhere else. */
|
|
79
|
+
jwksUrl?: string;
|
|
80
|
+
/** Expected `iss` claim. Defaults to `baseUrl` - must match the router's `PUBLIC_BASE_URL` exactly. */
|
|
81
|
+
issuer?: string;
|
|
82
|
+
/** Reject tokens issued longer ago than this, in seconds. Default 300 (the router's own TTL). */
|
|
83
|
+
maxAgeSeconds?: number;
|
|
84
|
+
/** Leeway for clock skew between you and the router, in seconds. Default 5. */
|
|
85
|
+
clockToleranceSeconds?: number;
|
|
86
|
+
/** Custom fetch for the JWKS request. */
|
|
87
|
+
fetch?: typeof globalThis.fetch;
|
|
88
|
+
/** Swap the whole key source, e.g. `createLocalJWKSet` in tests or air-gapped setups. */
|
|
89
|
+
keyStore?: JWTVerifyGetKey;
|
|
90
|
+
};
|
|
91
|
+
/** The minimum a caller has to hand over: the token and the exact bytes that were delivered. */
|
|
92
|
+
type VerifyInput = {
|
|
93
|
+
/** The `X-Payment-Router-Signature` header value. */
|
|
94
|
+
token: string | null | undefined;
|
|
95
|
+
/** The callback body exactly as received - not re-serialized JSON, or the hash will not match. */
|
|
96
|
+
rawBody: string | Uint8Array;
|
|
97
|
+
};
|
|
98
|
+
/** Either header shape a runtime hands you: fetch's `Headers` or Node's plain object. */
|
|
99
|
+
type HeaderBag = Record<string, string | string[] | undefined> | Headers;
|
|
100
|
+
declare function jwksUrlFor(baseUrl: string): string;
|
|
101
|
+
/** Pull a header out of either a fetch `Headers` or Node's plain object bag. */
|
|
102
|
+
declare function readHeader(headers: HeaderBag, name: string): string | null;
|
|
103
|
+
/** Hex SHA-256, matching how the router hashes the forwarded body (UTF-8 bytes). */
|
|
104
|
+
declare function sha256Hex(data: string | Uint8Array): Promise<string>;
|
|
105
|
+
|
|
106
|
+
type PaymentRouterClientOptions = {
|
|
107
|
+
/**
|
|
108
|
+
* The router's public origin. Defaults to {@link DEFAULT_BASE_URL}, the hosted one - set it only
|
|
109
|
+
* when running your own worker. A base path is respected.
|
|
110
|
+
*/
|
|
111
|
+
baseUrl?: string;
|
|
112
|
+
/** Custom fetch, for proxies, instrumentation or tests. Defaults to the global one. */
|
|
113
|
+
fetch?: FetchLike;
|
|
114
|
+
/** Per-attempt timeout. Default 10s. */
|
|
115
|
+
timeoutMs?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Retries for transient failures (network errors, 429, 502, 503, 504). Default 2.
|
|
118
|
+
*
|
|
119
|
+
* Route creation is not idempotent: if a create succeeded but its response was lost on the way
|
|
120
|
+
* back, the retry creates a second route and the first one is orphaned. That is harmless - an
|
|
121
|
+
* unused route is just a row nobody ever calls back on - but set this to 0 if you would rather
|
|
122
|
+
* see the failure.
|
|
123
|
+
*/
|
|
124
|
+
maxRetries?: number;
|
|
125
|
+
/** Base backoff between retries, doubled each attempt. Default 200ms. */
|
|
126
|
+
retryDelayMs?: number;
|
|
127
|
+
/** Extra headers on every request, e.g. one your own reverse proxy requires. */
|
|
128
|
+
headers?: Record<string, string>;
|
|
129
|
+
/**
|
|
130
|
+
* Overrides for the callback verifier hanging off this client (issuer, max age, clock tolerance,
|
|
131
|
+
* ...). `baseUrl` and `fetch` are inherited from the client, so this is usually left alone.
|
|
132
|
+
*/
|
|
133
|
+
verifier?: Omit<CallbackVerifierOptions, "baseUrl">;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Talks to the payment-router API.
|
|
137
|
+
*
|
|
138
|
+
* The router is a URL shortener for gateway webhooks: you register the real webhook URL, get a UUID
|
|
139
|
+
* back, and hand that UUID to the gateway as its external payment id. The gateway then calls the
|
|
140
|
+
* router's single webhook URL and the router replays the callback at your URL.
|
|
141
|
+
*
|
|
142
|
+
* The other end of the trip - verifying the callback the router forwards back - hangs off the same
|
|
143
|
+
* object, so one `baseUrl` covers both.
|
|
144
|
+
*
|
|
145
|
+
* ```ts
|
|
146
|
+
* const client = new PaymentRouterClient({ baseUrl: "https://router.example.com" });
|
|
147
|
+
* const route = await client.createRoute({ webhookUrl: "https://shop.example.com/webhooks/payu" });
|
|
148
|
+
* // route.id -> PayU's extOrderId, route.callbackUrls.payu -> PayU's notifyUrl
|
|
149
|
+
*
|
|
150
|
+
* const rawBody = await client.verifyWebhook(req.headers, req.body);
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
declare class PaymentRouterClient {
|
|
154
|
+
readonly baseUrl: string;
|
|
155
|
+
private readonly http;
|
|
156
|
+
private readonly verifierOptions;
|
|
157
|
+
private cachedVerifier;
|
|
158
|
+
constructor(options?: PaymentRouterClientOptions);
|
|
159
|
+
/**
|
|
160
|
+
* Register a webhook URL and get back the route whose `id` you send to the gateway as its
|
|
161
|
+
* external payment id.
|
|
162
|
+
*
|
|
163
|
+
* @throws {InvalidWebhookUrlError} the router refused the URL (not https, or a private host).
|
|
164
|
+
* @throws {RateLimitedError} the router's per-IP limit on route creation was hit.
|
|
165
|
+
*/
|
|
166
|
+
createRoute(input: CreateRouteInput): Promise<PaymentRoute>;
|
|
167
|
+
/**
|
|
168
|
+
* The URL to paste into a gateway's dashboard as its single webhook URL. The same for every
|
|
169
|
+
* route, so it can be read off the client without creating one.
|
|
170
|
+
*/
|
|
171
|
+
callbackUrl(gateway: GatewayName): string;
|
|
172
|
+
/** Where the router publishes the public keys that verify forwarded callbacks. */
|
|
173
|
+
get jwksUrl(): string;
|
|
174
|
+
/**
|
|
175
|
+
* Headers and the raw body in, the same raw body back out - or it throws. The short way to guard
|
|
176
|
+
* a webhook handler: verifies the router's EdDSA JWT against its published keys, and checks that
|
|
177
|
+
* the body still hashes to what the token claims.
|
|
178
|
+
*
|
|
179
|
+
* The body must be the raw bytes as they arrived - parsed-and-re-serialized JSON hashes
|
|
180
|
+
* differently and will be rejected. Use {@link verifyCallback} when you also want the claims.
|
|
181
|
+
*
|
|
182
|
+
* @throws {CallbackVerificationError} when the callback cannot be trusted.
|
|
183
|
+
*/
|
|
184
|
+
verifyWebhook(headers: HeaderBag, rawBody: string | Uint8Array): Promise<string>;
|
|
185
|
+
/**
|
|
186
|
+
* Verify a fetch-API `Request` (Workers, Hono, Next.js, Deno, Bun) and get its claims back.
|
|
187
|
+
*
|
|
188
|
+
* The body is read here, so do not read it yourself first - clone the request if you need it.
|
|
189
|
+
*/
|
|
190
|
+
verifyRequest(request: Request): Promise<VerifiedCallback>;
|
|
191
|
+
/** Verify a token against the body it covers, and get the claims back. */
|
|
192
|
+
verifyCallback(input: VerifyInput): Promise<VerifiedCallback>;
|
|
193
|
+
/**
|
|
194
|
+
* Built on first use and kept around: the verifier caches the router's key set, so reusing one
|
|
195
|
+
* client saves a JWKS fetch per callback.
|
|
196
|
+
*/
|
|
197
|
+
private verifier;
|
|
198
|
+
}
|
|
199
|
+
/** Turn the wire shape into the public one, parsing the ISO timestamps. */
|
|
200
|
+
declare function parseRoute(raw: RawPaymentRoute): PaymentRoute;
|
|
201
|
+
/** Has this route stopped accepting callbacks? Routes without an expiry never do. */
|
|
202
|
+
declare function isRouteExpired(route: PaymentRoute, now?: Date): boolean;
|
|
203
|
+
/** The callback URL for one gateway, or null when the router published none. */
|
|
204
|
+
declare function callbackUrlFor(route: PaymentRoute, gateway: GatewayName): string | null;
|
|
205
|
+
|
|
206
|
+
/** The slice of Node's `IncomingMessage` needed to drain a body, kept structural to avoid `@types/node` in the public API. */
|
|
207
|
+
type ReadableRequest = AsyncIterable<Uint8Array | string>;
|
|
208
|
+
/**
|
|
209
|
+
* Collect the raw request body from a Node stream, for servers with no body parser of their own.
|
|
210
|
+
*
|
|
211
|
+
* The signature covers the bytes as they arrived, so this deliberately returns the untouched text -
|
|
212
|
+
* parse it afterwards, never before.
|
|
213
|
+
*/
|
|
214
|
+
declare function readRawBody(request: ReadableRequest): Promise<string>;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Headers the router adds to a forwarded callback. All lowercase - Node lowercases incoming header
|
|
218
|
+
* names, and the fetch `Headers` lookup is case-insensitive anyway.
|
|
219
|
+
*
|
|
220
|
+
* Only the signature header is trustworthy: the others are convenience copies of claims that live
|
|
221
|
+
* inside the signed JWT, and anyone who can reach your webhook can set them. Read them for logs,
|
|
222
|
+
* verify the token for decisions.
|
|
223
|
+
*/
|
|
224
|
+
declare const SIGNATURE_HEADER = "x-payment-router-signature";
|
|
225
|
+
declare const ROUTE_ID_HEADER = "x-payment-router-id";
|
|
226
|
+
declare const GATEWAY_HEADER = "x-payment-router-gateway";
|
|
227
|
+
declare const EXTERNAL_ID_HEADER = "x-payment-router-external-id";
|
|
228
|
+
declare const FORWARDED_HOST_HEADER = "x-forwarded-host";
|
|
229
|
+
|
|
230
|
+
/** Base class for everything this SDK throws, so a single `catch` can tell our failures apart. */
|
|
231
|
+
declare class PaymentRouterError extends Error {
|
|
232
|
+
constructor(message: string, options?: {
|
|
233
|
+
cause?: unknown;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/** The router answered, but with a non-2xx status. `code` is the machine-readable `error` field. */
|
|
237
|
+
declare class PaymentRouterApiError extends PaymentRouterError {
|
|
238
|
+
readonly status: number;
|
|
239
|
+
/** The `error` field of the response body, when the router sent one. */
|
|
240
|
+
readonly code: string | null;
|
|
241
|
+
/** The parsed (or raw string) response body, for anything the typed fields do not cover. */
|
|
242
|
+
readonly body: unknown;
|
|
243
|
+
constructor(message: string, status: number, code: string | null, body: unknown);
|
|
244
|
+
}
|
|
245
|
+
/** 429 from `POST /v1/routes` - the router's per-IP rate limit kicked in. Safe to retry later. */
|
|
246
|
+
declare class RateLimitedError extends PaymentRouterApiError {
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* 422 - the router refused the webhook URL (not https, or pointing at a private/loopback host).
|
|
250
|
+
* Never retry this one, the URL itself has to change.
|
|
251
|
+
*/
|
|
252
|
+
declare class InvalidWebhookUrlError extends PaymentRouterApiError {
|
|
253
|
+
/** The router's human-readable reason, e.g. "webhookUrl must use https". */
|
|
254
|
+
get reason(): string;
|
|
255
|
+
}
|
|
256
|
+
/** The request never produced a response: DNS, TCP, TLS, timeout or abort. */
|
|
257
|
+
declare class PaymentRouterNetworkError extends PaymentRouterError {
|
|
258
|
+
}
|
|
259
|
+
/** The request did not survive the client-side checks, so nothing was sent. */
|
|
260
|
+
declare class PaymentRouterValidationError extends PaymentRouterError {
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* A forwarded callback failed verification: missing/expired/forged JWT, wrong issuer, or a body that
|
|
264
|
+
* does not match the `body_sha256` claim. Treat it as an unauthenticated request, never as a payment.
|
|
265
|
+
*/
|
|
266
|
+
declare class CallbackVerificationError extends PaymentRouterError {
|
|
267
|
+
/** Machine-readable failure reason, for logging and metrics. */
|
|
268
|
+
readonly reason: CallbackVerificationFailure;
|
|
269
|
+
constructor(reason: CallbackVerificationFailure, message: string, options?: {
|
|
270
|
+
cause?: unknown;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
type CallbackVerificationFailure = "missing_signature" | "invalid_signature" | "body_mismatch" | "malformed_claims" | "body_already_consumed";
|
|
274
|
+
|
|
275
|
+
export { type CallbackClaims, CallbackVerificationError, type CallbackVerificationFailure, type CallbackVerifierOptions, type CreateRouteInput, DEFAULT_BASE_URL, EXTERNAL_ID_HEADER, FORWARDED_HOST_HEADER, type FetchLike, GATEWAY_HEADER, type GatewayName, type HeaderBag, InvalidWebhookUrlError, type KnownGateway, type PaymentRoute, PaymentRouterApiError, PaymentRouterClient, type PaymentRouterClientOptions, PaymentRouterError, PaymentRouterNetworkError, PaymentRouterValidationError, ROUTE_ID_HEADER, RateLimitedError, type RawPaymentRoute, type ReadableRequest, SIGNATURE_HEADER, type VerifiedCallback, type VerifyInput, callbackUrlFor, isRouteExpired, jwksUrlFor, parseRoute, readHeader, readRawBody, sha256Hex };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { JWTPayload, JWTVerifyGetKey } from 'jose';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The hosted router, used whenever no `baseUrl` is given. Self-hosting the worker? Pass your own
|
|
5
|
+
* origin to the client and the verifier - the verifier's expected `iss` follows it.
|
|
6
|
+
*/
|
|
7
|
+
declare const DEFAULT_BASE_URL = "https://payment-r.fanth.pl";
|
|
8
|
+
|
|
9
|
+
/** Injectable so tests, proxies and edge runtimes can swap in their own implementation. */
|
|
10
|
+
type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
|
|
11
|
+
|
|
12
|
+
/** Gateways the router ships an adapter for. Any other string is accepted too - adapters are added server-side. */
|
|
13
|
+
type KnownGateway = "payu";
|
|
14
|
+
type GatewayName = KnownGateway | (string & {});
|
|
15
|
+
type CreateRouteInput = {
|
|
16
|
+
/** The real webhook URL the gateway callback should end up at. Must be https and publicly resolvable. */
|
|
17
|
+
webhookUrl: string;
|
|
18
|
+
/** Your own id for this payment, echoed back on the forwarded callback (header + `external_id` claim). */
|
|
19
|
+
externalId?: string;
|
|
20
|
+
/** When callbacks carrying this route id stop being accepted. Anything `Date`-ish is fine. */
|
|
21
|
+
expiresAt?: Date | string | number;
|
|
22
|
+
};
|
|
23
|
+
/** A route as it comes back from `POST /v1/routes`, with timestamps parsed. */
|
|
24
|
+
type PaymentRoute = {
|
|
25
|
+
/**
|
|
26
|
+
* The id to hand the gateway as its external payment id (PayU: `extOrderId`). This is the whole
|
|
27
|
+
* point of the router - the callback carries it back and that is how the real URL is found.
|
|
28
|
+
*/
|
|
29
|
+
id: string;
|
|
30
|
+
webhookUrl: string;
|
|
31
|
+
externalId: string | null;
|
|
32
|
+
expiresAt: Date | null;
|
|
33
|
+
createdAt: Date;
|
|
34
|
+
/**
|
|
35
|
+
* The fixed URL to paste into each gateway's dashboard, per gateway. The same for every route by
|
|
36
|
+
* design. `null` when the router has no `PUBLIC_BASE_URL` configured.
|
|
37
|
+
*/
|
|
38
|
+
callbackUrls: Record<GatewayName, string> | null;
|
|
39
|
+
};
|
|
40
|
+
/** The wire shape of a route, before timestamps are parsed. */
|
|
41
|
+
type RawPaymentRoute = {
|
|
42
|
+
id: string;
|
|
43
|
+
webhookUrl: string;
|
|
44
|
+
externalId: string | null;
|
|
45
|
+
expiresAt: string | null;
|
|
46
|
+
createdAt: string;
|
|
47
|
+
callbackUrls: Record<string, string> | null;
|
|
48
|
+
};
|
|
49
|
+
/** The verified claims of the EdDSA JWT the router puts on every forwarded callback. */
|
|
50
|
+
type CallbackClaims = {
|
|
51
|
+
/** The route id that matched - the id you handed the gateway. */
|
|
52
|
+
routeId: string;
|
|
53
|
+
/** The adapter that handled the callback, e.g. "payu". */
|
|
54
|
+
gateway: GatewayName;
|
|
55
|
+
/** Your own id, if you set one when creating the route. */
|
|
56
|
+
externalId: string | null;
|
|
57
|
+
/** Hex SHA-256 of the callback body, already checked against the body you passed in. */
|
|
58
|
+
bodySha256: string;
|
|
59
|
+
/** The router's public origin (`iss`). */
|
|
60
|
+
issuer: string;
|
|
61
|
+
issuedAt: Date;
|
|
62
|
+
expiresAt: Date;
|
|
63
|
+
/** The raw JWT payload, for claims this type does not model. */
|
|
64
|
+
raw: JWTPayload;
|
|
65
|
+
};
|
|
66
|
+
type VerifiedCallback = {
|
|
67
|
+
claims: CallbackClaims;
|
|
68
|
+
/** The callback body, byte for byte as the gateway sent it - parse it with the gateway's own schema. */
|
|
69
|
+
rawBody: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type CallbackVerifierOptions = {
|
|
73
|
+
/**
|
|
74
|
+
* The router's public origin: the JWKS URL and, unless overridden, the expected `iss`. Defaults
|
|
75
|
+
* to {@link DEFAULT_BASE_URL}, the hosted router - set it only when running your own worker.
|
|
76
|
+
*/
|
|
77
|
+
baseUrl?: string;
|
|
78
|
+
/** Override the JWKS location, e.g. when it is mirrored somewhere else. */
|
|
79
|
+
jwksUrl?: string;
|
|
80
|
+
/** Expected `iss` claim. Defaults to `baseUrl` - must match the router's `PUBLIC_BASE_URL` exactly. */
|
|
81
|
+
issuer?: string;
|
|
82
|
+
/** Reject tokens issued longer ago than this, in seconds. Default 300 (the router's own TTL). */
|
|
83
|
+
maxAgeSeconds?: number;
|
|
84
|
+
/** Leeway for clock skew between you and the router, in seconds. Default 5. */
|
|
85
|
+
clockToleranceSeconds?: number;
|
|
86
|
+
/** Custom fetch for the JWKS request. */
|
|
87
|
+
fetch?: typeof globalThis.fetch;
|
|
88
|
+
/** Swap the whole key source, e.g. `createLocalJWKSet` in tests or air-gapped setups. */
|
|
89
|
+
keyStore?: JWTVerifyGetKey;
|
|
90
|
+
};
|
|
91
|
+
/** The minimum a caller has to hand over: the token and the exact bytes that were delivered. */
|
|
92
|
+
type VerifyInput = {
|
|
93
|
+
/** The `X-Payment-Router-Signature` header value. */
|
|
94
|
+
token: string | null | undefined;
|
|
95
|
+
/** The callback body exactly as received - not re-serialized JSON, or the hash will not match. */
|
|
96
|
+
rawBody: string | Uint8Array;
|
|
97
|
+
};
|
|
98
|
+
/** Either header shape a runtime hands you: fetch's `Headers` or Node's plain object. */
|
|
99
|
+
type HeaderBag = Record<string, string | string[] | undefined> | Headers;
|
|
100
|
+
declare function jwksUrlFor(baseUrl: string): string;
|
|
101
|
+
/** Pull a header out of either a fetch `Headers` or Node's plain object bag. */
|
|
102
|
+
declare function readHeader(headers: HeaderBag, name: string): string | null;
|
|
103
|
+
/** Hex SHA-256, matching how the router hashes the forwarded body (UTF-8 bytes). */
|
|
104
|
+
declare function sha256Hex(data: string | Uint8Array): Promise<string>;
|
|
105
|
+
|
|
106
|
+
type PaymentRouterClientOptions = {
|
|
107
|
+
/**
|
|
108
|
+
* The router's public origin. Defaults to {@link DEFAULT_BASE_URL}, the hosted one - set it only
|
|
109
|
+
* when running your own worker. A base path is respected.
|
|
110
|
+
*/
|
|
111
|
+
baseUrl?: string;
|
|
112
|
+
/** Custom fetch, for proxies, instrumentation or tests. Defaults to the global one. */
|
|
113
|
+
fetch?: FetchLike;
|
|
114
|
+
/** Per-attempt timeout. Default 10s. */
|
|
115
|
+
timeoutMs?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Retries for transient failures (network errors, 429, 502, 503, 504). Default 2.
|
|
118
|
+
*
|
|
119
|
+
* Route creation is not idempotent: if a create succeeded but its response was lost on the way
|
|
120
|
+
* back, the retry creates a second route and the first one is orphaned. That is harmless - an
|
|
121
|
+
* unused route is just a row nobody ever calls back on - but set this to 0 if you would rather
|
|
122
|
+
* see the failure.
|
|
123
|
+
*/
|
|
124
|
+
maxRetries?: number;
|
|
125
|
+
/** Base backoff between retries, doubled each attempt. Default 200ms. */
|
|
126
|
+
retryDelayMs?: number;
|
|
127
|
+
/** Extra headers on every request, e.g. one your own reverse proxy requires. */
|
|
128
|
+
headers?: Record<string, string>;
|
|
129
|
+
/**
|
|
130
|
+
* Overrides for the callback verifier hanging off this client (issuer, max age, clock tolerance,
|
|
131
|
+
* ...). `baseUrl` and `fetch` are inherited from the client, so this is usually left alone.
|
|
132
|
+
*/
|
|
133
|
+
verifier?: Omit<CallbackVerifierOptions, "baseUrl">;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Talks to the payment-router API.
|
|
137
|
+
*
|
|
138
|
+
* The router is a URL shortener for gateway webhooks: you register the real webhook URL, get a UUID
|
|
139
|
+
* back, and hand that UUID to the gateway as its external payment id. The gateway then calls the
|
|
140
|
+
* router's single webhook URL and the router replays the callback at your URL.
|
|
141
|
+
*
|
|
142
|
+
* The other end of the trip - verifying the callback the router forwards back - hangs off the same
|
|
143
|
+
* object, so one `baseUrl` covers both.
|
|
144
|
+
*
|
|
145
|
+
* ```ts
|
|
146
|
+
* const client = new PaymentRouterClient({ baseUrl: "https://router.example.com" });
|
|
147
|
+
* const route = await client.createRoute({ webhookUrl: "https://shop.example.com/webhooks/payu" });
|
|
148
|
+
* // route.id -> PayU's extOrderId, route.callbackUrls.payu -> PayU's notifyUrl
|
|
149
|
+
*
|
|
150
|
+
* const rawBody = await client.verifyWebhook(req.headers, req.body);
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
declare class PaymentRouterClient {
|
|
154
|
+
readonly baseUrl: string;
|
|
155
|
+
private readonly http;
|
|
156
|
+
private readonly verifierOptions;
|
|
157
|
+
private cachedVerifier;
|
|
158
|
+
constructor(options?: PaymentRouterClientOptions);
|
|
159
|
+
/**
|
|
160
|
+
* Register a webhook URL and get back the route whose `id` you send to the gateway as its
|
|
161
|
+
* external payment id.
|
|
162
|
+
*
|
|
163
|
+
* @throws {InvalidWebhookUrlError} the router refused the URL (not https, or a private host).
|
|
164
|
+
* @throws {RateLimitedError} the router's per-IP limit on route creation was hit.
|
|
165
|
+
*/
|
|
166
|
+
createRoute(input: CreateRouteInput): Promise<PaymentRoute>;
|
|
167
|
+
/**
|
|
168
|
+
* The URL to paste into a gateway's dashboard as its single webhook URL. The same for every
|
|
169
|
+
* route, so it can be read off the client without creating one.
|
|
170
|
+
*/
|
|
171
|
+
callbackUrl(gateway: GatewayName): string;
|
|
172
|
+
/** Where the router publishes the public keys that verify forwarded callbacks. */
|
|
173
|
+
get jwksUrl(): string;
|
|
174
|
+
/**
|
|
175
|
+
* Headers and the raw body in, the same raw body back out - or it throws. The short way to guard
|
|
176
|
+
* a webhook handler: verifies the router's EdDSA JWT against its published keys, and checks that
|
|
177
|
+
* the body still hashes to what the token claims.
|
|
178
|
+
*
|
|
179
|
+
* The body must be the raw bytes as they arrived - parsed-and-re-serialized JSON hashes
|
|
180
|
+
* differently and will be rejected. Use {@link verifyCallback} when you also want the claims.
|
|
181
|
+
*
|
|
182
|
+
* @throws {CallbackVerificationError} when the callback cannot be trusted.
|
|
183
|
+
*/
|
|
184
|
+
verifyWebhook(headers: HeaderBag, rawBody: string | Uint8Array): Promise<string>;
|
|
185
|
+
/**
|
|
186
|
+
* Verify a fetch-API `Request` (Workers, Hono, Next.js, Deno, Bun) and get its claims back.
|
|
187
|
+
*
|
|
188
|
+
* The body is read here, so do not read it yourself first - clone the request if you need it.
|
|
189
|
+
*/
|
|
190
|
+
verifyRequest(request: Request): Promise<VerifiedCallback>;
|
|
191
|
+
/** Verify a token against the body it covers, and get the claims back. */
|
|
192
|
+
verifyCallback(input: VerifyInput): Promise<VerifiedCallback>;
|
|
193
|
+
/**
|
|
194
|
+
* Built on first use and kept around: the verifier caches the router's key set, so reusing one
|
|
195
|
+
* client saves a JWKS fetch per callback.
|
|
196
|
+
*/
|
|
197
|
+
private verifier;
|
|
198
|
+
}
|
|
199
|
+
/** Turn the wire shape into the public one, parsing the ISO timestamps. */
|
|
200
|
+
declare function parseRoute(raw: RawPaymentRoute): PaymentRoute;
|
|
201
|
+
/** Has this route stopped accepting callbacks? Routes without an expiry never do. */
|
|
202
|
+
declare function isRouteExpired(route: PaymentRoute, now?: Date): boolean;
|
|
203
|
+
/** The callback URL for one gateway, or null when the router published none. */
|
|
204
|
+
declare function callbackUrlFor(route: PaymentRoute, gateway: GatewayName): string | null;
|
|
205
|
+
|
|
206
|
+
/** The slice of Node's `IncomingMessage` needed to drain a body, kept structural to avoid `@types/node` in the public API. */
|
|
207
|
+
type ReadableRequest = AsyncIterable<Uint8Array | string>;
|
|
208
|
+
/**
|
|
209
|
+
* Collect the raw request body from a Node stream, for servers with no body parser of their own.
|
|
210
|
+
*
|
|
211
|
+
* The signature covers the bytes as they arrived, so this deliberately returns the untouched text -
|
|
212
|
+
* parse it afterwards, never before.
|
|
213
|
+
*/
|
|
214
|
+
declare function readRawBody(request: ReadableRequest): Promise<string>;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Headers the router adds to a forwarded callback. All lowercase - Node lowercases incoming header
|
|
218
|
+
* names, and the fetch `Headers` lookup is case-insensitive anyway.
|
|
219
|
+
*
|
|
220
|
+
* Only the signature header is trustworthy: the others are convenience copies of claims that live
|
|
221
|
+
* inside the signed JWT, and anyone who can reach your webhook can set them. Read them for logs,
|
|
222
|
+
* verify the token for decisions.
|
|
223
|
+
*/
|
|
224
|
+
declare const SIGNATURE_HEADER = "x-payment-router-signature";
|
|
225
|
+
declare const ROUTE_ID_HEADER = "x-payment-router-id";
|
|
226
|
+
declare const GATEWAY_HEADER = "x-payment-router-gateway";
|
|
227
|
+
declare const EXTERNAL_ID_HEADER = "x-payment-router-external-id";
|
|
228
|
+
declare const FORWARDED_HOST_HEADER = "x-forwarded-host";
|
|
229
|
+
|
|
230
|
+
/** Base class for everything this SDK throws, so a single `catch` can tell our failures apart. */
|
|
231
|
+
declare class PaymentRouterError extends Error {
|
|
232
|
+
constructor(message: string, options?: {
|
|
233
|
+
cause?: unknown;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/** The router answered, but with a non-2xx status. `code` is the machine-readable `error` field. */
|
|
237
|
+
declare class PaymentRouterApiError extends PaymentRouterError {
|
|
238
|
+
readonly status: number;
|
|
239
|
+
/** The `error` field of the response body, when the router sent one. */
|
|
240
|
+
readonly code: string | null;
|
|
241
|
+
/** The parsed (or raw string) response body, for anything the typed fields do not cover. */
|
|
242
|
+
readonly body: unknown;
|
|
243
|
+
constructor(message: string, status: number, code: string | null, body: unknown);
|
|
244
|
+
}
|
|
245
|
+
/** 429 from `POST /v1/routes` - the router's per-IP rate limit kicked in. Safe to retry later. */
|
|
246
|
+
declare class RateLimitedError extends PaymentRouterApiError {
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* 422 - the router refused the webhook URL (not https, or pointing at a private/loopback host).
|
|
250
|
+
* Never retry this one, the URL itself has to change.
|
|
251
|
+
*/
|
|
252
|
+
declare class InvalidWebhookUrlError extends PaymentRouterApiError {
|
|
253
|
+
/** The router's human-readable reason, e.g. "webhookUrl must use https". */
|
|
254
|
+
get reason(): string;
|
|
255
|
+
}
|
|
256
|
+
/** The request never produced a response: DNS, TCP, TLS, timeout or abort. */
|
|
257
|
+
declare class PaymentRouterNetworkError extends PaymentRouterError {
|
|
258
|
+
}
|
|
259
|
+
/** The request did not survive the client-side checks, so nothing was sent. */
|
|
260
|
+
declare class PaymentRouterValidationError extends PaymentRouterError {
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* A forwarded callback failed verification: missing/expired/forged JWT, wrong issuer, or a body that
|
|
264
|
+
* does not match the `body_sha256` claim. Treat it as an unauthenticated request, never as a payment.
|
|
265
|
+
*/
|
|
266
|
+
declare class CallbackVerificationError extends PaymentRouterError {
|
|
267
|
+
/** Machine-readable failure reason, for logging and metrics. */
|
|
268
|
+
readonly reason: CallbackVerificationFailure;
|
|
269
|
+
constructor(reason: CallbackVerificationFailure, message: string, options?: {
|
|
270
|
+
cause?: unknown;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
type CallbackVerificationFailure = "missing_signature" | "invalid_signature" | "body_mismatch" | "malformed_claims" | "body_already_consumed";
|
|
274
|
+
|
|
275
|
+
export { type CallbackClaims, CallbackVerificationError, type CallbackVerificationFailure, type CallbackVerifierOptions, type CreateRouteInput, DEFAULT_BASE_URL, EXTERNAL_ID_HEADER, FORWARDED_HOST_HEADER, type FetchLike, GATEWAY_HEADER, type GatewayName, type HeaderBag, InvalidWebhookUrlError, type KnownGateway, type PaymentRoute, PaymentRouterApiError, PaymentRouterClient, type PaymentRouterClientOptions, PaymentRouterError, PaymentRouterNetworkError, PaymentRouterValidationError, ROUTE_ID_HEADER, RateLimitedError, type RawPaymentRoute, type ReadableRequest, SIGNATURE_HEADER, type VerifiedCallback, type VerifyInput, callbackUrlFor, isRouteExpired, jwksUrlFor, parseRoute, readHeader, readRawBody, sha256Hex };
|