@distilled.cloud/stripe 1.0.0-rc.13 → 1.0.0-rc.15

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.
Files changed (44) hide show
  1. package/lib/credentials.d.ts +2 -2
  2. package/lib/credentials.d.ts.map +1 -1
  3. package/lib/credentials.js +2 -2
  4. package/lib/credentials.js.map +1 -1
  5. package/lib/errors.d.ts +1 -1
  6. package/lib/errors.d.ts.map +1 -1
  7. package/lib/errors.js +1 -1
  8. package/lib/errors.js.map +1 -1
  9. package/lib/protocol.d.ts +3 -3
  10. package/lib/protocol.d.ts.map +1 -1
  11. package/lib/protocol.js +13 -18
  12. package/lib/protocol.js.map +1 -1
  13. package/lib/protocol.test.d.ts +2 -0
  14. package/lib/protocol.test.d.ts.map +1 -0
  15. package/lib/protocol.test.js +353 -0
  16. package/lib/protocol.test.js.map +1 -0
  17. package/lib/retry.d.ts +1 -1
  18. package/lib/retry.d.ts.map +1 -1
  19. package/lib/retry.js +1 -1
  20. package/lib/retry.js.map +1 -1
  21. package/lib/services/stripe.d.ts +4970 -1571
  22. package/lib/services/stripe.d.ts.map +1 -1
  23. package/lib/services/stripe.js +5608 -6855
  24. package/lib/services/stripe.js.map +1 -1
  25. package/lib/traits.d.ts.map +1 -1
  26. package/lib/traits.js +1 -1
  27. package/lib/traits.js.map +1 -1
  28. package/lib/webhooks.d.ts.map +1 -1
  29. package/lib/webhooks.js +4 -4
  30. package/lib/webhooks.js.map +1 -1
  31. package/package.json +12 -10
  32. package/src/credentials.ts +6 -7
  33. package/src/errors.ts +20 -29
  34. package/src/protocol.test.ts +519 -0
  35. package/src/protocol.ts +54 -104
  36. package/src/retry.ts +2 -8
  37. package/src/services/stripe.ts +40402 -47044
  38. package/src/traits.ts +1 -4
  39. package/src/webhooks.ts +11 -30
  40. package/lib/response-validation.test.d.ts +0 -4
  41. package/lib/response-validation.test.d.ts.map +0 -1
  42. package/lib/response-validation.test.js +0 -38
  43. package/lib/response-validation.test.js.map +0 -1
  44. package/src/response-validation.test.ts +0 -52
package/src/traits.ts CHANGED
@@ -8,10 +8,7 @@
8
8
  * expansion), which core's `HttpTrait` doesn't model — the Stripe protocol
9
9
  * keys its body encoding on `contentType: "form-urlencoded"`.
10
10
  */
11
- import {
12
- Http as CoreHttp,
13
- type HttpTrait as CoreHttpTrait,
14
- } from "@distilled.cloud/core/trait";
11
+ import { Http as CoreHttp, type HttpTrait as CoreHttpTrait } from "@distilled.cloud/core/trait";
15
12
 
