@better-auth/stripe 1.3.10-beta.1 → 1.3.10-beta.2

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,15 +1,15 @@
1
1
 
2
- > @better-auth/stripe@1.3.10-beta.1 build /home/runner/work/better-auth/better-auth/packages/stripe
2
+ > @better-auth/stripe@1.3.10-beta.2 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: 45.5 kB, chunk size: 45.5 kB, exports: stripe)
8
+ [log] dist/index.cjs (total size: 45.4 kB, chunk size: 45.4 kB, exports: stripe)
9
9
 
10
10
  [log] dist/client.cjs (total size: 224 B, chunk size: 224 B, exports: stripeClient)
11
11
 
12
- [log] dist/index.mjs (total size: 44.6 kB, chunk size: 44.6 kB, exports: stripe)
12
+ [log] dist/index.mjs (total size: 44.5 kB, chunk size: 44.5 kB, exports: stripe)
13
13
 
14
14
  [log] dist/client.mjs (total size: 197 B, chunk size: 197 B, exports: stripeClient)
15
15
 
package/dist/index.cjs CHANGED
@@ -660,9 +660,8 @@ const stripe = (options) => {
660
660
  ctx
661
661
  );
662
662
  const hasEverTrialed = subscriptions.some((s) => {
663
- const samePlan = s.plan?.toLowerCase() === plan.name.toLowerCase();
664
663
  const hadTrial = !!(s.trialStart || s.trialEnd) || s.status === "trialing";
665
- return samePlan && hadTrial;
664
+ return hadTrial;
666
665
  });
667
666
  const freeTrial = !hasEverTrialed && plan.freeTrial ? { trial_period_days: plan.freeTrial.days } : void 0;
668
667
  let priceIdToUse = void 0;
package/dist/index.mjs CHANGED
@@ -644,9 +644,8 @@ const stripe = (options) => {
644
644
  ctx
645
645
  );
646
646
  const hasEverTrialed = subscriptions.some((s) => {
647
- const samePlan = s.plan?.toLowerCase() === plan.name.toLowerCase();
648
647
  const hadTrial = !!(s.trialStart || s.trialEnd) || s.status === "trialing";
649
- return samePlan && hadTrial;
648
+ return hadTrial;
650
649
  });
651
650
  const freeTrial = !hasEverTrialed && plan.freeTrial ? { trial_period_days: plan.freeTrial.days } : void 0;
652
651
  let priceIdToUse = void 0;
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.10-beta.1",
4
+ "version": "1.3.10-beta.2",
5
5
  "main": "dist/index.cjs",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -41,13 +41,13 @@
41
41
  },
42
42
  "peerDependencies": {
43
43
  "stripe": "^18",
44
- "better-auth": "1.3.10-beta.1"
44
+ "better-auth": "1.3.10-beta.2"
45
45
  },
46
46
  "devDependencies": {
47
47
  "better-call": "1.0.18",
48
48
  "stripe": "^18.5.0",
49
49
  "unbuild": "3.6.1",
50
- "better-auth": "1.3.10-beta.1"
50
+ "better-auth": "1.3.10-beta.2"
51
51
  },
