stripe / react-stripe-js

React components for Stripe.js and Stripe Elements
https://stripe.com/docs/stripe-js/react
MIT License
1.76k stars 270 forks source link

[BUG]: Requires 'unsafe-eval' in Content Security Policy directive #380

Closed MyHouseIsBig closed 1 year ago

MyHouseIsBig commented 1 year ago

What happened?

I followed the guide on stripe.com to correctly setup CSP but when I'm in production and trying to load the Stripe Elements form I get the following error in the browser console:

Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' https://js.stripe.com https://maps.googleapis.com".

The following snippets are the components that are part of the payment flow and are hosted on the same route:

Deposit.jsx

<div
      className="mb-180"
      style={{
        width: `${smallScreen ? "100%" : "80%"}`,
        marginLeft: `${smallScreen ? "0" : "16%"}`,
      }}
    >
      <div className={classes.root}>
        <div
          style={{
            display: "flex",
            flexDirection: "row",
            width: "100%",
            justifyContent: `${extraSmallScreen ? "center" : "flex-start"}`,
          }}
        >
          <Typography
            variant="h4"
            fontWeight="bold"
            color="rgb(237, 245, 255)"
            mb="2em"
            mt="1em"
          >
            Deposit
          </Typography>
        </div>
        <Grid container spacing={3}>
          <Grid item xs={12} sm={8}>
            <StyledTabs
              value={paymentMethod}
              onChange={handlePaymentMethodChange}
              aria-label="payment method selection tabs"
            >
              <StyledTab
                icon={<CurrencyBitcoin />}
                iconPosition="start"
                value="crypto"
                label="Cryptocurrency"
              />
              <StyledTab
                icon={<CreditCard />}
                iconPosition="start"
                value="cc"
                label="Credit Card"
              />
            </StyledTabs>
            <Paper sx={{ padding: "2em" }} variant="outlined">
              <div>
                <div
                  style={{
                    textAlign: "left",
                    marginBottom: `${extraSmallScreen ? "1.5em" : "0.3em"}`,
                  }}
                >
                  <h3 style={{ fontSize: 20, fontFamily: "montserrat" }}>
                    <span style={{ color: "#EDF5FF" }}>Amount</span>
                  </h3>
                </div>
                <StyledToggleButtonGroup
                  orientation="horizontal"
                  variant="scrollable"
                  value={view}
                  exclusive
                  onChange={handleChange}
                  fullWidth
                >
                  <StyledToggleButton
                    value="1000"
                    aria-label="1000"
                    style={{
                      marginLeft: 0,
                    }}
                  >
                    <Typography component="h5" variant="h6" color="#EDF5FF">
                      $1000
                    </Typography>
                    <p style={{ color: "#73798D", marginBottom: 0 }}>
                      Bonus +20%
                    </p>
                  </StyledToggleButton>
                  <StyledToggleButton value="100" aria-label="100">
                    <Typography component="h5" variant="h6" color="#EDF5FF">
                      $100
                    </Typography>
                    <p style={{ color: "#73798D", marginBottom: 0 }}>
                      Bonus +15%
                    </p>
                  </StyledToggleButton>
                  <StyledToggleButton value="50" aria-label="50">
                    <Typography component="h5" variant="h6" color="#EDF5FF">
                      $50
                    </Typography>
                    <p style={{ color: "#73798D", marginBottom: 0 }}>
                      Bonus +10%
                    </p>
                  </StyledToggleButton>
                  <StyledToggleButton value="10" aria-label="10">
                    <Typography component="h5" variant="h6" color="#EDF5FF">
                      $10
                    </Typography>
                    <p style={{ color: "#73798D", marginBottom: 0 }}>
                      Bonus +5%
                    </p>
                  </StyledToggleButton>
                </StyledToggleButtonGroup>
              </div>
            </Paper>
          </Grid>

          <Grid item xs={12} sm={4}>
            <Paper variant="outlined" style={{ padding: "2em" }}>
              <div style={{ textAlign: "left", marginBottom: "2.5em" }}>
                <h3 style={{ fontSize: 20, fontFamily: "montserrat" }}>
                  <span style={{ color: "#EDF5FF" }}>Summary</span>
                </h3>
              </div>
              <div
                style={{
                  marginBottom: "0.3em",
                  width: "100%",
                }}
              >
                <p
                  style={{
                    color: "#485876",
                    display: "flex",
                    justifyContent: "space-between",
                  }}
                >
                  <span style={{ color: "#dce4ed" }}>To Deposit</span>
                  <span
                    style={{
                      color: "#EDF5FF",
                      fontStyle: "bold",
                      fontFamily: "montserrat",
                      fontSize: 20,
                    }}
                  >
                    $
                    {prezzo === "10"
                      ? "10,5"
                      : prezzo === "50"
                      ? "55"
                      : prezzo === "100"
                      ? "115"
                      : prezzo === "1000"
                      ? "1200"
                      : "0"}
                  </span>
                </p>
                <Divider
                  variant="fullWidth"
                  style={{ borderWidth: 1, borderColor: "#283747" }}
                />
                <p
                  style={{
                    color: "#485876",
                    display: "flex",
                    justifyContent: "space-between",
                    marginTop: "1.5em",
                  }}
                >
                  <span style={{ color: "#dce4ed" }}>To Pay</span>
                  <span
                    style={{
                      color: "#EDF5FF",
                      fontStyle: "bold",
                      fontFamily: "montserrat",
                      fontSize: 20,
                    }}
                  >
                    ${prezzo}
                  </span>
                </p>
              </div>
              <div
                style={{
                  display: "flex",
                  flexDirection: "row",
                  alignItems: "center",
                  justifyContent: "center",
                }}
              >
                {paymentMethod === "crypto" ? (
                  <LoadingButton
                    loading={createChargeResponse.loading}
                    disabled={createChargeResponse.loading}
                    onClick={payBTC}
                    variant="contained"
                    size="large"
                    fullWidth
                    style={{
                      marginTop: "2em",
                      backgroundColor: "#D36381",
                      color: "#EDF5FF",
                      height: "3em",
                      fontSize: 22,
                      textTransform: "capitalize",
                    }}
                    loadingIndicator={
                      <CircularProgress sx={{ color: "#EDF5FF" }} size={30} />
                    }
                  >
                    <span
                      style={{
                        display: createChargeResponse.loading
                          ? "none"
                          : "block",
                      }}
                    >
                      Deposit
                    </span>
                  </LoadingButton>
                ) : !paymentSheetResponse.loading &&
                  paymentSheetResponse?.data?.paymentSheet &&
                  !updatePaymentIntentResponse.loading ? (
                  <Elements
                    stripe={stripePromise}
                    options={stripeElementsOptions}
                  >
                    <StripeForm />
                  </Elements>
                ) : (
                  <CircularProgress
                    sx={{ color: "#EDF5FF", marginTop: "2em" }}
                    size={30}
                  />
                )}
              </div>
            </Paper>
          </Grid>
        </Grid>
      </div>
    </div>