16
13
  export {
17
14
  Body,
package/src/webhooks.ts CHANGED
@@ -1,9 +1,6 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Redacted from "effect/Redacted";
3
- import {
4
- StripeWebhookPayloadParseError,
5
- StripeWebhookSignatureError,
6
- } from "./errors.ts";
3
+ import { StripeWebhookPayloadParseError, StripeWebhookSignatureError } from "./errors.ts";
7
4
 
8
5
  export const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
9
6
 
@@ -69,8 +66,8 @@ const payloadToString = (payload: WebhookPayload): string => {
69
66
  return textDecoder.decode(payloadToBytes(payload));
70
67
  };
71
68
 
72
- const secretToString = (secret: WebhookSecret): string =>
73
- Redacted.isRedacted(secret) ? (Redacted.value(secret) as string) : secret;
69
+ const redactSecret = (secret: WebhookSecret): Redacted.Redacted<string> =>
70
+ Redacted.isRedacted(secret) ? secret : Redacted.make(secret);
74
71
 
75
72
  const parseSignatureHeader = (
76
73
  signature: string | null | undefined,
@@ -126,15 +123,10 @@ const parseSignatureHeader = (
126
123
  return { timestamp, timestampSeconds, signatures };
127
124
  };
128
125
 
129
- const signedPayloadBytes = (
130
- timestamp: string,
131
- payload: WebhookPayload,
132
- ): ArrayBuffer => {
126
+ const signedPayloadBytes = (timestamp: string, payload: WebhookPayload): ArrayBuffer => {
133
127
  const timestampBytes = textEncoder.encode(`${timestamp}.`);
134
128
  const bodyBytes = payloadToBytes(payload);
135
- const signedPayload = new Uint8Array(
136
- timestampBytes.byteLength + bodyBytes.byteLength,
137
- );
129
+ const signedPayload = new Uint8Array(timestampBytes.byteLength + bodyBytes.byteLength);
138
130
 
139
131
  signedPayload.set(timestampBytes, 0);
140
132
  signedPayload.set(bodyBytes, timestampBytes.byteLength);
@@ -143,7 +135,7 @@ const signedPayloadBytes = (
143
135
  };
144
136
 
145
137
  const hmacSha256 = (
146
- secret: string,
138
+ secret: Redacted.Redacted<string>,
147
139
  timestamp: string,
148
140
  payload: WebhookPayload,
149
141
  ): Effect.Effect<Uint8Array, StripeWebhookSignatureError> =>
@@ -151,7 +143,7 @@ const hmacSha256 = (
151
143
  try: async () => {
152
144
  const key = await crypto.subtle.importKey(
153
145
  "raw",
154
- textEncoder.encode(secret),
146
+ textEncoder.encode(Redacted.value(secret)),
155
147
  { name: "HMAC", hash: "SHA-256" },
156
148
  false,
157
149
  ["sign"],
@@ -183,10 +175,7 @@ const hexToBytes = (hex: string): Uint8Array | undefined => {
183
175
  return bytes;
184
176
  };
185
177
 
186
- const constantTimeEqual = (
187
- expected: Uint8Array,
188
- candidate: Uint8Array,
189
- ): boolean => {
178
+ const constantTimeEqual = (expected: Uint8Array, candidate: Uint8Array): boolean => {
190
179
  // JavaScript cannot guarantee CPU-level constant time, but length mismatches
191
180
  // are folded into the accumulator so a matching prefix can never succeed.
192
181
  let difference = expected.byteLength ^ candidate.byteLength;
@@ -251,25 +240,17 @@ export const verifySignature = ({
251
240
  return yield* Effect.fail(parsed);
252
241
  }
253
242
 
254
- const expectedSignature = yield* hmacSha256(
255
- secretToString(secret),
256
- parsed.timestamp,
257
- payload,
258
- );
243
+ const expectedSignature = yield* hmacSha256(redactSecret(secret), parsed.timestamp, payload);
259
244
 
260
245
  if (!hasMatchingSignature(expectedSignature, parsed.signatures)) {
261
246
  return yield* Effect.fail(
262
247
  new StripeWebhookSignatureError({
263
- message:
264
- "No v1 signature matches the expected Stripe webhook signature",
248
+ message: "No v1 signature matches the expected Stripe webhook signature",
265
249
  }),
266
250
  );
267
251
  }
268
252
 
269
- const toleranceError = validateTolerance(
270
- parsed.timestampSeconds,
271
- toleranceSeconds,
272
- );
253
+ const toleranceError = validateTolerance(parsed.timestampSeconds, toleranceSeconds);
273
254
  if (toleranceError !== undefined) {
274
255
  return yield* Effect.fail(toleranceError);
275
256
  }
@@ -1,4 +0,0 @@
1
- import { StripeParseError } from "./errors.ts";
2
- import type { StripeOpError } from "./protocol.ts";
3
- export declare const parseErrorIsDeclared: [StripeParseError] extends [StripeOpError] ? true : false;
4
- //# sourceMappingURL=response-validation.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"response-validation.test.d.ts","sourceRoot":"","sources":["../src/response-validation.test.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AA0CnD,eAAO,MAAM,oBAAoB,EAAE,CAAC,gBAAgB,CAAC,SAAS,CAAC,aAAa,CAAC,GACzE,IAAI,GACJ,KAAY,CAAC"}
@@ -1,38 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { runValidationModes } from "@distilled.cloud/core/testing";
3
- import * as Effect from "effect/Effect";
4
- import { credentials } from "./credentials.js";
5
- import { StripeParseError } from "./errors.js";
6
- import * as Retry from "./retry.js";
7
- import { GetBalance } from "./services/stripe.js";
8
- // GetBalance declares `{ object; available; pending; livemode; … }`.
9
- const run = (body) => runValidationModes(GetBalance({}).pipe(Retry.none, Effect.provide(credentials({ apiKey: "sk_test_123" }))), { body });
10
- describe("Stripe response validation", () => {
11
- test("a matching body succeeds unchanged in both modes", async () => {
12
- const body = {
13
- object: "balance",
14
- available: [],
15
- pending: [],
16
- livemode: false,
17
- };
18
- const { lenient, strict } = await run(JSON.stringify(body));
19
- expect(lenient).toMatchObject({ _tag: "Success", success: body });
20
- expect(strict).toMatchObject({ _tag: "Success", success: body });
21
- });
22
- test("a body missing required members: lenient returns it, strict fails", async () => {
23
- const body = {};
24
- const { lenient, strict } = await run(JSON.stringify(body));
25
- expect(lenient).toMatchObject({ _tag: "Success", success: body });
26
- expect(strict._tag).toBe("Failure");
27
- expect(strict.failure).toBeInstanceOf(StripeParseError);
28
- });
29
- test("a non-JSON body: lenient returns the text, strict fails", async () => {
30
- const { lenient, strict } = await run("not json");
31
- expect(lenient).toMatchObject({ _tag: "Success", success: "not json" });
32
- expect(strict._tag).toBe("Failure");
33
- expect(strict.failure).toBeInstanceOf(StripeParseError);
34
- });
35
- });
36
- // StripeParseError is part of every operation's declared error type.
37
- export const parseErrorIsDeclared = true;
38
- //# sourceMappingURL=response-validation.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"response-validation.test.js","sourceRoot":"","sources":["../src/response-validation.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlD,qEAAqE;AACrE,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAC3B,kBAAkB,CAChB,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CACjB,KAAK,CAAC,IAAI,EACV,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,CACvD,EACD,EAAE,IAAI,EAAE,CACT,CAAC;AAEJ,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,IAAI,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,IAAI,GAAG;YACX,MAAM,EAAE,SAAS;YACjB,SAAS,EAAE,EAAE;YACb,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,KAAK;SAChB,CAAC;QACF,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,MAAM,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACnF,MAAM,IAAI,GAAG,EAAE,CAAC;QAChB,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACpC,MAAM,CAAE,MAAc,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACpC,MAAM,CAAE,MAAc,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,oBAAoB,GAErB,IAAI,CAAC"}
@@ -1,52 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { runValidationModes } from "@distilled.cloud/core/testing";
3
- import * as Effect from "effect/Effect";
4
- import { credentials } from "./credentials.ts";
5
- import { StripeParseError } from "./errors.ts";
6
- import * as Retry from "./retry.ts";
7
- import { GetBalance } from "./services/stripe.ts";
8
- import type { StripeOpError } from "./protocol.ts";
9
-
10
- // GetBalance declares `{ object; available; pending; livemode; … }`.
11
- const run = (body: string) =>
12
- runValidationModes(
13
- GetBalance({}).pipe(
14
- Retry.none,
15
- Effect.provide(credentials({ apiKey: "sk_test_123" })),
16
- ),
17
- { body },
18
- );
19
-
20
- describe("Stripe response validation", () => {
21
- test("a matching body succeeds unchanged in both modes", async () => {
22
- const body = {
23
- object: "balance",
24
- available: [],
25
- pending: [],
26
- livemode: false,
27
- };
28
- const { lenient, strict } = await run(JSON.stringify(body));
29
- expect(lenient).toMatchObject({ _tag: "Success", success: body });
30
- expect(strict).toMatchObject({ _tag: "Success", success: body });
31
- });
32
-
33
- test("a body missing required members: lenient returns it, strict fails", async () => {
34
- const body = {};
35
- const { lenient, strict } = await run(JSON.stringify(body));
36
- expect(lenient).toMatchObject({ _tag: "Success", success: body });
37
- expect(strict._tag).toBe("Failure");
38
- expect((strict as any).failure).toBeInstanceOf(StripeParseError);
39
- });
40
-
41
- test("a non-JSON body: lenient returns the text, strict fails", async () => {
42
- const { lenient, strict } = await run("not json");
43
- expect(lenient).toMatchObject({ _tag: "Success", success: "not json" });
44
- expect(strict._tag).toBe("Failure");
45
- expect((strict as any).failure).toBeInstanceOf(StripeParseError);
46
- });
47
- });
48
-
49
- // StripeParseError is part of every operation's declared error type.
50
- export const parseErrorIsDeclared: [StripeParseError] extends [StripeOpError]
51
- ? true
52
- : false = true;