条带在生产中无法工作,表示元素仍已安装

问题描述 投票:0回答:1

付款在开发模式下成功完成,但在生产中我收到此错误:v3:1未捕获(承诺中)集成错误:我们无法从指定元素检索数据。请确保您尝试使用的元素仍然已安装。

结账表格:

import { useState } from "react";
import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js";
import axios from "axios";
import { Link, useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import {
  selectLoadingState,
  startLoading,
  stopLoading,
  startCheckout,
  completeCheckout,
  cancelCheckout,
  cleanCart,
} from "../redux/user/userSlice";
import PropTypes from "prop-types";
import Loader from "react-loader-spinner";
import { useTranslation } from "react-i18next";

import "../styles/CheckoutForm.css";

const CARD_OPTIONS = {
  iconStyle: "solid",
  style: {
    base: {
      iconColor: "#c4f0ff",
      color: "#fff",
      fontWeight: 500,
      fontFamily: "Roboto, Open Sans, Segoe UI, sans-serif",
      fontSize: "16px",
      fontSmoothing: "antialiased",
      ":-webkit-autofill": { color: "#fce883" },
      "::placeholder": { color: "#87bbfd" },
    },
    invalid: {
      iconColor: "#ffc7ee",
      color: "#ffc7ee",
    },
  },
};

export default function CheckoutForm({ location }) {
  const [success, setSuccess] = useState(false);
  const dispatch = useDispatch();
  const loading = useSelector(selectLoadingState);
  const stripe = useStripe();
  const elements = useElements();
  const navigate = useNavigate();
  const { t } = useTranslation();

  const handleSubmit = async (e) => {
    e.preventDefault();
    const { amount, items } = location.state || {};
    dispatch(startLoading());

    console.log("Total Price:", amount);

    const { error, paymentMethod } = await stripe.createPaymentMethod({
      type: "card",
      card: elements.getElement(CardElement),
    });

    if (!error) {
      try {
        dispatch(startCheckout());
        const { id } = paymentMethod;
        const response = await axios.post(
          "https://pizzasi.vercel.app/payment",
          {
            amount: amount,
            items: items,
            id,
          }
        );
        console.log();

        if (response.data.success) {
          console.log("Successful payment");
          dispatch(stopLoading());
          setSuccess(true);
          dispatch(completeCheckout({ items, amount }));
          dispatch(cleanCart());
          navigate("/success");
        }
      } catch (error) {
        console.log("Error", error);
        dispatch(stopLoading());
        dispatch(cancelCheckout());
        navigate("/canceled");
      }
    } else {
      console.log(error.message);
    }
  };

  return (
    <div className="container">
      {!success && !loading ? (
        <form onSubmit={handleSubmit}>
          <fieldset className="form-group">
            <div className="form-row">
              <CardElement options={CARD_OPTIONS} />
            </div>
          </fieldset>
          <button className="payBtn" type="submit">
            {loading ? (
              <Loader type="Oval" color="#FFF" height={20} width={20} />
            ) : (
              "Pay"
            )}
          </button>
          <Link to="/cart">
            <button className="goBacktBtn">{t("goBack")}</button>
          </Link>
        </form>
      ) : (
        <div>
          <h2>{t("paymentSuccess")}</h2>
        </div>
      )}
    </div>
  );
}

CheckoutForm.propTypes = {
  location: PropTypes.shape({
    state: PropTypes.shape({
      amount: PropTypes.number.isRequired,
      items: PropTypes.array.isRequired,
    }).isRequired,
  }).isRequired,
};

条纹容器:

import { Elements } from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js";
import CheckoutForm from "../pages/CheckoutForm";
import { useSelector } from "react-redux";
import { selectUserCart } from "../redux/user/userSlice";
import { useLocation } from "react-router-dom";

const stripeKey =
  import.meta.env.MODE === "development"
    ? import.meta.env.VITE_STRIPE_KEY_TEST
    : import.meta.env.VITE_STRIPE_KEY_LIVE;

const stripeTestPromise = loadStripe(stripeKey);

export default function StripeContainer() {
  const location = useLocation();
  const cartItems = useSelector(selectUserCart);
  const amount = cartItems.reduce(
    (total, cartItem) => total + cartItem.price * cartItem.quantity,
    0
  );

  console.log("Total Price stripe:", amount);

  return (
    <Elements stripe={stripeTestPromise}>
      <CheckoutForm amount={amount} location={location} items={cartItems} />
    </Elements>
  );
}

应用程序:

import { Suspense } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
import AnimatedRoutes from "./components/AnimatedRoutes";
import Menu from "./pages/Menu";
import About from "./pages/About";
import Contact from "./pages/Contact";
import Cart from "./pages/Cart";
import Login from "./pages/Login";
import CashOnDeliveryForm from "./pages/CashOnDeliveryForm";
import StripeContainer from "./components/StripeContainer";
import NotFoundPage from "./pages/NotFoundPage";
import ProtectedRoute from "./utils/ProtectedRoute";
import SuccessPage from "./pages/SuccessPage";
import CanceledPage from "./pages/CanceledPage";

function App() {
  return (
    <Suspense fallback={null}>
      <Router>
        <Navbar />
        <Routes>
          <Route path="/" element={<AnimatedRoutes />} />
          <Route path="/menu" element={<Menu />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
          <Route path="/login" element={<Login />} />
          <Route element={<ProtectedRoute />}>
            <Route exact path="/cart" element={<Cart />} />
            <Route path="/stripe-checkout" element={<StripeContainer />} />
            <Route path="/cash-on-delivery" element={<CashOnDeliveryForm />} />
          </Route>
          <Route path="/success" element={<SuccessPage />} />
          <Route path="/canceled" element={<CanceledPage />} />
          <Route path="/*" replace element={<NotFoundPage />} />
        </Routes>
        <Footer />
      </Router>
    </Suspense>
  );
}

export default App;

用户切片:

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  user: null,
  cart: [],
  checkoutStatus: null,
  paymentDetails: null,
  loading: false,
};

export const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    startLoading: (state) => {
      state.loading = true;
    },
    stopLoading: (state) => {
      state.loading = false;
    },
    signIn: (state, action) => {
      state.user = action.payload;
    },
    signOut: (state) => {
      state.user = null;
    },
    updateUserCart: (state, action) => {
      state.cart = action.payload.cartItems;
    },
    addItem: (state, action) => {
      state.cart.push(action.payload);
    },
    removeItem: (state, action) => {
      const itemIdToRemove = action.payload;
      state.cart = state.cart.filter((item) => item.itemId !== itemIdToRemove);
    },
    cleanCart: (state) => {
      state.cart = [];
    },
    startCheckout: (state) => {
      state.checkoutStatus = "processing";
    },
    completeCheckout: (state, action) => {
      state.checkoutStatus = "completed";
      state.paymentDetails = action.payload;
    },
    cancelCheckout: (state) => {
      state.checkoutStatus = "canceled";
    },
  },
});

