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

Express.js集成Stripe支付出现500内部服务器错误排查求助

问题排查与支付方式管理方案

核心问题

提交支付表单时触发500内部服务器错误,同时提示无法GET目标端点;目标是实现用户支付方式的添加、编辑、设置默认、删除功能,存储到paymentInfoDB.json临时数据库。

项目结构

根目录blackboard包含以下主目录:

  • backend:存放.env(Stripe测试密钥)、空数组格式的paymentInfoDB.json、server.js
  • src:存放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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 01:37:02