52
52
  "scripts": {
53
53
  "test": "vitest",
package/src/index.ts CHANGED
@@ -468,10 +468,11 @@ export const stripe = <O extends StripeOptions>(options: O) => {
468
468
  );
469
469
 
470
470
  const hasEverTrialed = subscriptions.some((s) => {
471
- const samePlan = s.plan?.toLowerCase() === plan.name.toLowerCase();
471
+ // Check if user has ever had a trial for any plan (not just the same plan)
472
+ // This prevents users from getting multiple trials by switching plans
472
473
  const hadTrial =
473
474
  !!(s.trialStart || s.trialEnd) || s.status === "trialing";
474
- return samePlan && hadTrial;
475
+ return hadTrial;
475
476
  });
476
477
 
477
478
  const freeTrial =
@@ -1103,68 +1103,181 @@ describe("stripe", async () => {
1103
1103
  expect(personalAfter?.status).toBe("active");
1104
1104
  });
1105
1105
 
1106
- it("should reuse incomplete subscription when upgrading again", async () => {
1106
+ it("should prevent multiple free trials for the same user", async () => {
1107
1107
  // Create a user
1108
1108
  const userRes = await authClient.signUp.email(
1109
- {
1110
- email: "incomplete@example.com",
1111
- password: "password",
1112
- name: "Incomplete Test",
1113
- },
1109
+ { ...testUser, email: "trial-prevention@email.com" },
1110
+ { throw: true },
1111
+ );
1112
+
1113
+ const headers = new Headers();
1114
+ await authClient.signIn.email(
1115
+ { ...testUser, email: "trial-prevention@email.com" },
1114
1116
  {
1115
1117
  throw: true,
1118
+ onSuccess: setCookieToHeader(headers),
1116
1119
  },
1117
1120
  );
1118
1121
 
1122
+ // First subscription with trial
1123
+ const firstUpgradeRes = await authClient.subscription.upgrade({
1124
+ plan: "starter",
1125
+ fetchOptions: { headers },
1126
+ });
1127
+
1128
+ expect(firstUpgradeRes.data?.url).toBeDefined();
1129
+
1130
+ // Simulate the subscription being created with trial data
1131
+ await ctx.adapter.update({
1132
+ model: "subscription",
1133
+ update: {
1134
+ status: "trialing",
1135
+ trialStart: new Date(),
1136
+ trialEnd: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days from now
1137
+ },
1138
+ where: [
1139
+ {
1140
+ field: "referenceId",
1141
+ value: userRes.user.id,
1142
+ },
1143
+ ],
1144
+ });
1145
+
1146
+ // Cancel the subscription
1147
+ await ctx.adapter.update({
1148
+ model: "subscription",
1149
+ update: {
1150
+ status: "canceled",
1151
+ },
1152
+ where: [
1153
+ {
1154
+ field: "referenceId",
1155
+ value: userRes.user.id,
1156
+ },
1157
+ ],
1158
+ });
1159
+
1160
+ // Try to subscribe again - should NOT get a trial
1161
+ const secondUpgradeRes = await authClient.subscription.upgrade({
1162
+ plan: "starter",
1163
+ fetchOptions: { headers },
1164
+ });
1165
+
1166
+ expect(secondUpgradeRes.data?.url).toBeDefined();
1167
+
1168
+ // Verify that the checkout session was created without trial_period_days
1169
+ // We can't directly test the Stripe session, but we can verify the logic
1170
+ // by checking that the user has trial history
1171
+ const subscriptions = (await ctx.adapter.findMany({
1172
+ model: "subscription",
1173
+ where: [
1174
+ {
1175
+ field: "referenceId",
1176
+ value: userRes.user.id,
1177
+ },
1178
+ ],
1179
+ })) as Subscription[];
1180
+
1181
+ // Should have 2 subscriptions (first canceled, second new)
1182
+ expect(subscriptions).toHaveLength(2);
1183
+
1184
+ // At least one should have trial data
1185
+ const hasTrialData = subscriptions.some(
1186
+ (s: Subscription) => s.trialStart || s.trialEnd,
1187
+ );
1188
+ expect(hasTrialData).toBe(true);
1189
+ });
1190
+
1191
+ it("should prevent multiple free trials across different plans", async () => {
1192
+ // Create a user
1193
+ const userRes = await authClient.signUp.email(
1194
+ { ...testUser, email: "cross-plan-trial@email.com" },
1195
+ { throw: true },
1196
+ );
1197
+
1119
1198
  const headers = new Headers();
1120
1199
  await authClient.signIn.email(
1121
- {
1122
- email: "incomplete@example.com",
1123
- password: "password",
1124
- },
1200
+ { ...testUser, email: "cross-plan-trial@email.com" },
1125
1201
  {
1126
1202
  throw: true,
1127
1203
  onSuccess: setCookieToHeader(headers),
1128
1204
  },
1129
1205
  );
1130
1206
 
1131
- // First upgrade attempt - creates incomplete subscription
1132
- const firstUpgrade = await authClient.subscription.upgrade({
1207
+ // First subscription with trial on starter plan
1208
+ const firstUpgradeRes = await authClient.subscription.upgrade({
1133
1209
  plan: "starter",
1134
- fetchOptions: {
1135
- headers,
1210
+ fetchOptions: { headers },
1211
+ });
1212
+
1213
+ expect(firstUpgradeRes.data?.url).toBeDefined();
1214
+
1215
+ // Simulate the subscription being created with trial data
1216
+ await ctx.adapter.update({
1217
+ model: "subscription",
1218
+ update: {
1219
+ status: "trialing",
1220
+ trialStart: new Date(),
1221
+ trialEnd: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
1136
1222
  },
1223
+ where: [
1224
+ {
1225
+ field: "referenceId",
1226
+ value: userRes.user.id,
1227
+ },
1228
+ ],
1137
1229
  });
1138
- expect(firstUpgrade.data?.url).toBe("https://checkout.stripe.com/mock");
1139
1230
 
1140
- // Check that an incomplete subscription was created
1141
- const subscriptions = await ctx.adapter.findMany<Subscription>({
1231
+ // Cancel the subscription
1232
+ await ctx.adapter.update({
1142
1233
  model: "subscription",
1143
- where: [{ field: "referenceId", value: userRes.user.id }],
1234
+ update: {
1235
+ status: "canceled",
1236
+ },
1237
+ where: [
1238
+ {
1239
+ field: "referenceId",
1240
+ value: userRes.user.id,
1241
+ },
1242
+ ],
1144
1243
  });
1145
- expect(subscriptions).toHaveLength(1);
1146
- expect(subscriptions[0].status).toBe("incomplete");
1147
- const firstSubId = subscriptions[0].id;
1148
1244
 
1149
- // Second upgrade attempt - should reuse the same subscription
1150
- const secondUpgrade = await authClient.subscription.upgrade({
1245
+ // Try to subscribe to a different plan - should NOT get a trial
1246
+ const secondUpgradeRes = await authClient.subscription.upgrade({
1151
1247
  plan: "premium",
1152
- seats: 2,
1153
- fetchOptions: {
1154
- headers,
1155
- },
1248
+ fetchOptions: { headers },
1156
1249
  });
1157
- expect(secondUpgrade.data?.url).toBe("https://checkout.stripe.com/mock");
1158
1250
 
1159
- // Check that the same subscription was updated, not a new one created
1160
- const subscriptionsAfter = await ctx.adapter.findMany<Subscription>({
1251
+ expect(secondUpgradeRes.data?.url).toBeDefined();
1252
+
1253
+ // Verify that the user has trial history from the first plan
1254
+ const subscriptions = (await ctx.adapter.findMany({
1161
1255
  model: "subscription",
1162
- where: [{ field: "referenceId", value: userRes.user.id }],
1256
+ where: [
1257
+ {
1258
+ field: "referenceId",
1259
+ value: userRes.user.id,
1260
+ },
1261
+ ],
1262
+ })) as Subscription[];
1263
+
1264
+ // Should have at least 1 subscription (the starter with trial data)
1265
+ expect(subscriptions.length).toBeGreaterThanOrEqual(1);
1266
+
1267
+ // The starter subscription should have trial data
1268
+ const starterSub = subscriptions.find(
1269
+ (s: Subscription) => s.plan === "starter",
1270
+ ) as Subscription | undefined;
1271
+ expect(starterSub?.trialStart).toBeDefined();
1272
+ expect(starterSub?.trialEnd).toBeDefined();
1273
+
1274
+ // Verify that the trial eligibility logic is working by checking
1275
+ // that the user has ever had a trial (which should prevent future trials)
1276
+ const hasEverTrialed = subscriptions.some((s: Subscription) => {
1277
+ const hadTrial =
1278
+ !!(s.trialStart || s.trialEnd) || s.status === "trialing";
1279
+ return hadTrial;
1163
1280
  });
1164
- expect(subscriptionsAfter).toHaveLength(1);
1165
- expect(subscriptionsAfter[0].id).toBe(firstSubId);
1166
- expect(subscriptionsAfter[0].status).toBe("incomplete");
1167
- expect(subscriptionsAfter[0].plan).toBe("premium");
1168
- expect(subscriptionsAfter[0].seats).toBe(2);
1281
+ expect(hasEverTrialed).toBe(true);
1169
1282
  });
1170
1283
  });