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

NestJS集成Stripe报价PDF下载报错:循环结构转JSON失败

问题描述

我正在将Stripe的发票与报价功能集成到NestJS API中,实现报价PDF下载功能时碰到了Node.js createWriteStream相关问题。

Stripe API提供的示例代码:

const {createWriteStream} = require("fs");

// 返回一个 stream.Readable
const pdf = await stripe.quotes
  .pdf("qt_0J1EnX589O8KAxCGEdmhZY3r");

await new Promise((resolve) => {
  pdf.pipe(createWriteStream("/tmp/tmp.pdf"));
  pdf.on("end", () => resolve());
})

我的实现代码:

@Get('/:id/pdf')
async downloadQuote(@Param('id') id: string) {
    const pdf = await this._stripeClient.quotes.pdf(id)

    await new Promise<void>((resolve) => {
        pdf.pipe(createWriteStream('/tmp/tmp.pdf'))
        pdf.on('end', () => resolve())
    })


    return {
        success: true,
        data: pdf
    }
}

运行后出现错误:

TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'IncomingMessage'
    |     property 'req' -> object with constructor 'ClientRequest'
    --- property 'res' closes the circle
解决方案

问题核心是你最后返回了pdf这个流对象。NestJS会自动将返回的对象序列化为JSON,但pdf是包含循环引用的可读流(比如IncomingMessage里的req和res互相引用),无法直接转成JSON。

另外你的逻辑存在冗余:Stripe返回的PDF流不需要先写入本地文件再返回,直接将流pipe到响应对象即可,前端能直接触发PDF下载,省去本地文件IO的开销。

推荐实现方式

import { Response } from '@nestjs/common';

@Get('/:id/pdf')
async downloadQuote(@Param('id') id: string, @Res() res: Response) {
    const pdf = await this._stripeClient.quotes.pdf(id);
    
    // 设置响应头,让浏览器识别这是可下载的PDF文件
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename="quote.pdf"');
    
    // 直接将PDF流导向响应对象
    pdf.pipe(res);
}

若需先写入本地文件再返回(不推荐)

如果业务必须先保存本地文件,写完后要返回文件内容而非原流对象:

import { createReadStream } from 'fs';
import { Response } from '@nestjs/common';

@Get('/:id/pdf')
async downloadQuote(@Param('id') id: string, @Res() res: Response) {
    const pdf = await this._stripeClient.quotes.pdf(id);
    const filePath = '/tmp/tmp.pdf';

    await new Promise<void>((resolve) => {
        pdf.pipe(createWriteStream(filePath)).on('finish', resolve);
    });

    // 将本地PDF文件返回给前端下载
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename="quote.pdf"');
    createReadStream(filePath).pipe(res);
}

关键注意点

  • 绝对不要返回Stripe的原始流对象给前端,NestJS无法序列化带循环引用的流结构
  • 直接pipe流到响应对象是最高效的实现方式,避免不必要的本地文件操作
  • 必须设置正确的Content-Type和Content-Disposition响应头,确保浏览器正确处理PDF文件

内容的提问来源于stack exchange,提问作者Jason McFarlane

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 12:15:25