请求 Paypal 沙箱时跨源请求被阻止,但不同的本地主机端口工作正常

And*_*rex 5 paypal node.js cors express reactjs

我无法理解为什么在订购产品时尝试将应用程序中的用户发送到 sandbox.paypal.com 付款页面时收到“跨源请求被阻止”错误。我的应用程序(使用 React)的前端使用 localhost 端口 3000,而后端使用 localhost 端口 4000。执行 CRUD 操作时两个端口之间的通信按预期工作。但现在我将贝宝引入其中,当尝试订购产品时,应用程序不会进入沙箱贝宝页面。这是控制台中的错误消息:

\n\n
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 \xe2\x80\x98Access-Control-Allow-Origin\xe2\x80\x99 missing).\n
Run Code Online (Sandbox Code Playgroud)\n\n

我很困惑,因为两个本地主机之间的通信已经可以正常工作。Access-Control-Allow-Origin 不是默认为“*”吗?我在 Node.js 中使用“paypal-rest-sdk”

\n\n

前端逻辑:

\n\n
import React, { useState, useEffect } from "react";\nimport { Link } from "react-router-dom";\nimport { useID, useAdmin } from "../../context/auth";\nimport { Button, Accordion, Card, ListGroup, Form } from "react-bootstrap";\nimport axios from "axios";\n\nfunction ProductDetails(props) {\n    const [isError, setIsError] = useState(false);\n    const [id, setID] = useState("");\n    const [name, setName] = useState("");\n    const [description, setDescription] = useState("");\n    const [price, setPrice] = useState(0);\n    const [stock, setStock] = useState(0);\n    const [messages, setMessages] = useState([]);\n    const { IDTokens } = useID();\n    const { adminTokens } = useAdmin();\n\n    const Message = props => (\n        <Card>\n            <Card.Body>\n                <Card.Title>\n                    {props.message.owner.username === "(User removed)" ? (\n                        <span>{props.message.owner.username}</span>\n                    ) : (\n                        <Link to={`/users/${props.message.owner.id}`}>{props.message.owner.username}</Link>                        \n                    )}\n                </Card.Title>\n                <Card.Text>\n                    {props.message.content}\n                </Card.Text>\n                {IDTokens === props.message.owner.id || adminTokens ? (\n                    <span>\n                        <Link  to={`/products/list/${id}/messages/${props.message._id}/edit/`} style={{ marginRight: 10, marginLeft: 10 }}>\n                            Edit\n                        </Link>\n                        <Link to={`/products/list/${id}/messages/${props.message._id}/delete/`}>Delete</Link>\n                    </span>\n                ) : (\n                    <span></span>\n                )}\n            </Card.Body>\n        </Card>\n    )\n\n    useEffect(() => {\n        axios.get(`http://localhost:4000/products/${props.match.params.id}`)\n        .then(res => {\n            setID(res.data.product._id);\n            setName(res.data.product.name);\n            setDescription(res.data.product.description);\n            setPrice(res.data.product.price);\n            setStock(res.data.product.stock);\n            setMessages(res.data.messages);\n        }).catch(function(err) {\n            setIsError(true);\n        })\n    }, [props, IDTokens]);\n\n    function messageList() {\n        return messages.map(function(currentMessage, i){\n            return <Message message={currentMessage} key={i} />;\n        })\n    }\n\n    function postOrder() {\n        if(stock > 0) {\n            let productInfo = {\n                name,\n                description,\n                price\n            };\n\n            axios.post("http://localhost:4000/orders/pay",\n                productInfo\n            ).then(res => {\n                if(res.status === 200) {\n                    console.log(res.data);\n                } else {\n                    setIsError(true);\n                }\n            }).catch(err => {\n                setIsError(true);\n            });\n        }\n    }\n\n    return (\n        <div className="text-center">\n            <h2>Products Details</h2>\n            <Accordion>\n                <Card>\n                    <Card.Header>\n                        <Accordion.Toggle as={Button} variant="link" eventKey="0">\n                            Product Info\n                        </Accordion.Toggle>\n                    </Card.Header>\n                    <Accordion.Collapse eventKey="0">\n                        <Card.Body>\n                            <ListGroup>\n                                <ListGroup.Item>Name: {name}</ListGroup.Item>\n                                <ListGroup.Item>Description: {description}</ListGroup.Item>\n                                <ListGroup.Item>Price: ${price.toFixed(2)}</ListGroup.Item>\n                                <ListGroup.Item>Stock: {stock}</ListGroup.Item>\n                            </ListGroup>\n                            {stock > 0 ? (\n                                <Form>\n                                    <Button onClick={postOrder} variant="success">Order Now</Button>\n                                    { isError &&<p>Something went wrong with making the order!</p> }\n                                </Form>\n                            ) : (\n                                "Cannot order, currently out of stock"\n                            )}\n                        </Card.Body>\n                    </Accordion.Collapse>\n                </Card>\n            </Accordion>\n            <Link to={`/products/list/${id}/messages/new`}>Questions or Comments Regarding this Product? Leave a Message.</Link>\n            <h3>Messages: </h3>\n            {messages.length > 0 ? (\n                messageList()\n            ) : (\n                <p>(No messages)</p>\n            )}\n            { isError &&<p>Something went wrong with getting the product!</p> }\n        </div>\n    )\n}\n\nexport default ProductDetails;\n
Run Code Online (Sandbox Code Playgroud)\n\n