export const {
  startLoading,
  stopLoading,
  signIn,
  signOut: performingSignOut,
  updateUserCart,
  addItem,
  removeItem,
  cleanCart,
  startCheckout,
  completeCheckout,
  cancelCheckout,
} = userSlice.actions;

export const selectLoadingState = (state) => state.user.loading;

export const selectUser = (state) => state.user.user;
export const selectUserCart = (state) => state.user.cart;

export const selectCheckoutStatus = (state) => state.user?.checkoutStatus;
export const selectPaymentDetails = (state) => state.user?.paymentDetails;

export default userSlice.reducer;

服务器:

const express = require("express");
const bodyParser = require("body-parser");
const app = express();
require("dotenv").config();
const cors = require("cors");
const { body, validationResult } = require("express-validator");
const admin = require("firebase-admin");
const serviceAccount = require("./serviceAccountKey.js");

const stripeSecretKey =
  process.env.NODE_ENV === "development"
    ? process.env.STRIPE_SECRET_KEY_TEST
    : process.env.STRIPE_SECRET_KEY_LIVE;

const stripe = require("stripe")(stripeSecretKey);

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: process.env.DB_URL,
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use(cors());

const corsOptions = {
  origin: ["https://pizzasi.vercel.app/"],
  methods: "POST",
};

app.post("/payment", cors(corsOptions), async (req, res) => {
  console.log(req.body);
  let { amount, id, items } = req.body;

  try {
    if (isNaN(amount)) {
      console.error("Invalid amount:", amount);
      throw new Error("Invalid amount");
    }

    const paymentIntent = await stripe.paymentIntents.create({
      amount: amount * 100,
      currency: "EUR",
      description: "PizzaSi company",
      payment_method_types: ["card", "ideal"],
      payment_method: id,
      confirm: true,
      return_url: "https://pizzasi.vercel.app/success",
    });

    console.log("Payment", paymentIntent);
    res.status(200).json({
      message: "Payment successful",
      success: true,
      loading: false,
    });
  } catch (error) {
    console.log("Error", error);
    res.status(500).json({
      message: "Payment failed",
      success: false,
      loading: false,
    });
  }
});

app.post("/create-order", cors(), async (req, res) => {
  const orderDetails = req.body;

  [
    body("formData.name").isString(),
    body("formData.address").isString(),
    body("formData.phone").custom((value) => {
      const normalizePhone = value.replace(/[^0-9]/g, "");
      const phoneRegex = /^(\+)?(216)?(\d{8})£/;

      if (!phoneRegex.test(normalizePhone)) {
        throw new Error("Please enter a valid phone number");
      }

      return true;
    }),
  ],
    async (req, res) => {
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        return res.status(400).json({ errors: errors.array() });
      }
    };

  try {
    const db = admin.database();
    const ordersRef = db.ref("orders");

    const newOrderRef = ordersRef.push();
    await newOrderRef.set(orderDetails);

    res.status(200).json({
      message: "Order created successfully",
      success: true,
      loading: false,
    });
  } catch (error) {
    console.log("Error", error);
    res.status(500).json({
      message: "Failed to create order",
      success: false,
      loading: false,
    });
  }
});

app.listen(3001, () => {
  console.log("Server is running on port 3001");
});
javascript reactjs stripe-payments production
1个回答
0
投票

这几乎肯定是由于您的

loading
状态导致,一旦您点击“支付”并拨打
CardElement
handleSubmit
就会卸载。您需要确保
CardElement
保持安装状态,以便 Stripe.js 访问客户卡详细信息。

{!success && !loading ? (
    ...
          <CardElement options={CARD_OPTIONS} />
    ...
  ) : (
    <div>
      <h2>{t("paymentSuccess")}</h2>
    </div>
  )}

一旦

loading
设置为 true,卡片元素就会从此处的 DOM 中删除,这将导致像您遇到的错误。

© www.soinside.com 2019 - 2024. All rights reserved.