feat(watcher): add Tokopedia order parsing and rules
Implement parsing for Tokopedia "Pesanan Selesai" messages, including support for single and multi-item orders. Update rules to recognize Tokopedia order completion and enhance spending record mapping. Add tests to validate new functionality.
This commit is contained in:
@@ -3,11 +3,13 @@ import { describe, it } from "node:test";
|
||||
import { parseBriQris, parseBriTransfer } from "../src/parsers/bri.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 { livinQrPayment } from "../src/rules/livin-qr-payment.js";
|
||||
import { grabEreceipt } from "../src/rules/grab-ereceipt.js";
|
||||
import { tokopediaOrder } from "../src/rules/tokopedia-order.js";
|
||||
import {
|
||||
briQrisDirectBody,
|
||||
briQrisForwardedBody,
|
||||
@@ -20,6 +22,8 @@ import {
|
||||
grabTipForwardedBody,
|
||||
livinDirectBody,
|
||||
livinForwardedBody,
|
||||
tokopediaMultiForwardedBody,
|
||||
tokopediaSingleForwardedBody,
|
||||
} from "./fixtures.js";
|
||||
|
||||
describe("splitMerchantLocation", () => {
|
||||
@@ -346,4 +350,56 @@ describe("toSpendingRecord", () => {
|
||||
assert.equal(rec.description, "Shopee Indonesia");
|
||||
assert.equal(rec.trx_date, toPocketBaseDate(parsed.occurredAt));
|
||||
});
|
||||
|
||||
it("maps Tokopedia envelope to spendings fields", () => {
|
||||
const parsed = {
|
||||
kind: "tokopedia.order.completed",
|
||||
messageId: "1a09ba1248c7d2e8",
|
||||
amount: 48805,
|
||||
reference: "585871780568401286",
|
||||
occurredAt: "2026-09-05",
|
||||
details: { shop: "LuxePad.Creations" },
|
||||
};
|
||||
const rec = toSpendingRecord(parsed, tokopediaOrder);
|
||||
assert.equal(rec.invoice_id, "585871780568401286");
|
||||
assert.equal(rec.source, "tokopedia");
|
||||
assert.equal(rec.description, "LuxePad.Creations - Tokopedia");
|
||||
assert.equal(rec.trx_date, "2026-09-05 00:00:00.000Z");
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseTokopediaOrder", () => {
|
||||
it("parses single-item Pesanan Selesai", () => {
|
||||
const r = parseTokopediaOrder(tokopediaSingleForwardedBody);
|
||||
assert.ok(r);
|
||||
assert.equal(r.amount, 48805);
|
||||
assert.equal(r.reference, "585871780568401286");
|
||||
assert.equal(r.occurredAt, "2026-09-05");
|
||||
assert.equal(r.details.shop, "LuxePad.Creations");
|
||||
assert.equal(r.details.trackingNumber, "JY1590448249");
|
||||
assert.equal(r.details.shippingService, "Standard");
|
||||
assert.equal(r.details.items.length, 1);
|
||||
assert.match(r.details.items[0].name, /Mouse Pad Besar Anime Style/);
|
||||
assert.equal(r.details.items[0].qty, 1);
|
||||
assert.equal(r.details.items[0].unitPrice, 88750);
|
||||
assert.equal(r.details.items[0].lineTotal, 88750);
|
||||
});
|
||||
|
||||
it("parses multi-item order as one invoice", () => {
|
||||
const r = parseTokopediaOrder(tokopediaMultiForwardedBody);
|
||||
assert.ok(r);
|
||||
assert.equal(r.amount, 26000);
|
||||
assert.equal(r.reference, "585494052374283654");
|
||||
assert.equal(r.occurredAt, "2026-08-14");
|
||||
assert.equal(r.details.shop, "NNcolection09");
|
||||
assert.equal(r.details.items.length, 2);
|
||||
assert.match(r.details.items[0].name, /KOMBINASI BIRU/);
|
||||
assert.match(r.details.items[1].name, /KOMBINASI MERAH/);
|
||||
assert.equal(r.details.items[0].unitPrice, 10500);
|
||||
assert.equal(r.details.items[1].lineTotal, 10500);
|
||||
});
|
||||
|
||||
it("returns null without invoice/total", () => {
|
||||
assert.equal(parseTokopediaOrder("Hai Roby\nPesananmu selesai."), null);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user