You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NodeJS中PayPal交易成功后更新MySQL数据库?

问题

我正在使用NodeJS,想了解如何在PayPal交易成功后更新MySQL数据库,不确定应该在app.js还是server.js中执行数据库插入语句?
我启动项目时运行server.js,视图view.ejs与app.js关联,希望交易成功后更新数据库并跳转到成功页面。

server.js 原代码

import "dotenv/config";
import express from "express";
import * as paypal from "./paypal-api.js";
import mysql from "mysql";

const app = express();
var mysqlConnection = mysql.createConnection({
  host: "localhost",
  user: "xxx",
  password: "xxx",
  database: "xxx"
});

mysqlConnection.connect((err)=> {
  if(!err)
  {
      console.log("Connected");
  }
  else
  {
      console.log("Connection Failed");
  }
})

const {PORT = 8888} = process.env;

app.set("view engine", "ejs");
app.use(express.static("public"));

// render checkout page with client id & unique client token
app.get("/", async (req, res) => {
  const clientId = process.env.CLIENT_ID;
  try {
    const clientToken = await paypal.generateClientToken();
    res.render("checkout", { clientId, clientToken });
  } catch (err) {
    res.status(500).send(err.message);
  }
});

// create order
app.post("/api/orders", async (req, res) => {
  try {
    const order = await paypal.createOrder();
    res.json(order);
    console.log(`test1`);
  } catch (err) {
    res.status(500).send(err.message);
  }
});

// capture payment
app.post("/api/orders/:orderID/capture", async (req, res) => {
  const { orderID } = req.params;
  try {
    const captureData = await paypal.capturePayment(orderID);
    res.json(captureData);
    console.log(`test2`);
  } catch (err) {
    res.status(500).send(err.message);
  }
});

app.listen(PORT, () => {
  console.log(`Server listening at http://localhost:${PORT}/`);
});

app.js 原代码

paypal
  .Buttons({
    // Sets up the transaction when a payment button is clicked
    createOrder: function () {
      return fetch("/api/orders", {
        method: "post",
        // use the "body" param to optionally pass additional order information
        // like product skus and quantities
        body: JSON.stringify({
          cart: [
            {
              sku: "<YOUR_PRODUCT_STOCK_KEEPING_UNIT>",
              quantity: "<YOUR_PRODUCT_QUANTITY>",
            },
          ],
        }),
      })
        .then((response) => response.json())
        .then((order) => order.id);
    },
    // Finalize the transaction after payer approval
    onApprove: function (data) {
      return fetch(`/api/orders/${data.orderID}/capture`, {
        method: "post",
      })
        .then((response) => response.json())
        .then((orderData) => {
          // Successful capture! For dev/demo purposes:
          console.log(
            "Capture result",
            orderData,
            JSON.stringify(orderData, null, 2)
          );
          const transaction = orderData.purchase_units[0].payments.captures[0];

          alert(`Transaction ${transaction.status}: ${transaction.id}

            See console for all available details
          `);
          location.replace(`/success.html?id=${transaction.id}`)
          // When ready to go live, remove the alert and show a success message within this page. For example:
          // var element = document.getElementById('paypal-button-container');
          // element.innerHTML = '<h3>Thank you for your payment!</h3>';
          // Or go to another URL:  actions.redirect('thank_you.html');
        });
    },
  })
  .render("#paypal-button-container");

