我无法理解为什么在尝试订购产品时尝试将我的应用程序中的用户发送到sandbox.paypal.com付款页面时收到跨域请求阻止错误。我的应用程序的前端(带有React)使用localhost端口3000,而后端使用localhost端口4000。执行CRUD操作时,两个端口之间的通信按预期进行。但是现在,我将Paypal引入了组合,在尝试订购产品时,该应用程序不会转到沙盒Paypal页面。这是控制台中的错误消息:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-0Y510528E2479935T. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
我很困惑,因为两个本地主机之间已经可以进行通信。 Access-Control-Allow-Origin是否默认为“ *”?我在Node.js中使用“ paypal-rest-sdk”
前端逻辑:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { useID, useAdmin } from "../../context/auth";
import { Button, Accordion, Card, ListGroup, Form } from "react-bootstrap";
import axios from "axios";
function ProductDetails(props) {
const [isError, setIsError] = useState(false);
const [id, setID] = useState("");
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [price, setPrice] = useState(0);
const [stock, setStock] = useState(0);
const [messages, setMessages] = useState([]);
const { IDTokens } = useID();
const { adminTokens } = useAdmin();
const Message = props => (
<Card>
<Card.Body>
<Card.Title>
{props.message.owner.username === "(User removed)" ? (
<span>{props.message.owner.username}</span>
) : (
<Link to={`/users/${props.message.owner.id}`}>{props.message.owner.username}</Link>
)}
</Card.Title>
<Card.Text>
{props.message.content}
</Card.Text>
{IDTokens === props.message.owner.id || adminTokens ? (
<span>
<Link to={`/products/list/${id}/messages/${props.message._id}/edit/`} style={{ marginRight: 10, marginLeft: 10 }}>
Edit
</Link>
<Link to={`/products/list/${id}/messages/${props.message._id}/delete/`}>Delete</Link>
</span>
) : (
<span></span>
)}
</Card.Body>
</Card>
)
useEffect(() => {
axios.get(`http://localhost:4000/products/${props.match.params.id}`)
.then(res => {
setID(res.data.product._id);
setName(res.data.product.name);
setDescription(res.data.product.description);
setPrice(res.data.product.price);
setStock(res.data.product.stock);
setMessages(res.data.messages);
}).catch(function(err) {
setIsError(true);
})
}, [props, IDTokens]);
function messageList() {
return messages.map(function(currentMessage, i){
return <Message message={currentMessage} key={i} />;
})
}
function postOrder() {
if(stock > 0) {
let productInfo = {
name,
description,
price
};
axios.post("http://localhost:4000/orders/pay",
productInfo
).then(res => {
if(res.status === 200) {
console.log(res.data);
} else {
setIsError(true);
}
}).catch(err => {
setIsError(true);
});
}
}
return (
<div className="text-center">
<h2>Products Details</h2>
<Accordion>
<Card>
<Card.Header>
<Accordion.Toggle as={Button} variant="link" eventKey="0">
Product Info
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
<ListGroup>
<ListGroup.Item>Name: {name}</ListGroup.Item>
<ListGroup.Item>Description: {description}</ListGroup.Item>
<ListGroup.Item>Price: ${price.toFixed(2)}</ListGroup.Item>
<ListGroup.Item>Stock: {stock}</ListGroup.Item>
</ListGroup>
{stock > 0 ? (
<Form>
<Button onClick={postOrder} variant="success">Order Now</Button>
{ isError &&<p>Something went wrong with making the order!</p> }
</Form>
) : (
"Cannot order, currently out of stock"
)}
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
<Link to={`/products/list/${id}/messages/new`}>Questions or Comments Regarding this Product? Leave a Message.</Link>
<h3>Messages: </h3>
{messages.length > 0 ? (
messageList()
) : (
<p>(No messages)</p>
)}
{ isError &&<p>Something went wrong with getting the product!</p> }
</div>
)
}
export default ProductDetails;
后端逻辑:
const express = require("express"),
router = express.Router(),
paypal = require("paypal-rest-sdk"),
Order = require("../database/models/order");
router.post("/pay", function(req, res) {
console.log("req.body: ", req.body);
const create_payment_json = {
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"redirect_urls": {
"return_url": "http://localhost:3000/orders/success",
"cancel_url": "http://localhost:3000/orders/cancel"
},
"transactions": [{
"item_list": {
"items": [{
"name": req.body.name,
"sku": "001",
"price": req.body.price,
"currency": "USD",
"quantity": 1
}]
},
"amount": {
"currency": "USD",
"total": req.body.price
},
"description": req.body.description
}]
};
paypal.payment.create(create_payment_json, function (err, payment) {
if(err) {
console.log(err.message);
} else {
for(let i = 0; i < payment.links.length; i++){
if(payment.links[i].rel === 'approval_url'){
res.redirect(payment.links[i].href);
}
}
}
});
});
router.get('/success', (req, res) => {
console.log("req.query: ", req.query)
const payerId = req.query.PayerID;
const paymentId = req.query.paymentId;
const execute_payment_json = {
"payer_id": payerId,
"transactions": [{
"amount": {
"currency": "USD",
"total": req.query.total
}
}]
};
paypal.payment.execute(paymentId, execute_payment_json, function (error, payment) {
if(err) {
console.log(err.message);
} else {
let order = new Order(JSON.stringify(payment));
order.save().then(order => {
res.status(200).json(`Order added successfully! Created order details: ${order}`);
}).catch(err => {
console.log("Order create error: ", err.message);
});
}
});
});
server.js(client_id和client_secret更改为stackoverflow):
const express = require("express"),
app = express(),
bodyParser = require("body-parser"),
mongoose = require("mongoose"),
session = require("express-session"),
passport = require("passport"),
localStrategy = require("passport-local"),
paypal = require("paypal-rest-sdk"),
cors = require("cors"),
PORT = 4000,
// Require models
User = require("./database/models/user"),
// Require routes
productRoutes = require("./routes/products"),
messageRoutes = require("./routes/messages"),
orderRoutes = require("./routes/orders"),
userRoutes = require("./routes/users");
app.use(bodyParser.json());
app.use(cors());
// Paypal config
paypal.configure({
"mode": "sandbox", //sandbox or live
"client_id": "...",
"client_secret": "..."
});
// Mongoose config
mongoose.set('useUnifiedTopology', true);
mongoose.set('useFindAndModify', false);
mongoose.connect("mongodb://localhost/barnwood", {
useNewUrlParser: true,
useCreateIndex: true
});
// Sessions
app.use(
session({
secret: "Birdhouses are cool.", // Secret can be any string
resave: false,
saveUninitialized: false
})
);
app.use(passport.initialize());
app.use(passport.session());
passport.use(new localStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// Routes config
app.use("/products", productRoutes);
app.use("/messages", messageRoutes);
app.use("/orders", orderRoutes);
app.use("/users", userRoutes);
// Start server
app.listen(PORT, function() {
console.log("Server is running on Port: " + PORT);
});
[您要让后端服务器处理付款,并且后端服务器执行res.redirect(payment.links[i].href);
后,您的浏览器(默认将maxRedirects设置为5的浏览器)将遵循重定向,然后读取响应在与您的域不同的域上,贝宝(PayPal)拒绝您以跨来源的方式阅读。您被CORS阻止的原因。
您对此问题有两种解决方案:
res.redirect(payment.links[i].href);
,您应该回复链接并让浏览器重定向。例如:
// replace res.redirect(payment.links[i].href); by
res.json({forwardLink: payment.links[i].href});
然后在您的React应用程序中,您应该阅读响应并执行window.location = response.forwardLink
axios
.post('http://localhost:4000/orders/pay', productInfo)
.then((res) => {
if (res.status === 200) {
console.log(res.data)
} else {
setIsError(true)
}
})
.catch((err) => {
setIsError(true)
})
成为
axios
.post('http://localhost:4000/orders/pay', productInfo)
.then((res) => {
if (res.status === 200) {
console.log(res.data)
window.location = res.data.forwardLink
} else {
setIsError(true)
}
})
.catch((err) => {
setIsError(true)
})
maxRedirects: 0
进行一些参数调整),在这种情况下,您的响应代码将为302
(而不是200),并且您可以阅读与之对应的headers.location
参数然后可以执行window.location = headers.location
您的代码将变为:
axios({
maxRedirects: 0,
method: 'post',
url: 'http://localhost:4000/orders/pay',
data: productInfo,
})
.then((res) => {
if (res.status === 302) {
console.log(res.headers)
window.location = res.headers.location
} else {
setIsError(true)
}
})
.catch((err) => {
setIsError(true)
})