@fanth/payment-router-sdk 0.1.0 → 0.1.2
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 +674 -9
- package/README.md +22 -174
- package/dist/index.cjs +71 -431
- package/dist/index.d.ts +23 -257
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -410
- package/package.json +27 -38
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -275
- package/dist/index.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,275 +1,41 @@
|
|
|
1
|
-
|
|
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. */
|
|
1
|
+
export type CreateRouteOptions = {
|
|
17
2
|
webhookUrl: string;
|
|
18
|
-
/**
|
|
3
|
+
/** Caller's own id for this payment, echoed back on the forwarded callback. */
|
|
19
4
|
externalId?: string;
|
|
20
|
-
/**
|
|
21
|
-
expiresAt?:
|
|
5
|
+
/** Defaults to 12h out on the server if omitted. */
|
|
6
|
+
expiresAt?: string | Date;
|
|
22
7
|
};
|
|
23
|
-
|
|
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 = {
|
|
8
|
+
export type PaymentRoute = {
|
|
42
9
|
id: string;
|
|
43
10
|
webhookUrl: string;
|
|
44
11
|
externalId: string | null;
|
|
45
12
|
expiresAt: string | null;
|
|
46
13
|
createdAt: string;
|
|
14
|
+
/** Gateway name -> the fixed callback URL to paste into that gateway's dashboard. */
|
|
47
15
|
callbackUrls: Record<string, string> | null;
|
|
48
16
|
};
|
|
49
|
-
|
|
50
|
-
type CallbackClaims = {
|
|
51
|
-
/** The route id that matched - the id you handed the gateway. */
|
|
17
|
+
export type CallbackClaims = {
|
|
52
18
|
routeId: string;
|
|
53
|
-
|
|
54
|
-
gateway: GatewayName;
|
|
55
|
-
/** Your own id, if you set one when creating the route. */
|
|
19
|
+
gateway: string;
|
|
56
20
|
externalId: string | null;
|
|
57
|
-
/** Hex SHA-256 of the callback body, already checked against the body you passed in. */
|
|
58
21
|
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
22
|
};
|
|
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 {
|
|
23
|
+
export declare class PaymentRouterError extends Error {
|
|
238
24
|
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
25
|
readonly body: unknown;
|
|
243
|
-
constructor(message: string, status: number,
|
|
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;
|
|
26
|
+
constructor(message: string, status: number, body: unknown);
|
|
255
27
|
}
|
|
256
|
-
/**
|
|
257
|
-
declare class
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
readonly reason: CallbackVerificationFailure;
|
|
269
|
-
constructor(reason: CallbackVerificationFailure, message: string, options?: {
|
|
270
|
-
cause?: unknown;
|
|
271
|
-
});
|
|
28
|
+
/** Client for a payment-router deployment: create routes, verify signed callbacks. */
|
|
29
|
+
export declare class PaymentRouterClient {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(baseUrl?: string);
|
|
32
|
+
/** Register a webhook URL, get back the route id to hand the gateway plus its callback URLs. */
|
|
33
|
+
createRoute(options: CreateRouteOptions): Promise<PaymentRoute>;
|
|
34
|
+
/**
|
|
35
|
+
* Verify the `X-Payment-Router-Signature` JWT on a forwarded callback. `rawBody` must be the
|
|
36
|
+
* exact bytes received, since the signature binds `body_sha256`. Throws if the token is
|
|
37
|
+
* invalid, expired, or the body does not match what was signed.
|
|
38
|
+
*/
|
|
39
|
+
verifyCallback(token: string, rawBody: string): Promise<CallbackClaims>;
|
|
272
40
|
}
|
|
273
|
-
|
|
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 };
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,KAAK;aAGrB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,OAAO;IAHjC,YACI,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EAGhC;CACJ;AASD,sFAAsF;AACtF,qBAAa,mBAAmB;;IAI5B,YAAY,OAAO,GAAE,MAAyB,EAG7C;IAED,gGAAgG;IAC1F,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAiBpE;IAED;;;;OAIG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAc5E;CACJ"}
|