// If this returns false or the card fields aren't visible, see Step #1.
if (paypal.HostedFields.isEligible()) {
  let orderId;

  // Renders card fields
  paypal.HostedFields.render({
    // Call your server to set up the transaction
    createOrder: () => {
      return fetch("/api/orders", {
        method: "post",
        // use the "body" param to optionally pass additional order information
        // like product skus and quantities
        body: JSON.stringify({
          cart: [
            {
              sku: "<YOUR_PRODUCT_STOCK_KEEPING_UNIT>",
              quantity: "<YOUR_PRODUCT_QUANTITY>",
            },
          ],
        }),
      })
        .then((res) => res.json())
        .then((orderData) => {
          orderId = orderData.id; // needed later to complete capture
          return orderData.id;
        });
    },
    styles: {
      ".valid": {
        color: "green",
      },
      ".invalid": {
        color: "red",
      },
    },
    fields: {
      number: {
        selector: "#card-number",
        placeholder: "4111 1111 1111 1111",
      },
      cvv: {
        selector: "#cvv",
        placeholder: "123",
      },
      expirationDate: {
        selector: "#expiration-date",
        placeholder: "MM/YY",
      },
    },
  }).then((cardFields) => {
    document.querySelector("#card-form").addEventListener("submit", (event) => {
      event.preventDefault();
      cardFields
        .submit({
          // Cardholder's first and last name
          cardholderName: document.getElementById("card-holder-name").value,
          // Billing Address
          billingAddress: {
            // Street address, line 1
            streetAddress: document.getElementById(
              "card-billing-address-street"
            ).value,
            // Street address, line 2 (Ex: Unit, Apartment, etc.)
            extendedAddress: document.getElementById(
              "card-billing-address-unit"
            ).value,
            // State
            region: document.getElementById("card-billing-address-state").value,
            // City
            locality: document.getElementById("card-billing-address-city")
              .value,
            // Postal Code
            postalCode: document.getElementById("card-billing-address-zip")
              .value,
            // Country Code
            countryCodeAlpha2: document.getElementById(
              "card-billing-address-country"
            ).value,
          },
        })
        .then(() => {
          fetch(`/api/orders/${orderId}/capture`, {
            method: "post",
          })
            .then((res) => res.json())
            .then((orderData) => {
              // Two cases to handle:
              //   (1) Other non-recoverable errors -> Show a failure message
              //   (2) Successful transaction -> Show confirmation or thank you
              // This example reads a v2/checkout/orders capture response, propagated from the server
              // You could use a different API or structure for your 'orderData'
              const errorDetail =
                Array.isArray(orderData.details) && orderData.details[0];
              if (errorDetail) {
                var msg = "Sorry, your transaction could not be processed.";
                if (errorDetail.description)
                  msg += "\n\n" + errorDetail.description;
                if (orderData.debug_id) msg += " (" + orderData.debug_id + ")";
                return alert(msg); // Show a failure message
              }
              // Show a success message or redirect
              alert("Transaction completed!");
            });
        })
        .catch((err) => {
          alert("Payment could not be captured! " + JSON.stringify(err));
        });
    });
  });
} else {
  // Hides card fields if the merchant isn't eligible
  document.querySelector("#card-form").style = "display: none";
}
解决方案

核心结论:必须在server.js中执行数据库更新操作

app.js是运行在浏览器端的前端代码,绝对不能在这里处理数据库操作——会直接暴露数据库的账号密码,导致严重的安全风险;而且前端代码可以被用户篡改,无法保证数据的真实性和完整性。

正确的做法是在服务器端的支付捕获接口中完成数据库更新,也就是server.js里的/api/orders/:orderID/capture路由,因为这个接口是PayPal支付成功后,服务器端确认交易有效性的关键节点,这里的数据是可信的。

具体修改步骤

  1. 在server.js的支付捕获接口中添加数据库插入逻辑
    把PayPal返回的交易信息(比如交易ID、状态、金额等)插入到MySQL数据库中,注意要处理异步操作,确保数据库操作完成后再给前端返回响应。

修改后的/api/orders/:orderID/capture路由代码如下:

// capture payment
app.post("/api/orders/:orderID/capture", async (req, res) => {
  const { orderID } = req.params;
  try {
    const captureData = await paypal.capturePayment(orderID);
    // 获取关键交易信息
    const transaction = captureData.purchase_units[0].payments.captures[0];
    const transactionId = transaction.id;
    const status = transaction.status;
    const amount = transaction.amount.value;
    const currency = transaction.amount.currency_code;

    // 插入数据库的SQL语句,根据你的表结构调整字段
    const insertQuery = `INSERT INTO transactions (order_id, transaction_id, status, amount, currency) VALUES (?, ?, ?, ?, ?)`;
    
    // 执行数据库插入,转为Promise处理异步
    await new Promise((resolve, reject) => {
      mysqlConnection.query(insertQuery, [orderID, transactionId, status, amount, currency], (err, results) => {
        if (err) {
          console.error("数据库插入失败:", err);
          reject(err);
        } else {
          console.log("交易记录已保存到数据库");
          resolve(results);
        }
      });
    });

    // 返回数据给前端,前端再跳转成功页面
    res.json(captureData);
    console.log(`test2`);
  } catch (err) {
    res.status(500).send(err.message);
  }
});
  1. 确保前端跳转逻辑正常
    当前app.js中的location.replace(/success.html?id=${transaction.id})逻辑可以保留,因为服务器端已经完成了数据库更新,前端收到响应后再跳转是安全的。

额外注意事项

  • 确保你的MySQL表结构已经创建,比如上面示例中的transactions表,需要包含对应的字段(order_id、transaction_id、status、amount、currency等)。
  • 建议使用连接池替代单一连接,避免高并发下的连接问题:可以用mysql.createPool()代替mysql.createConnection()。
  • 要处理数据库操作失败的情况,比如如果插入失败,应该返回错误给前端,避免用户以为支付成功但实际数据库没有记录。

内容的提问来源于stack exchange,提问作者Lawrence

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 10:57:25