如何在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
相关产品推荐
相关产品推荐

