react 和 paypal-rest-sdk:如何将数据从支付页面传回应用程序

Posted

技术标签:

【中文标题】react 和 paypal-rest-sdk:如何将数据从支付页面传回应用程序【英文标题】:react and paypal-rest-sdk: how to pass data from the payment page back to the application 【发布时间】:2020-07-16 06:08:44 【问题描述】:

我有一个 React 单页应用程序,它允许用户通过 paypal-rest-sdk 购买保存的产品(文档 here)。我希望这个工作的方式是让登录的用户能够订购有库存的产品。用户输入他们想要多少产品,然后包含产品和数量的订单被创建并存储为“已创建”。然后用户被重定向到他们确认或取消付款的 Paypal 购买页面。确认之前创建的订单再次找到并设置为“已完成”,并创建了一个用于存储 Paypal 付款信息的交易。目前我可以在沙盒环境下成功支付、下单、交易。

我现在的问题是,我用于往返 Paypal 付款页面的重定向导致我的应用忘记了登录用户,因此在成功付款后我无法获取用户的“已创建”订单。我必须在我的 postOrder() 函数中使用 window.location ,否则我会收到交叉引用错误,正如用户对我的previous question 的回答中所解释的那样。我需要记住用户,否则任何标有“已创建”的订单都可能被抓取并设置为“已完成”,不用说这会在我的应用程序中造成大量问题。我应该如何解决这个问题,还是应该尝试另一种方法?

前端逻辑:

