NextJS 项目接入Stripe支付
💴

NextJS 项目接入Stripe支付

Date
Created
Dec 16, 2024 05:53 AM
Descrption
好记性不如烂笔头
Tags
记录
三方
server
notion image
 
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 会自动触发续费。
 
不得不感叹国外生态环境的健全,以及文档的完善,开发体验很爽,做产品的体验也很爽;希望国内更加优秀;