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

Stripe优惠券验证问题:无法传递有效券至服务端并应用折扣

问题解决:Stripe优惠券无法在结算时应用的问题

核心问题分析

你当前的代码存在两个关键问题:

  1. 请求体数据结构错误:你将validCoupon作为数组cartItems的附加属性添加,但JSON序列化数组时会忽略非索引属性,导致服务端根本接收不到validCoupon数据。
  2. 前端暴露Stripe密钥:直接在前端使用NEXT_PUBLIC_STRIPE_SECRET_KEY调用Stripe API会泄露密钥,这是严重的安全风险。

修复步骤

1. 客户端Cart.js修改

重构请求体,将购物车商品和优惠券信息封装成一个独立对象,而非给数组附加属性:

const handleCheckout = async () => {
    const stripeClient = await getStripe();
    // 构造包含购物车和优惠券的请求体
    const checkoutData = {
        cartItems: cartItems,
        validCoupon: validCoupon
    };
    const response = await fetch('/api/stripe', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(checkoutData)
    });

    if (!response.ok) {
        const error = await response.json();
        toast.error(error.message || '结算请求失败');
        return;
    }
    
    const data = await response.json();

    toast.loading('Redirecting...');
    stripeClient.redirectToCheckout({sessionId: data.id});
}

同时,将优惠券验证逻辑移到服务端(避免暴露密钥),新建pages/api/validate-coupon.js:

// pages/api/validate-coupon.js
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export default async function handler(req, res) {
    if (req.method !== 'POST') {
        res.setHeader('Allow', 'POST');
        return res.status(405).end('Method Not Allowed');
    }

    try {
        const { couponId } = req.body;
        const coupon = await stripe.coupons.retrieve(couponId);
        res.status(200).json({ valid: true, coupon });
    } catch (err) {
        res.status(400).json({ valid: false, message: '优惠券无效或不存在' });
    }
}

修改前端checkCoupon函数调用服务端验证:

const checkCoupon = async () => {
    if(isCouponAdded){
        toast.success("Coupon already added!");
        return;
    }
    try {
        const response = await fetch('/api/validate-coupon', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ couponId: coupontext })
        });
        const result = await response.json();
        if (result.valid) {
            toast.success("Coupon is valid!");
            setValidCoupon(result.coupon);
            setisCouponAdded(true);
        } else {
            toast.error(result.message);
        }
    } catch (err) {
        toast.error("Error validating coupon");
    }
}

2. 服务端stripe.js修改

正确解析请求体中的cartItems和validCoupon,避免遍历到优惠券数据:

import Stripe from 'stripe';

// 注意:不要用NEXT_PUBLIC_前缀,密钥不能暴露在前端
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export default async function handler(req, res) {
    if (req.method === 'POST') {
        try {
            const { cartItems, validCoupon } = req.body;
            
            const discounts = [];
            if(validCoupon && validCoupon.id) {
                discounts.push({coupon: validCoupon.id});
            }

            let shipping_options = [];
            let total_amount = 0;
            for (const item of cartItems) {
                total_amount += item.price * item.quantity;
            }

            if (total_amount >= 40) {
                shipping_options = [
                    { shipping_rate: 'shr_1MMq3iJFNgzNJo7s2nVvfATq' },
                ];
            } else {
                shipping_options = [
                    { shipping_rate: "shr_1MMq2YJFNgzNJo7sbwDNnqhz"}
                ];
            }

            const params = {
                submit_type: 'pay',
                mode: 'payment',
                discounts: discounts,
                payment_method_types: ['card'],
                billing_address_collection: 'auto',
                shipping_address_collection: {
                    allowed_countries: ["PT"],
                },
                shipping_options: shipping_options,
                line_items: cartItems.map((item) => {
                    const img = item.image[0].asset._ref;
                    const newImage = img.replace('image-', 'https://cdn.sanity.io/images/REDACTED/production/').replace('-png', '.png');
                    
                    return {
                        price_data: { 
                            currency: 'eur',
                            product_data: { 
                                name: item.name,
                                images: [newImage],
                            },
                            unit_amount: item.price * 100,
                        },
                        quantity: item.quantity
                    }
                }),
                success_url: `${req.headers.origin}/success`,
                cancel_url: `${req.headers.origin}/`,
            }

            const session = await stripe.checkout.sessions.create(params);
            res.status(200).json(session);
        } catch (err) {
            res.status(err.statusCode || 500).json(err.message);
        }
    } else {
        res.setHeader('Allow', 'POST');
        res.status(405).end('Method Not Allowed');
    }
}

额外注意事项

  • 确保STRIPE_SECRET_KEY没有用NEXT_PUBLIC_前缀,只在服务端环境变量中配置,绝对不能暴露给前端。
  • 优惠券验证必须在服务端完成,前端只负责输入和展示结果,避免恶意用户伪造优惠券信息。

内容的提问来源于stack exchange,提问作者André Gomes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 05:55:17