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

如何在Stripe Checkout支付后生成收据页面及解决3D Secure认证触发问题

解决方案

1. 3D Secure认证不触发&支付状态未完成修复

问题根因

你当前的实现逻辑跳过了3D Secure的交互步骤:服务端创建PaymentIntent时直接设置confirm: true,没有处理requires_action(需要身份认证)的状态,前端也没有调用Stripe的认证处理接口,导致需要3D Secure的交易直接被标记为未完成。

后端代码修改(端口4000服务)

const express = require("express")
const app = express()
require("dotenv").config()
const stripe = require("stripe")(process.env.STRIPE_SECRET_TEST)
const bodyParser = require("body-parser")
const cors = require("cors")

app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.use(cors())

// 修改接口,先创建PaymentIntent返回client_secret给前端
app.post("/create-payment-intent", cors(), async (req, res) => {
    let { amount } = req.body
    try {
        const paymentIntent = await stripe.paymentIntents.create({
            amount,
            currency: "USD",
            description: "pay",
            automatic_payment_methods: {enabled: true}
        })
        res.json({
            clientSecret: paymentIntent.client_secret
        })
    } catch (error) {
        console.log("Error", error)
        res.status(400).json({
            message: "Payment intent creation failed",
            success: false
        })
    }
})

// 支付成功后查询收据信息的可选接口
app.get("/payment-receipt/:paymentIntentId", cors(), async (req, res) => {
    try {
        const paymentIntent = await stripe.paymentIntents.retrieve(req.params.paymentIntentId)
        res.json({
            amount: paymentIntent.amount / 100,
            currency: paymentIntent.currency,
            paymentDate: new Date(paymentIntent.created * 1000),
            last4: paymentIntent.charges.data[0].payment_method_details.card.last4,
            receiptUrl: paymentIntent.charges.data[0].receipt_url,
            paymentId: paymentIntent.id
        })
    } catch (error) {
        res.status(400).json({message: "Receipt query failed"})
    }
})

app.listen(process.env.PORT || 4000, () => {
    console.log("Server is listening on port 4000")
})

前端结账表单代码修改

import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js"
import { CardNumberElement, CardExpiryElement, CardCvcElement } from "@stripe/react-stripe-js";
import {ArrowForward } from '@material-ui/icons';
import axios from "axios"
import React, { useState, useEffect } from 'react'
import './styles.css';
import { Typography, Button } from "@material-ui/core";
import { Alert } from "@material-ui/core";
import { LoadingButton } from "@material-ui/lab";
import {TextField, Paper, Grid } from "@material-ui/core";
import StripeInput from "../../src/components/StripeInput";

