@vaam-apps/vpay-sdk 0.2.1
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 +21 -0
- package/README.md +732 -0
- package/dist/auth.d.ts +180 -0
- package/dist/auth.js +297 -0
- package/dist/client.d.ts +75 -0
- package/dist/client.js +79 -0
- package/dist/errors.d.ts +91 -0
- package/dist/errors.js +112 -0
- package/dist/form.d.ts +47 -0
- package/dist/form.js +127 -0
- package/dist/http.d.ts +33 -0
- package/dist/http.js +115 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/dist/resources/account-holders.d.ts +36 -0
- package/dist/resources/account-holders.js +46 -0
- package/dist/resources/balance.d.ts +8 -0
- package/dist/resources/balance.js +10 -0
- package/dist/resources/checkout-sessions.d.ts +55 -0
- package/dist/resources/checkout-sessions.js +127 -0
- package/dist/resources/customers.d.ts +97 -0
- package/dist/resources/customers.js +157 -0
- package/dist/resources/events.d.ts +8 -0
- package/dist/resources/events.js +10 -0
- package/dist/resources/invoices.d.ts +193 -0
- package/dist/resources/invoices.js +275 -0
- package/dist/resources/payment-intents.d.ts +12 -0
- package/dist/resources/payment-intents.js +44 -0
- package/dist/resources/refunds.d.ts +92 -0
- package/dist/resources/refunds.js +106 -0
- package/dist/stripe-auth.d.ts +177 -0
- package/dist/stripe-auth.js +192 -0
- package/dist/types.d.ts +1268 -0
- package/dist/types.js +48 -0
- package/dist/validate.d.ts +16 -0
- package/dist/validate.js +20 -0
- package/dist/version.d.ts +12 -0
- package/dist/version.js +12 -0
- package/dist/webhooks.d.ts +22 -0
- package/dist/webhooks.js +94 -0
- package/package.json +63 -0
- package/scripts/mint-assertion.mjs +73 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@vaam-apps/vpay-sdk/stripe` — a `config.authenticator` for the **official Stripe
|
|
3
|
+
* Node SDK**, so `stripe-node` can talk to vpay's `/v1` API.
|
|
4
|
+
*
|
|
5
|
+
* vpay never accepts an API key ([ADR-0010](../../../docs/adr/0010-merchant-auth-private-key-jwt.md)):
|
|
6
|
+
* every `/v1` call carries a short-lived bearer token minted from a
|
|
7
|
+
* `private_key_jwt` client assertion (docs/flows/merchant-auth.md). stripe-node
|
|
8
|
+
* has no notion of that handshake, but it does accept an arbitrary async
|
|
9
|
+
* `authenticator` invoked once per request attempt with the whole outbound
|
|
10
|
+
* request — which is exactly the seam this module fills:
|
|
11
|
+
*
|
|
12
|
+
* ```js
|
|
13
|
+
* import Stripe from "stripe";
|
|
14
|
+
* import { createStripeAuthenticator } from "@vaam-apps/vpay-sdk/stripe";
|
|
15
|
+
*
|
|
16
|
+
* const stripe = new Stripe("", {
|
|
17
|
+
* authenticator: createStripeAuthenticator({
|
|
18
|
+
* baseUrl: "http://localhost:8080",
|
|
19
|
+
* clientId: "acme-cameroon",
|
|
20
|
+
* privateKey: readFileSync("./merchant-key.pem", "utf8"),
|
|
21
|
+
* }),
|
|
22
|
+
* host: "localhost", port: "8080", protocol: "http",
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The `host`/`port`/`protocol` in that snippet are **not optional garnish** —
|
|
27
|
+
* see {@link createStripeAuthenticator}'s "Where the token may be sent".
|
|
28
|
+
*
|
|
29
|
+
* This module deliberately imports **nothing** from `stripe`. `stripe` is an
|
|
30
|
+
* optional peer dependency, and the request type below is written out
|
|
31
|
+
* structurally rather than imported, so `@vaam-apps/vpay-sdk` type-checks and builds
|
|
32
|
+
* with `stripe` absent. `src/stripe-auth.test.ts` pins the structural type
|
|
33
|
+
* against the real `Stripe.StripeConfig["authenticator"]`, so a divergence in
|
|
34
|
+
* a future `stripe` release fails the build rather than the merchant's
|
|
35
|
+
* deployment.
|
|
36
|
+
*
|
|
37
|
+
* See the README's "Using the official Stripe SDK" section for what does and
|
|
38
|
+
* does not carry over from real Stripe.
|
|
39
|
+
*/
|
|
40
|
+
import { type MerchantAuthOptions } from "./auth.js";
|
|
41
|
+
/**
|
|
42
|
+
* Configuration for {@link createStripeAuthenticator}.
|
|
43
|
+
*
|
|
44
|
+
* **The same options `VpayClient` takes** ({@link MerchantAuthOptions}, which
|
|
45
|
+
* `VpayClientOptions` also extends): both entry points run the same handshake
|
|
46
|
+
* through the same {@link resolveMerchantAuth}, so a knob one of them offers
|
|
47
|
+
* and the other does not would be a bug rather than a feature. Only the
|
|
48
|
+
* *meaning* of `timeoutMs` narrows, and it is redeclared below to say so —
|
|
49
|
+
* `stripe-node` owns the resource requests, their timeout and their retries,
|
|
50
|
+
* so this module configures the **token exchange** alone.
|
|
51
|
+
*
|
|
52
|
+
* Every optional property is declared `?: T | undefined` rather than `?: T`
|
|
53
|
+
* so a consumer compiling with `exactOptionalPropertyTypes` can pass a value
|
|
54
|
+
* read from configuration (`string | undefined`) without a conditional
|
|
55
|
+
* spread — the same rule the core entry point follows.
|
|
56
|
+
*/
|
|
57
|
+
export interface StripeAuthenticatorOptions extends MerchantAuthOptions {
|
|
58
|
+
/**
|
|
59
|
+
* Default 30000. Applies to the **token exchange only** — stripe-node's own
|
|
60
|
+
* `timeout` and `httpClient` govern every resource call.
|
|
61
|
+
*/
|
|
62
|
+
timeoutMs?: number | undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `stripe`'s `StripeRequest` (`Types.d.ts`: `{host, port, path, method,
|
|
66
|
+
* headers, body, protocol}`), written out structurally so this module needs
|
|
67
|
+
* no `stripe` import.
|
|
68
|
+
*/
|
|
69
|
+
export interface StripeRequestShape {
|
|
70
|
+
host: string;
|
|
71
|
+
port: string;
|
|
72
|
+
path: string;
|
|
73
|
+
method: string;
|
|
74
|
+
headers: Record<string, string | number | string[]>;
|
|
75
|
+
body: string;
|
|
76
|
+
protocol: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* What an authenticator is actually called with.
|
|
80
|
+
*
|
|
81
|
+
* Wider than {@link StripeRequestShape} on purpose, in the one direction that
|
|
82
|
+
* is safe: `headers` is required (it is the only field this module writes),
|
|
83
|
+
* everything else is optional. Two reasons, and neither is convenience:
|
|
84
|
+
*
|
|
85
|
+
* - the README's startup probe is `await authenticator({ headers: {} })` —
|
|
86
|
+
* mint a token at boot so a wrong key fails deployment rather than the
|
|
87
|
+
* first payment — and that call has no host, path or body to give;
|
|
88
|
+
* - a parameter type *wider* than `StripeRequest` keeps the returned function
|
|
89
|
+
* assignable to `Stripe.StripeConfig["authenticator"]` (parameters are
|
|
90
|
+
* contravariant), which `stripe-auth.test.ts` pins.
|
|
91
|
+
*
|
|
92
|
+
* A narrower parameter would have made the probe a type error and the
|
|
93
|
+
* assignment unsound.
|
|
94
|
+
*/
|
|
95
|
+
export type StripeAuthenticatorRequest = Pick<StripeRequestShape, "headers"> & Partial<Omit<StripeRequestShape, "headers">>;
|
|
96
|
+
/**
|
|
97
|
+
* What {@link createStripeAuthenticator} returns: a function assignable to
|
|
98
|
+
* stripe-node's `StripeConfig["authenticator"]`, carrying one extra method.
|
|
99
|
+
*/
|
|
100
|
+
export interface VpayStripeAuthenticator {
|
|
101
|
+
(request: StripeAuthenticatorRequest): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Discards the cached access token so the next request mints a fresh one.
|
|
104
|
+
*
|
|
105
|
+
* Needed because the authenticator **cannot see the response**: it runs
|
|
106
|
+
* before the request and its promise settles before any status code
|
|
107
|
+
* exists, so a `401` cannot trigger a re-auth from in here the way
|
|
108
|
+
* `VpayClient` does it. Normal expiry is handled by the cache's own safety
|
|
109
|
+
* margin; this is the escape hatch for the rest:
|
|
110
|
+
*
|
|
111
|
+
* ```js
|
|
112
|
+
* try { await stripe.paymentIntents.retrieve(id); }
|
|
113
|
+
* catch (err) {
|
|
114
|
+
* if (err instanceof Stripe.errors.StripeAuthenticationError) {
|
|
115
|
+
* authenticator.invalidate();
|
|
116
|
+
* }
|
|
117
|
+
* throw err;
|
|
118
|
+
* }
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
invalidate(): void;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Builds a stripe-node `authenticator` that performs vpay's
|
|
125
|
+
* `client_credentials` + `private_key_jwt` handshake and puts the resulting
|
|
126
|
+
* bearer token on the outbound request.
|
|
127
|
+
*
|
|
128
|
+
* Token caching, single-flight refresh under concurrency, and the
|
|
129
|
+
* `expires_in` safety margin are the *same* `TokenManager` `VpayClient` uses
|
|
130
|
+
* — not a second implementation of the handshake.
|
|
131
|
+
*
|
|
132
|
+
* # Where the token may be sent
|
|
133
|
+
*
|
|
134
|
+
* **The authenticator is bound to `baseUrl`'s origin and refuses every other
|
|
135
|
+
* one.** stripe-node calls it with the whole outbound request, for *any*
|
|
136
|
+
* request the client makes; `host`/`port`/`protocol` are configured on the
|
|
137
|
+
* `Stripe` instance, separately from this module, and when they are omitted
|
|
138
|
+
* stripe-node addresses `api.stripe.com:443`. An authenticator that wrote
|
|
139
|
+
* `Authorization` unconditionally would therefore hand a merchant's vpay
|
|
140
|
+
* bearer token to Stripe the moment someone forgot one line of config — a
|
|
141
|
+
* live credential for someone else's API, sent over TLS to a party that has
|
|
142
|
+
* no reason to want it and every reason to log it.
|
|
143
|
+
*
|
|
144
|
+
* So a request whose host, effective port or protocol is not `baseUrl`'s is
|
|
145
|
+
* a {@link VpayConfigError} naming both origins, thrown **before** a token is
|
|
146
|
+
* minted (a refused request costs no assertion and spends no `jti`). A
|
|
147
|
+
* request with no `host` at all — the README's startup probe — is allowed.
|
|
148
|
+
*
|
|
149
|
+
* # What it writes
|
|
150
|
+
*
|
|
151
|
+
* The returned function writes exactly one thing: `headers.Authorization`.
|
|
152
|
+
* Mutating anything else is possible (stripe-node re-reads
|
|
153
|
+
* `host`/`port`/`path`/`method`/`headers`/`body`/`protocol` after the
|
|
154
|
+
* authenticator resolves) but must not be done: `Content-Length` is computed
|
|
155
|
+
* from the body *before* this runs, so rewriting the body desynchronises the
|
|
156
|
+
* two and the request is truncated or hangs.
|
|
157
|
+
*
|
|
158
|
+
* A failed token exchange rejects, and that rejection is the caller's to
|
|
159
|
+
* handle when the authenticator is called directly.
|
|
160
|
+
*
|
|
161
|
+
* **Through stripe-node it is not.** Measured against `stripe@22.6.1`: it
|
|
162
|
+
* builds the right error (`StripeError: Unable to authenticate the request`,
|
|
163
|
+
* with the `VpayAuthError` at `err.raw.exception`) but throws it inside a
|
|
164
|
+
* detached promise chain that never calls its own callback — so the error
|
|
165
|
+
* arrives as a process-level `unhandledRejection` and the promise the
|
|
166
|
+
* merchant awaited never settles. See the README's "A stripe-node defect you
|
|
167
|
+
* will hit if your key is wrong", and the test that pins it. It is also why
|
|
168
|
+
* both of this function's construction-time checks are construction-time:
|
|
169
|
+
* a `VpayConfigError` a merchant sees when they build the client is worth
|
|
170
|
+
* more than the same fact delivered as an unhandled rejection at 3am.
|
|
171
|
+
*
|
|
172
|
+
* @throws `VpayConfigError` at construction if `baseUrl`, `clientId` or
|
|
173
|
+
* `privateKey` is missing, if `baseUrl` is not an absolute URL, if
|
|
174
|
+
* `privateKey` is not a readable private key, or if
|
|
175
|
+
* `assertionLifetimeSeconds` is out of range.
|
|
176
|
+
*/
|
|
177
|
+
export declare function createStripeAuthenticator(options: StripeAuthenticatorOptions): VpayStripeAuthenticator;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@vaam-apps/vpay-sdk/stripe` — a `config.authenticator` for the **official Stripe
|
|
3
|
+
* Node SDK**, so `stripe-node` can talk to vpay's `/v1` API.
|
|
4
|
+
*
|
|
5
|
+
* vpay never accepts an API key ([ADR-0010](../../../docs/adr/0010-merchant-auth-private-key-jwt.md)):
|
|
6
|
+
* every `/v1` call carries a short-lived bearer token minted from a
|
|
7
|
+
* `private_key_jwt` client assertion (docs/flows/merchant-auth.md). stripe-node
|
|
8
|
+
* has no notion of that handshake, but it does accept an arbitrary async
|
|
9
|
+
* `authenticator` invoked once per request attempt with the whole outbound
|
|
10
|
+
* request — which is exactly the seam this module fills:
|
|
11
|
+
*
|
|
12
|
+
* ```js
|
|
13
|
+
* import Stripe from "stripe";
|
|
14
|
+
* import { createStripeAuthenticator } from "@vaam-apps/vpay-sdk/stripe";
|
|
15
|
+
*
|
|
16
|
+
* const stripe = new Stripe("", {
|
|
17
|
+
* authenticator: createStripeAuthenticator({
|
|
18
|
+
* baseUrl: "http://localhost:8080",
|
|
19
|
+
* clientId: "acme-cameroon",
|
|
20
|
+
* privateKey: readFileSync("./merchant-key.pem", "utf8"),
|
|
21
|
+
* }),
|
|
22
|
+
* host: "localhost", port: "8080", protocol: "http",
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The `host`/`port`/`protocol` in that snippet are **not optional garnish** —
|
|
27
|
+
* see {@link createStripeAuthenticator}'s "Where the token may be sent".
|
|
28
|
+
*
|
|
29
|
+
* This module deliberately imports **nothing** from `stripe`. `stripe` is an
|
|
30
|
+
* optional peer dependency, and the request type below is written out
|
|
31
|
+
* structurally rather than imported, so `@vaam-apps/vpay-sdk` type-checks and builds
|
|
32
|
+
* with `stripe` absent. `src/stripe-auth.test.ts` pins the structural type
|
|
33
|
+
* against the real `Stripe.StripeConfig["authenticator"]`, so a divergence in
|
|
34
|
+
* a future `stripe` release fails the build rather than the merchant's
|
|
35
|
+
* deployment.
|
|
36
|
+
*
|
|
37
|
+
* See the README's "Using the official Stripe SDK" section for what does and
|
|
38
|
+
* does not carry over from real Stripe.
|
|
39
|
+
*/
|
|
40
|
+
import { resolveMerchantAuth } from "./auth.js";
|
|
41
|
+
import { VpayConfigError } from "./errors.js";
|
|
42
|
+
function defaultPortFor(protocol) {
|
|
43
|
+
return protocol === "http" ? "80" : "443";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `api.vpay.example`, `API.Vpay.Example.` and `[::1]` all have to compare
|
|
47
|
+
* equal to the same configured host, because a merchant writes `baseUrl` and
|
|
48
|
+
* stripe-node's `host` by hand and nothing makes them agree on case or on
|
|
49
|
+
* bracket notation. Everything else about the value is left alone: this is a
|
|
50
|
+
* comparison, not a validator.
|
|
51
|
+
*/
|
|
52
|
+
function normaliseHost(host) {
|
|
53
|
+
const lowered = host.trim().toLowerCase();
|
|
54
|
+
const unbracketed = lowered.startsWith("[") && lowered.endsWith("]")
|
|
55
|
+
? lowered.slice(1, -1)
|
|
56
|
+
: lowered;
|
|
57
|
+
return unbracketed.endsWith(".") ? unbracketed.slice(0, -1) : unbracketed;
|
|
58
|
+
}
|
|
59
|
+
function normaliseProtocol(protocol) {
|
|
60
|
+
const lowered = protocol.trim().toLowerCase();
|
|
61
|
+
return lowered.endsWith(":") ? lowered.slice(0, -1) : lowered;
|
|
62
|
+
}
|
|
63
|
+
function parseBoundOrigin(baseUrl) {
|
|
64
|
+
let url;
|
|
65
|
+
try {
|
|
66
|
+
url = new URL(baseUrl);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
throw new VpayConfigError(`baseUrl is not an absolute URL: ${baseUrl}`, {
|
|
70
|
+
cause: err,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const protocol = normaliseProtocol(url.protocol);
|
|
74
|
+
return {
|
|
75
|
+
host: normaliseHost(url.hostname),
|
|
76
|
+
port: url.port === "" ? defaultPortFor(protocol) : url.port,
|
|
77
|
+
protocol,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function describeOrigin(origin) {
|
|
81
|
+
return `${origin.protocol}://${origin.host}:${origin.port}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Refuses to mint a token for a request bound somewhere other than
|
|
85
|
+
* {@link BoundOrigin}.
|
|
86
|
+
*
|
|
87
|
+
* See {@link createStripeAuthenticator}'s "Where the token may be sent" for
|
|
88
|
+
* why this is a refusal and not a warning.
|
|
89
|
+
*
|
|
90
|
+
* A request with **no `host`** is allowed: that is the README's startup probe
|
|
91
|
+
* (`authenticator({ headers: {} })`), which has no destination to check
|
|
92
|
+
* against and never reaches a socket. Every field stripe-node does supply is
|
|
93
|
+
* checked; a `protocol` it somehow omitted is read as the configured one
|
|
94
|
+
* rather than as a mismatch, because a missing field is not evidence of a
|
|
95
|
+
* different destination and this check exists to catch a *different* one.
|
|
96
|
+
*/
|
|
97
|
+
function assertRequestIsBound(request, bound) {
|
|
98
|
+
if (request.host === undefined) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const protocol = request.protocol === undefined
|
|
102
|
+
? bound.protocol
|
|
103
|
+
: normaliseProtocol(request.protocol);
|
|
104
|
+
const host = normaliseHost(request.host);
|
|
105
|
+
const port = request.port === undefined || request.port === ""
|
|
106
|
+
? defaultPortFor(protocol)
|
|
107
|
+
: String(request.port);
|
|
108
|
+
if (host === bound.host &&
|
|
109
|
+
port === bound.port &&
|
|
110
|
+
protocol === bound.protocol) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
throw new VpayConfigError(`refusing to authenticate a request to ${describeOrigin({ host, port, protocol })}: ` +
|
|
114
|
+
`this authenticator mints tokens for ${describeOrigin(bound)} (its baseUrl) and will not ` +
|
|
115
|
+
`send one anywhere else. Set stripe-node's host, port and protocol to match baseUrl — ` +
|
|
116
|
+
`with them omitted stripe-node addresses api.stripe.com:443, and your vpay access token ` +
|
|
117
|
+
`would be sent to Stripe.`);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Builds a stripe-node `authenticator` that performs vpay's
|
|
121
|
+
* `client_credentials` + `private_key_jwt` handshake and puts the resulting
|
|
122
|
+
* bearer token on the outbound request.
|
|
123
|
+
*
|
|
124
|
+
* Token caching, single-flight refresh under concurrency, and the
|
|
125
|
+
* `expires_in` safety margin are the *same* `TokenManager` `VpayClient` uses
|
|
126
|
+
* — not a second implementation of the handshake.
|
|
127
|
+
*
|
|
128
|
+
* # Where the token may be sent
|
|
129
|
+
*
|
|
130
|
+
* **The authenticator is bound to `baseUrl`'s origin and refuses every other
|
|
131
|
+
* one.** stripe-node calls it with the whole outbound request, for *any*
|
|
132
|
+
* request the client makes; `host`/`port`/`protocol` are configured on the
|
|
133
|
+
* `Stripe` instance, separately from this module, and when they are omitted
|
|
134
|
+
* stripe-node addresses `api.stripe.com:443`. An authenticator that wrote
|
|
135
|
+
* `Authorization` unconditionally would therefore hand a merchant's vpay
|
|
136
|
+
* bearer token to Stripe the moment someone forgot one line of config — a
|
|
137
|
+
* live credential for someone else's API, sent over TLS to a party that has
|
|
138
|
+
* no reason to want it and every reason to log it.
|
|
139
|
+
*
|
|
140
|
+
* So a request whose host, effective port or protocol is not `baseUrl`'s is
|
|
141
|
+
* a {@link VpayConfigError} naming both origins, thrown **before** a token is
|
|
142
|
+
* minted (a refused request costs no assertion and spends no `jti`). A
|
|
143
|
+
* request with no `host` at all — the README's startup probe — is allowed.
|
|
144
|
+
*
|
|
145
|
+
* # What it writes
|
|
146
|
+
*
|
|
147
|
+
* The returned function writes exactly one thing: `headers.Authorization`.
|
|
148
|
+
* Mutating anything else is possible (stripe-node re-reads
|
|
149
|
+
* `host`/`port`/`path`/`method`/`headers`/`body`/`protocol` after the
|
|
150
|
+
* authenticator resolves) but must not be done: `Content-Length` is computed
|
|
151
|
+
* from the body *before* this runs, so rewriting the body desynchronises the
|
|
152
|
+
* two and the request is truncated or hangs.
|
|
153
|
+
*
|
|
154
|
+
* A failed token exchange rejects, and that rejection is the caller's to
|
|
155
|
+
* handle when the authenticator is called directly.
|
|
156
|
+
*
|
|
157
|
+
* **Through stripe-node it is not.** Measured against `stripe@22.6.1`: it
|
|
158
|
+
* builds the right error (`StripeError: Unable to authenticate the request`,
|
|
159
|
+
* with the `VpayAuthError` at `err.raw.exception`) but throws it inside a
|
|
160
|
+
* detached promise chain that never calls its own callback — so the error
|
|
161
|
+
* arrives as a process-level `unhandledRejection` and the promise the
|
|
162
|
+
* merchant awaited never settles. See the README's "A stripe-node defect you
|
|
163
|
+
* will hit if your key is wrong", and the test that pins it. It is also why
|
|
164
|
+
* both of this function's construction-time checks are construction-time:
|
|
165
|
+
* a `VpayConfigError` a merchant sees when they build the client is worth
|
|
166
|
+
* more than the same fact delivered as an unhandled rejection at 3am.
|
|
167
|
+
*
|
|
168
|
+
* @throws `VpayConfigError` at construction if `baseUrl`, `clientId` or
|
|
169
|
+
* `privateKey` is missing, if `baseUrl` is not an absolute URL, if
|
|
170
|
+
* `privateKey` is not a readable private key, or if
|
|
171
|
+
* `assertionLifetimeSeconds` is out of range.
|
|
172
|
+
*/
|
|
173
|
+
export function createStripeAuthenticator(options) {
|
|
174
|
+
// Validation, defaulting, the one-time private-key parse and the
|
|
175
|
+
// `TokenManager` itself all live in `resolveMerchantAuth`, shared with
|
|
176
|
+
// `VpayClient`: both entry points must mint the same assertion against the
|
|
177
|
+
// same endpoint with the same defaults, and one copy is how that stays
|
|
178
|
+
// true.
|
|
179
|
+
const { baseUrl, tokenManager } = resolveMerchantAuth(options);
|
|
180
|
+
// Parsed once, here, rather than per request: the answer cannot change
|
|
181
|
+
// between calls, and a `baseUrl` that is not a URL is a configuration
|
|
182
|
+
// mistake the merchant should be told about at construction.
|
|
183
|
+
const bound = parseBoundOrigin(baseUrl);
|
|
184
|
+
const authenticator = Object.assign(async (request) => {
|
|
185
|
+
assertRequestIsBound(request, bound);
|
|
186
|
+
const token = await tokenManager.getToken();
|
|
187
|
+
request.headers["Authorization"] = `Bearer ${token}`;
|
|
188
|
+
}, {
|
|
189
|
+
invalidate: () => tokenManager.invalidate(),
|
|
190
|
+
});
|
|
191
|
+
return authenticator;
|
|
192
|
+
}
|