@distilled.cloud/stripe 1.0.0-rc.12 → 1.0.0-rc.14
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/README.md +36 -0
- package/lib/credentials.d.ts +2 -2
- package/lib/credentials.d.ts.map +1 -1
- package/lib/credentials.js +2 -2
- package/lib/credentials.js.map +1 -1
- package/lib/errors.d.ts +1 -1
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +1 -1
- package/lib/errors.js.map +1 -1
- package/lib/protocol.d.ts +4 -4
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +20 -19
- package/lib/protocol.js.map +1 -1
- package/lib/protocol.test.d.ts +2 -0
- package/lib/protocol.test.d.ts.map +1 -0
- package/lib/protocol.test.js +353 -0
- package/lib/protocol.test.js.map +1 -0
- package/lib/retry.d.ts +1 -1
- package/lib/retry.d.ts.map +1 -1
- package/lib/retry.js +1 -1
- package/lib/retry.js.map +1 -1
- package/lib/services/stripe.d.ts +4970 -1571
- package/lib/services/stripe.d.ts.map +1 -1
- package/lib/services/stripe.js +5608 -6855
- package/lib/services/stripe.js.map +1 -1
- package/lib/traits.d.ts.map +1 -1
- package/lib/traits.js +1 -1
- package/lib/traits.js.map +1 -1
- package/lib/webhooks.d.ts.map +1 -1
- package/lib/webhooks.js +4 -4
- package/lib/webhooks.js.map +1 -1
- package/package.json +13 -11
- package/src/credentials.ts +6 -7
- package/src/errors.ts +20 -29
- package/src/protocol.test.ts +519 -0
- package/src/protocol.ts +65 -105
- package/src/retry.ts +2 -8
- package/src/services/stripe.ts +40402 -47044
- package/src/traits.ts +1 -4
- package/src/webhooks.ts +11 -30
package/src/protocol.ts
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
import * as API from "@distilled.cloud/core/api";
|
|
2
|
+
import {
|
|
3
|
+
ConfigError,
|
|
4
|
+
HTTP_STATUS_MAP,
|
|
5
|
+
InternalServerError,
|
|
6
|
+
type DefaultErrors as CoreDefaultErrors,
|
|
7
|
+
BadRequest,
|
|
8
|
+
Conflict,
|
|
9
|
+
Forbidden,
|
|
10
|
+
Locked,
|
|
11
|
+
NotFound,
|
|
12
|
+
UnprocessableEntity,
|
|
13
|
+
} from "@distilled.cloud/core/errors";
|
|
14
|
+
import {
|
|
15
|
+
buildRequest,
|
|
16
|
+
getAnn,
|
|
17
|
+
getProps,
|
|
18
|
+
hasPropAnn,
|
|
19
|
+
mapKeys,
|
|
20
|
+
matchTypedError,
|
|
21
|
+
nameOf,
|
|
22
|
+
} from "@distilled.cloud/core/protocol-http";
|
|
23
|
+
import { unwrapRedactedDeep, wrapSensitive } from "@distilled.cloud/core/protocol-rest";
|
|
24
|
+
import { validateResponse } from "@distilled.cloud/core/response-validation";
|
|
25
|
+
import { parseRetryAfterForStatus } from "@distilled.cloud/core/retry-after";
|
|
1
26
|
/**
|
|
2
27
|
* StripeProtocol — hand-written.
|
|
3
28
|
*
|
|
@@ -31,42 +56,15 @@
|
|
|
31
56
|
*/
|
|
32
57
|
import * as Context from "effect/Context";
|
|
33
58
|
import * as Effect from "effect/Effect";
|
|
59
|
+
import * as HttpBody from "effect/http/HttpBody";
|
|
60
|
+
import type * as HttpClient from "effect/http/HttpClient";
|
|
61
|
+
import type * as HttpClientError from "effect/http/HttpClientError";
|
|
62
|
+
import * as HttpClientRequest from "effect/http/HttpClientRequest";
|
|
63
|
+
import type * as HttpClientResponse from "effect/http/HttpClientResponse";
|
|
34
64
|
import * as Layer from "effect/Layer";
|
|
35
65
|
import * as Option from "effect/Option";
|
|
36
66
|
import * as Redacted from "effect/Redacted";
|
|
37
67
|
import type * as AST from "effect/SchemaAST";
|
|
38
|
-
import type * as HttpClient from "effect/unstable/http/HttpClient";
|
|
39
|
-
import type * as HttpClientError from "effect/unstable/http/HttpClientError";
|
|
40
|
-
import * as HttpBody from "effect/unstable/http/HttpBody";
|
|
41
|
-
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
42
|
-
import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
|
|
43
|
-
import * as API from "@distilled.cloud/core/api";
|
|
44
|
-
import {
|
|
45
|
-
buildRequest,
|
|
46
|
-
getAnn,
|
|
47
|
-
getProps,
|
|
48
|
-
hasPropAnn,
|
|
49
|
-
mapKeys,
|
|
50
|
-
matchTypedError,
|
|
51
|
-
nameOf,
|
|
52
|
-
} from "@distilled.cloud/core/protocol-http";
|
|
53
|
-
import {
|
|
54
|
-
unwrapRedactedDeep,
|
|
55
|
-
wrapSensitive,
|
|
56
|
-
} from "@distilled.cloud/core/protocol-rest";
|
|
57
|
-
import {
|
|
58
|
-
ConfigError,
|
|
59
|
-
HTTP_STATUS_MAP,
|
|
60
|
-
InternalServerError,
|
|
61
|
-
type DefaultErrors as CoreDefaultErrors,
|
|
62
|
-
BadRequest,
|
|
63
|
-
Conflict,
|
|
64
|
-
Forbidden,
|
|
65
|
-
Locked,
|
|
66
|
-
NotFound,
|
|
67
|
-
UnprocessableEntity,
|
|
68
|
-
} from "@distilled.cloud/core/errors";
|
|
69
|
-
import { parseRetryAfterForStatus } from "@distilled.cloud/core/retry-after";
|
|
70
68
|
import { Credentials, type Config } from "./credentials.ts";
|
|
71
69
|
import {
|
|
72
70
|
ApiError,
|
|
@@ -112,10 +110,9 @@ export type StripeRequestOptions = {
|
|
|
112
110
|
* call argument; v1 operations take only their input, so options travel on
|
|
113
111
|
* the calling fiber's context instead — see {@link withRequestOptions}.
|
|
114
112
|
*/
|
|
115
|
-
export class RequestOptions extends Context.Service<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
>()("StripeRequestOptions") {}
|
|
113
|
+
export class RequestOptions extends Context.Service<RequestOptions, StripeRequestOptions>()(
|
|
114
|
+
"StripeRequestOptions",
|
|
115
|
+
) {}
|
|
119
116
|
|
|
120
117
|
/**
|
|
121
118
|
* Provide {@link StripeRequestOptions} to every Stripe API call below it.
|
|
@@ -185,8 +182,7 @@ export type StripeOpContext = Credentials | HttpClient.HttpClient;
|
|
|
185
182
|
// Stripe failures are real typed errors that an operation re-surfaces via its
|
|
186
183
|
// `errors: [...]` list / StripeOpError annotation. Fail with the instance and
|
|
187
184
|
// erase the error type here; the generated annotations reintroduce it.
|
|
188
|
-
const fail = (e: unknown): Effect.Effect<never> =>
|
|
189
|
-
Effect.fail(e) as Effect.Effect<never>;
|
|
185
|
+
const fail = (e: unknown): Effect.Effect<never> => Effect.fail(e) as Effect.Effect<never>;
|
|
190
186
|
|
|
191
187
|
// =============================================================================
|
|
192
188
|
// Stripe query / form encoding (ported from distilled v0)
|
|
@@ -194,36 +190,23 @@ const fail = (e: unknown): Effect.Effect<never> =>
|
|
|
194
190
|
|
|
195
191
|
const BODYLESS = new Set(["GET", "HEAD"]);
|
|
196
192
|
|
|
197
|
-
const appendQueryValue = (
|
|
198
|
-
query: URLSearchParams,
|
|
199
|
-
key: string,
|
|
200
|
-
value: unknown,
|
|
201
|
-
): void => {
|
|
193
|
+
const appendQueryValue = (query: URLSearchParams, key: string, value: unknown): void => {
|
|
202
194
|
if (value === undefined || value === null) return;
|
|
203
|
-
query.append(
|
|
204
|
-
key,
|
|
205
|
-
typeof value === "boolean" ? (value ? "true" : "false") : String(value),
|
|
206
|
-
);
|
|
195
|
+
query.append(key, typeof value === "boolean" ? (value ? "true" : "false") : String(value));
|
|
207
196
|
};
|
|
208
197
|
|
|
209
198
|
/**
|
|
210
199
|
* Stripe GET-query bracket expansion (v0's `appendStripeQuery`): arrays as
|
|
211
200
|
* repeated `key[]` entries, nested objects as `key[nested]`, recursively.
|
|
212
201
|
*/
|
|
213
|
-
const appendStripeQuery = (
|
|
214
|
-
query: URLSearchParams,
|
|
215
|
-
key: string,
|
|
216
|
-
value: unknown,
|
|
217
|
-
): void => {
|
|
202
|
+
const appendStripeQuery = (query: URLSearchParams, key: string, value: unknown): void => {
|
|
218
203
|
if (value === undefined || value === null) return;
|
|
219
204
|
if (Array.isArray(value)) {
|
|
220
205
|
for (const item of value) appendQueryValue(query, `${key}[]`, item);
|
|
221
206
|
return;
|
|
222
207
|
}
|
|
223
208
|
if (typeof value === "object" && !(value instanceof Date)) {
|
|
224
|
-
for (const [nestedKey, nestedValue] of Object.entries(
|
|
225
|
-
value as Record<string, unknown>,
|
|
226
|
-
)) {
|
|
209
|
+
for (const [nestedKey, nestedValue] of Object.entries(value as Record<string, unknown>)) {
|
|
227
210
|
appendStripeQuery(query, `${key}[${nestedKey}]`, nestedValue);
|
|
228
211
|
}
|
|
229
212
|
return;
|
|
@@ -261,20 +244,13 @@ const flattenToFormPairs = (
|
|
|
261
244
|
typeof item === "object" &&
|
|
262
245
|
!Array.isArray(item)
|
|
263
246
|
) {
|
|
264
|
-
pairs.push(
|
|
265
|
-
...flattenToFormPairs(
|
|
266
|
-
item as Record<string, unknown>,
|
|
267
|
-
`${fullKey}[${i}]`,
|
|
268
|
-
),
|
|
269
|
-
);
|
|
247
|
+
pairs.push(...flattenToFormPairs(item as Record<string, unknown>, `${fullKey}[${i}]`));
|
|
270
248
|
} else if (item !== undefined && item !== null) {
|
|
271
249
|
pairs.push([`${fullKey}[${i}]`, String(item)]);
|
|
272
250
|
}
|
|
273
251
|
}
|
|
274
252
|
} else if (typeof value === "object") {
|
|
275
|
-
pairs.push(
|
|
276
|
-
...flattenToFormPairs(value as Record<string, unknown>, fullKey),
|
|
277
|
-
);
|
|
253
|
+
pairs.push(...flattenToFormPairs(value as Record<string, unknown>, fullKey));
|
|
278
254
|
} else if (typeof value === "boolean") {
|
|
279
255
|
pairs.push([fullKey, value ? "true" : "false"]);
|
|
280
256
|
} else {
|
|
@@ -303,19 +279,11 @@ const buildFormUrlEncoded = (body: Record<string, unknown>): string => {
|
|
|
303
279
|
// per-call RequestOptions) from the calling fiber's context on every
|
|
304
280
|
// request. The requirement is erased at this boundary and reintroduced for
|
|
305
281
|
// callers by the generated `StripeOpContext` annotations.
|
|
306
|
-
const encode = ({
|
|
307
|
-
input,
|
|
308
|
-
inputAst,
|
|
309
|
-
}: {
|
|
310
|
-
readonly input: unknown;
|
|
311
|
-
readonly inputAst: AST.AST;
|
|
312
|
-
}) =>
|
|
282
|
+
const encode = ({ input, inputAst }: { readonly input: unknown; readonly inputAst: AST.AST }) =>
|
|
313
283
|
Effect.gen(function* () {
|
|
314
284
|
const resolveCredentials = yield* Credentials;
|
|
315
285
|
const creds = yield* resolveCredentials as Effect.Effect<Config>;
|
|
316
|
-
const options = Option.getOrUndefined(
|
|
317
|
-
yield* Effect.serviceOption(RequestOptions),
|
|
318
|
-
);
|
|
286
|
+
const options = Option.getOrUndefined(yield* Effect.serviceOption(RequestOptions));
|
|
319
287
|
|
|
320
288
|
const http = getAnn(inputAst, httpSymbol) as HttpTrait | undefined;
|
|
321
289
|
if (!http) {
|
|
@@ -340,10 +308,7 @@ const encode = ({
|
|
|
340
308
|
});
|
|
341
309
|
}
|
|
342
310
|
|
|
343
|
-
const inputObj = (unwrapRedactedDeep(input) ?? {}) as Record<
|
|
344
|
-
string,
|
|
345
|
-
unknown
|
|
346
|
-
>;
|
|
311
|
+
const inputObj = (unwrapRedactedDeep(input) ?? {}) as Record<string, unknown>;
|
|
347
312
|
const headers: Record<string, string> = { ...baseHeaders };
|
|
348
313
|
const body: Record<string, unknown> = {};
|
|
349
314
|
const query = new URLSearchParams();
|
|
@@ -404,10 +369,7 @@ const encode = ({
|
|
|
404
369
|
http.contentType === "form-urlencoded"
|
|
405
370
|
? request.pipe(
|
|
406
371
|
HttpClientRequest.setBody(
|
|
407
|
-
HttpBody.text(
|
|
408
|
-
buildFormUrlEncoded(body),
|
|
409
|
-
"application/x-www-form-urlencoded",
|
|
410
|
-
),
|
|
372
|
+
HttpBody.text(buildFormUrlEncoded(body), "application/x-www-form-urlencoded"),
|
|
411
373
|
),
|
|
412
374
|
)
|
|
413
375
|
: // The /v2/* operations are plain JSON.
|
|
@@ -420,22 +382,17 @@ const encode = ({
|
|
|
420
382
|
// Response decoding / error matching (ported from v0's matchError)
|
|
421
383
|
// =============================================================================
|
|
422
384
|
|
|
423
|
-
const str = (v: unknown): string | undefined =>
|
|
424
|
-
typeof v === "string" ? v : undefined;
|
|
385
|
+
const str = (v: unknown): string | undefined => (typeof v === "string" ? v : undefined);
|
|
425
386
|
|
|
426
387
|
// Structural views of the status maps for numeric lookup (the const objects'
|
|
427
388
|
// per-status class types don't unify without widening the constructor).
|
|
428
|
-
const stripeStatusMap: Readonly<
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
Record<number, (new (args: any) => unknown) | undefined>
|
|
433
|
-
> = HTTP_STATUS_MAP;
|
|
389
|
+
const stripeStatusMap: Readonly<Record<number, (new (args: any) => unknown) | undefined>> =
|
|
390
|
+
STRIPE_HTTP_STATUS_MAP;
|
|
391
|
+
const coreStatusMap: Readonly<Record<number, (new (args: any) => unknown) | undefined>> =
|
|
392
|
+
HTTP_STATUS_MAP;
|
|
434
393
|
|
|
435
394
|
/** Stripe's `{ error: { type, message, … } }` object, when present. */
|
|
436
|
-
const stripeErrorEnvelope = (
|
|
437
|
-
errorBody: unknown,
|
|
438
|
-
): Record<string, unknown> | undefined => {
|
|
395
|
+
const stripeErrorEnvelope = (errorBody: unknown): Record<string, unknown> | undefined => {
|
|
439
396
|
const envelope =
|
|
440
397
|
errorBody !== null && typeof errorBody === "object"
|
|
441
398
|
? (errorBody as Record<string, unknown>).error
|
|
@@ -505,15 +462,11 @@ const matchStripeError = (
|
|
|
505
462
|
return new StripeErrorClass({
|
|
506
463
|
message: common.message,
|
|
507
464
|
code: common.code,
|
|
508
|
-
...(str(err.decline_code) !== undefined
|
|
509
|
-
? { decline_code: str(err.decline_code) }
|
|
510
|
-
: {}),
|
|
465
|
+
...(str(err.decline_code) !== undefined ? { decline_code: str(err.decline_code) } : {}),
|
|
511
466
|
...(str(err.charge) !== undefined ? { charge: str(err.charge) } : {}),
|
|
512
467
|
...(str(err.param) !== undefined ? { param: str(err.param) } : {}),
|
|
513
468
|
...(common.doc_url !== undefined ? { doc_url: common.doc_url } : {}),
|
|
514
|
-
...(common.request_log_url !== undefined
|
|
515
|
-
? { request_log_url: common.request_log_url }
|
|
516
|
-
: {}),
|
|
469
|
+
...(common.request_log_url !== undefined ? { request_log_url: common.request_log_url } : {}),
|
|
517
470
|
});
|
|
518
471
|
}
|
|
519
472
|
|
|
@@ -574,25 +527,32 @@ const decode = ({
|
|
|
574
527
|
if (status >= 400) {
|
|
575
528
|
const errorBody: unknown = nonJson ? text : json;
|
|
576
529
|
const message =
|
|
577
|
-
stripeErrorMessage(errorBody) ??
|
|
578
|
-
|
|
579
|
-
|
|
530
|
+
stripeErrorMessage(errorBody) ?? (nonJson && text.trim() ? text.trim() : `HTTP ${status}`);
|
|
531
|
+
const typed = matchTypedError(errorClasses, status, [{ message }], {
|
|
532
|
+
body: nonJson ? text : json,
|
|
533
|
+
headers,
|
|
534
|
+
});
|
|
580
535
|
if (typed !== undefined) return yield* fail(typed);
|
|
581
536
|
return yield* fail(matchStripeError(status, errorBody, headers));
|
|
582
537
|
}
|
|
583
538
|
|
|
584
539
|
// 2xx: the response body IS the payload (no envelope). Wire→TS key
|
|
585
540
|
// mapping is schema-driven; sensitive members come back Redacted.
|
|
541
|
+
// Strict mode checks the mapped body against the output schema first.
|
|
586
542
|
const body: unknown = nonJson ? text : (json ?? {});
|
|
587
|
-
|
|
543
|
+
const mapped = yield* validateResponse(
|
|
544
|
+
outputAst,
|
|
545
|
+
mapKeys(outputAst, body, "decode"),
|
|
546
|
+
(cause) => new StripeParseError({ body: nonJson ? text : json, cause }),
|
|
547
|
+
).pipe(Effect.catch(fail));
|
|
548
|
+
return wrapSensitive(outputAst, mapped);
|
|
588
549
|
});
|
|
589
550
|
|
|
590
551
|
export const StripeProtocol: Layer.Layer<API.Protocol> = Layer.succeed(
|
|
591
552
|
API.Protocol,
|
|
592
553
|
API.Protocol.of({
|
|
593
554
|
// Erase encode's Credentials requirement (see comment above).
|
|
594
|
-
encode: (args) =>
|
|
595
|
-
encode(args) as Effect.Effect<HttpClientRequest.HttpClientRequest>,
|
|
555
|
+
encode: (args) => encode(args) as Effect.Effect<HttpClientRequest.HttpClientRequest>,
|
|
596
556
|
decode,
|
|
597
557
|
}),
|
|
598
558
|
);
|
package/src/retry.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Policy, throttlingFactory, transientFactory } from "@distilled.cloud/core/retry";
|
|
1
2
|
/**
|
|
2
3
|
* Stripe retry configuration.
|
|
3
4
|
*
|
|
@@ -16,11 +17,6 @@
|
|
|
16
17
|
import * as Context from "effect/Context";
|
|
17
18
|
import * as Effect from "effect/Effect";
|
|
18
19
|
import * as Layer from "effect/Layer";
|
|
19
|
-
import {
|
|
20
|
-
type Policy,
|
|
21
|
-
throttlingFactory,
|
|
22
|
-
transientFactory,
|
|
23
|
-
} from "@distilled.cloud/core/retry";
|
|
24
20
|
|
|
25
21
|
export {
|
|
26
22
|
type Options,
|
|
@@ -43,9 +39,7 @@ export const policy = (optionsOrFactory: Policy) =>
|
|
|
43
39
|
Effect.provide(Layer.succeed(Retry, optionsOrFactory));
|
|
44
40
|
|
|
45
41
|
/** Disables all automatic retries. */
|
|
46
|
-
export const none = Effect.provide(
|
|
47
|
-
Layer.succeed(Retry, { while: () => false }),
|
|
48
|
-
);
|
|
42
|
+
export const none = Effect.provide(Layer.succeed(Retry, { while: () => false }));
|
|
49
43
|
|
|
50
44
|
/** Apply the throttling retry policy (retries throttling errors indefinitely). */
|
|
51
45
|
export const throttling = policy(throttlingFactory);
|