@@ -19,7 +19,6 @@ import {
1919import {
2020 ApiTags ,
2121 ApiOperation ,
22- ApiResponse ,
2322 ApiBody ,
2423 ApiCreatedResponse ,
2524 ApiOkResponse ,
@@ -42,7 +41,7 @@ import { PaymentReportResponseDto } from './dtos/payment-report.dto';
4241@ApiTags ( 'Payments' )
4342@Controller ( 'payments' )
4443export class PaymentsController {
45- constructor ( private paymentService : PaymentService ) { }
44+ constructor ( private readonly paymentService : PaymentService ) { }
4645
4746 @Post ( 'initiate' )
4847 @UseFilters ( new AllExceptionsFilter ( APIID . PAYMENT_INITIATE ) )
@@ -193,29 +192,55 @@ export class PaymentsController {
193192 ) ;
194193 }
195194
196- @Get ( 'report/:contextId' )
195+ @Post ( 'report/:contextId' )
197196 @UseFilters ( new AllExceptionsFilter ( APIID . PAYMENT_STATUS ) )
198- @ApiOperation ( { summary : 'Get payment report by contextId with pagination' } )
199- @ApiQuery ( {
200- name : 'limit' ,
201- required : false ,
202- type : Number ,
203- description : 'Number of records to return (default: 50, max: 1000)' ,
204- example : 50 ,
205- } )
206- @ApiQuery ( {
207- name : 'offset' ,
208- required : false ,
209- type : Number ,
210- description : 'Number of records to skip (default: 0)' ,
211- example : 0 ,
212- } )
213- @ApiQuery ( {
214- name : 'search' ,
215- required : false ,
216- type : String ,
217- description : 'Free text search on firstName, lastName, and email (case-insensitive)' ,
218- example : 'john' ,
197+ @ApiOperation ( { summary : 'Get payment report by contextId with filters and pagination' } )
198+ @ApiBody ( {
199+ schema : {
200+ type : 'object' ,
201+ properties : {
202+ limit : {
203+ type : 'number' ,
204+ example : 50 ,
205+ description : 'Number of records to return (default: 50, max: 1000)' ,
206+ } ,
207+ offset : {
208+ type : 'number' ,
209+ example : 0 ,
210+ description : 'Number of records to skip (default: 0)' ,
211+ } ,
212+ search : {
213+ type : 'string' ,
214+ example : 'john' ,
215+ description :
216+ 'Free text search on firstName, lastName, and email (case-insensitive)' ,
217+ } ,
218+ status : {
219+ oneOf : [
220+ { type : 'string' , example : 'SUCCESS,FAILED' } ,
221+ {
222+ type : 'array' ,
223+ items : { type : 'string' , enum : [ 'SUCCESS' , 'INITIATED' , 'FAILED' ] } ,
224+ example : [ 'SUCCESS' , 'FAILED' ] ,
225+ } ,
226+ ] ,
227+ description :
228+ 'Filter by transaction status. Accepts SUCCESS, INITIATED, FAILED' ,
229+ } ,
230+ certificateGenerated : {
231+ oneOf : [
232+ { type : 'boolean' , example : true } ,
233+ { type : 'string' , example : 'true' } ,
234+ ] ,
235+ description : 'Filter by certificate generation status (true/false)' ,
236+ } ,
237+ couponCode : {
238+ type : 'string' ,
239+ example : 'WELCOME10' ,
240+ description : 'Filter by applied coupon code (case-insensitive exact match)' ,
241+ } ,
242+ } ,
243+ } ,
219244 } )
220245 @ApiOkResponse ( {
221246 description : 'Payment report retrieved successfully' ,
@@ -224,9 +249,12 @@ export class PaymentsController {
224249 @ApiBadRequestResponse ( { description : 'Invalid pagination parameters' } )
225250 async getPaymentReport (
226251 @Param ( 'contextId' , ParseUUIDPipe ) contextId : string ,
227- @Query ( 'limit' , new DefaultValuePipe ( 50 ) , ParseIntPipe ) limit : number ,
228- @Query ( 'offset' , new DefaultValuePipe ( 0 ) , ParseIntPipe ) offset : number ,
229- @Query ( 'search' ) search ?: string ,
252+ @Body ( 'limit' , new DefaultValuePipe ( 50 ) , ParseIntPipe ) limit : number ,
253+ @Body ( 'offset' , new DefaultValuePipe ( 0 ) , ParseIntPipe ) offset : number ,
254+ @Body ( 'search' ) search ?: string ,
255+ @Body ( 'status' ) status ?: string | string [ ] ,
256+ @Body ( 'certificateGenerated' ) certificateGenerated ?: string | boolean ,
257+ @Body ( 'couponCode' ) couponCode ?: string ,
230258 ) : Promise < PaymentReportResponseDto > {
231259 // Validate pagination parameters
232260 if ( limit < 1 || limit > 1000 ) {
@@ -237,12 +265,33 @@ export class PaymentsController {
237265 }
238266
239267 const searchTerm = typeof search === 'string' ? search . trim ( ) : undefined ;
268+ const normalizedStatuses = this . normalizeStatuses ( status ) ;
269+
270+ const allowedStatuses = new Set ( [ 'SUCCESS' , 'INITIATED' , 'FAILED' ] ) ;
271+ const invalidStatuses = normalizedStatuses . filter (
272+ ( value ) => ! allowedStatuses . has ( value ) ,
273+ ) ;
274+ if ( invalidStatuses . length > 0 ) {
275+ throw new BadRequestException (
276+ `Invalid status filter(s): ${ invalidStatuses . join ( ', ' ) } . Allowed values are SUCCESS, INITIATED, FAILED` ,
277+ ) ;
278+ }
279+
280+ const certificateGeneratedFilter =
281+ this . parseBooleanLikeQueryParam ( certificateGenerated ) ;
282+ const couponCodeFilter =
283+ typeof couponCode === 'string' && couponCode . trim ( ) . length > 0
284+ ? couponCode . trim ( )
285+ : undefined ;
240286
241287 const result = await this . paymentService . getPaymentReportByContextId (
242288 contextId ,
243289 limit ,
244290 offset ,
245291 searchTerm ,
292+ normalizedStatuses . length > 0 ? normalizedStatuses : undefined ,
293+ certificateGeneratedFilter ,
294+ couponCodeFilter ,
246295 ) ;
247296
248297 return {
@@ -253,4 +302,48 @@ export class PaymentsController {
253302 hasMore : offset + result . data . length < result . totalCount ,
254303 } ;
255304 }
305+
306+ private parseBooleanLikeQueryParam ( value ?: string | boolean ) : boolean | undefined {
307+ if ( typeof value === 'boolean' ) {
308+ return value ;
309+ }
310+
311+ if ( typeof value !== 'string' ) {
312+ return undefined ;
313+ }
314+
315+ const normalized = value . trim ( ) . toLowerCase ( ) ;
316+ if ( normalized === '' ) {
317+ return undefined ;
318+ }
319+
320+ if ( normalized === 'true' ) {
321+ return true ;
322+ }
323+
324+ if ( normalized === 'false' ) {
325+ return false ;
326+ }
327+
328+ throw new BadRequestException (
329+ 'certificateGenerated must be one of: true, false' ,
330+ ) ;
331+ }
332+
333+ private normalizeStatuses ( status ?: string | string [ ] ) : string [ ] {
334+ if ( Array . isArray ( status ) ) {
335+ return status
336+ . map ( ( value ) => value . trim ( ) . toUpperCase ( ) )
337+ . filter ( ( value ) => value . length > 0 ) ;
338+ }
339+
340+ if ( typeof status === 'string' ) {
341+ return status
342+ . split ( ',' )
343+ . map ( ( value ) => value . trim ( ) . toUpperCase ( ) )
344+ . filter ( ( value ) => value . length > 0 ) ;
345+ }
346+
347+ return [ ] ;
348+ }
256349}
0 commit comments