StripeForm.jsx

/* eslint-disable jsx-a11y/anchor-is-valid */
import {
  useStripe,
  useElements,
  PaymentElement,
} from "@stripe/react-stripe-js";
import { useState } from "react";
import { LoadingButton } from "@mui/lab";
import { CircularProgress } from "@mui/material";

export const StripeForm = (props) => {
  const stripe = useStripe();
  const elements = useElements();

  const [error, setError] = useState("");

  const handleStripeSubmit = async (event) => {
    // We don't want to let default form submission happen here,
    // which would refresh the page.
    event.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js has not yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }

    const result = await stripe.confirmPayment({
      //`Elements` instance that was used to create the Payment Element
      elements,
      confirmParams: {
        return_url: "https://domain.tld/success",
      },
    });

    if (result.error) {
      // Show error to your customer (for example, payment details incomplete)
      setError(result.error.message);
    } else {
      console.log("YEY");
      // Your customer will be redirected to your `return_url`. For some payment
      // methods like iDEAL, your customer will be redirected to an intermediate
      // site first to authorize the payment, then redirected to the `return_url`.
    }
  };
  return (
    <form
      onSubmit={handleStripeSubmit}
      style={{ marginTop: "3.5em", width: "100%" }}
    >
      <PaymentElement />
      {error && (
        <small
          style={{
            marginTop: "1em",
            color: "red",
            display: "flex",
            alignSelf: "center",
            justifyContent: "center",
          }}
        >
          {error}
        </small>
      )}
      <LoadingButton
        loading={!stripe}
        disabled={!stripe}
        type="submit"
        variant="contained"
        size="large"
        fullWidth
        style={{
          marginTop: "2em",
          backgroundColor: "#D36381",
          color: "#EDF5FF",
          height: "3em",
          fontSize: 22,
          textTransform: "capitalize",
        }}
        loadingIndicator={
          <CircularProgress sx={{ color: "#EDF5FF" }} size={30} />
        }
      >
        <span
          style={{
            display: !stripe ? "none" : "block",
          }}
        >
          Deposit
        </span>
      </LoadingButton>
    </form>
  );
};

Another error that I'm getting similar to the previous is the following:

Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src https://m.stripe.network 'sha256-e/Jqu4k9Gk1ZCWO6StAsfhF3i7qgIwfuitaD1g9DyvE='".

The difference is that this error is logged randomly sometimes even when I'm not on the /deposit route.

Environment

Brave Browser 1.47.186 Chromium: 109.0.5414.119 On Win10 21H1 19043.1237

Reproduction

No response

madhav-stripe commented 1 year ago

Hey @MyHouseIsBig, thanks for filing an issue. If you use a Content Security Policy it must be configured to allow Stripe.js: https://stripe.com/docs/security/guide#content-security-policy

Relevant issue: https://github.com/stripe/stripe-js/issues/244

Please feel free to reopen this issue if it is not resolved