12月没怎么水文章,主要是最近在研究Vercel的AI-SDK的落地,每天都在学东西,所以最近没怎么输出,不过最近在接入Stripe for NextJS项目的时候觉得还是有必要记录一下,方便以后迅速接入。
Stripe账户创建和Key的建立就不讲了,这部分一般不需要开发人员去操作,我们一般会直接拿到这些密钥,没什么操作难度;
1.安装Stripe:
npm install stripe @stripe/stripe-js
stripe:是后端使用
@stripe/stripe-js:是前端使用
callout:这里的前提都是以你是NextJS的SSR前后端同构项目来进行的;
2.配置环境变量:
STRIPE_SECRET_KEY=sk_test_your_secret_key NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key
callout:NEXT_PUBLIC_前缀可以让你在前端访问到环境变量
3.配置后端路由:
在 pages/api/checkout_sessions.ts 文件中创建 API 路由,用于生成 Stripe Checkout 会话:
import { NextApiRequest, NextApiResponse } from 'next' import Stripe from 'stripe' const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', { apiVersion: '2024-04-10', }) export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === 'POST') { try { const { priceId } = req.body // 创建 Stripe Checkout 会话 const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [ { price: priceId, // 从 Stripe Dashboard 获取价格 ID quantity: 1, }, ], mode: 'payment', success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/cancel`, }) res.status(200).json({ sessionId: session.id }) } catch (error) { res.status(500).json({ error: 'Failed to create session' }) } } else { res.setHeader('Allow', 'POST') res.status(405).end('Method Not Allowed') } }
4.配置前端组件:
'use client' import { loadStripe } from '@stripe/stripe-js' import { useState } from 'react' const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!) export default function CheckoutButton({ priceId }: { priceId: string }) { const [loading, setLoading] = useState(false) const handleCheckout = async () => { setLoading(true) const stripe = await stripePromise const response = await fetch('/api/checkout_sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ priceId }), }) const { sessionId } = await response.json() // 跳转到 Stripe Checkout if (stripe) { await stripe.redirectToCheckout({ sessionId }) } setLoading(false) } return ( <button onClick={handleCheckout} disabled={loading} style={{ padding: '10px 20px', backgroundColor: 'blue', color: 'white', border: 'none' }} > {loading ? 'Loading...' : 'Pay Now'} </button> ) }
5.处理支付成功和取消页面
创建 pages/success.tsx 和 pages/cancel.tsx 页面,显示支付结果。
// success export default function Success() { return ( <div> <h1>Payment Successful!</h1> <p>Thank you for your purchase.</p> </div> ) }
//cancel export default function Cancel() { return ( <div> <h1>Payment Canceled</h1> <p>Your payment was canceled. Please try again later.</p> </div> ) }
6.测试号
卡号:4242 4242 4242 4242 有效期:任意未来日期 CVC:任意 3 位数
以下是一些其他的测试账号
卡号 | 效果 |
4242 4242 4242 4242 | 支付成功 |
4000 0000 0000 9995 | 需要 3D Secure 验证 |
4000 0000 0000 0002 | 被拒绝的支付 |
4000 0000 0000 0119 | 卡号过期 |
5105 1051 0510 5100 | 万事达卡测试卡(支付成功) |
6011 0000 0000 0004 | Discover 测试卡(支付成功) |
7.订阅续费的处理
如果对于普通支付,以上的处理以及完整了,如果对于订阅类型,还需要做额外的处理
1. 自动扣款(根据您设定的周期:月、年等)。 2. 失败重试(如扣款失败时,Stripe 提供重试策略)。 3. 发送事件通知(Webhook 支持处理账单成功、失败、取消等状态)。
8.订阅-后端创建订阅会话
import { NextApiRequest, NextApiResponse } from 'next' import Stripe from 'stripe' const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', { apiVersion: '2024-04-10', }) export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === 'POST') { try { const { priceId, customerEmail } = req.body // 创建 Stripe Checkout 订阅会话 const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], mode: 'subscription', // 设置为订阅模式 customer_email: customerEmail, // 用户邮箱(自动创建 Stripe Customer) line_items: [ { price: priceId, // Stripe 价格 ID quantity: 1, }, ], success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/cancel`, }) res.status(200).json({ sessionId: session.id }) } catch (error) { console.error('Error creating subscription session:', error) res.status(500).json({ error: 'Failed to create subscription' }) } } else { res.setHeader('Allow', 'POST') res.status(405).end('Method Not Allowed') } }
9.订阅-前端发起订阅操作
import { loadStripe } from '@stripe/stripe-js' const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!) async function handleSubscription(priceId: string, customerEmail: string) { const response = await fetch('/api/create-subscription', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ priceId, customerEmail }), }) const { sessionId } = await response.json() const stripe = await stripePromise if (stripe) { await stripe.redirectToCheckout({ sessionId }) } } export default function SubscriptionButton() { return ( <button onClick={() => handleSubscription('price_1Hh1Y2LZCwZ0JXqSmNABCd34', 'test@example.com')} > Subscribe Now </button> ) }
10.处理 Webhook 事件
常见事件:
• invoice.payment_succeeded:订阅付款成功。
• invoice.payment_failed:订阅付款失败。
• customer.subscription.deleted:订阅被取消。
import { NextApiRequest, NextApiResponse } from 'next' import Stripe from 'stripe' const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', { apiVersion: '2024-04-10', }) export default async function handler(req: NextApiRequest, res: NextApiResponse) { const sig = req.headers['stripe-signature'] as string const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET || '' let event: Stripe.Event try { event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret) } catch (err) { res.status(400).send(`Webhook Error: ${(err as Error).message}`) return } // 处理不同事件 switch (event.type) { case 'invoice.payment_succeeded': console.log('Subscription payment succeeded') break case 'invoice.payment_failed': console.log('Subscription payment failed') break case 'customer.subscription.deleted': console.log('Subscription canceled') break default: console.log(`Unhandled event type ${event.type}`) } res.json({ received: true }) }
11.测试订阅续费
1. Stripe 测试卡号:
成功支付:4242 4242 4242 4242
失败支付:4000 0000 0000 0341(触发付款失败事件)。
2. 设置测试 Webhook:
在 Stripe Dashboard 的 Developers > Webhooks 中配置测试 Webhook URL。
3. 触发续费事件:
订阅周期结束时(如 1 分钟/1 天),Stripe 会自动触发续费。
不得不感叹国外生态环境的健全,以及文档的完善,开发体验很爽,做产品的体验也很爽;希望国内更加优秀;