# All files / modules/billing/controllers stripe-webhook.controller.ts

0% Statements 0/31
0% Branches 0/3
0% Functions 0/2
0% Lines 0/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90

     import {
      Controller,
      Post,
      Headers,
      Req,
      Res,
      HttpCode,
      HttpStatus,
      Logger,
      RawBodyRequest,
    } from '@nestjs/common';
    import { ApiTags, ApiOperation, ApiHeader, ApiExcludeEndpoint } from '@nestjs/swagger';
    import { Request, Response } from 'express';
    import { StripeService } from '../services/stripe.service';
    import { StripeWebhookResponseDto } from '../dto/stripe-webhook.dto';

    @ApiTags('stripe-webhooks')
    @Controller('webhooks/stripe')
    export class StripeWebhookController {
      private readonly logger = new Logger(StripeWebhookController.name);

      constructor(private readonly stripeService: StripeService) {}

      @Post()
      @HttpCode(HttpStatus.OK)
      @ApiExcludeEndpoint()
      @ApiOperation({ summary: 'Handle Stripe webhook events' })
      @ApiHeader({
        name: 'stripe-signature',
        description: 'Stripe webhook signature',
        required: true,
      })
      async handleWebhook(
        @Req() req: RawBodyRequest<Request>,
        @Res() res: Response,
        @Headers('stripe-signature') signature: string,
      ): Promise<void> {
        const response: StripeWebhookResponseDto = {
          received: false,
        };

        Iif (!signature) {
          this.logger.warn('Webhook received without signature');
          res.status(HttpStatus.BAD_REQUEST).json({
            received: false,
            error: 'Missing stripe-signature header',
          });
          return;
        }

        const rawBody = req.rawBody;
        Iif (!rawBody) {
          this.logger.warn('Webhook received without raw body');
          res.status(HttpStatus.BAD_REQUEST).json({
            received: false,
            error: 'Missing raw body',
          });
          return;
        }

        try {
          const event = this.stripeService.constructWebhookEvent(rawBody, signature);

          this.logger.log(`Received webhook event: ${event.type} (${event.id})`);

          await this.stripeService.handleWebhookEvent(event);

          response.received = true;
          response.event_type = event.type;

          res.status(HttpStatus.OK).json(response);
        } catch (error) {
          this.logger.error(`Webhook error: ${error.message}`, error.stack);

          Iif (error.type === 'StripeSignatureVerificationError') {
            res.status(HttpStatus.BAD_REQUEST).json({
              received: false,
              error: 'Invalid signature',
            });
            return;
          }

          res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
            received: false,
            error: error.message,
          });
        }
      }
    }