后端逻辑:

\n\n
const express = require("express"),\nrouter = express.Router(),\npaypal = require("paypal-rest-sdk"),\nOrder = require("../database/models/order");\n\nrouter.post("/pay", function(req, res) {\n    console.log("req.body: ", req.body);\n    const create_payment_json = {\n        "intent": "sale",\n        "payer": {\n            "payment_method": "paypal"\n        },\n        "redirect_urls": {\n            "return_url": "http://localhost:3000/orders/success",\n            "cancel_url": "http://localhost:3000/orders/cancel"\n        },\n        "transactions": [{\n            "item_list": {\n                "items": [{\n                    "name": req.body.name,\n                    "sku": "001",\n                    "price": req.body.price,\n                    "currency": "USD",\n                    "quantity": 1\n                }]\n            },\n            "amount": {\n                "currency": "USD",\n                "total": req.body.price\n            },\n            "description": req.body.description\n        }]\n    };\n\n    paypal.payment.create(create_payment_json, function (err, payment) {\n        if(err) {\n            console.log(err.message);\n        } else {\n            for(let i = 0; i < payment.links.length; i++){\n              if(payment.links[i].rel === \'approval_url\'){\n                res.redirect(payment.links[i].href);\n              }\n            }\n        }\n      });\n});\n\nrouter.get(\'/success\', (req, res) => {\n    console.log("req.query: ", req.query)\n    const payerId = req.query.PayerID;\n    const paymentId = req.query.paymentId;\n\n    const execute_payment_json = {\n      "payer_id": payerId,\n      "transactions": [{\n          "amount": {\n              "currency": "USD",\n              "total": req.query.total\n          }\n      }]\n    };\n\n    paypal.payment.execute(paymentId, execute_payment_json, function (error, payment) {\n        if(err) {\n            console.log(err.message);\n        } else {\n            let order = new Order(JSON.stringify(payment));\n            order.save().then(order => {\n                res.status(200).json(`Order added successfully! Created order details: ${order}`);\n            }).catch(err => {\n                console.log("Order create error: ", err.message);\n            });\n        }\n    });\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n

server.js(client_id 和 client_secret 已针对 stackoverflow 进行更改):

\n\n
const express = require("express"),\napp = express(),\nbodyParser = require("body-parser"),\nmongoose = require("mongoose"),\nsession = require("express-session"),\npassport = require("passport"),\nlocalStrategy = require("passport-local"),\npaypal = require("paypal-rest-sdk"),\ncors = require("cors"),\nPORT = 4000,\n\n// Require models\nUser = require("./database/models/user"),\n\n// Require routes\nproductRoutes = require("./routes/products"),\nmessageRoutes = require("./routes/messages"),\norderRoutes = require("./routes/orders"),\nuserRoutes = require("./routes/users");\n\napp.use(bodyParser.json());\napp.use(cors());\n\n// Paypal config\npaypal.configure({\n    "mode": "sandbox", //sandbox or live\n    "client_id": "...",\n    "client_secret": "..."\n});\n\n// Mongoose config\nmongoose.set(\'useUnifiedTopology\', true);\nmongoose.set(\'useFindAndModify\', false);\nmongoose.connect("mongodb://localhost/barnwood", { \n    useNewUrlParser: true,\n    useCreateIndex: true\n});\n\n// Sessions\napp.use(\n    session({\n        secret: "Birdhouses are cool.", // Secret can be any string\n        resave: false,\n        saveUninitialized: false\n    })\n);\napp.use(passport.initialize());\napp.use(passport.session());\npassport.use(new localStrategy(User.authenticate()));\npassport.serializeUser(User.serializeUser());\npassport.deserializeUser(User.deserializeUser());\n\n// Routes config\napp.use("/products", productRoutes);\napp.use("/messages", messageRoutes);\napp.use("/orders", orderRoutes);\napp.use("/users", userRoutes);\n\n// Start server\napp.listen(PORT, function() {\n    console.log("Server is running on Port: " + PORT);\n});\n
Run Code Online (Sandbox Code Playgroud)\n

rel*_*els 4

您要求后端服务器处理付款,成功后,您的后端服务器将执行res.redirect(payment.links[i].href);您的浏览器(axios 默认情况下 maxRedirects 设置为 5)将遵循重定向,然后读取位于不同域的响应您的,PayPal 拒绝您以跨源方式阅读。CORS 被阻止的原因。

对于这个问题你有两种解决方案:

  1. res.redirect(payment.links[i].href);您应该回复链接并让浏览器重定向,而不是这样做。

例如:

// replace res.redirect(payment.links[i].href); by
res.json({forwardLink: payment.links[i].href});
Run Code Online (Sandbox Code Playgroud)

然后在你的 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)
  })
Run Code Online (Sandbox Code Playgroud)

变成

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)
  })
Run Code Online (Sandbox Code Playgroud)
  1. 您还可以禁止 Axios 遵循重定向(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)
  })
Run Code Online (Sandbox Code Playgroud)