@better-auth/stripe 1.5.0-beta.7 → 1.5.0-beta.9

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @better-auth/stripe@1.5.0-beta.7 build /home/runner/work/better-auth/better-auth/packages/stripe
2
+ > @better-auth/stripe@1.5.0-beta.9 build /home/runner/work/better-auth/better-auth/packages/stripe
3
3
  > tsdown
4
4
 
5
5
  ℹ tsdown v0.19.0 powered by rolldown v1.0.0-beta.59
@@ -7,11 +7,11 @@
7
7
  ℹ entry: src/index.ts, src/client.ts
8
8
  ℹ tsconfig: tsconfig.json
9
9
  ℹ Build start
10
- ℹ dist/index.mjs 55.90 kB │ gzip: 10.02 kB
10
+ ℹ dist/index.mjs 56.45 kB │ gzip: 10.16 kB
11
11
  ℹ dist/client.mjs  0.39 kB │ gzip: 0.26 kB
12
12
  ℹ dist/error-codes-Clj-xYDP.mjs  1.62 kB │ gzip: 0.75 kB
13
13
  ℹ dist/client.d.mts  6.71 kB │ gzip: 1.32 kB
14
14
  ℹ dist/index.d.mts  0.21 kB │ gzip: 0.14 kB
15
- ℹ dist/index-CkImGy5x.d.mts 31.76 kB │ gzip: 5.34 kB
16
- ℹ 6 files, total: 96.59 kB
17
- ✔ Build complete in 21218ms
15
+ ℹ dist/index-BqGWQFAv.d.mts 31.88 kB │ gzip: 5.35 kB
16
+ ℹ 6 files, total: 97.26 kB
17
+ ✔ Build complete in 25066ms
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as stripe } from "./index-CkImGy5x.mjs";
1
+ import { n as stripe } from "./index-BqGWQFAv.mjs";
2
2
 
3
3
  //#region src/error-codes.d.ts
4
4
  declare const STRIPE_ERROR_CODES: {
@@ -483,6 +483,7 @@ declare const stripe: <O extends StripeOptions>(options: O) => {
483
483
  }>>;
484
484
  metadata: zod0.ZodOptional<zod0.ZodRecord<zod0.ZodString, zod0.ZodAny>>;
485
485
  seats: zod0.ZodOptional<zod0.ZodNumber>;
486
+ locale: zod0.ZodOptional<zod0.ZodCustom<Stripe.Checkout.Session.Locale, Stripe.Checkout.Session.Locale>>;
486
487
  successUrl: zod0.ZodDefault<zod0.ZodString>;
487
488
  cancelUrl: zod0.ZodDefault<zod0.ZodString>;
488
489
  returnUrl: zod0.ZodOptional<zod0.ZodString>;
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as SubscriptionOptions, i as Subscription, n as stripe, r as StripePlan, t as StripePlugin } from "./index-CkImGy5x.mjs";
1
+ import { a as SubscriptionOptions, i as Subscription, n as stripe, r as StripePlan, t as StripePlugin } from "./index-BqGWQFAv.mjs";
2
2
  export { StripePlan, StripePlugin, Subscription, SubscriptionOptions, stripe };
