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.
Configuration → Payments (super-admin only). Direct URL: /admin/super/payments./pay/{token} — unguessable per-invoice token, no menu link, no login. This is what the link/QR points to.create_invoice_payment_link / list_invoices on the helpinghand-bulk-actions MCP server.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.
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.
Configuration → Payments → New invoice.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.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.
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:
GenerateMonthlyBillingCommand stamps that channel's charges as Volley instead of Stripe.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.
request.status_updated=paid or payment.status_updated=successful), matched on the invoice's merchant_identifier. The browser polling is only a convenience; the webhook is the source of truth.The helpinghand-bulk-actions MCP server exposes:
create_invoice_payment_link — create an invoice and return its pay link. Example arguments:
{
"channelCode": "hh",
"payerName": "Acme Limited",
"description": "Managed cloud hosting - June & July",
"gstPercent": 15,
"lines": [
{ "description": "Cloud infrastructure and backups", "quantity": 2, "unitPriceDollars": 50 },
{ "description": "Monitoring and on-call cover", "quantity": 2, "unitPriceDollars": 45 }
]
}
Returns the invoice number, totals and the payUrl. Line prices are GST-inclusive by default; pass pricesIncludeGst: false for GST-exclusive amounts (or set priceIncludesGst per line). GST is broken out at 15%.list_invoices — recent invoices for a channel with totals, status and pay links.The tool drives the admin API POST/GET /api/v2/admin/channels/{channelCode}/invoices (JWT admin auth, ROLE_API_ACCESS).
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).
billing_invoice — one row per invoice: channel, number (INV-NNNNN), public token, GST-exclusive subtotal_cents, gst_cents, total_cents, status, and the Volley request_id / merchant_identifier.billing_invoice_line — line items (description, quantity, unit_price_cents, position).Money is stored in integer cents throughout.