bank tranfer fix
All checks were successful
Deploy / build-deploy (push) Successful in 3m11s

This commit is contained in:
Jorijn van der Graaf 2026-08-20 23:33:50 +02:00
commit aaa7a8ce99
10 changed files with 543 additions and 8 deletions

View file

@ -602,6 +602,43 @@ export namespace Catcrafts::Server {
// "sepa" is final, "card" can be reversed for months.
std::string_view BunqMethodFor(std::string_view paymentType);
// ── the bunq callback (webhook) ───────────────────────────────────
//
// bunq pushes a notification when the account changes, which is the only
// way this shop learns about money when the API key's IP allowlist forbids
// the web host from asking. Shape:
//
// {"NotificationUrl":{"category":"MUTATION",
// "object":{"Payment":{…the same Payment object…}}}}
//
// SECURITY, stated plainly because the design depends on understanding it:
// **bunq does not sign these.** Verified against doc.bunq.com — no HMAC, no
// server signature; certificate pinning authenticates US to bunq, not bunq
// to us. So the body is an unauthenticated claim that money arrived, and
// the only things standing between it and the ledger are transport-level:
// a source-IP allowlist for bunq's published range (185.40.108.0/22, which
// bunq warns may change), a secret path segment, and the fact that nothing
// ships without a human. Treat a callback as evidence exactly as strong as
// those controls, and keep a periodic --pull-credits as the backstop:
// bunq retries roughly six times and then drops the notification forever,
// so a backend that was down during a deploy loses that payment silently.
std::optional<BankCredit> ParseBunqCallback(std::string_view json);
// Append one credit to the file the transfer rail reads, unless an entry
// with the same id is already there. Returns false on a write failure or a
// duplicate — the caller answers the webhook 200 either way, because a
// duplicate is a SUCCESS from bunq's point of view and retrying it would
// achieve nothing.
bool AppendCreditTo(const std::filesystem::path& creditsPath,
const BankCredit& credit);
// Enable the bunq callback endpoint. Both arguments are required and the
// secret must be at least 24 characters, or the endpoint stays off — see
// ParseBunqCallback for why the path IS the authentication here, and why
// that is only acceptable alongside Caddy's source-IP allowlist.
void ConfigureBunqCallback(std::filesystem::path creditsPath,
std::string secretPath);
// One pull: read the account and append every credit not already in the
// file to it, newest last. Returns the number appended, or nullopt if the
// bank could not be reached. This is what `--pull-credits` runs, and it is