• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GEWIS / sudosos-backend / 25753937432

12 May 2026 09:17AM UTC coverage: 88.117% (-1.0%) from 89.089%
25753937432

push

github

web-flow
chore(deps): fix missing dependencies for running docs:dev (#911)

3925 of 4574 branches covered (85.81%)

Branch coverage included in aggregate %.

20093 of 22683 relevant lines covered (88.58%)

1125.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.94
/src/controller/stripe-webhook-controller.ts
1
/**
1✔
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2026 Study association GEWIS
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as published
7
 *  by the Free Software Foundation, either version 3 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
 *
18
 *  @license
19
 */
1✔
20

21
/**
1✔
22
 * This is the module page of the stripe-webhook-controller.
23
 *
24
 * @module stripe
25
 */
1✔
26

27
import log4js, { Logger } from 'log4js';
28
import { Request, Response } from 'express';
29
import BaseController, { BaseControllerOptions } from './base-controller';
30
import Policy from './policy';
31
import StripeService from '../service/stripe-service';
32
import { RequestWithRawBody } from '../helpers/raw-body';
33
import { StripePublicKeyResponse } from './response/stripe-response';
34
import { AppDataSource } from '../database/database';
35
import Stripe from 'stripe';
36
import Config from '../config';
37

38
export default class StripeWebhookController extends BaseController {
1✔
39
  private logger: Logger = log4js.getLogger('StripeController');
1✔
40

41
  /**
1✔
42
   * Create a new stripe webhook controller instance
43
   * @param options
44
   */
1✔
45
  public constructor(options: BaseControllerOptions) {
1✔
46
    super(options);
1✔
47
    this.configureLogger(this.logger);
1✔
48
  }
1✔
49

50
  /**
1✔
51
   * @inheritDoc
52
   */
1✔
53
  public getPolicy(): Policy {
1✔
54
    return {
1✔
55
      '/public': {
1✔
56
        GET: {
1✔
57
          policy: async () => true,
1✔
58
          handler: this.getStripePublicKey.bind(this),
1✔
59
        },
1✔
60
      },
1✔
61
      '/webhook': {
1✔
62
        POST: {
1✔
63
          policy: async () => true,
1✔
64
          handler: this.handleWebhookEvent.bind(this),
1✔
65
        },
1✔
66
      },
1✔
67
    };
1✔
68
  }
1✔
69

70
  /**
1✔
71
   * GET /stripe/public
72
   * @operationId getStripePublicKey
73
   * @summary Get the Stripe public key
74
   * @tags stripe - Operations of the stripe controller
75
   * @returns {string} 200 - Public key
76
   */
1✔
77
  public async getStripePublicKey(req: Request, res: Response): Promise<void> {
1✔
78
    this.logger.trace('Get Stripe public key by IP', req.ip);
1✔
79
    const config = Config.get();
1✔
80

81
    const response: StripePublicKeyResponse = {
1✔
82
      publicKey: config.stripe.publicKey,
1✔
83
      returnUrl: config.stripe.returnUrl,
1✔
84
    };
1✔
85

86
    res.json(response);
1✔
87
  }
1✔
88

89
  /**
1✔
90
   * Webhook for Stripe event updates
91
   *
92
   * @route POST /stripe/webhook
93
   * @operationId webhook
94
   * @tags stripe - Operations of the stripe controller
95
   * @return 204 - Success
96
   * @return 400 - Event invalid error
97
   */
1✔
98
  public async handleWebhookEvent(req: RequestWithRawBody, res: Response): Promise<void> {
1✔
99
    this.logger.trace('Receive Stripe webhook event with body', req.body);
7✔
100
    const config = Config.get();
7✔
101
    const { rawBody } = req;
7✔
102
    const signature = req.headers['stripe-signature'];
7✔
103

104
    let webhookEvent: Stripe.Event;
7✔
105
    try {
7✔
106
      webhookEvent = await new StripeService().constructWebhookEvent(rawBody, signature);
7✔
107
    } catch (error) {
7✔
108
      res.status(400).json('Event could not be verified');
2✔
109
      return;
2✔
110
    }
2✔
111

112
    if (!webhookEvent.type.includes('payment_intent')) {
7✔
113
      this.logger.trace(`Event ignored, because it is type "${webhookEvent.type}"`);
1✔
114
      res.status(204).send();
1✔
115
      return;
1✔
116
    }
1✔
117

118
    if ((webhookEvent.data.object as any)?.metadata?.service !== config.app.name) {
7✔
119
      this.logger.trace(`Event ignored, because it is not for service "${config.app.name}"`);
2✔
120
      res.status(204).send();
2✔
121
      return;
2✔
122
    }
2✔
123

124
    const service = new StripeService();
2✔
125
    const { id } = (webhookEvent.data.object as Stripe.PaymentIntent);
2✔
126
    const paymentIntent = await service.getPaymentIntent(id);
2✔
127
    if (!paymentIntent) {
7✔
128
      this.logger.warn(`PaymentIntent with ID "${id}" not found.`);
1✔
129
      res.status(400).json(`PaymentIntent with ID "${id}" not found.`);
1✔
130
      return;
1✔
131
    }
1✔
132

133
    // NO await here, because we should execute the action asynchronously
1✔
134
    AppDataSource.manager.transaction(async (manager) => {
1✔
135
      const stripeService = new StripeService(manager);
1✔
136
      await stripeService.handleWebhookEvent(webhookEvent);
1✔
137
    }).catch((error) => {
1✔
138
      this.logger.error(error);
×
139
    });
×
140

141
    res.status(204).send();
1✔
142
  }
1✔
143
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc