@@ -79,6 +79,32 @@ describe('Billing service unit tests:', () => {
7979 ) ;
8080 } ) ;
8181
82+ test ( 'should throw when redirect URL is invalid' , async ( ) => {
83+ jest . unstable_mockModule ( '../../../config/index.js' , ( ) => ( {
84+ default : { stripe : { secretKey : 'sk_test_url' } } ,
85+ } ) ) ;
86+
87+ const mod = await import ( '../services/billing.service.js' ) ;
88+ BillingService = mod . default ;
89+
90+ await expect ( BillingService . createCheckout ( mockOrganization , 'price_123' , 'not-a-url' , 'http://cancel' ) ) . rejects . toThrow (
91+ 'Invalid redirect URL' ,
92+ ) ;
93+ } ) ;
94+
95+ test ( 'should reject URL with wrong domain when config.domain is set' , async ( ) => {
96+ jest . unstable_mockModule ( '../../../config/index.js' , ( ) => ( {
97+ default : { stripe : { secretKey : 'sk_test_domain' } , domain : 'https://myapp.com' } ,
98+ } ) ) ;
99+
100+ const mod = await import ( '../services/billing.service.js' ) ;
101+ BillingService = mod . default ;
102+
103+ await expect (
104+ BillingService . createCheckout ( mockOrganization , 'price_123' , 'http://evil.com/success' , 'http://myapp.com/cancel' ) ,
105+ ) . rejects . toThrow ( 'Invalid redirect URL' ) ;
106+ } ) ;
107+
82108 test ( 'should create customer and subscription when none exists' , async ( ) => {
83109 jest . unstable_mockModule ( '../../../config/index.js' , ( ) => ( {
84110 default : { stripe : { secretKey : 'sk_test_123' } } ,
@@ -111,7 +137,6 @@ describe('Billing service unit tests:', () => {
111137 const existingSub = {
112138 _id : 'sub_existing' ,
113139 stripeCustomerId : null ,
114- toObject : jest . fn ( ) . mockReturnValue ( { _id : 'sub_existing' , stripeCustomerId : null } ) ,
115140 } ;
116141 mockSubscriptionRepository . findByOrganization . mockResolvedValue ( existingSub ) ;
117142 mockSubscriptionRepository . update . mockResolvedValue ( { stripeCustomerId : 'cus_new123' } ) ;
@@ -196,7 +221,7 @@ describe('Billing service unit tests:', () => {
196221 ) ;
197222 } ) ;
198223
199- test ( 'should return portal session URL when customer exists ' , async ( ) => {
224+ test ( 'should return portal session URL without returnUrl ' , async ( ) => {
200225 jest . unstable_mockModule ( '../../../config/index.js' , ( ) => ( {
201226 default : { stripe : { secretKey : 'sk_test_portal3' } } ,
202227 } ) ) ;
@@ -213,6 +238,38 @@ describe('Billing service unit tests:', () => {
213238 customer : 'cus_portal' ,
214239 } ) ;
215240 } ) ;
241+
242+ test ( 'should include return_url when returnUrl is provided' , async ( ) => {
243+ jest . unstable_mockModule ( '../../../config/index.js' , ( ) => ( {
244+ default : { stripe : { secretKey : 'sk_test_portal4' } } ,
245+ } ) ) ;
246+
247+ mockSubscriptionRepository . findByOrganization . mockResolvedValue ( { stripeCustomerId : 'cus_portal' } ) ;
248+
249+ const mod = await import ( '../services/billing.service.js' ) ;
250+ BillingService = mod . default ;
251+
252+ const url = await BillingService . createPortalSession ( mockOrganization , 'http://app/settings' ) ;
253+
254+ expect ( url ) . toBe ( 'https://billing.stripe.com/portal_123' ) ;
255+ expect ( mockStripeInstance . billingPortal . sessions . create ) . toHaveBeenCalledWith ( {
256+ customer : 'cus_portal' ,
257+ return_url : 'http://app/settings' ,
258+ } ) ;
259+ } ) ;
260+
261+ test ( 'should throw when returnUrl is invalid' , async ( ) => {
262+ jest . unstable_mockModule ( '../../../config/index.js' , ( ) => ( {
263+ default : { stripe : { secretKey : 'sk_test_portal5' } } ,
264+ } ) ) ;
265+
266+ mockSubscriptionRepository . findByOrganization . mockResolvedValue ( { stripeCustomerId : 'cus_portal' } ) ;
267+
268+ const mod = await import ( '../services/billing.service.js' ) ;
269+ BillingService = mod . default ;
270+
271+ await expect ( BillingService . createPortalSession ( mockOrganization , 'not-a-url' ) ) . rejects . toThrow ( 'Invalid return URL' ) ;
272+ } ) ;
216273 } ) ;
217274
218275 describe ( 'getSubscription' , ( ) => {
0 commit comments