package/dist/index.mjs CHANGED
@@ -424,6 +424,9 @@ const upgradeSubscriptionBodySchema = z.object({
424
424
  customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional(),
425
425
  metadata: z.record(z.string(), z.any()).optional(),
426
426
  seats: z.number().meta({ description: "Number of seats to upgrade to (if applicable). Eg: 1" }).optional(),
427
+ locale: z.custom((localization) => {
428
+ return typeof localization === "string";
429
+ }).meta({ description: "The locale to display Checkout in. Eg: 'en', 'ko'. If not provided or set to `auto`, the browser's locale is used." }).optional(),
427
430
  successUrl: z.string().meta({ description: "Callback URL to redirect back after successful subscription. Eg: \"https://example.com/success\"" }).default("/"),
428
431
  cancelUrl: z.string().meta({ description: "If set, checkout shows a back button and customers will be directed here if they cancel payment. Eg: \"https://example.com/pricing\"" }).default("/"),
429
432
  returnUrl: z.string().meta({ description: "URL to take customers to when they click on the billing portal’s link to return to your website. Eg: \"https://example.com/dashboard\"" }).optional(),
@@ -576,7 +579,7 @@ const upgradeSubscription = (options) => {
576
579
  return false;
577
580
  });
578
581
  const incompleteSubscription = subscriptions$1.find((sub) => sub.status === "incomplete");
579
- if (activeOrTrialingSubscription && activeOrTrialingSubscription.status === "active" && activeOrTrialingSubscription.plan === ctx.body.plan && activeOrTrialingSubscription.seats === (ctx.body.seats || 1)) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN);
582
+ if (activeOrTrialingSubscription && activeOrTrialingSubscription.status === "active" && activeOrTrialingSubscription.plan === ctx.body.plan && activeOrTrialingSubscription.seats === (ctx.body.seats || 1) && (!activeOrTrialingSubscription.periodEnd || activeOrTrialingSubscription.periodEnd > /* @__PURE__ */ new Date())) throw APIError$1.from("BAD_REQUEST", STRIPE_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN);
580
583
  if (activeSubscription && customerId) {
581
584
  let dbSubscription = await ctx.context.adapter.findOne({
582
585
  model: "subscription",
@@ -695,6 +698,7 @@ const upgradeSubscription = (options) => {
695
698
  address: "auto"
696
699
  }
697
700
  } : { customer_email: user$1.email },
701
+ locale: ctx.body.locale,
698
702
  success_url: getUrl(ctx, `${ctx.context.baseURL}/subscription/success?callbackURL=${encodeURIComponent(ctx.body.successUrl)}&subscriptionId=${encodeURIComponent(subscription.id)}`),
699
703
  cancel_url: getUrl(ctx, ctx.body.cancelUrl),
700
704
  line_items: [{
@@ -1080,7 +1084,7 @@ const subscriptionSuccess = (options) => {
1080
1084
  const createBillingPortalBodySchema = z.object({
1081
1085
  locale: z.custom((localization) => {
1082
1086
  return typeof localization === "string";
1083
- }).optional(),
1087
+ }).meta({ description: "The IETF language tag of the locale Customer Portal is displayed in. Eg: 'en', 'ko'. If not provided or set to `auto`, the browser's locale is used." }).optional(),
1084
1088
  referenceId: z.string().optional(),
1085
1089
  customerType: z.enum(["user", "organization"]).meta({ description: "Customer type for the subscription. Eg: \"user\" or \"organization\"" }).optional(),
1086
1090
  returnUrl: z.string().default("/"),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@better-auth/stripe",
3
3
  "author": "Bereket Engida",
4
- "version": "1.5.0-beta.7",
4
+ "version": "1.5.0-beta.9",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
7
7
  "types": "dist/index.d.mts",
@@ -46,19 +46,19 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "defu": "^6.1.4",
49
- "zod": "^4.1.12"
49
+ "zod": "^4.3.5"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "stripe": "^18 || ^19 || ^20",
53
- "@better-auth/core": "1.5.0-beta.7",
54
- "better-auth": "1.5.0-beta.7"
53
+ "@better-auth/core": "1.5.0-beta.9",
54
+ "better-auth": "1.5.0-beta.9"
55
55
  },
56
56
  "devDependencies": {
57
- "better-call": "1.1.8",
57
+ "better-call": "1.2.0",
58
58
  "stripe": "^20.0.0",
59
59
  "tsdown": "^0.19.0",
60
- "@better-auth/core": "1.5.0-beta.7",
61
- "better-auth": "1.5.0-beta.7"
60
+ "@better-auth/core": "1.5.0-beta.9",
61
+ "better-auth": "1.5.0-beta.9"
62
62
  },
63
63
  "scripts": {
64
64
  "test": "vitest",
package/src/routes.ts CHANGED
@@ -161,6 +161,19 @@ const upgradeSubscriptionBodySchema = z.object({
161
161
  description: "Number of seats to upgrade to (if applicable). Eg: 1",
162
162
  })
163
163
  .optional(),
164
+ /**
165
+ * The IETF language tag of the locale Checkout is displayed in.
166
+ * If not provided or set to `auto`, the browser's locale is used.
167
+ */
168
+ locale: z
169
+ .custom<StripeType.Checkout.Session.Locale>((localization) => {
170
+ return typeof localization === "string";
171
+ })
172
+ .meta({
173
+ description:
174
+ "The locale to display Checkout in. Eg: 'en', 'ko'. If not provided or set to `auto`, the browser's locale is used.",
175
+ })
176
+ .optional(),
164
177
  /**
165
178
  * The URL to which Stripe should send customers when payment or setup is complete.
166
179
  */
@@ -498,7 +511,10 @@ export const upgradeSubscription = (options: StripeOptions) => {
498
511
  activeOrTrialingSubscription &&
499
512
  activeOrTrialingSubscription.status === "active" &&
500
513
  activeOrTrialingSubscription.plan === ctx.body.plan &&
501
- activeOrTrialingSubscription.seats === (ctx.body.seats || 1)
514
+ activeOrTrialingSubscription.seats === (ctx.body.seats || 1) &&
515
+ // Skip if periodEnd has passed, in case status is stale
516
+ (!activeOrTrialingSubscription.periodEnd ||
517
+ activeOrTrialingSubscription.periodEnd > new Date())
502
518
  ) {
503
519
  throw APIError.from(
504
520
  "BAD_REQUEST",
@@ -702,6 +718,7 @@ export const upgradeSubscription = (options: StripeOptions) => {
702
718
  : {
703
719
  customer_email: user.email,
704
720
  }),
721
+ locale: ctx.body.locale,
705
722
  success_url: getUrl(
706
723
  ctx,
707
724
  `${
@@ -1429,10 +1446,18 @@ export const subscriptionSuccess = (options: StripeOptions) => {
1429
1446
  };
1430
1447
 
1431
1448
  const createBillingPortalBodySchema = z.object({
1449
+ /**
1450
+ * The IETF language tag of the locale Customer Portal is displayed in.
1451
+ * If not provided or set to `auto`, the browser's locale is used.
1452
+ */
1432
1453
  locale: z
1433
1454
  .custom<StripeType.Checkout.Session.Locale>((localization) => {
1434
1455
  return typeof localization === "string";
1435
1456
  })
1457
+ .meta({
1458
+ description:
1459
+ "The IETF language tag of the locale Customer Portal is displayed in. Eg: 'en', 'ko'. If not provided or set to `auto`, the browser's locale is used.",
1460
+ })
1436
1461
  .optional(),
1437
1462
  referenceId: z.string().optional(),
1438
1463
  /**
@@ -1915,6 +1915,72 @@ describe("stripe", () => {
1915
1915
  expect(upgradeRes.error?.message).toContain("already subscribed");
1916
1916
  });
1917
1917
 
1918
+ it.each([
1919
+ {
1920
+ name: "past",
1921
+ periodEnd: new Date(Date.now() - 24 * 60 * 60 * 1000),
1922
+ shouldAllow: true,
1923
+ },
1924
+ {
1925
+ name: "future",
1926
+ periodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
1927
+ shouldAllow: false,
1928
+ },
1929
+ ])("should handle re-subscribing when periodEnd is in the $name", async ({
1930
+ periodEnd,
1931
+ shouldAllow,
1932
+ }) => {
1933
+ const { client, auth, sessionSetter } = await getTestInstance(
1934
+ {
1935
+ database: memory,
1936
+ plugins: [stripe(stripeOptions)],
1937
+ },
1938
+ {
1939
+ disableTestUser: true,
1940
+ clientOptions: {
1941
+ plugins: [stripeClient({ subscription: true })],
1942
+ },
1943
+ },
1944
+ );
1945
+ const ctx = await auth.$context;
1946
+
1947
+ const userRes = await client.signUp.email(
1948
+ { ...testUser, email: `periodend-${periodEnd.getTime()}@email.com` },
1949
+ { throw: true },
1950
+ );
1951
+
1952
+ const headers = new Headers();
1953
+ await client.signIn.email(
1954
+ { ...testUser, email: `periodend-${periodEnd.getTime()}@email.com` },
1955
+ { throw: true, onSuccess: sessionSetter(headers) },
1956
+ );
1957
+
1958
+ await client.subscription.upgrade({
1959
+ plan: "starter",
1960
+ seats: 1,
1961
+ fetchOptions: { headers },
1962
+ });
1963
+
1964
+ await ctx.adapter.update({
1965
+ model: "subscription",
1966
+ update: { status: "active", seats: 1, periodEnd },
1967
+ where: [{ field: "referenceId", value: userRes.user.id }],
1968
+ });
1969
+
1970
+ const upgradeRes = await client.subscription.upgrade({
1971
+ plan: "starter",
1972
+ seats: 1,
1973
+ fetchOptions: { headers },
1974
+ });
1975
+
1976
+ if (shouldAllow) {
1977
+ expect(upgradeRes.error).toBeNull();
1978
+ expect(upgradeRes.data?.url).toBeDefined();
1979
+ } else {
1980
+ expect(upgradeRes.error?.message).toContain("already subscribed");
1981
+ }
1982
+ });
1983
+
1918
1984
  it("should only call Stripe customers.create once for signup and upgrade", async () => {
1919
1985
  const { client, sessionSetter } = await getTestInstance(
1920
1986
  {