Skip to content

API: Implement Invoice PDF Generation with Puppeteer #112

@0xdevcollins

Description

@0xdevcollins

Severity: HIGH — Invoice PDFs Cannot Be Generated or Stored

Problem

The Invoice module has no service or controller — the Prisma model exists but no business logic is wired. PDF generation via Puppeteer and storage via R2 are completely absent.

Implementation Required

1. Invoice Service (core CRUD)

```typescript
// apps/api/src/modules/invoices/invoices.service.ts
@Injectable()
export class InvoicesService {
async create(merchantId: string, dto: CreateInvoiceDto): Promise
async findAll(merchantId: string, filters: InvoiceFiltersDto): Promise<PaginatedResult>
async findOne(merchantId: string, invoiceId: string): Promise
async update(merchantId: string, invoiceId: string, dto: UpdateInvoiceDto): Promise
async send(merchantId: string, invoiceId: string, dto: SendInvoiceDto): Promise
async generatePdf(invoiceId: string): Promise
async getPdfUrl(merchantId: string, invoiceId: string): Promise
}
```

2. PDF generation with Puppeteer

```typescript
async generatePdf(invoiceId: string): Promise {
const invoice = await this.prisma.invoice.findUnique({
where: { id: invoiceId },
include: { merchant: true },
});

const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();

// Render invoice HTML template
const html = this.renderInvoiceTemplate(invoice);
await page.setContent(html, { waitUntil: 'networkidle0' });

const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '40px', bottom: '40px', left: '48px', right: '48px' },
});

await browser.close();
return Buffer.from(pdf);
}
```

3. Invoice HTML template

Styled with:

  • Merchant logo + branding color
  • Invoice number, dates, due date
  • Line items table (description, qty, unit price, total)
  • Subtotal, tax, discount, total
  • Payment instructions / pay link button
  • "Powered by Useroutr" footer

4. Wire to R2 storage

After generating PDF:

  1. Upload to R2: invoices/{merchantId}/{invoiceId}.pdf
  2. Save signed URL to invoice.pdfUrl
  3. Return URL on GET /v1/invoices/:id/pdf

Dependencies

Acceptance Criteria

  • Invoice CRUD endpoints functional
  • PDF generates with real merchant data (no Lorem Ipsum)
  • PDF includes merchant branding (logo, color)
  • PDF stored in R2, signed URL saved to DB
  • GET /v1/invoices/:id/pdf returns signed download URL
  • Works with Puppeteer in Docker (no-sandbox flag)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions