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

GEWIS / sudosos-backend / 18712298456

22 Oct 2025 09:50AM UTC coverage: 88.8% (-1.1%) from 89.868%
18712298456

Pull #612

github

web-flow
Merge 43836e960 into 33c42eb30
Pull Request #612: Fix UserDebtNotification not triggered when first product in multi-item transaction does not cause debt

1346 of 1628 branches covered (82.68%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 1 file covered. (100.0%)

79 existing lines in 4 files now uncovered.

7130 of 7917 relevant lines covered (90.06%)

1108.62 hits per line

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

20.45
/src/controller/stripe-webhook-controller.ts
1
/**
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2024  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
 */
20

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

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

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

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

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

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

UNCOV
79
    const response: StripePublicKeyResponse = {
×
80
      publicKey: process.env.STRIPE_PUBLIC_KEY,
81
      returnUrl: process.env.STRIPE_RETURN_URL,
82
    };
83

UNCOV
84
    res.json(response);
×
85
  }
86

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

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

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

UNCOV
115
    if ((webhookEvent.data.object as any)?.metadata?.service !== process.env.NAME) {
×
UNCOV
116
      this.logger.trace(`Event ignored, because it is not for service "${process.env.NAME}"`);
×
UNCOV
117
      res.status(204).send();
×
UNCOV
118
      return;
×
119
    }
120

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

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

UNCOV
138
    res.status(204).send();
×
139
  }
140
}
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