feat(watcher): add Flip transfer receipt provider
Match no-reply@flip.id transfer success mail (direct or forwarded), parse stacked Nominal/ID Transaksi fields, and map spendings with source=flip. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { parseBriQris, parseBriTransfer } from "../src/parsers/bri.js";
|
||||
import { parseFlipTransfer } from "../src/parsers/flip.js";
|
||||
import { parseGrabReceipt, extractGrabTripTimesFromHtml } from "../src/parsers/grab.js";
|
||||
import { parseLivinReceipt, splitMerchantLocation } from "../src/parsers/livin.js";
|
||||
import { parseTokopediaOrder } from "../src/parsers/tokopedia.js";
|
||||
import { toSpendingRecord, toPocketBaseDate } from "../src/pocketbase/map.js";
|
||||
import { briQris } from "../src/rules/bri-qris.js";
|
||||
import { briTransfer } from "../src/rules/bri-transfer.js";
|
||||
import { flipTransfer } from "../src/rules/flip-transfer.js";
|
||||
import { livinQrPayment } from "../src/rules/livin-qr-payment.js";
|
||||
import { grabEreceipt } from "../src/rules/grab-ereceipt.js";
|
||||
import { tokopediaOrder } from "../src/rules/tokopedia-order.js";
|
||||
@@ -15,6 +17,8 @@ import {
|
||||
briQrisForwardedBody,
|
||||
briTransferDirectBody,
|
||||
briTransferForwardedBody,
|
||||
flipDirectBody,
|
||||
flipForwardedBody,
|
||||
grabBody,
|
||||
grabFoodForwardedBody,
|
||||
grabForwardedBody,
|
||||
@@ -366,6 +370,58 @@ describe("toSpendingRecord", () => {
|
||||
assert.equal(rec.description, "LuxePad.Creations - Tokopedia");
|
||||
assert.equal(rec.trx_date, "2026-09-05 00:00:00.000Z");
|
||||
});
|
||||
|
||||
it("maps Flip transfer envelope to spendings fields", () => {
|
||||
const parsed = {
|
||||
kind: "flip.transfer.success",
|
||||
messageId: "1a0c704164e0cd54",
|
||||
amount: 40001,
|
||||
reference: "ST260918145355861M3ZG4",
|
||||
occurredAt: "2026-09-18T14:53:00+07:00",
|
||||
details: {
|
||||
namaTujuan: "Rwen Ibrahim",
|
||||
bankTujuan: "BCA",
|
||||
nomorRekening: "0020215810",
|
||||
},
|
||||
};
|
||||
const rec = toSpendingRecord(parsed, flipTransfer);
|
||||
assert.equal(rec.invoice_id, "ST260918145355861M3ZG4");
|
||||
assert.equal(rec.amount, 40001);
|
||||
assert.equal(rec.source, "flip");
|
||||
assert.equal(rec.description, "Rwen Ibrahim");
|
||||
assert.equal(rec.trx_date, toPocketBaseDate(parsed.occurredAt));
|
||||
assert.deepEqual(rec.parsed, parsed);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseFlipTransfer", () => {
|
||||
it("parses direct stacked-label body", () => {
|
||||
const r = parseFlipTransfer(flipDirectBody);
|
||||
assert.ok(r);
|
||||
assert.equal(r.amount, 40001);
|
||||
assert.equal(r.reference, "ST260918145355861M3ZG4");
|
||||
assert.equal(r.occurredAt, "2026-09-18T14:53:00+07:00");
|
||||
assert.equal(r.details.namaTujuan, "Rwen Ibrahim");
|
||||
assert.equal(r.details.bankTujuan, "BCA");
|
||||
assert.equal(r.details.nomorRekening, "0020215810");
|
||||
});
|
||||
|
||||
it("parses forwarded body", () => {
|
||||
const r = parseFlipTransfer(flipForwardedBody);
|
||||
assert.ok(r);
|
||||
assert.equal(r.amount, 40001);
|
||||
assert.equal(r.reference, "ST260918145355861M3ZG4");
|
||||
assert.equal(r.occurredAt, "2026-09-18T14:53:00+07:00");
|
||||
assert.equal(r.details.namaTujuan, "Rwen Ibrahim");
|
||||
});
|
||||
|
||||
it("returns null when Nominal or ID Transaksi missing", () => {
|
||||
assert.equal(parseFlipTransfer("BUKTI TRANSFER\nNominal\nRp10.000\n"), null);
|
||||
assert.equal(
|
||||
parseFlipTransfer("ID Transaksi\n#ABC\nNama Tujuan\nX\n"),
|
||||
null,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseTokopediaOrder", () => {
|
||||
|
||||
Reference in New Issue
Block a user