@better-auth/stripe 1.3.28 → 1.3.30
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/.turbo/turbo-build.log +4 -4
- package/dist/index.cjs +9 -5
- package/dist/index.d.cts +6 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +9 -5
- package/package.json +7 -7
- package/src/index.ts +12 -5
- package/src/stripe.test.ts +409 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
> @better-auth/stripe@1.3.
|
|
2
|
+
> @better-auth/stripe@1.3.30 build /home/runner/work/better-auth/better-auth/packages/stripe
|
|
3
3
|
> unbuild
|
|
4
4
|
|
|
5
5
|
[info] Automatically detected entries: src/index, src/client [esm] [cjs] [dts]
|
|
6
6
|
[info] Building stripe
|
|
7
7
|
[success] Build succeeded for stripe
|
|
8
|
-
[log] dist/index.cjs (total size: 49.
|
|
8
|
+
[log] dist/index.cjs (total size: 49.4 kB, chunk size: 49.4 kB, exports: stripe)
|
|
9
9
|
|
|
10
10
|
[log] dist/client.cjs (total size: 270 B, chunk size: 270 B, exports: stripeClient)
|
|
11
11
|
|
|
12
|
-
[log] dist/index.mjs (total size: 48.
|
|
12
|
+
[log] dist/index.mjs (total size: 48.5 kB, chunk size: 48.5 kB, exports: stripe)
|
|
13
13
|
|
|
14
14
|
[log] dist/client.mjs (total size: 243 B, chunk size: 243 B, exports: stripeClient)
|
|
15
15
|
|
|
16
|
-
Σ Total dist size (byte size):
|
|
16
|
+
Σ Total dist size (byte size): 238 kB
|
|
17
17
|
[log]
|
package/dist/index.cjs
CHANGED
|
@@ -1319,11 +1319,15 @@ const stripe = (options) => {
|
|
|
1319
1319
|
message: "Stripe webhook secret not found"
|
|
1320
1320
|
});
|
|
1321
1321
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1322
|
+
if (typeof client.webhooks.constructEventAsync === "function") {
|
|
1323
|
+
event = await client.webhooks.constructEventAsync(
|
|
1324
|
+
buf,
|
|
1325
|
+
sig,
|
|
1326
|
+
webhookSecret
|
|
1327
|
+
);
|
|
1328
|
+
} else {
|
|
1329
|
+
event = client.webhooks.constructEvent(buf, sig, webhookSecret);
|
|
1330
|
+
}
|
|
1327
1331
|
} catch (err) {
|
|
1328
1332
|
ctx.context.logger.error(`${err.message}`);
|
|
1329
1333
|
throw new api.APIError("BAD_REQUEST", {
|
package/dist/index.d.cts
CHANGED
|
@@ -460,6 +460,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
460
460
|
amount_total: number | null;
|
|
461
461
|
automatic_tax: Stripe.Checkout.Session.AutomaticTax;
|
|
462
462
|
billing_address_collection: Stripe.Checkout.Session.BillingAddressCollection | null;
|
|
463
|
+
branding_settings?: Stripe.Checkout.Session.BrandingSettings;
|
|
463
464
|
cancel_url: string | null;
|
|
464
465
|
client_reference_id: string | null;
|
|
465
466
|
client_secret: string | null;
|
|
@@ -476,6 +477,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
476
477
|
customer_details: Stripe.Checkout.Session.CustomerDetails | null;
|
|
477
478
|
customer_email: string | null;
|
|
478
479
|
discounts: Array<Stripe.Checkout.Session.Discount> | null;
|
|
480
|
+
excluded_payment_method_types?: Array<string>;
|
|
479
481
|
expires_at: number;
|
|
480
482
|
invoice: string | Stripe.Invoice | null;
|
|
481
483
|
invoice_creation: Stripe.Checkout.Session.InvoiceCreation | null;
|
|
@@ -484,6 +486,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
484
486
|
locale: Stripe.Checkout.Session.Locale | null;
|
|
485
487
|
metadata: Stripe.Metadata | null;
|
|
486
488
|
mode: Stripe.Checkout.Session.Mode;
|
|
489
|
+
name_collection?: Stripe.Checkout.Session.NameCollection;
|
|
487
490
|
optional_items?: Array<Stripe.Checkout.Session.OptionalItem> | null;
|
|
488
491
|
origin_context: Stripe.Checkout.Session.OriginContext | null;
|
|
489
492
|
payment_intent: string | Stripe.PaymentIntent | null;
|
|
@@ -538,6 +541,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
538
541
|
amount_total: number | null;
|
|
539
542
|
automatic_tax: Stripe.Checkout.Session.AutomaticTax;
|
|
540
543
|
billing_address_collection: Stripe.Checkout.Session.BillingAddressCollection | null;
|
|
544
|
+
branding_settings?: Stripe.Checkout.Session.BrandingSettings;
|
|
541
545
|
cancel_url: string | null;
|
|
542
546
|
client_reference_id: string | null;
|
|
543
547
|
client_secret: string | null;
|
|
@@ -554,6 +558,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
554
558
|
customer_details: Stripe.Checkout.Session.CustomerDetails | null;
|
|
555
559
|
customer_email: string | null;
|
|
556
560
|
discounts: Array<Stripe.Checkout.Session.Discount> | null;
|
|
561
|
+
excluded_payment_method_types?: Array<string>;
|
|
557
562
|
expires_at: number;
|
|
558
563
|
invoice: string | Stripe.Invoice | null;
|
|
559
564
|
invoice_creation: Stripe.Checkout.Session.InvoiceCreation | null;
|
|
@@ -562,6 +567,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
562
567
|
locale: Stripe.Checkout.Session.Locale | null;
|
|
563
568
|
metadata: Stripe.Metadata | null;
|
|
564
569
|
mode: Stripe.Checkout.Session.Mode;
|
|
570
|
+
name_collection?: Stripe.Checkout.Session.NameCollection;
|
|
565
571
|
optional_items?: Array<Stripe.Checkout.Session.OptionalItem> | null;
|
|
566
572
|
origin_context: Stripe.Checkout.Session.OriginContext | null;
|
|
567
573
|
payment_intent: string | Stripe.PaymentIntent | null;
|
package/dist/index.d.mts
CHANGED
|
@@ -460,6 +460,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
460
460
|
amount_total: number | null;
|
|
461
461
|
automatic_tax: Stripe.Checkout.Session.AutomaticTax;
|
|
462
462
|
billing_address_collection: Stripe.Checkout.Session.BillingAddressCollection | null;
|
|
463
|
+
branding_settings?: Stripe.Checkout.Session.BrandingSettings;
|
|
463
464
|
cancel_url: string | null;
|
|
464
465
|
client_reference_id: string | null;
|
|
465
466
|
client_secret: string | null;
|
|
@@ -476,6 +477,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
476
477
|
customer_details: Stripe.Checkout.Session.CustomerDetails | null;
|
|
477
478
|
customer_email: string | null;
|
|
478
479
|
discounts: Array<Stripe.Checkout.Session.Discount> | null;
|
|
480
|
+
excluded_payment_method_types?: Array<string>;
|
|
479
481
|
expires_at: number;
|
|
480
482
|
invoice: string | Stripe.Invoice | null;
|
|
481
483
|
invoice_creation: Stripe.Checkout.Session.InvoiceCreation | null;
|
|
@@ -484,6 +486,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
484
486
|
locale: Stripe.Checkout.Session.Locale | null;
|
|
485
487
|
metadata: Stripe.Metadata | null;
|
|
486
488
|
mode: Stripe.Checkout.Session.Mode;
|
|
489
|
+
name_collection?: Stripe.Checkout.Session.NameCollection;
|
|
487
490
|
optional_items?: Array<Stripe.Checkout.Session.OptionalItem> | null;
|
|
488
491
|
origin_context: Stripe.Checkout.Session.OriginContext | null;
|
|
489
492
|
payment_intent: string | Stripe.PaymentIntent | null;
|
|
@@ -538,6 +541,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
538
541
|
amount_total: number | null;
|
|
539
542
|
automatic_tax: Stripe.Checkout.Session.AutomaticTax;
|
|
540
543
|
billing_address_collection: Stripe.Checkout.Session.BillingAddressCollection | null;
|
|
544
|
+
branding_settings?: Stripe.Checkout.Session.BrandingSettings;
|
|
541
545
|
cancel_url: string | null;
|
|
542
546
|
client_reference_id: string | null;
|
|
543
547
|
client_secret: string | null;
|
|
@@ -554,6 +558,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
554
558
|
customer_details: Stripe.Checkout.Session.CustomerDetails | null;
|
|
555
559
|
customer_email: string | null;
|
|
556
560
|
discounts: Array<Stripe.Checkout.Session.Discount> | null;
|
|
561
|
+
excluded_payment_method_types?: Array<string>;
|
|
557
562
|
expires_at: number;
|
|
558
563
|
invoice: string | Stripe.Invoice | null;
|
|
559
564
|
invoice_creation: Stripe.Checkout.Session.InvoiceCreation | null;
|
|
@@ -562,6 +567,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
562
567
|
locale: Stripe.Checkout.Session.Locale | null;
|
|
563
568
|
metadata: Stripe.Metadata | null;
|
|
564
569
|
mode: Stripe.Checkout.Session.Mode;
|
|
570
|
+
name_collection?: Stripe.Checkout.Session.NameCollection;
|
|
565
571
|
optional_items?: Array<Stripe.Checkout.Session.OptionalItem> | null;
|
|
566
572
|
origin_context: Stripe.Checkout.Session.OriginContext | null;
|
|
567
573
|
payment_intent: string | Stripe.PaymentIntent | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -460,6 +460,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
460
460
|
amount_total: number | null;
|
|
461
461
|
automatic_tax: Stripe.Checkout.Session.AutomaticTax;
|
|
462
462
|
billing_address_collection: Stripe.Checkout.Session.BillingAddressCollection | null;
|
|
463
|
+
branding_settings?: Stripe.Checkout.Session.BrandingSettings;
|
|
463
464
|
cancel_url: string | null;
|
|
464
465
|
client_reference_id: string | null;
|
|
465
466
|
client_secret: string | null;
|
|
@@ -476,6 +477,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
476
477
|
customer_details: Stripe.Checkout.Session.CustomerDetails | null;
|
|
477
478
|
customer_email: string | null;
|
|
478
479
|
discounts: Array<Stripe.Checkout.Session.Discount> | null;
|
|
480
|
+
excluded_payment_method_types?: Array<string>;
|
|
479
481
|
expires_at: number;
|
|
480
482
|
invoice: string | Stripe.Invoice | null;
|
|
481
483
|
invoice_creation: Stripe.Checkout.Session.InvoiceCreation | null;
|
|
@@ -484,6 +486,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
484
486
|
locale: Stripe.Checkout.Session.Locale | null;
|
|
485
487
|
metadata: Stripe.Metadata | null;
|
|
486
488
|
mode: Stripe.Checkout.Session.Mode;
|
|
489
|
+
name_collection?: Stripe.Checkout.Session.NameCollection;
|
|
487
490
|
optional_items?: Array<Stripe.Checkout.Session.OptionalItem> | null;
|
|
488
491
|
origin_context: Stripe.Checkout.Session.OriginContext | null;
|
|
489
492
|
payment_intent: string | Stripe.PaymentIntent | null;
|
|
@@ -538,6 +541,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
538
541
|
amount_total: number | null;
|
|
539
542
|
automatic_tax: Stripe.Checkout.Session.AutomaticTax;
|
|
540
543
|
billing_address_collection: Stripe.Checkout.Session.BillingAddressCollection | null;
|
|
544
|
+
branding_settings?: Stripe.Checkout.Session.BrandingSettings;
|
|
541
545
|
cancel_url: string | null;
|
|
542
546
|
client_reference_id: string | null;
|
|
543
547
|
client_secret: string | null;
|
|
@@ -554,6 +558,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
554
558
|
customer_details: Stripe.Checkout.Session.CustomerDetails | null;
|
|
555
559
|
customer_email: string | null;
|
|
556
560
|
discounts: Array<Stripe.Checkout.Session.Discount> | null;
|
|
561
|
+
excluded_payment_method_types?: Array<string>;
|
|
557
562
|
expires_at: number;
|
|
558
563
|
invoice: string | Stripe.Invoice | null;
|
|
559
564
|
invoice_creation: Stripe.Checkout.Session.InvoiceCreation | null;
|
|
@@ -562,6 +567,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
|
|
|
562
567
|
locale: Stripe.Checkout.Session.Locale | null;
|
|
563
568
|
metadata: Stripe.Metadata | null;
|
|
564
569
|
mode: Stripe.Checkout.Session.Mode;
|
|
570
|
+
name_collection?: Stripe.Checkout.Session.NameCollection;
|
|
565
571
|
optional_items?: Array<Stripe.Checkout.Session.OptionalItem> | null;
|
|
566
572
|
origin_context: Stripe.Checkout.Session.OriginContext | null;
|
|
567
573
|
payment_intent: string | Stripe.PaymentIntent | null;
|
package/dist/index.mjs
CHANGED
|
@@ -1303,11 +1303,15 @@ const stripe = (options) => {
|
|
|
1303
1303
|
message: "Stripe webhook secret not found"
|
|
1304
1304
|
});
|
|
1305
1305
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1306
|
+
if (typeof client.webhooks.constructEventAsync === "function") {
|
|
1307
|
+
event = await client.webhooks.constructEventAsync(
|
|
1308
|
+
buf,
|
|
1309
|
+
sig,
|
|
1310
|
+
webhookSecret
|
|
1311
|
+
);
|
|
1312
|
+
} else {
|
|
1313
|
+
event = client.webhooks.constructEvent(buf, sig, webhookSecret);
|
|
1314
|
+
}
|
|
1311
1315
|
} catch (err) {
|
|
1312
1316
|
ctx.context.logger.error(`${err.message}`);
|
|
1313
1317
|
throw new APIError("BAD_REQUEST", {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/stripe",
|
|
3
3
|
"author": "Bereket Engida",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.30",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -41,16 +41,16 @@
|
|
|
41
41
|
"zod": "^4.1.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"stripe": "^18",
|
|
45
|
-
"@better-auth/core": "1.3.
|
|
46
|
-
"better-auth": "1.3.
|
|
44
|
+
"stripe": "^18 || ^19",
|
|
45
|
+
"@better-auth/core": "1.3.30",
|
|
46
|
+
"better-auth": "1.3.30"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"better-call": "1.0.19",
|
|
50
|
-
"stripe": "^
|
|
50
|
+
"stripe": "^19.1.0",
|
|
51
51
|
"unbuild": "3.6.1",
|
|
52
|
-
"@better-auth/core": "1.3.
|
|
53
|
-
"better-auth": "1.3.
|
|
52
|
+
"@better-auth/core": "1.3.30",
|
|
53
|
+
"better-auth": "1.3.30"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"test": "vitest",
|
package/src/index.ts
CHANGED
|
@@ -1244,11 +1244,18 @@ export const stripe = <O extends StripeOptions>(options: O) => {
|
|
|
1244
1244
|
message: "Stripe webhook secret not found",
|
|
1245
1245
|
});
|
|
1246
1246
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1247
|
+
// Support both Stripe v18 (constructEvent) and v19+ (constructEventAsync)
|
|
1248
|
+
if (typeof client.webhooks.constructEventAsync === "function") {
|
|
1249
|
+
// Stripe v19+ - use async method
|
|
1250
|
+
event = await client.webhooks.constructEventAsync(
|
|
1251
|
+
buf,
|
|
1252
|
+
sig,
|
|
1253
|
+
webhookSecret,
|
|
1254
|
+
);
|
|
1255
|
+
} else {
|
|
1256
|
+
// Stripe v18 - use sync method
|
|
1257
|
+
event = client.webhooks.constructEvent(buf, sig, webhookSecret);
|
|
1258
|
+
}
|
|
1252
1259
|
} catch (err: any) {
|
|
1253
1260
|
ctx.context.logger.error(`${err.message}`);
|
|
1254
1261
|
throw new APIError("BAD_REQUEST", {
|
package/src/stripe.test.ts
CHANGED
|
@@ -49,7 +49,7 @@ describe("stripe", async () => {
|
|
|
49
49
|
update: vi.fn(),
|
|
50
50
|
},
|
|
51
51
|
webhooks: {
|
|
52
|
-
|
|
52
|
+
constructEventAsync: vi.fn(),
|
|
53
53
|
},
|
|
54
54
|
};
|
|
55
55
|
|
|
@@ -1739,4 +1739,412 @@ describe("stripe", async () => {
|
|
|
1739
1739
|
});
|
|
1740
1740
|
});
|
|
1741
1741
|
});
|
|
1742
|
+
|
|
1743
|
+
describe("Webhook Error Handling (Stripe v19)", () => {
|
|
1744
|
+
it("should handle invalid webhook signature with constructEventAsync", async () => {
|
|
1745
|
+
const mockError = new Error("Invalid signature");
|
|
1746
|
+
const stripeWithError = {
|
|
1747
|
+
...stripeOptions.stripeClient,
|
|
1748
|
+
webhooks: {
|
|
1749
|
+
constructEventAsync: vi.fn().mockRejectedValue(mockError),
|
|
1750
|
+
},
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
const testOptions = {
|
|
1754
|
+
...stripeOptions,
|
|
1755
|
+
stripeClient: stripeWithError as unknown as Stripe,
|
|
1756
|
+
stripeWebhookSecret: "test_secret",
|
|
1757
|
+
};
|
|
1758
|
+
|
|
1759
|
+
const testAuth = betterAuth({
|
|
1760
|
+
baseURL: "http://localhost:3000",
|
|
1761
|
+
database: memory,
|
|
1762
|
+
emailAndPassword: { enabled: true },
|
|
1763
|
+
plugins: [stripe(testOptions)],
|
|
1764
|
+
});
|
|
1765
|
+
|
|
1766
|
+
const mockRequest = new Request(
|
|
1767
|
+
"http://localhost:3000/api/auth/stripe/webhook",
|
|
1768
|
+
{
|
|
1769
|
+
method: "POST",
|
|
1770
|
+
headers: {
|
|
1771
|
+
"stripe-signature": "invalid_signature",
|
|
1772
|
+
},
|
|
1773
|
+
body: JSON.stringify({ type: "test.event" }),
|
|
1774
|
+
},
|
|
1775
|
+
);
|
|
1776
|
+
|
|
1777
|
+
const response = await testAuth.handler(mockRequest);
|
|
1778
|
+
expect(response.status).toBe(400);
|
|
1779
|
+
const data = await response.json();
|
|
1780
|
+
expect(data.message).toContain("Webhook Error");
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
it("should reject webhook request without stripe-signature header", async () => {
|
|
1784
|
+
const testAuth = betterAuth({
|
|
1785
|
+
baseURL: "http://localhost:3000",
|
|
1786
|
+
database: memory,
|
|
1787
|
+
emailAndPassword: { enabled: true },
|
|
1788
|
+
plugins: [stripe(stripeOptions)],
|
|
1789
|
+
});
|
|
1790
|
+
|
|
1791
|
+
const mockRequest = new Request(
|
|
1792
|
+
"http://localhost:3000/api/auth/stripe/webhook",
|
|
1793
|
+
{
|
|
1794
|
+
method: "POST",
|
|
1795
|
+
headers: {
|
|
1796
|
+
"content-type": "application/json",
|
|
1797
|
+
},
|
|
1798
|
+
body: JSON.stringify({ type: "test.event" }),
|
|
1799
|
+
},
|
|
1800
|
+
);
|
|
1801
|
+
|
|
1802
|
+
const response = await testAuth.handler(mockRequest);
|
|
1803
|
+
expect(response.status).toBe(400);
|
|
1804
|
+
const data = await response.json();
|
|
1805
|
+
expect(data.message).toContain("Stripe webhook secret not found");
|
|
1806
|
+
});
|
|
1807
|
+
|
|
1808
|
+
it("should handle constructEventAsync returning null/undefined", async () => {
|
|
1809
|
+
const stripeWithNull = {
|
|
1810
|
+
...stripeOptions.stripeClient,
|
|
1811
|
+
webhooks: {
|
|
1812
|
+
constructEventAsync: vi.fn().mockResolvedValue(null),
|
|
1813
|
+
},
|
|
1814
|
+
};
|
|
1815
|
+
|
|
1816
|
+
const testOptions = {
|
|
1817
|
+
...stripeOptions,
|
|
1818
|
+
stripeClient: stripeWithNull as unknown as Stripe,
|
|
1819
|
+
stripeWebhookSecret: "test_secret",
|
|
1820
|
+
};
|
|
1821
|
+
|
|
1822
|
+
const testAuth = betterAuth({
|
|
1823
|
+
baseURL: "http://localhost:3000",
|
|
1824
|
+
database: memory,
|
|
1825
|
+
emailAndPassword: { enabled: true },
|
|
1826
|
+
plugins: [stripe(testOptions)],
|
|
1827
|
+
});
|
|
1828
|
+
|
|
1829
|
+
const mockRequest = new Request(
|
|
1830
|
+
"http://localhost:3000/api/auth/stripe/webhook",
|
|
1831
|
+
{
|
|
1832
|
+
method: "POST",
|
|
1833
|
+
headers: {
|
|
1834
|
+
"stripe-signature": "test_signature",
|
|
1835
|
+
},
|
|
1836
|
+
body: JSON.stringify({ type: "test.event" }),
|
|
1837
|
+
},
|
|
1838
|
+
);
|
|
1839
|
+
|
|
1840
|
+
const response = await testAuth.handler(mockRequest);
|
|
1841
|
+
expect(response.status).toBe(400);
|
|
1842
|
+
const data = await response.json();
|
|
1843
|
+
expect(data.message).toContain("Failed to construct event");
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
it("should handle async errors in webhook event processing", async () => {
|
|
1847
|
+
const errorThrowingHandler = vi
|
|
1848
|
+
.fn()
|
|
1849
|
+
.mockRejectedValue(new Error("Event processing failed"));
|
|
1850
|
+
|
|
1851
|
+
const mockEvent = {
|
|
1852
|
+
type: "checkout.session.completed",
|
|
1853
|
+
data: {
|
|
1854
|
+
object: {
|
|
1855
|
+
mode: "subscription",
|
|
1856
|
+
subscription: "sub_123",
|
|
1857
|
+
metadata: {
|
|
1858
|
+
referenceId: "user_123",
|
|
1859
|
+
subscriptionId: "sub_123",
|
|
1860
|
+
},
|
|
1861
|
+
},
|
|
1862
|
+
},
|
|
1863
|
+
};
|
|
1864
|
+
|
|
1865
|
+
const stripeForTest = {
|
|
1866
|
+
...stripeOptions.stripeClient,
|
|
1867
|
+
subscriptions: {
|
|
1868
|
+
retrieve: vi.fn().mockRejectedValue(new Error("Stripe API error")),
|
|
1869
|
+
},
|
|
1870
|
+
webhooks: {
|
|
1871
|
+
constructEventAsync: vi.fn().mockResolvedValue(mockEvent),
|
|
1872
|
+
},
|
|
1873
|
+
};
|
|
1874
|
+
|
|
1875
|
+
const testOptions = {
|
|
1876
|
+
...stripeOptions,
|
|
1877
|
+
stripeClient: stripeForTest as unknown as Stripe,
|
|
1878
|
+
stripeWebhookSecret: "test_secret",
|
|
1879
|
+
subscription: {
|
|
1880
|
+
...stripeOptions.subscription,
|
|
1881
|
+
onSubscriptionComplete: errorThrowingHandler,
|
|
1882
|
+
},
|
|
1883
|
+
};
|
|
1884
|
+
|
|
1885
|
+
const testAuth = betterAuth({
|
|
1886
|
+
baseURL: "http://localhost:3000",
|
|
1887
|
+
database: memory,
|
|
1888
|
+
emailAndPassword: { enabled: true },
|
|
1889
|
+
plugins: [stripe(testOptions as StripeOptions)],
|
|
1890
|
+
});
|
|
1891
|
+
|
|
1892
|
+
await ctx.adapter.create({
|
|
1893
|
+
model: "subscription",
|
|
1894
|
+
data: {
|
|
1895
|
+
referenceId: "user_123",
|
|
1896
|
+
stripeCustomerId: "cus_123",
|
|
1897
|
+
status: "incomplete",
|
|
1898
|
+
plan: "starter",
|
|
1899
|
+
id: "sub_123",
|
|
1900
|
+
},
|
|
1901
|
+
});
|
|
1902
|
+
|
|
1903
|
+
const mockRequest = new Request(
|
|
1904
|
+
"http://localhost:3000/api/auth/stripe/webhook",
|
|
1905
|
+
{
|
|
1906
|
+
method: "POST",
|
|
1907
|
+
headers: {
|
|
1908
|
+
"stripe-signature": "test_signature",
|
|
1909
|
+
},
|
|
1910
|
+
body: JSON.stringify(mockEvent),
|
|
1911
|
+
},
|
|
1912
|
+
);
|
|
1913
|
+
|
|
1914
|
+
const response = await testAuth.handler(mockRequest);
|
|
1915
|
+
// Errors inside event handlers are caught and logged but don't fail the webhook
|
|
1916
|
+
// This prevents Stripe from retrying and is the expected behavior
|
|
1917
|
+
expect(response.status).toBe(200);
|
|
1918
|
+
const data = await response.json();
|
|
1919
|
+
expect(data).toEqual({ success: true });
|
|
1920
|
+
// Verify the error was logged (via the stripeClient.subscriptions.retrieve rejection)
|
|
1921
|
+
expect(stripeForTest.subscriptions.retrieve).toHaveBeenCalled();
|
|
1922
|
+
});
|
|
1923
|
+
|
|
1924
|
+
it("should successfully process webhook with valid async signature verification", async () => {
|
|
1925
|
+
const mockEvent = {
|
|
1926
|
+
type: "customer.subscription.updated",
|
|
1927
|
+
data: {
|
|
1928
|
+
object: {
|
|
1929
|
+
id: "sub_test_async",
|
|
1930
|
+
customer: "cus_test_async",
|
|
1931
|
+
status: "active",
|
|
1932
|
+
items: {
|
|
1933
|
+
data: [
|
|
1934
|
+
{
|
|
1935
|
+
price: { id: process.env.STRIPE_PRICE_ID_1 },
|
|
1936
|
+
quantity: 1,
|
|
1937
|
+
current_period_start: Math.floor(Date.now() / 1000),
|
|
1938
|
+
current_period_end:
|
|
1939
|
+
Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
|
1940
|
+
},
|
|
1941
|
+
],
|
|
1942
|
+
},
|
|
1943
|
+
current_period_start: Math.floor(Date.now() / 1000),
|
|
1944
|
+
current_period_end:
|
|
1945
|
+
Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
|
1946
|
+
},
|
|
1947
|
+
},
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
const stripeForTest = {
|
|
1951
|
+
...stripeOptions.stripeClient,
|
|
1952
|
+
webhooks: {
|
|
1953
|
+
// Simulate async verification success
|
|
1954
|
+
constructEventAsync: vi.fn().mockResolvedValue(mockEvent),
|
|
1955
|
+
},
|
|
1956
|
+
};
|
|
1957
|
+
|
|
1958
|
+
const testOptions = {
|
|
1959
|
+
...stripeOptions,
|
|
1960
|
+
stripeClient: stripeForTest as unknown as Stripe,
|
|
1961
|
+
stripeWebhookSecret: "test_secret_async",
|
|
1962
|
+
};
|
|
1963
|
+
|
|
1964
|
+
const testAuth = betterAuth({
|
|
1965
|
+
baseURL: "http://localhost:3000",
|
|
1966
|
+
database: memory,
|
|
1967
|
+
emailAndPassword: { enabled: true },
|
|
1968
|
+
plugins: [stripe(testOptions)],
|
|
1969
|
+
});
|
|
1970
|
+
|
|
1971
|
+
const { id: subId } = await ctx.adapter.create({
|
|
1972
|
+
model: "subscription",
|
|
1973
|
+
data: {
|
|
1974
|
+
referenceId: userId,
|
|
1975
|
+
stripeCustomerId: "cus_test_async",
|
|
1976
|
+
stripeSubscriptionId: "sub_test_async",
|
|
1977
|
+
status: "incomplete",
|
|
1978
|
+
plan: "starter",
|
|
1979
|
+
},
|
|
1980
|
+
});
|
|
1981
|
+
|
|
1982
|
+
const mockRequest = new Request(
|
|
1983
|
+
"http://localhost:3000/api/auth/stripe/webhook",
|
|
1984
|
+
{
|
|
1985
|
+
method: "POST",
|
|
1986
|
+
headers: {
|
|
1987
|
+
"stripe-signature": "valid_async_signature",
|
|
1988
|
+
},
|
|
1989
|
+
body: JSON.stringify(mockEvent),
|
|
1990
|
+
},
|
|
1991
|
+
);
|
|
1992
|
+
|
|
1993
|
+
const response = await testAuth.handler(mockRequest);
|
|
1994
|
+
expect(response.status).toBe(200);
|
|
1995
|
+
expect(stripeForTest.webhooks.constructEventAsync).toHaveBeenCalledWith(
|
|
1996
|
+
expect.any(String),
|
|
1997
|
+
"valid_async_signature",
|
|
1998
|
+
"test_secret_async",
|
|
1999
|
+
);
|
|
2000
|
+
|
|
2001
|
+
const data = await response.json();
|
|
2002
|
+
expect(data).toEqual({ success: true });
|
|
2003
|
+
});
|
|
2004
|
+
|
|
2005
|
+
it("should call constructEventAsync with exactly 3 required parameters", async () => {
|
|
2006
|
+
const mockEvent = {
|
|
2007
|
+
type: "customer.subscription.created",
|
|
2008
|
+
data: {
|
|
2009
|
+
object: {
|
|
2010
|
+
id: "sub_test_params",
|
|
2011
|
+
customer: "cus_test_params",
|
|
2012
|
+
status: "active",
|
|
2013
|
+
},
|
|
2014
|
+
},
|
|
2015
|
+
};
|
|
2016
|
+
|
|
2017
|
+
const stripeForTest = {
|
|
2018
|
+
...stripeOptions.stripeClient,
|
|
2019
|
+
webhooks: {
|
|
2020
|
+
constructEventAsync: vi.fn().mockResolvedValue(mockEvent),
|
|
2021
|
+
},
|
|
2022
|
+
};
|
|
2023
|
+
|
|
2024
|
+
const testOptions = {
|
|
2025
|
+
...stripeOptions,
|
|
2026
|
+
stripeClient: stripeForTest as unknown as Stripe,
|
|
2027
|
+
stripeWebhookSecret: "test_secret_params",
|
|
2028
|
+
};
|
|
2029
|
+
|
|
2030
|
+
const testAuth = betterAuth({
|
|
2031
|
+
baseURL: "http://localhost:3000",
|
|
2032
|
+
database: memory,
|
|
2033
|
+
emailAndPassword: { enabled: true },
|
|
2034
|
+
plugins: [stripe(testOptions)],
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
const mockRequest = new Request(
|
|
2038
|
+
"http://localhost:3000/api/auth/stripe/webhook",
|
|
2039
|
+
{
|
|
2040
|
+
method: "POST",
|
|
2041
|
+
headers: {
|
|
2042
|
+
"stripe-signature": "test_signature_params",
|
|
2043
|
+
},
|
|
2044
|
+
body: JSON.stringify(mockEvent),
|
|
2045
|
+
},
|
|
2046
|
+
);
|
|
2047
|
+
|
|
2048
|
+
await testAuth.handler(mockRequest);
|
|
2049
|
+
|
|
2050
|
+
// Verify that constructEventAsync is called with exactly 3 required parameters
|
|
2051
|
+
// (payload, signature, secret) and no optional parameters
|
|
2052
|
+
expect(stripeForTest.webhooks.constructEventAsync).toHaveBeenCalledWith(
|
|
2053
|
+
expect.any(String), // payload
|
|
2054
|
+
"test_signature_params", // signature
|
|
2055
|
+
"test_secret_params", // secret
|
|
2056
|
+
);
|
|
2057
|
+
|
|
2058
|
+
// Verify it was called exactly once
|
|
2059
|
+
expect(stripeForTest.webhooks.constructEventAsync).toHaveBeenCalledTimes(
|
|
2060
|
+
1,
|
|
2061
|
+
);
|
|
2062
|
+
});
|
|
2063
|
+
|
|
2064
|
+
it("should support Stripe v18 with sync constructEvent method", async () => {
|
|
2065
|
+
const mockEvent = {
|
|
2066
|
+
type: "customer.subscription.updated",
|
|
2067
|
+
data: {
|
|
2068
|
+
object: {
|
|
2069
|
+
id: "sub_test_v18",
|
|
2070
|
+
customer: "cus_test_v18",
|
|
2071
|
+
status: "active",
|
|
2072
|
+
items: {
|
|
2073
|
+
data: [
|
|
2074
|
+
{
|
|
2075
|
+
price: { id: process.env.STRIPE_PRICE_ID_1 },
|
|
2076
|
+
quantity: 1,
|
|
2077
|
+
current_period_start: Math.floor(Date.now() / 1000),
|
|
2078
|
+
current_period_end:
|
|
2079
|
+
Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
|
2080
|
+
},
|
|
2081
|
+
],
|
|
2082
|
+
},
|
|
2083
|
+
current_period_start: Math.floor(Date.now() / 1000),
|
|
2084
|
+
current_period_end:
|
|
2085
|
+
Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
|
2086
|
+
},
|
|
2087
|
+
},
|
|
2088
|
+
};
|
|
2089
|
+
|
|
2090
|
+
// Simulate Stripe v18 - only has sync constructEvent, no constructEventAsync
|
|
2091
|
+
const stripeV18 = {
|
|
2092
|
+
...stripeOptions.stripeClient,
|
|
2093
|
+
webhooks: {
|
|
2094
|
+
constructEvent: vi.fn().mockReturnValue(mockEvent),
|
|
2095
|
+
// v18 doesn't have constructEventAsync
|
|
2096
|
+
constructEventAsync: undefined,
|
|
2097
|
+
},
|
|
2098
|
+
};
|
|
2099
|
+
|
|
2100
|
+
const testOptions = {
|
|
2101
|
+
...stripeOptions,
|
|
2102
|
+
stripeClient: stripeV18 as unknown as Stripe,
|
|
2103
|
+
stripeWebhookSecret: "test_secret_v18",
|
|
2104
|
+
};
|
|
2105
|
+
|
|
2106
|
+
const testAuth = betterAuth({
|
|
2107
|
+
baseURL: "http://localhost:3000",
|
|
2108
|
+
database: memory,
|
|
2109
|
+
emailAndPassword: { enabled: true },
|
|
2110
|
+
plugins: [stripe(testOptions)],
|
|
2111
|
+
});
|
|
2112
|
+
|
|
2113
|
+
const { id: subId } = await ctx.adapter.create({
|
|
2114
|
+
model: "subscription",
|
|
2115
|
+
data: {
|
|
2116
|
+
referenceId: userId,
|
|
2117
|
+
stripeCustomerId: "cus_test_v18",
|
|
2118
|
+
stripeSubscriptionId: "sub_test_v18",
|
|
2119
|
+
status: "incomplete",
|
|
2120
|
+
plan: "starter",
|
|
2121
|
+
},
|
|
2122
|
+
});
|
|
2123
|
+
|
|
2124
|
+
const mockRequest = new Request(
|
|
2125
|
+
"http://localhost:3000/api/auth/stripe/webhook",
|
|
2126
|
+
{
|
|
2127
|
+
method: "POST",
|
|
2128
|
+
headers: {
|
|
2129
|
+
"stripe-signature": "test_signature_v18",
|
|
2130
|
+
},
|
|
2131
|
+
body: JSON.stringify(mockEvent),
|
|
2132
|
+
},
|
|
2133
|
+
);
|
|
2134
|
+
|
|
2135
|
+
const response = await testAuth.handler(mockRequest);
|
|
2136
|
+
expect(response.status).toBe(200);
|
|
2137
|
+
|
|
2138
|
+
// Verify that constructEvent (sync) was called instead of constructEventAsync
|
|
2139
|
+
expect(stripeV18.webhooks.constructEvent).toHaveBeenCalledWith(
|
|
2140
|
+
expect.any(String),
|
|
2141
|
+
"test_signature_v18",
|
|
2142
|
+
"test_secret_v18",
|
|
2143
|
+
);
|
|
2144
|
+
expect(stripeV18.webhooks.constructEvent).toHaveBeenCalledTimes(1);
|
|
2145
|
+
|
|
2146
|
+
const data = await response.json();
|
|
2147
|
+
expect(data).toEqual({ success: true });
|
|
2148
|
+
});
|
|
2149
|
+
});
|
|
1742
2150
|
});
|