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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 17:38:14