ERPNext (collab.vycept.com) now picks up every shop invoice with its PDF, books it as a Wabenhain sales invoice and matches it to the Stripe payment via the PaymentIntent, so fees, revenue and the invoice document sit on one booking. - GET /api/erp/rechnungen?seit=<ISO>&limit=<1-200>: invoices oldest first, with order, payment intent, customer address (no phone, no notes), items and the VAT split per rate exactly as printed (lib/vat.ts), including the shipping and discount share per rate, so ERPNext books the same cents. - GET /api/erp/rechnungen/<RE-YYYY-NNNN>/pdf: the stored invoice PDF. - Bearer token ERP_EXPORT_TOKEN (min. 32 chars, timing-safe compare); without it the export is off. Failed attempts are rate limited. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
28 lines
1.2 KiB
TypeScript
28 lines
1.2 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { erpExportAuthorized, erpInvoicePdf, isInvoiceNumber } from "@/lib/erp-export";
|
|
import { rateLimit, getClientIp } from "@/lib/rate-limit";
|
|
|
|
const FAILED_ATTEMPTS_PER_WINDOW = 20;
|
|
const FAILED_WINDOW_MS = 15 * 60 * 1000;
|
|
|
|
/** Rechnungs-PDF fuer ERPNext (Anhang an der Ausgangsrechnung). Nur mit ERP_EXPORT_TOKEN. */
|
|
export async function GET(request: Request, { params }: { params: Promise<{ invoiceNumber: string }> }) {
|
|
if (!erpExportAuthorized(request.headers.get("authorization"))) {
|
|
const ip = await getClientIp();
|
|
const status = rateLimit(`erp-export:${ip}`, FAILED_ATTEMPTS_PER_WINDOW, FAILED_WINDOW_MS) ? 401 : 429;
|
|
return NextResponse.json({ error: "Nicht berechtigt." }, { status });
|
|
}
|
|
const { invoiceNumber } = await params;
|
|
const pdf = isInvoiceNumber(invoiceNumber) ? await erpInvoicePdf(invoiceNumber) : null;
|
|
if (!pdf) {
|
|
return NextResponse.json({ error: "Rechnung nicht gefunden." }, { status: 404 });
|
|
}
|
|
return new NextResponse(new Uint8Array(pdf), {
|
|
headers: {
|
|
"Content-Type": "application/pdf",
|
|
"Content-Disposition": `attachment; filename="${invoiceNumber}.pdf"`,
|
|
"Cache-Control": "no-store",
|
|
},
|
|
});
|
|
}
|