我不断收到付款失败:错误:网络响应不正常 makePayment CheckoutOrderSummary.jsx:113
当我尝试完成条纹付款订单时。我无法弄清楚我调用“/api/stripe/create-checkout-session”的方式有什么问题。我相信问题出在前端,但无法确认
这是我的前端,makePayment 启动 stripe api 调用:
import {
Flex,
Heading,
Stack,
Text,
useColorModeValue as mode,
Badge,
Box,
Divider,
Link,
Button,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { useEffect, useState, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link as ReactLink, useNavigate } from "react-router-dom";
import { createOrder, resetOrder } from "../redux/actions/orderActions";
import { resetCart } from "../redux/actions/cartActions";
import { PhoneIcon, EmailIcon, ChatIcon } from "@chakra-ui/icons";
import CheckoutItem from "./CheckoutItem";
import { loadStripe } from "@stripe/stripe-js";
const CheckoutOrderSummary = () => {
//chakra
const navigate = useNavigate();
const toast = useToast();
const colorMode = mode("gray.600", "gray.400");
//redux
const cartItems = useSelector((state) => state.cart);
const { cart, subtotal, expressShipping } = cartItems;
const user = useSelector((state) => state.user);
const { userInfo } = user;
const shippingInfo = useSelector((state) => state.order);
const { error, shippingAddress } = shippingInfo;
const dispatch = useDispatch();
const shipping = useCallback(() => {
let shippingCost =
expressShipping === "true" ? 14.99 : subtotal <= 1000 ? 4.99 : 0;
return parseFloat(shippingCost);
}, [expressShipping, subtotal]);
const hst = useCallback(() => {
const hstValue = ((Number(subtotal) + Number(shipping())) * 0.13).toFixed(
2
);
return parseFloat(hstValue); // Convert hstValue to a number
}, [expressShipping, subtotal, shipping]);
const total = useCallback(
() =>
Number(
shipping() === 0
? Number(subtotal) + hst()
: Number(subtotal) + shipping() + hst()
).toFixed(2),
[shipping, subtotal, hst]
);
const onPaymentSuccess = async (data) => {
dispatch(
createOrder({
orderItems: cart,
shippingAddress,
paymentMethod: data.paymentSource,
paymentDetails: data,
shippingPrice: shipping(),
totalPrice: total(),
userInfo,
})
);
dispatch(resetOrder());
dispatch(resetCart());
navigate("/orderSuccess");
};
const onPaymentError = () => {
toast({
description:
"Something went wrong during the payment process. Please try again or make sure that your PayPal account balance is enough for this purchase.",
status: "error",
duration: "600000",
isClosable: true,
});
};
const makePayment = async () => {
const stripe = await loadStripe(
REACT_APP_STRIPE_KEY
);
try {
const body = {
products: cart,
};
const response = await fetch("/api/stripe/create-checkout-session", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const session = await response.json();
if (session.error) {
throw new Error(session.error);
}
const result = await stripe.redirectToCheckout({
sessionId: session.id,
});
if (result.error) {
throw new Error(result.error.message);
}
} catch (error) {
console.error("Payment failed:", error);
toast({
title: "Payment error",
description: error.message,
status: "error",
duration: 9000,
isClosable: true,
});
}
};
return (
<Stack spacing="8px" rounded="xl" padding="0" width="full">
<Heading size="md">OrderSummary</Heading>
{cart.map((item) => (
<CheckoutItem key={item.id} cartItem={item} />
))}
<Stack spacing="6">
<Flex justify="space-between">
<Text fontWeight="medium" color={colorMode}>
Subtotal
</Text>
<Text fontWeight="medium" color={colorMode}>
${subtotal.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
</Text>
</Flex>
<Flex justify="space-between">
<Text fontWeight="medium" color={colorMode}>
Shipping
</Text>
<Text fontWeight="medium" color={colorMode}>
{shipping() === 0 ? (
<Badge
rounded="full"
px="2px"
fontSize="15px"
colorScheme="green"
>
Free
</Badge>
) : (
`$${shipping()}`
)}
</Text>
</Flex>
<Flex justify="space-between">
<Text fontWeight="medium" color={colorMode} textTransform="upperCase">
hst
</Text>
<Text>
$
{hst()
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
</Text>
</Flex>
<Flex justify="space-between">
<Text fontSize="lg" fontWeight="semibold">
Total
</Text>
<Text fontSize="xl" fontWeight="extrabold">
$
{total()
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
</Text>
</Flex>
</Stack>
<Stack>
<Button as={ReactLink} to="/cart">
Back to cart
</Button>
</Stack>
<Stack>
<Button onClick={makePayment}>Confirm</Button>
</Stack>
<Box alignItems="center">
<Text fontSize="sm">Questions or need help?</Text>
<Flex justifyContent="center">
<Flex align="center">
<ChatIcon />
<Text m="2px">Live Chat</Text>
</Flex>
<Flex align="center">
<PhoneIcon />
<Text m="2px">Call us</Text>
</Flex>
<Flex align="center">
<EmailIcon />
<Text m="2px">Email</Text>
</Flex>
</Flex>
</Box>
<Divider bg={mode("gray.400", "gray.300")} />
<Flex justifyContent="center" my="6px" fontWeight="semibold">
<Text>or</Text>
<Link as={ReactLink} to="/products" ml="1px">
Continue Shopping
</Link>
</Flex>
</Stack>
);
};
export default CheckoutOrderSummary;
这是我在后端的 stripeRoutes
import express from "express";
import dotenv from "dotenv";
import Stripe from "stripe";
dotenv.config();
const stripe = new Stripe("process.env.STRIPE_SECRET_KEY");
const stripeRoutes = express.Router();
stripeRoutes.get("/", (req, res) => {
res.send("Response from Get Route");
});
stripeRoutes.post("/create-checkout-session", async (req, res) => {
const { products } = req.body;
if (!products || !Array.isArray(products) || products.length === 0) {
return res.status(400).json({ error: "No products provided." });
}
try {
const lineItems = products.map((product) => ({
price_data: {
currency: "cad",
product_data: {
name: product.name,
images: product.image ? [product.image] : undefined, // Ensure this is a valid URL if uncommented
},
unit_amount: Math.round(product.price * 100), // Ensure price is in cents
},
quantity: product.qty,
}));
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: lineItems,
mode: "payment",
success_url: `${req.protocol}://${req.get("host")}/OrderSuccessScreen`, // Use dynamic host
cancel_url: `${req.protocol}://${req.get("host")}/OrderCancelScreen`, // Use dynamic host
});
res.json({ sessionId: session.id });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
export default stripeRoutes;
这是我的后端index.js
import dotenv from "dotenv";
import connectToDatabase from "./database.js";
import express from "express";
import path from "path";
//ROUTES
import productRoutes from "./routes/productRoutes.js";
import userRoutes from "./routes/userRoutes.js";
import brandRoutes from "./routes/brandRoutes.js";
import orderRoutes from "./routes/orderRoutes.js";
import stripeRoutes from "./routes/stripeRoutes.js";
dotenv.config();
connectToDatabase();
const app = express();
const port = process.env.PORT || 5000;
app.use(
express.json({
limit: "10mb",
})
);
//names after/api/ must match mongoDB collection names
app.use("/api/products", productRoutes);
app.use("/api/brands", brandRoutes);
app.use("/api/users", userRoutes);
app.use("/api/order", orderRoutes);
app.use("/api/stripe", stripeRoutes);
const __dirname = path.resolve();
app.use("/uploads", express.static(path.join(__dirname, "/uploads")));
if (process.env.NODE_ENV == "production") {
app.use(express.static(path.join(__dirname, "/client/build")));
app.get("*", (req, res) =>
res.sendFile(path.resolve(__dirname, "client", "build", "index.html"))
);
}
app.listen(port, () => {
console.log(`Server runs on port ${port}.`);
});
我也遇到了同样的问题。我做了一些调试并收到此错误消息:
Error creating Stripe portal session: You can’t create a portal session in test mode until you save your customer portal settings in test mode at https://dashboard.stripe.com/test/settings/billing/portal.
仔细检查您是否已正确设置门户,然后重试。