Payments - Volley invoice links

Generate a one-off invoice and get a shareable bank-transfer pay link + QR code (powered by Volley open banking) to attach to it. The payer opens a standalone page, pays by bank transfer, and the invoice is marked Paid automatically once Volley confirms the transfer. No card, no login for the payer.

This is for operator-raised invoices (e.g. Prosper Technologies billing a client for hosting), separate from shop-order checkout.

Where it lives

Who settles what

Two different bank accounts are in play, and picking the wrong one is the easy mistake here:

The platform billing channel is set by a super admin: Configuration → Channels → <the Prosper channel> → Edit → Platform billing and tick Platform billing channel. Only one channel should carry it, and it needs its own Volley API key and bank account code filled in. Until it is set, a tenant cannot set up bank transfer for their subscription - the authorise flow refuses rather than settling into the wrong account.

On an invoice this is the difference between the two channel fields: Bill to channel (payerChannel) is the tenant that owes it, and the invoice's channel is whoever collects. Set Bill to channel and the invoice appears on that tenant's own billing page for them to pay, and settles to the platform channel. Leave it empty and nothing changes from before.

Prerequisites — the channel's Volley settings

Payments settle into the bank account configured on the channel that raises them. Before using this, the channel must have Volley set up (Admin → channel settings → payments):

Setting Field Notes
Open banking enabled useOpenBanking Must be on
Volley API key volleyApiKey Live key, or a MOCK… / mock_… key for testing (no real money)
Bank account number bankAccountNumber Where the money lands (sent to Volley as bank_account_id)
Webhook secret volleyWebhookSecret Used to verify Volley's webhook signature

In the Volley dashboard, point the webhook at POST https://<host>/volley/webhook (the same endpoint checkout already uses) and register the request.status_updated and payment.status_updated events.

Create an invoice (admin)

  1. Configuration → Payments → New invoice.
  2. Fill in Bill to (name/email, optional) and a Description (shown to the payer and used as the Volley payment message).
  3. Add line items — each has a description, quantity (supports decimals, e.g. 4.5 hours), a unit price, and a "Price includes GST" toggle. When a line's price includes GST, its GST is broken out on the invoice (net + GST shown); when it excludes GST, GST is added on top. Use + Add line for more. The top-level "New lines include GST" switch sets the default each new line adopts (defaults to includes); flip any individual line as needed.
  4. Leave GST at 15% (PTL is GST-registered) or choose No GST for an exempt invoice.
  5. Create invoice. You land on the invoice page showing the totals, the pay link and the QR code.

Attach the QR image or the pay link to the invoice you send (e.g. in the emailed PDF). Anyone with the link can pay; they cannot see any other invoice.

How a food bank pays us

A channel administrator manages this at Billing → Platform costs → How you pay (/admin/billing/platform-costs/card), which offers three things beside the card on file:

Stopping bank transfer always stops us collecting that way, even when Volley will not accept the revoke (it only does so when it gave us a consent id, and it does not always). In that case the message asks them to also clear the authority in their banking app - the standing authority is theirs to remove, but we will not use it again.

How the payer pays

  1. They open the pay link (or scan the QR).
  2. They see the invoice summary and Pay with bank transfer.
  3. On click, the app creates a Volley payment request and shows Volley's QR + a Continue to Volley button (on mobile it goes straight through). They authorise the transfer in their banking app.
  4. The pay page polls and updates to Paid once Volley confirms. The admin invoice status flips to Paid via the webhook.

Marking paid / cancelling

Using the MCP tool

The helpinghand-bulk-actions MCP server exposes:

The tool drives the admin API POST/GET /api/v2/admin/channels/{channelCode}/invoices (JWT admin auth, ROLE_API_ACCESS).

Testing without real money

Set the channel's volleyApiKey to a value starting MOCK (e.g. MOCK_test). VolleyService then returns a fake request URL and makes no real API call, so you can exercise the whole admin + pay-page flow safely. The pay page shows a "Test mode" note. To simulate the webhook marking it paid, POST a Volley-shaped payload to /volley/webhook with the invoice's merchant_identifier (or flip the status in the DB for a quick check).

Data model

Money is stored in integer cents throughout.