@@ -25,6 +25,9 @@ export const DELETE = createSmartRouteHandler({
2525 customer_id : yupString ( ) . defined ( ) ,
2626 product_id : yupString ( ) . defined ( ) ,
2727 } ) . defined ( ) ,
28+ query : yupObject ( {
29+ subscription_id : yupString ( ) . optional ( ) ,
30+ } ) . default ( ( ) => ( { } ) ) . defined ( ) ,
2831 } ) ,
2932 response : yupObject ( {
3033 statusCode : yupNumber ( ) . oneOf ( [ 200 ] ) . defined ( ) ,
@@ -33,7 +36,7 @@ export const DELETE = createSmartRouteHandler({
3336 success : yupBoolean ( ) . oneOf ( [ true ] ) . defined ( ) ,
3437 } ) . defined ( ) ,
3538 } ) ,
36- handler : async ( { auth, params } , fullReq ) => {
39+ handler : async ( { auth, params, query } , fullReq ) => {
3740 if ( auth . type === "client" ) {
3841 const currentUser = fullReq . auth ?. user ;
3942 if ( ! currentUser ) {
@@ -59,49 +62,67 @@ export const DELETE = createSmartRouteHandler({
5962 }
6063
6164 const prisma = await getPrismaClientForTenancy ( auth . tenancy ) ;
62- const product = await ensureProductIdOrInlineProduct ( auth . tenancy , auth . type , params . product_id , undefined ) ;
63- if ( params . customer_type !== product . customerType ) {
64- throw new KnownErrors . ProductCustomerTypeDoesNotMatch (
65- params . product_id ,
66- params . customer_id ,
67- product . customerType ,
68- params . customer_type ,
69- ) ;
70- }
7165
72- const ownedProducts = await getOwnedProductsForCustomer ( {
73- prisma,
74- tenancy : auth . tenancy ,
75- customerType : params . customer_type ,
76- customerId : params . customer_id ,
77- } ) ;
78- const ownedProductsForProduct = ownedProducts . filter ( ( p ) => p . id === params . product_id ) ;
79- if ( ownedProductsForProduct . length === 0 ) {
80- throw new StatusError ( 400 , "Customer does not have this product." ) ;
81- }
82- if ( ownedProductsForProduct . some ( ( product ) => product . type === "one_time" ) ) {
83- throw new StatusError ( 400 , "This product is a one time purchase and cannot be canceled." ) ;
84- }
66+ let subscriptions ;
67+ if ( query . subscription_id ) {
68+ // Cancel by subscription DB ID (used for inline products that have no product_id)
69+ subscriptions = await prisma . subscription . findMany ( {
70+ where : {
71+ tenancyId : auth . tenancy . id ,
72+ id : query . subscription_id ,
73+ customerType : typedToUppercase ( params . customer_type ) ,
74+ customerId : params . customer_id ,
75+ status : { in : [ SubscriptionStatus . active , SubscriptionStatus . trialing ] } ,
76+ } ,
77+ } ) ;
78+ if ( subscriptions . length === 0 ) {
79+ throw new StatusError ( 400 , "No active subscription found with this ID for the given customer." ) ;
80+ }
81+ } else {
82+ const product = await ensureProductIdOrInlineProduct ( auth . tenancy , auth . type , params . product_id , undefined ) ;
83+ if ( params . customer_type !== product . customerType ) {
84+ throw new KnownErrors . ProductCustomerTypeDoesNotMatch (
85+ params . product_id ,
86+ params . customer_id ,
87+ product . customerType ,
88+ params . customer_type ,
89+ ) ;
90+ }
8591
86- const subscriptions = await prisma . subscription . findMany ( {
87- where : {
88- tenancyId : auth . tenancy . id ,
89- customerType : typedToUppercase ( params . customer_type ) ,
92+ const ownedProducts = await getOwnedProductsForCustomer ( {
93+ prisma ,
94+ tenancy : auth . tenancy ,
95+ customerType : params . customer_type ,
9096 customerId : params . customer_id ,
91- productId : params . product_id ,
92- status : { in : [ SubscriptionStatus . active , SubscriptionStatus . trialing ] } ,
93- } ,
94- } ) ;
95- if ( subscriptions . length === 0 ) {
96- captureError ( "cancel-subscription-missing" , new StackAssertionError (
97- "Owned subscription product missing active/trialing subscription record." ,
98- {
99- customerType : params . customer_type ,
97+ } ) ;
98+ const ownedProductsForProduct = ownedProducts . filter ( ( p ) => p . id === params . product_id ) ;
99+ if ( ownedProductsForProduct . length === 0 ) {
100+ throw new StatusError ( 400 , "Customer does not have this product." ) ;
101+ }
102+ if ( ownedProductsForProduct . some ( ( product ) => product . type === "one_time" ) ) {
103+ throw new StatusError ( 400 , "This product is a one time purchase and cannot be canceled." ) ;
104+ }
105+
106+ subscriptions = await prisma . subscription . findMany ( {
107+ where : {
108+ tenancyId : auth . tenancy . id ,
109+ customerType : typedToUppercase ( params . customer_type ) ,
100110 customerId : params . customer_id ,
101111 productId : params . product_id ,
112+ status : { in : [ SubscriptionStatus . active , SubscriptionStatus . trialing ] } ,
102113 } ,
103- ) ) ;
104- throw new StatusError ( 400 , "This subscription cannot be canceled." ) ;
114+ } ) ;
115+ if ( subscriptions . length === 0 ) {
116+ captureError ( "cancel-subscription-missing" , new StackAssertionError (
117+ "Owned subscription product missing active/trialing subscription record." ,
118+ {
119+ customerType : params . customer_type ,
120+ customerId : params . customer_id ,
121+ productId : params . product_id ,
122+ } ,
123+ ) ) ;
124+ throw new StatusError ( 400 , "This subscription cannot be canceled." ) ;
125+ }
105126 }
106127
107128 const hasStripeSubscription = subscriptions . some ( ( subscription ) => subscription . stripeSubscriptionId ) ;
0 commit comments