import React,  useState, useEffect  from "react";
import  Link  from "react-router-dom";
import  useID, useUserName, useAdmin  from "../../context/auth";
import  Button, Accordion, Card, ListGroup, Form, Col  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 [amount, setAmount] = useState(0);
    const [messages, setMessages] = useState([]);
    const  IDTokens  = useID();
    const  userNameTokens  = useUserName();
    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(amount <= stock) 
            let productInfo = 
                id,
                name,
                description,
                price,
                amount
            ;

            let orderInfo = 
                owner: 
                    id: IDTokens,
                    username: userNameTokens
                ,
                status: "Created"
            ;

            axios.post("http://localhost:4000/orders/pay",
                productInfo, orderInfo
            ).then((res) => 
                if(res.status === 200) 
                    window.location = res.data.forwardLink;
                 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 && IDTokens ? (
                                <Form>
                                    <h2>Order This Product</h2>
                                    <Form.Row>
                                        <Form.Group as=Col sm= span: 6, offset: 3 >
                                            <Form.Label htmlFor="formAmount">Amount</Form.Label>
                                            <Form.Control
                                                        controlid="formAmount"
                                                        type="number"
                                                        min="1"
                                                        max="5"
                                                        step="1"
                                                        onChange=e => 
                                                            setAmount(e.target.value);
                                                        
                                                        placeholder="Enter amount to order (max 5)"
                                                        />
                                        </Form.Group>
                                    </Form.Row>
                                    <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 or user is not currently logged in"
                            )
                        </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"),
Transaction = require("../database/models/transaction");

// Order pay logic route
router.post("/pay", function(req, res) 
    const productInfo = req.body.productInfo;

    const create_payment_json = 
        "intent": "sale",
        "payer": 
            "payment_method": "paypal"
        ,
        "redirect_urls": 
            "return_url": "http://localhost:4000/orders/success",
            "cancel_url": "http://localhost:4000/orders/cancel"
        ,
        "transactions": [
            "item_list": 
                "items": [
                    "name": productInfo.name,
                    "sku": "001",
                    "price": productInfo.price,
                    "currency": "USD",
                    "quantity": productInfo.amount
                ]
            ,
            "amount": 
                "currency": "USD",
                "total": productInfo.price * productInfo.amount
            ,
            "description": productInfo.description
        ]
    ;

    paypal.payment.create(create_payment_json, function(err, payment) 
        if(err) 
            console.log(err.message);
         else 
            let order = new Order(req.body.orderInfo);
            order.product = 
                _id: productInfo.id,
                name: productInfo.name,
                description: productInfo.description,
                price: productInfo.price,
                amount: productInfo.amount
            
            order.total = productInfo.price * productInfo.amount;
            order.save().then(order => 
                console.log(`Order saved successfully! Created order details: $order`);
            ).catch(err => 
                console.log("Order create error: ", err.message);
            );

            for(let i = 0; i < payment.links.length; i++) 
              if(payment.links[i].rel === "approval_url") 
                  res.status(200).json(forwardLink: payment.links[i].href);
               else 
                  console.log("approval_url not found");
              
            
        
      );
);

router.get("/success", function(req, res) 
    const payerId = req.query.PayerID;
    const paymentId = req.query.paymentId;

    Order.findOneAndUpdate( "owner.id": req.user, status: "Created" ).then((order) => 
        order.status = "Completed";

        const execute_payment_json = 
            "payer_id": payerId,
            "transactions": [
                "amount": 
                    "currency": "USD",
                    "total": order.total
                
            ]
        ;

        paypal.payment.execute(paymentId, execute_payment_json, function(err, payment) 
            if(err) 
                console.log("paypal.payment.execute error: ", err.response);
             else 
                let transaction = new Transaction();
                transaction.details = JSON.stringify(payment);
                order.transaction = transaction;
                order.save().then(order => 
                    transaction.save().then(transaction => 
                        res.status(200).json(`Payment accepted! Order details: $order`);
                    ).catch(err => 
                        console.log("Transaction create error: ", err.message);
                    );
                ).catch(err => 
                    console.log("Order complete error: ", err.message);
                );
            
        );
    ).catch(err => 
        console.log("Payment error: ", err.message);
    );
);

module.exports = router;

订购型号:

const mongoose = require("mongoose"),
Schema = mongoose.Schema;

let orderSchema = new Schema(
    product: 
        _id: 
            type: String
        ,
        name: 
            type: String
        ,
        description: 
            type: String
        ,
        price: 
            type: Number
        ,
        amount: 
            type: Number
        
    ,
    total: 
        type: Number
    ,
    status:  // Created or Completed
        type: String
    ,
    transaction: 
        type: mongoose.Schema.Types.ObjectId,
        ref: "Transaction"
    ,
    owner: 
        id: type: mongoose.Schema.Types.ObjectId, ref: "User",
        username: String
    ,
    createdAt: 
        type: Date,
        default: Date.now()
    
);

module.exports = mongoose.model("Order", orderSchema);

交易模型:

const mongoose = require("mongoose"),
Schema = mongoose.Schema;

let transactionSchema = new Schema(
    details: [],
    createdAt: 
        type: Date,
        default: Date.now()
    
);

module.exports = mongoose.model("Transaction", transactionSchema);

【问题讨论】:

【参考方案1】:

为什么要重定向到 PayPal?

最好的解决方案是切换到这种前端模式,并且根本不重定向:https://developer.paypal.com/demo/checkout/#/pattern/server

那些 fetch "/demo/...." 需要替换为您服务器上的路由,以分别创建订单并返回 OrderID 和捕获 OrderID。

这里的好处是您的网站在后台保持打开状态,提供“上下文”结帐体验。


如果您想看看体验是什么样的,请尝试仅客户端 JS 的版本,它不依赖于那些 '/demo/...' 占位符的工作获取路由:https://developer.paypal.com/demo/checkout/#/pattern/client

【讨论】:

以上是关于react 和 paypal-rest-sdk:如何将数据从支付页面传回应用程序的主要内容,如果未能解决你的问题,请参考以下文章

paypal-rest-sdk 仍然是一种有效的方法还是我们应该切换到braintree?

Paypal rest sdk沙盒账户在0资金后仍然可以付款

如何用 redux 和 react-router 组织状态?

如何为 useReducer 键入 state 和 dispatch - typescript 和 react

如何为 react.cordova、ionic 和 android 设置 ssl 证书?

Express 和 React Routes 如何处理来自浏览器的初始 GET 请求?