@@ -227,6 +227,92 @@ describe("stripe", async () => {
227227 } ) ;
228228 } ) ;
229229
230+ it ( "should not allow cross-user subscriptionId operations (upgrade/cancel/restore)" , async ( ) => {
231+ const userA = {
232+ email : "user-a@email.com" ,
233+ password : "password" ,
234+ name : "User A" ,
235+ } ;
236+ const userARes = await authClient . signUp . email ( userA , { throw : true } ) ;
237+
238+ const userAHeaders = new Headers ( ) ;
239+ await authClient . signIn . email ( userA , {
240+ throw : true ,
241+ onSuccess : setCookieToHeader ( userAHeaders ) ,
242+ } ) ;
243+ await authClient . subscription . upgrade ( {
244+ plan : "starter" ,
245+ fetchOptions : { headers : userAHeaders } ,
246+ } ) ;
247+
248+ const userASub = await ctx . adapter . findOne < Subscription > ( {
249+ model : "subscription" ,
250+ where : [ { field : "referenceId" , value : userARes . user . id } ] ,
251+ } ) ;
252+ expect ( userASub ) . toBeTruthy ( ) ;
253+
254+ const userB = {
255+ email : "user-b@email.com" ,
256+ password : "password" ,
257+ name : "User B" ,
258+ } ;
259+ await authClient . signUp . email ( userB , { throw : true } ) ;
260+ const userBHeaders = new Headers ( ) ;
261+ await authClient . signIn . email ( userB , {
262+ throw : true ,
263+ onSuccess : setCookieToHeader ( userBHeaders ) ,
264+ } ) ;
265+
266+ mockStripe . checkout . sessions . create . mockClear ( ) ;
267+ mockStripe . billingPortal . sessions . create . mockClear ( ) ;
268+ mockStripe . subscriptions . list . mockClear ( ) ;
269+ mockStripe . subscriptions . update . mockClear ( ) ;
270+
271+ const upgradeRes = await authClient . subscription . upgrade ( {
272+ plan : "premium" ,
273+ subscriptionId : userASub ! . id ,
274+ fetchOptions : { headers : userBHeaders } ,
275+ } ) ;
276+ expect ( upgradeRes . error ?. message ) . toContain ( "Subscription not found" ) ;
277+ expect ( mockStripe . checkout . sessions . create ) . not . toHaveBeenCalled ( ) ;
278+ expect ( mockStripe . billingPortal . sessions . create ) . not . toHaveBeenCalled ( ) ;
279+
280+ const cancelHeaders = new Headers ( userBHeaders ) ;
281+ cancelHeaders . set ( "content-type" , "application/json" ) ;
282+ const cancelResponse = await auth . handler (
283+ new Request ( "http://localhost:3000/api/auth/subscription/cancel" , {
284+ method : "POST" ,
285+ headers : cancelHeaders ,
286+ body : JSON . stringify ( {
287+ subscriptionId : userASub ! . id ,
288+ returnUrl : "/account" ,
289+ } ) ,
290+ } ) ,
291+ ) ;
292+ expect ( cancelResponse . status ) . toBe ( 400 ) ;
293+ expect ( ( await cancelResponse . json ( ) ) . message ) . toContain (
294+ "Subscription not found" ,
295+ ) ;
296+ expect ( mockStripe . billingPortal . sessions . create ) . not . toHaveBeenCalled ( ) ;
297+
298+ const restoreHeaders = new Headers ( userBHeaders ) ;
299+ restoreHeaders . set ( "content-type" , "application/json" ) ;
300+ const restoreResponse = await auth . handler (
301+ new Request ( "http://localhost:3000/api/auth/subscription/restore" , {
302+ method : "POST" ,
303+ headers : restoreHeaders ,
304+ body : JSON . stringify ( {
305+ subscriptionId : userASub ! . id ,
306+ } ) ,
307+ } ) ,
308+ ) ;
309+ expect ( restoreResponse . status ) . toBe ( 400 ) ;
310+ expect ( ( await restoreResponse . json ( ) ) . message ) . toContain (
311+ "Subscription not found" ,
312+ ) ;
313+ expect ( mockStripe . subscriptions . update ) . not . toHaveBeenCalled ( ) ;
314+ } ) ;
315+
230316 it ( "should list active subscriptions" , async ( ) => {
231317 const userRes = await authClient . signUp . email (
232318 {
0 commit comments