export default function PaymentForm() {
    const [success, setSuccess ] = useState(false)
    const [clientSecret, setClientSecret] = useState("")
    const [paymentInfo, setPaymentInfo] = useState({})
    const stripe = useStripe()
    const elements = useElements()
    const [loading, setLoading] = React.useState(false);

    // 页面加载时先请求创建PaymentIntent拿到client_secret
    useEffect(() => {
        const getClientSecret = async () => {
            const res = await axios.post("http://localhost:4000/create-payment-intent", {
                amount: 1000
            })
            setClientSecret(res.data.clientSecret)
        }
        getClientSecret()
    }, [])

    const handleSubmit = async (e) => {
        setLoading(true);
        e.preventDefault()
        if (!stripe || !elements) return

        // 调用Stripe的confirmCardPayment方法,会自动弹出3D Secure认证窗口
        const {error, paymentIntent} = await stripe.confirmCardPayment(clientSecret, {
            payment_method: {
                card: elements.getElement(CardNumberElement),
            }
        })

        if(!error) {
            if(paymentIntent.status === 'succeeded') {
                // 支付成功后查询收据信息
                const receiptRes = await axios.get(`http://localhost:4000/payment-receipt/${paymentIntent.id}`)
                setPaymentInfo(receiptRes.data)
                setSuccess(true)
            }
        } else {
            console.log(error.message)
        }
        setLoading(false)
    }

    const paperStyle={padding:'30px 20px', width: 600, margin: "20px auto"}
    const marginTop = { marginTop: 10} 
    return (
        <center>
        <>
        {!success ? 
        <div>
          <Grid>
          <Paper  elevation ={20} style = {paperStyle} >
          <Grid align='center'></Grid>
        <form>
           <Grid item xs={12} sm={6}>
            <TextField 
            label="Card Number"
            style={marginTop}
            fullWidth
            InputLabelProps={{
                shrink: true,
              }}
            InputProps={{
                inputComponent: StripeInput,
                inputProps: {
                     component: CardNumberElement
                },
           }}
            />
            </Grid>
            <Grid item xs={12} sm={6}>
            <TextField 
            fullWidth
            label="Expiry Date"
            style={marginTop}
            InputLabelProps={{
                shrink: true,
              }}
            InputProps={{
                inputComponent: StripeInput,
                inputProps: {
                    component: CardExpiryElement
                },
            }}
            />
            </Grid>
            <Grid item xs={12} sm={6}>
            <TextField 
            fullWidth
            label="CVV"
            style={marginTop}
            InputLabelProps={{
                shrink: true,
              }}
            InputProps={{
                inputComponent: StripeInput,
                inputProps: {
                    component: CardCvcElement
                },
            }}
            />
            </Grid>
            
            <Grid item xs={12} sm={6}>
            <LoadingButton 
            disabled={!stripe}
            loading ={loading}
            loadingPosition="start"
            startIcon={<ArrowForward />}
            fullWidth 
            variant="outlined"  
            onClick={handleSubmit}
            style={marginTop}>
                Pay
            </LoadingButton>
            </Grid>
        </form>
        </Paper>
       </Grid>
       </div>
        :
        // 收据页面实现
        <Grid>
          <Paper  elevation ={20} style = {paperStyle} >
          <Grid align='center'>
            <Typography variant="h5">支付收据</Typography>
          </Grid>
          <Alert severity="success" style={marginTop}>
           支付成功,感谢您使用我们的服务
          </Alert>
          <Grid container spacing={2} style={marginTop}>
            <Grid item xs={6}>
              <Typography variant="subtitle1">交易编号:</Typography>
            </Grid>
            <Grid item xs={6}>
              <Typography variant="body1">{paymentInfo.paymentId}</Typography>
            </Grid>
            <Grid item xs={6}>
              <Typography variant="subtitle1">支付金额:</Typography>
            </Grid>
            <Grid item xs={6}>
              <Typography variant="body1">{paymentInfo.amount} {paymentInfo.currency?.toUpperCase()}</Typography>
            </Grid>
            <Grid item xs={6}>
              <Typography variant="subtitle1">支付时间:</Typography>
            </Grid>
            <Grid item xs={6}>
              <Typography variant="body1">{paymentInfo.paymentDate?.toLocaleString()}</Typography>
            </Grid>
            <Grid item xs={6}>
              <Typography variant="subtitle1">支付卡号:</Typography>
            </Grid>
            <Grid item xs={6}>
              <Typography variant="body1">**** **** **** {paymentInfo.last4}</Typography>
            </Grid>
          </Grid>
          {/* 可选:直接跳转Stripe官方收据链接 */}
          <Button 
            variant="contained" 
            style={marginTop}
            onClick={() => window.open(paymentInfo.receiptUrl, '_blank')}
          >
            查看官方收据
          </Button>
       </Paper>
       </Grid>
        }
        </>
        </center>
    )
}

2. 测试验证说明

要测试3D Secure功能,可以使用Stripe官方测试卡4000 0025 0000 3155,输入任意未来到期时间和任意3位CVV,支付时会自动弹出3D Secure认证窗口,完成认证后支付状态会变为成功。

3. 收据页面实现逻辑说明

你可以选择两种实现方式:

  • 直接使用Stripe自动生成的官方收据,拿到receiptUrl后跳转即可,收据样式可在Stripe后台自定义
  • 自行开发收据页面,从PaymentIntent接口中提取你需要展示的所有交易信息渲染即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 14:09:02