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

GEWIS / sudosos-backend / 27712399675

17 Jun 2026 06:54PM UTC coverage: 91.227% (-0.8%) from 92.023%
27712399675

Pull #735

github

web-flow
Merge e1b5512d3 into 29d0df55c
Pull Request #735: feat: enhance product/banner image validation (#14)

4171 of 4767 branches covered (87.5%)

Branch coverage included in aggregate %.

65 of 72 new or added lines in 4 files covered. (90.28%)

227 existing lines in 4 files now uncovered.

21555 of 23433 relevant lines covered (91.99%)

837.52 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✔
UNCOV
46
    super(options);
×
UNCOV
47
    this.configureLogger(this.logger);
×
UNCOV
48
  }
×
49

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

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

UNCOV
86
    res.json(response);
×
UNCOV
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✔
UNCOV
99
    this.logger.trace('Receive Stripe webhook event with body', req.body);
×
UNCOV
100
    const config = Config.get();
×
UNCOV
101
    const { rawBody } = req;
×
UNCOV
102
    const signature = req.headers['stripe-signature'];
×
103

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

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

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

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

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

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