Show diff
diff --git a/src/payout-export-schedule.test.ts b/src/payout-export-schedule.test.ts
new file mode 100644
index 0000000..b52cbfb
--- /dev/null
+++ b/src/payout-export-schedule.test.ts
@@ -0,0 +1,76 @@
+import { scheduleExport } from './payout-export';
+import { Payment } from './types';
+
+jest.mock('fast-pay-utils', () => ({
+ processPayment: (details: unknown) => details,
+}));
+
+const base: Payment = {
+ id: 'pay_001',
+ merchantId: 'm_demo_001',
+ amountMinor: 2000,
+ currency: 'EUR',
+ pan: '4111111111111111',
+ status: 'settled',
+ createdAt: '2026-01-01T00:00:00.000Z',
+};
+
+beforeEach(() => {
+ jest.useFakeTimers();
+});
+
+afterEach(() => {
+ jest.useRealTimers();
+});
+
+test('scheduleExport calls sink after one interval', () => {
+ const sink = jest.fn();
+ const cancel = scheduleExport(1000, [base], sink);
+
+ expect(sink).not.toHaveBeenCalled();
+ jest.advanceTimersByTime(1000);
+ expect(sink).toHaveBeenCalledTimes(1);
+
+ const csv: string = sink.mock.calls[0][0];
+ expect(csv).toContain('id,merchantId,amount,currency,maskedPan,status');
+ expect(csv).toContain('pay_001');
+
+ cancel();
+});
+
+test('scheduleExport calls sink on every interval tick', () => {
+ const sink = jest.fn();
+ const cancel = scheduleExport(500, [base], sink);
+
+ jest.advanceTimersByTime(1500);
+ expect(sink).toHaveBeenCalledTimes(3);
+
+ cancel();
+});
+
+test('cancel stops further sink calls', () => {
+ const sink = jest.fn();
+ const cancel = scheduleExport(1000, [base], sink);
+
+ jest.advanceTimersByTime(1000);
+ expect(sink).toHaveBeenCalledTimes(1);
+
+ cancel();
+ jest.advanceTimersByTime(3000);
+ expect(sink).toHaveBeenCalledTimes(1); // still 1 — interval was cleared
+});
+
+test('scheduleExport passes correct CSV to sink', () => {
+ const sink = jest.fn();
+ const cancel = scheduleExport(1000, [base], sink);
+
+ jest.advanceTimersByTime(1000);
+
+ const csv: string = sink.mock.calls[0][0];
+ const lines = csv.split('\n');
+ expect(lines[0]).toBe('id,merchantId,amount,currency,maskedPan,status');
+ expect(lines[1]).toContain('411111******1111');
+ expect(lines[1]).toContain('EUR 20.00');
+
+ cancel();
+});
diff --git a/src/payout-export.ts b/src/payout-export.ts
index 601ca01..0a733c9 100644
--- a/src/payout-export.ts
+++ b/src/payout-export.ts
@@ -22,3 +22,20 @@ export function exportPayouts(payments: Payment[]): string {
});
return [header, ...rows].join('\n');
}
+
+/**
+ * Schedule a recurring export.
+ * Every `intervalMs` milliseconds the `sink` function is called with the
+ * CSV produced by exportPayouts for the given payments list.
+ * Returns a cleanup function that cancels the interval.
+ */
+export function scheduleExport(
+ intervalMs: number,
+ payments: Payment[],
+ sink: (csv: string) => void,
+): () => void {
+ const id = setInterval(() => {
+ sink(exportPayouts(payments));
+ }, intervalMs);
+ return () => clearInterval(id);
+}