@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.
- 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 +3 -3
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +13 -18
- 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 +12 -10
- package/src/credentials.ts +6 -7
- package/src/errors.ts +20 -29
- package/src/protocol.test.ts +519 -0
- package/src/protocol.ts +54 -104
- 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/lib/response-validation.test.d.ts +0 -4
- package/lib/response-validation.test.d.ts.map +0 -1
- package/lib/response-validation.test.js +0 -38
- package/lib/response-validation.test.js.map +0 -1
- 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
|
|
73
|
-
Redacted.isRedacted(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 +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;
|