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

GEWIS / sudosos-backend / 26049684448

18 May 2026 05:32PM UTC coverage: 87.049% (-0.7%) from 87.786%
26049684448

Pull #921

github

web-flow
Merge 68b8265c6 into 9633ffc7c
Pull Request #921: chore(deps): bump better-sqlite3 from 12.9.0 to 12.10.0

3897 of 4542 branches covered (85.8%)

Branch coverage included in aggregate %.

20106 of 23032 relevant lines covered (87.3%)

846.11 hits per line

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

22.89
/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);
×
47
    this.configureLogger(this.logger);
×
48
  }
×
49

50
  /**
1✔
51
   * @inheritDoc
52
   */
1✔
53
  public getPolicy(): Policy {
1✔
54
    return {
×
55
      '/public': {
×
56
        GET: {
×
57
          policy: async () => true,
×
58
          handler: this.getStripePublicKey.bind(this),
×
59
        },
×
60
      },
×
61
      '/webhook': {
×
62
        POST: {
×
63
          policy: async () => true,
×
64
          handler: this.handleWebhookEvent.bind(this),
×
65
        },
×
66
      },
×
67
    };
×
68
  }
×
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);
×
79
    const config = Config.get();
×
80

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

86
    res.json(response);
×
87
  }
×
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);
×
100
    const config = Config.get();
×
101
    const { rawBody } = req;
×
102
    const signature = req.headers['stripe-signature'];
×
103

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

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

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

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

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

141
    res.status(204).send();
×
142
  }
×
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