/* catcrafts.net Copyright (C) 2026 Catcrafts The source code of this website is made available for viewing purposes only. No permission is granted to copy, modify, distribute, or create derivative works. */ // MatchCredits: the decision that releases goods on the bank-transfer rail. // // Everything here is a case a real payer or a real bank actually produces. The // reference travels through a human retyping it and a bank reformatting the // field, so the interesting failures are all cosmetic-looking: a lower-case // reference, a space inserted every four characters, the structured RF form // quoted instead of the short one. Each of those arriving as "unpaid" would be // money sitting in the account against an order the shop thinks was abandoned. // // The other half is refusing to over-match. A matcher that credits an order // from money that was not for it is worse than one that misses: it ships goods // nobody paid for. import std; import Catcrafts.Shared; import Catcrafts.Server; using namespace Catcrafts; using Server::BankCredit; namespace { int failures = 0; void Check(bool ok, std::string_view what, std::string_view got = {}) { if (ok) return; ++failures; std::println(std::cerr, "FAIL: {}{}{}", what, got.empty() ? "" : " got: ", got); } BankCredit Credit(std::string reference, std::int64_t amountMinor, std::string method = "sepa", std::string id = "p1") { BankCredit c; c.id = std::move(id); c.reference = std::move(reference); c.amountMinor = amountMinor; c.method = std::move(method); return c; } std::int64_t PaidFor(std::vector credits, std::string_view reference) { return Server::MatchCredits(credits, reference).paidMinor; } } // namespace int main() { const std::string ref = "CC-2B6457"; // ── the forms a payer actually quotes ───────────────────────────── Check(PaidFor({ Credit("CC-2B6457 catcrafts.net", 5000) }, ref) == 5000, "the reference exactly as we printed it"); Check(PaidFor({ Credit("cc2b6457", 5000) }, ref) == 5000, "lower case and no hyphen"); Check(PaidFor({ Credit("RF70CC2B6457", 5000) }, ref) == 5000, "the structured RF form, whose body IS the reference"); Check(PaidFor({ Credit("CC 2B 64 57", 5000) }, ref) == 5000, "a bank grouping the field into pairs"); Check(PaidFor({ Credit("Payment for order CC-2B6457, thanks!", 5000) }, ref) == 5000, "the reference buried in a sentence"); Check(PaidFor({ Credit("betaling cc/2b/6457", 5000) }, ref) == 5000, "separators a payer invented"); // ── refusing to over-match ──────────────────────────────────────── Check(PaidFor({ Credit("CC-2B6458", 5000) }, ref) == 0, "one character different is a different order"); Check(PaidFor({ Credit("no reference at all", 5000) }, ref) == 0, "a transfer with no reference pays for nothing"); Check(PaidFor({ Credit("", 5000) }, ref) == 0, "an empty remittance field pays for nothing"); // The needle guard: an empty or near-empty reference must not match every // credit on the account. This is the difference between one order settling // and the whole ledger settling from a single payment. Check(PaidFor({ Credit("anything", 5000), Credit("something", 900) }, "") == 0, "an empty reference matches nothing"); Check(PaidFor({ Credit("anything", 5000) }, "CC") == 0, "a too-short reference matches nothing"); // Outgoing money must never pay for an order. A refund we sent quotes the // very reference of the order it refunds, so counting signed amounts // blindly would let a refund settle the thing it reversed. Check(PaidFor({ Credit("CC-2B6457 refund", -5000) }, ref) == 0, "an outgoing payment quoting the reference is not income"); Check(PaidFor({ Credit("CC-2B6457", 5000), Credit("CC-2B6457 refund", -2000) }, ref) == 5000, "a later refund does not reduce what arrived"); // ── partials accumulate ─────────────────────────────────────────── // The buyer is told to send the difference to the same IBAN with the same // reference, so two credits for one order is a supported path and not an // anomaly. Summing is what makes that instruction true. Check(PaidFor({ Credit("CC-2B6457", 3000, "sepa", "a"), Credit("CC-2B6457", 2000, "sepa", "b") }, ref) == 5000, "two credits for one order are summed"); { const Server::TransferMatch m = Server::MatchCredits( { Credit("CC-2B6457", 3000, "sepa", "a"), Credit("CC-2B6457", 2000, "sepa", "b") }, ref); Check(m.count == 2, "the count reports how many credits carried it", std::format("{}", m.count)); } // ── the method reaches the ledger ───────────────────────────────── // The `via` column decides whether an order is safe to ship: a plain SEPA // transfer is final, and anything with a dispute window is not. { const Server::TransferMatch m = Server::MatchCredits({ Credit("CC-2B6457", 5000, "ideal") }, ref); Check(m.method == "ideal", "the settling method is carried out", m.method); } // ── other orders' money is left alone ──────────────────────────── Check(PaidFor({ Credit("CC-AAAAAA", 90000), Credit("CC-2B6457", 5000), Credit("CC-BBBBBB", 12345) }, ref) == 5000, "only the credits quoting THIS reference are counted"); // ── the ambiguity tripwire ─────────────────────────────────────── // One transfer quoting two orders cannot be attributed by a per-order // matcher: both orders would see the full amount and both would settle on // the same money. The matcher cannot fix it, so it flags it for a human. { const Server::TransferMatch m = Server::MatchCredits({ Credit("CC-2B6457 and CC-AAAAAA", 10000) }, ref); Check(m.ambiguous, "a credit quoting a second order reference is flagged ambiguous"); const Server::TransferMatch clean = Server::MatchCredits({ Credit("CC-2B6457 thanks", 10000) }, ref); Check(!clean.ambiguous, "an ordinary credit is not flagged"); } // An empty account is a clean zero rather than anything alarming: it is // simply the state of every order between checkout and payment. Check(PaidFor({}, ref) == 0, "no credits at all is zero, not an error"); // ── pulling credits into the file the rail reads ────────────────── // // Every pull re-reads an overlapping window of the account, so the SAME // credit arrives on every run. Appending it twice would double a payment // and settle an order nobody paid twice for, which makes deduplication by // the bank's own payment id the load-bearing property here. A file source // stands in for the bank so this needs no network. { const std::filesystem::path dir = std::filesystem::temp_directory_path() / "cc-transfer-pull-test"; std::error_code ec; std::filesystem::remove_all(dir, ec); std::filesystem::create_directories(dir, ec); const std::filesystem::path bank = dir / "bank.jsonl"; const std::filesystem::path target = dir / "credits.jsonl"; { std::ofstream out(bank); out << R"({"id":"p1","reference":"CC-2B6457","amount_minor":2500,)" R"("method":"sepa"})" << "\n"; out << R"({"id":"p2","reference":"CC-AAAAAA","amount_minor":900,)" R"("method":"ideal"})" << "\n"; } auto pull = [&] { auto src = Server::MakeFileCreditSource(bank); return Server::PullCreditsInto(*src, target); }; const std::optional first = pull(); Check(first.has_value() && *first == 2, "the first pull appends both credits", first ? std::format("{}", *first) : "nullopt"); const std::optional second = pull(); Check(second.has_value() && *second == 0, "pulling the same window again appends nothing", second ? std::format("{}", *second) : "nullopt"); // A new payment arrives at the bank; only it should be appended. { std::ofstream out(bank, std::ios::app); out << R"({"id":"p3","reference":"CC-2B6457","amount_minor":100,)" R"("method":"sepa"})" << "\n"; } const std::optional third = pull(); Check(third.has_value() && *third == 1, "only the new credit is appended", third ? std::format("{}", *third) : "nullopt"); // And what the rail now reads settles correctly: 25.00 + 1.00. { auto reader = Server::MakeFileCreditSource(target); const auto all = reader->Recent(); Check(all.has_value() && all->size() == 3, "the credits file holds exactly the three distinct credits", all ? std::format("{}", all->size()) : "nullopt"); if (all) { Check(Server::MatchCredits(*all, ref).paidMinor == 2600, "the deduplicated file sums to the real total"); } } std::filesystem::remove_all(dir, ec); } if (failures != 0) { std::println(std::cerr, "{} check(s) failed", failures); return 1; } std::println("ShouldMatchBankTransfers: all checks passed"); return 0; }