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,4 +1,5 @@
|
||||
import { parseBriQris, parseBriTransfer } from "../parsers/bri.js";
|
||||
import { parseFlipTransfer } from "../parsers/flip.js";
|
||||
import { parseGrabReceipt } from "../parsers/grab.js";
|
||||
import { parseLivinReceipt } from "../parsers/livin.js";
|
||||
import { parseTokopediaOrder } from "../parsers/tokopedia.js";
|
||||
@@ -10,6 +11,7 @@ const PARSERS = {
|
||||
"bri.transfer.success": parseBriTransfer,
|
||||
"bri.qris.success": parseBriQris,
|
||||
"tokopedia.order.completed": parseTokopediaOrder,
|
||||
"flip.transfer.success": parseFlipTransfer,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
captureBlockAfterLabel,
|
||||
parseDayMonthYear,
|
||||
parseIdrAmount,
|
||||
parseTimeWib,
|
||||
toWibIso,
|
||||
} from "../lib/parse-helpers.js";
|
||||
|
||||
/**
|
||||
* First non-empty line after a Flip stacked label (value on the next line).
|
||||
*/
|
||||
function valueAfterLabel(body, label, nextLabels = []) {
|
||||
const lines = captureBlockAfterLabel(body, label, nextLabels);
|
||||
return lines?.[0]?.trim() || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Flip transfer-success body (plain text, stacked label/value lines).
|
||||
* Required: Nominal + ID Transaksi.
|
||||
*/
|
||||
export function parseFlipTransfer(body) {
|
||||
if (!body) return null;
|
||||
|
||||
const referenceRaw = valueAfterLabel(body, "ID Transaksi", [
|
||||
"Waktu Terkirim",
|
||||
"Nama Tujuan",
|
||||
]);
|
||||
const reference = referenceRaw
|
||||
? referenceRaw.replace(/^#/, "").trim()
|
||||
: null;
|
||||
|
||||
const waktuRaw = valueAfterLabel(body, "Waktu Terkirim", [
|
||||
"Nama Tujuan",
|
||||
"Bank Tujuan",
|
||||
]);
|
||||
const dateYmd = parseDayMonthYear(waktuRaw);
|
||||
const timeHms = parseTimeWib(waktuRaw);
|
||||
const occurredAt = toWibIso(dateYmd, timeHms);
|
||||
|
||||
const amount = parseIdrAmount(
|
||||
valueAfterLabel(body, "Nominal", [
|
||||
"Kalau ada pertanyaan",
|
||||
"Makasih",
|
||||
"Salam",
|
||||
]),
|
||||
);
|
||||
|
||||
if (amount == null || !reference) return null;
|
||||
|
||||
const namaTujuan = valueAfterLabel(body, "Nama Tujuan", [
|
||||
"Bank Tujuan",
|
||||
"Nomor Rekening Tujuan",
|
||||
]);
|
||||
const bankTujuan = valueAfterLabel(body, "Bank Tujuan", [
|
||||
"Nomor Rekening Tujuan",
|
||||
"Nominal",
|
||||
]);
|
||||
const nomorRekening = valueAfterLabel(body, "Nomor Rekening Tujuan", [
|
||||
"Nominal",
|
||||
]);
|
||||
|
||||
const details = {};
|
||||
if (namaTujuan) details.namaTujuan = namaTujuan;
|
||||
if (bankTujuan) details.bankTujuan = bankTujuan;
|
||||
if (nomorRekening) details.nomorRekening = nomorRekening;
|
||||
|
||||
return {
|
||||
amount,
|
||||
currency: "IDR",
|
||||
reference,
|
||||
occurredAt,
|
||||
details,
|
||||
};
|
||||
}
|
||||
@@ -24,6 +24,8 @@ export function toSpendingRecord(parsed, rule) {
|
||||
? `${base} tip`
|
||||
: base;
|
||||
}
|
||||
} else if (source === "flip") {
|
||||
description = parsed.details?.namaTujuan || "";
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export const flipTransfer = {
|
||||
kind: "flip.transfer.success",
|
||||
source: "flip",
|
||||
fromAddress: "no-reply@flip.id",
|
||||
subject: "Transfer ke",
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { briQris } from "./bri-qris.js";
|
||||
import { briTransfer } from "./bri-transfer.js";
|
||||
import { flipTransfer } from "./flip-transfer.js";
|
||||
import { grabEreceipt } from "./grab-ereceipt.js";
|
||||
import { livinQrPayment } from "./livin-qr-payment.js";
|
||||
import { tokopediaOrder } from "./tokopedia-order.js";
|
||||
@@ -10,4 +11,5 @@ export const rules = [
|
||||
briTransfer,
|
||||
briQris,
|
||||
tokopediaOrder,
|
||||
flipTransfer,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user