Express.js集成Stripe支付出现500内部服务器错误排查求助
问题排查与支付方式管理方案
核心问题
提交支付表单时触发500内部服务器错误,同时提示无法GET目标端点;目标是实现用户支付方式的添加、编辑、设置默认、删除功能,存储到paymentInfoDB.json临时数据库。
项目结构
根目录blackboard包含以下主目录:
backend:存放.env(Stripe测试密钥)、空数组格式的paymentInfoDB.json、server.jssrc:存放Payment组件、stripe.js、app.js、.env(Stripe可发布测试密钥)node_modules、public
相关代码
server.js代码
const express = require('express'); const bodyParser = require('body-parser'); const fs = require('fs'); const path = require('path'); const cors = require('cors'); const multer = require('multer'); const https = require('https'); require('dotenv').config({ path: path.resolve(__dirname, '.env') }); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const upload = multer({ dest: 'uploads/' }); const { v4: uuidv4 } = require('uuid'); const app = express(); const port = 5500; // SSL certificate paths const privateKey = fs.readFileSync(path.join(__dirname, '../key.pem'), 'utf8'); const certificate = fs.readFileSync(path.join(__dirname, '../cert.pem'), 'utf8'); const credentials = { key: privateKey, cert: certificate }; // Create HTTPS server const httpsServer = https.createServer(credentials, app); // Serve static files from 'public' directory app.use(express.static(path.join(__dirname, '../public'))); // Enable CORS for all routes app.use(cors()); // Middleware to parse JSON body app.use(bodyParser.json()); // A line that serves static files from the 'uploads' directory app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); // The file where my address data is currently stored const PAYMENT_INFO_FILE = path.join(__dirname, './paymentInfoDB.json') // GET route for all paymentMethods app.get('/paymentMethods', (req, res) => { fs.readFile(PAYMENT_INFO_FILE, 'utf8', (err, data) => { if (err) { console.error("Error reading file:", err); return res.status(500).send('An error occurred reading the DB.'); } // Send back the raw data since it's already a JSON string res.status(200).send(data); }); }); // POST route for new paymentInfo app.post('/paymentInfo', async (req, res) => { try { // Create a Stripe token const token = await stripe.tokens.create({ card: { number: req.body.cardNumber, exp_month: req.body.expMonth, exp_year: req.body.expYear, cvc: req.body.cvc, }, }); const newEntry = { id: uuidv4(), holderName: req.body.holderName, stripeToken: token.id, // Use the token id from the Stripe response }; // Read the current DB const data = await fs.promises.readFile(PAYMENT_INFO_FILE, 'utf8'); let DB = JSON.parse(data); // Add the new entry to the array DB.push(newEntry); // Write the updated DB back to the file await fs.promises.writeFile(PAYMENT_INFO_FILE, JSON.stringify(DB, null, 2), 'utf8'); res.status(201).json({ message: 'Payment method saved successfully.' }); } catch (error) { console.error("Error:", error); res.status(500).json({ error: 'An error occurred processing the payment info.' }); } }); // Start the server httpsServer.listen(port, () => { console.log(`HTTPS Server running on port ${port}`); });
Payment组件代码
import React, { useState } from 'react'; import { useStripe, useElements, CardNumberElement, CardExpiryElement, CardCvcElement } from '@stripe/react-stripe-js'; const Payment = () => { const stripe = useStripe(); const elements = useElements(); const [paymentLog, setPaymentLog] = useState([]); const [cardComplete, setCardComplete] = useState(false); const [indexToEdit, setIndexToEdit] = useState(null); const [expiryComplete, setExpiryComplete] = useState(false); const [cvcComplete, setCvcComplete] = useState(false); const [paymentData, setPaymentData] = useState({ holderName: '' }); const baseUrl = process.env.REACT_APP_SERVER_URL; const inputChange = (event) => { const { name, value } = event.target; setPaymentData({ ...paymentData, [name]: value }); }; const handleCardChange = (event) => { console.log('Card Number Complete:', event.complete); setCardComplete(event.complete); }; const handleExpiryChange = (event) => { console.log('Expiry Complete:', event.complete); setExpiryComplete(event.complete); }; const handleCvcChange = (event) => { console.log('CVC Complete:', event.complete); setCvcComplete(event.complete); }; const paymentInfoSubmit = async (event) => { event.preventDefault(); if (!stripe || !elements) { console.error('Stripe.js has not loaded yet.'); // Show an error message to your user return; } // Optionally add some UI indication that the process is happening // e.g., disabling the submit button, showing a spinner, etc. const cardNumberElement = elements.getElement(CardNumberElement); const cardExpiryElement = elements.getElement(CardExpiryElement); const cardCvcElement = elements.getElement(CardCvcElement); console.log('Card Details:', { cardComplete, expiryComplete, cvcComplete, holderName: paymentData.holderName, // Do not log the actual card number, expiry, or CVC for security reasons }); if (!cardComplete || !expiryComplete || !cvcComplete) { console.error('Please complete all card details.'); // Show an error message to your user return; } // Create a token using the card information const result = await stripe.createToken(cardNumberElement, { name: paymentData.holderName, // You can include additional card information here if needed }); // Re-enable the UI if you've disabled any elements if (result.error) { console.error('Error:', result.error.message); // Show an error message to your user return; } const stripeToken = result.token.id; // Get the token ID const paymentInfo = { holderName: paymentData.holderName, stripeToken: stripeToken // This should only be used on the server-side }; const url = indexToEdit == null ? `${baseUrl}/paymentInfo` : `${baseUrl}/paymentInfo/${paymentLog[indexToEdit].id}`; const method = indexToEdit == null ? 'POST' : 'PUT'; fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(paymentInfo), }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok: ' + response.statusText); } return response.json(); }) .then(data => { console.log('Success:', data); // Update your UI here to show the payment method was saved // For example, you could clear the form or show a success message setPaymentLog((currentLog) => { const updatedLog = [...currentLog]; if (indexToEdit != null) { updatedLog[indexToEdit] = data; } else { updatedLog.push(data); } return updatedLog; }); }) .catch((error) => { console.error('Error:', error); // Show an error message to your user }); }; return ( <form onSubmit={paymentInfoSubmit}> <input className='holderName' value={paymentData.holderName} name='holderName' type='text' onChange={inputChange} placeholder="Cardholder name"/> <CardNumberElement className='cardNumber' onChange={handleCardChange}/> <CardExpiryElement onChange={handleExpiryChange}/> <CardCvcElement onChange={handleCvcChange}/> <button type="submit" disabled={!stripe || !cardComplete || !expiryComplete || !cvcComplete}>Save Payment Method</button> </form> ); }; export default Payment;
stripe.js代码
import { loadStripe } from '@stripe/stripe-js'; const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY); export default stripePromise;
app.js代码
import React from 'react'; import { Elements } from '@stripe/react-stripe-js'; import stripePromise from './stripe'; import Payment from './components/payment'; function App() { return ( <Elements stripe={stripePromise}> <Payment /> </Elements> ); } export default App;
错误排查与修复
1. 核心错误原因
后端/paymentInfo的POST路由尝试从req.body获取cardNumber、expMonth等字段,但前端已经通过Stripe Elements生成了Token,只传了holderName和stripeToken,导致Stripe API调用失败,触发500错误。
2. 修复步骤
后端server.js的POST路由修改
删除重复创建Stripe Token的逻辑,直接使用前端传来的Token:
// POST route for new paymentInfo app.post('/paymentInfo', async (req, res) => { try { const newEntry = { id: uuidv4(), holderName: req.body.holderName, stripeToken: req.body.stripeToken, isDefault: false // 新增默认状态字段 }; const data = await fs.promises.readFile(PAYMENT_INFO_FILE, 'utf8'); let DB = JSON.parse(data); DB.push(newEntry); await fs.promises.writeFile(PAYMENT_INFO_FILE, JSON.stringify(DB, null, 2), 'utf8'); res.status(201).json(newEntry); // 返回完整条目给前端更新列表 } catch (error) { console.error("Error:", error); res.status(500).json({ error: '处理支付信息时出错' }); } });
补充编辑/删除/设置默认路由
// 编辑支付方式(支持修改姓名或设置默认) app.put('/paymentInfo/:id', async (req, res) => { try { const data = await fs.promises.readFile(PAYMENT_INFO_FILE, 'utf8'); let DB = JSON.parse(data); const index = DB.findIndex(item => item.id === req.params.id); if (index === -1) { return res.status(404).json({ error: '未找到该支付方式' }); } // 设置默认时,先将所有其他支付方式设为非默认 if (req.body.isDefault) { DB = DB.map(item => ({...item, isDefault: false})); } DB[index] = {...DB[index], ...req.body}; await fs.promises.writeFile(PAYMENT_INFO_FILE, JSON.stringify(DB, null, 2), 'utf8'); res.status(200).json(DB[index]); } catch (error) { console.error("Error:", error); res.status(500).json({ error: '更新支付信息时出错' }); } }); // 删除支付方式 app.delete('/paymentInfo/:id', async (req, res) => { try { const data = await fs.promises.readFile(PAYMENT_INFO_FILE, 'utf8'); let DB = JSON.parse(data); DB = DB.filter(item => item.id !== req.params.id); await fs.promises.writeFile(PAYMENT_INFO_FILE, JSON.stringify(DB, null, 2), 'utf8'); res.status(200).json({ message: '支付方式已删除' }); } catch (error) { console.error("Error:", error); res.status(500).json({ error: '删除支付信息时出错' }); } });
前端表单提交后清空状态
在paymentInfoSubmit的成功回调里添加:
// 清空表单 setPaymentData({holderName: ''}); setIndexToEdit(null);
3. 其他排查点
- 检查
REACT_APP_SERVER_URL是否正确设置为https://localhost:5500(后端用HTTPS) - 确保
paymentInfoDB.json有读写权限 - 测试时可暂时注释HTTPS代码,改用HTTP避免混合内容错误
替代方案(生产环境推荐)
用JSON文件存储仅适合测试,生产环境建议:
1. 使用轻量级数据库
比如SQLite或MongoDB Atlas免费版,避免文件读写的并发和权限问题。
2. 直接使用Stripe Customer管理支付方式
Stripe不建议长期存储Token,推荐将支付方式关联到Customer对象:
// 后端示例代码 app.post('/paymentInfo', async (req, res) => { try { // 创建Stripe Customer(可关联用户邮箱) const customer = await stripe.customers.create({ name: req.body.holderName, email: 'user@example.com' }); // 将Token转换为PaymentMethod并关联到Customer const paymentMethod = await stripe.paymentMethods.create({ type: 'card', card: {token: req.body.stripeToken}, }); await stripe.paymentMethods.attach(paymentMethod.id, {customer: customer.id}); // 存储Customer ID和PaymentMethod ID到自己的数据库 const newEntry = { id: uuidv4(), holderName: req.body.holderName, stripeCustomerId: customer.id, stripePaymentMethodId: paymentMethod.id, isDefault: true }; // 写入数据库... res.status(201).json(newEntry); } catch (error) { console.error("Error:", error); res.status(500).json({ error: '处理支付信息时出错' }); } });
内容的提问来源于stack exchange,提问作者Hatter7474
相关产品推荐
相关产品推荐

