如何在NestJS中实现Stripe支付链接支付后用户数据存储流程
在NestJS中实现Stripe支付链接+支付后存手机号与验证码流程
1. 依赖安装与环境配置
首先安装所需依赖:
npm install @nestjs/config stripe typeorm pg # pg替换为你使用的数据库驱动
在.env文件中配置关键参数:
STRIPE_SECRET_KEY=sk_your_secret_key STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret DB_HOST=localhost DB_PORT=5432 DB_USERNAME=your_db_user DB_PASSWORD=your_db_pass DB_DATABASE=your_db_name
2. 注册Stripe模块
创建stripe.module.ts,全局注入Stripe客户端:
import { Module, Global } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Stripe from 'stripe'; @Global() @Module({ providers: [ { provide: Stripe, useFactory: (configService: ConfigService) => { return new Stripe(configService.get('STRIPE_SECRET_KEY'), { apiVersion: '2024-06-20', // 使用Stripe最新稳定API版本 }); }, inject: [ConfigService], }, ], exports: [Stripe], }) export class StripeModule {}
在app.module.ts中导入核心模块:
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { StripeModule } from './stripe/stripe.module'; import { PaymentModule } from './payment/payment.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PaymentRecord } from './payment/entities/payment-record.entity'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), TypeOrmModule.forRoot({ type: 'postgres', host: process.env.DB_HOST, port: +process.env.DB_PORT, username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, entities: [PaymentRecord], synchronize: true, // 生产环境请禁用,改用数据库迁移 }), StripeModule, PaymentModule, ], }) export class AppModule {}
3. 创建支付链接服务
创建payment.service.ts,实现支付链接创建逻辑,同时添加自定义字段收集手机号和验证码:
import { Injectable } from '@nestjs/common'; import Stripe from 'stripe'; @Injectable() export class PaymentService { constructor(private readonly stripe: Stripe) {} async createPaymentLink() { const paymentLink = await this.stripe.paymentLinks.create({ line_items: [ { price_data: { currency: 'cny', product_data: { name: '你的商品名称', }, unit_amount: 1000, // 单位为分,对应10元 }, quantity: 1, }, ], custom_fields: [ { key: 'phone_number', label: { type: 'custom', custom: '手机号', }, type: 'text', optional: false, // 设置为必填字段 }, { key: 'verification_code', label: { type: 'custom', custom: '验证码', }, type: 'text', optional: false, }, ], after_completion: { type: 'redirect', redirect: { url: 'https://your-domain.com/payment-success', // 支付成功后跳转地址 }, }, }); return { url: paymentLink.url }; } }
对应的控制器payment.controller.ts暴露创建支付链接的接口:
import { Controller, Post } from '@nestjs/common'; import { PaymentService } from './payment.service'; @Controller('payment') export class PaymentController { constructor(private readonly paymentService: PaymentService) {} @Post('create-link') async createPaymentLink() { return this.paymentService.createPaymentLink(); } }
4. 配置Stripe Webhook处理支付完成事件
创建webhook.controller.ts,监听Stripe支付完成事件,提取数据并存库:
import { Controller, Post, Body, Headers } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Stripe from 'stripe'; import { PaymentRecordService } from './payment-record.service'; @Controller('stripe-webhook') export class StripeWebhookController { constructor( private readonly stripe: Stripe, private readonly configService: ConfigService, private readonly paymentRecordService: PaymentRecordService, ) {} @Post() async handleWebhook( @Body() rawBody: Buffer, @Headers('stripe-signature') signature: string, ) { const webhookSecret = this.configService.get('STRIPE_WEBHOOK_SECRET'); let event: Stripe.Event; try { // 验证Stripe事件签名,防止伪造请求 event = this.stripe.webhooks.constructEvent( rawBody, signature, webhookSecret, ); } catch (err) { throw new Error(`Webhook验证失败: ${err.message}`); } // 处理支付完成事件 if (event.type === 'checkout.session.completed') { const session = event.data.object as Stripe.Checkout.Session; // 提取自定义字段数据 const phoneNumber = session.custom_fields?.find( field => field.key === 'phone_number', )?.text?.value; const verificationCode = session.custom_fields?.find( field => field.key === 'verification_code', )?.text?.value; if (phoneNumber && verificationCode) { // 存入数据库 await this.paymentRecordService.create({ phoneNumber, verificationCode, paymentIntentId: session.payment_intent as string, status: 'completed', }); } } return { received: true }; } }
在main.ts中配置允许接收原始请求体(Stripe Webhook需要原始Body验证签名):
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 为Webhook路由配置原始Body解析 app.use('/stripe-webhook', (req, res, next) => { if (req.method === 'POST') { req.rawBody = ''; req.on('data', chunk => { req.rawBody += chunk; }); req.on('end', next); } else { next(); } }); await app.listen(3000); } bootstrap();
5. 数据库实体与服务
创建支付记录实体payment-record.entity.ts:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class PaymentRecord { @PrimaryGeneratedColumn() id: number; @Column() phoneNumber: string; @Column() verificationCode: string; @Column() paymentIntentId: string; @Column() status: string; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) createdAt: Date; }
对应的服务payment-record.service.ts实现存库逻辑:
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { PaymentRecord } from './entities/payment-record.entity'; @Injectable() export class PaymentRecordService { constructor( @InjectRepository(PaymentRecord) private readonly paymentRecordRepo: Repository<PaymentRecord>, ) {} async create(data: Partial<PaymentRecord>) { const record = this.paymentRecordRepo.create(data); return this.paymentRecordRepo.save(record); } }
在payment.module.ts中导入相关模块:
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PaymentService } from './payment.service'; import { PaymentController } from './payment.controller'; import { StripeWebhookController } from './webhook.controller'; import { PaymentRecordService } from './payment-record.service'; import { PaymentRecord } from './entities/payment-record.entity'; @Module({ imports: [TypeOrmModule.forFeature([PaymentRecord])], controllers: [PaymentController, StripeWebhookController], providers: [PaymentService, PaymentRecordService], }) export class PaymentModule {}
关键注意事项
- Webhook签名验证:必须开启,Stripe控制台配置Webhook时需复制正确的签名密钥,防止恶意请求。
- 自定义字段一致性:创建支付链接时的
key需与Webhook提取时的key完全一致。 - 生产环境优化:关闭TypeOrm的
synchronize选项,改用数据库迁移脚本;确保Webhook地址可被Stripe公网访问(本地测试可使用ngrok代理)。
内容的提问来源于stack exchange,提问作者Aayushi Vaghasiya
相关产品推荐
相关产品推荐

