'Stripe webhook needs different json body than standard api requests

I can have a stripe webhook in my typescript backend, or I can have the stripe API - but not both...

I have router setup so any url with "/payment/" calls on my stripe handlers in the paymentActions.tsx

Here is the app.ts:

// app.ts

import bodyParser from "body-parser";
import express from "express";

import { paymentActions } from "../controllers/paymentActions";

const app = express();

app.use(express.static("public"));
app.use(express.urlencoded({ extended: true }));

// IF I DISABLE THIS app.use THE STRIPE WEBHOOK WORKS
app.use(
  bodyParser.json({
    // Because Stripe needs the raw body, we compute it but only when hitting the Stripe callback URL.
    verify: function (req, res, buf) {
      var url = req.url;
      //@ts-ignore
      req.rawBody = buf.toString();
    },
  })
);

// APIs
app.use("/payment", paymentActions);

export default app;

In paymentActions.tsx I have three endpoints. The two GET requests handle the stripe API to create and update payment intents. This works perfectly.

The third endpoint is a stripe webhook that returns a 400 response. If I disable the app.use(bodyparser...) in app.ts above, the webhook now works (response 200) but the other two GET requests throw errors because the json body contains "amount is NaN".

Any ideas how I can fix this? Thanks!

// paymentActions.tsx

import express from "express";
import * as stripe from "../clients/stripe";
    
export const paymentActions = express.Router();

// Get payment intent
paymentActions.get<{}>("/intent", async (req, res) => {
  const { amount } = req.body;

  try {
    const paymentIntent = await stripe.createPaymentIntent(amount);
    return res.status(200).json(paymentIntent);
  } catch (err) {
    res.status(400).send(`Webhook Error: ${err.message}`);
    return;
  }
});

// Update payment intent
paymentActions.get<{}>("/updatedIntent", async (req, res) => {
  const { intentId, amount, metadata } = req.body;

  try {
    const paymentIntent = await stripe.updatePaymentIntent(intentId, amount, metadata);
    return res.status(200).json(paymentIntent);
  } catch (err) {
    res.status(400).send(`Webhook Error: ${err.message}`);
    return;
  }
});


// Stripe payment webhook
paymentActions.post(
  "/webhook",
  express.raw({ type: "application/json" }),
  (request, response) => {
    const stripe = require("stripe");
    const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

    let event = request.body;
    // Only verify the event if you have an endpoint secret defined.
    // Otherwise use the basic event deserialized with JSON.parse
    if (endpointSecret) {
      // Get the signature sent by Stripe
      const signature = request.headers["stripe-signature"];
      try {
        event = stripe.webhooks.constructEvent(
          request.body,
          signature,
          endpointSecret
        );
      } catch (err) {
        console.log(`⚠️  Webhook signature verification failed.`, err.message);
        return response.sendStatus(400);
      }
    }

    // Handle the event
    switch (event.type) {
      case "payment_intent.succeeded":
        // Handle payment intent
        break;
      default:
        // Unexpected event type
    }

    // Return a 200 response to acknowledge receipt of the event
    response.send();
  }
);


Solution 1:[1]

Not sure if this is the ideal solution but specifying express.json() inside the args of each get request solved the issue.

i.e.

// Get payment intent
paymentActions.get<{}>("/intent", express.json(), async (req, res) => {
  const { amount } = req.body;

  try {
    const paymentIntent = await stripe.createPaymentIntent(amount);
    return res.status(200).json(paymentIntent);
  } catch (err) {
    res.status(400).send(`Webhook Error: ${err.message}`);
    return;
  }
});

Solution 2:[2]

Please try app.use(express.json()) and remove the bodyParse app.use.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 DarioS
Solution 2 makoto-stripe