Security

Every control, and the file it lives in

Each claim below is about code in this repository, and names the file that makes it true.

  • Card details never reach our servers.

    Payment is completed on the payment provider’s hosted page. We store the provider’s transaction reference and the outcome, and nothing that could be used to make a further charge.

    src/lib/gateway/*
  • An invoice is settled only by a server-to-server confirmation.

    The payer’s return to this site is treated as a request to display state, never as evidence of payment. The callback route is the only code path that marks an invoice paid.

    src/app/api/gateway/[provider]/callback/route.ts
  • A confirmed amount is checked against the invoice.

    A valid signature proves where a message came from, not that the sum agreed. A confirmation reporting a different amount than the one billed is recorded and refused rather than settled.

    src/lib/invoices/service.ts
  • API keys are stored hashed.

    Only a SHA-256 digest of each platform key is stored, so a copy of the database yields no usable credential. A lost key is rotated, never recovered.

    src/lib/api-key.ts
  • Administrator passwords are stored as salted scrypt hashes.

    Verification is constant-time, and the cost parameters are recorded with each hash so they can be raised.

    src/lib/password.ts
  • Outbound notifications are signed, and cannot be replayed.

    Each notification carries an HMAC-SHA256 signature over its body with the timestamp inside the signed material, so a captured request stops verifying once it falls outside the tolerance window.

    src/lib/webhooks/signature.ts
  • Billing the same period twice is prevented by the database.

    An invoice reference is unique per platform, so a retried request returns the invoice that already exists instead of issuing a second demand.

    src/db/schema/index.ts
  • Scheduled work is authenticated.

    The route that drives redelivery and expiry changes state, so it requires a secret rather than relying on an unguessable path.

    src/app/api/internal/cron/route.ts

What we hold, and for how long

  • The invoice: amount, currency, period, description and the reference the platform gave us.
  • The payment attempt: the gateway’s own transaction reference and the outcome it reported.
  • The verified callback body, kept verbatim, because a dispute is argued from what the provider actually said.
  • Every outbound notification and each attempt to deliver it.

What we never hold

  • Card numbers, expiry dates and security codes. These are entered on the provider’s hosted page.
  • Cardholder authentication data of any kind.
  • Anything that could be used to make a further charge against a payer.
  • A recoverable copy of any platform API key — only a digest is stored.

Reporting a vulnerability

Report it through the agreement you hold with us, with a description and, if you have one, a reproduction. We will acknowledge within two working days. Please do not test against live invoices belonging to other customers.