Skip to content

Repository files navigation

Xero PayNow VPA QR

A Codex-first template for adding invoice-specific PayNow QR payments to Xero invoices. It derives a dynamic QR from merchant fields decoded from the official provider-issued QR, including support for virtual payment addresses (VPA).

This software does not register a merchant for PayNow, confirm receipt of money, or reconcile Xero. Complete a real low-value banking-app test before customer rollout.

Prerequisites

Required for every setup

  • An activated Singapore PayNow receiving service and the official provider-issued QR as a PNG file.
  • A registered recipient name that can be confirmed through a real banking-app scan.
  • Node.js 20.20 or newer, npm and Git on the machine running the setup.
  • Access to the Xero organisation's payment-service and branding-theme settings.
  • An HTTPS payment-page URL. A custom domain is optional, but using one requires DNS access.
  • Explicit approval before the agent changes hosting, DNS or Xero.

Check the local toolchain with:

node --version
npm --version
git --version

Choose one hosting path

You do not need both Netlify and a VPS.

Option A: Netlify

Required:

  • A Netlify account and permission to create or manage the target site.
  • Either Git-based deployment from your copy of this repository, or Netlify CLI access for an agent-led/manual deployment.
  • Netlify CLI authentication when using the CLI path.
  • DNS access if attaching a custom domain.

Recommended CLI checks:

npx netlify --version
npx netlify status

For a new Git-connected site, use npx netlify init. For a manual deployment, first create a draft with npx netlify deploy --dir=dist, verify its preview URL, then promote with npx netlify deploy --prod --dir=dist.

The VITE_ merchant fields are compiled into the browser and are not secrets. Configure them for the intended Netlify deploy context; never place tokens or private credentials in VITE_ variables.

Option B: VPS with Docker

Required:

  • A Linux VPS and authorised SSH access.
  • Docker Engine with the Docker Compose plugin.
  • A reverse proxy such as Caddy or Nginx terminating HTTPS.
  • DNS access for the payment-page hostname.
  • A firewall allowing public HTTPS while the application container remains bound to host loopback.

Recommended checks:

ssh <host> 'docker --version && docker compose version'

The included Compose file binds the application to 127.0.0.1:18080. The reverse proxy should be the only public path to it. Netlify CLI is not needed for this route.

Start with Codex

Give Codex this repository URL and say:

Set this up for my Xero organisation. Follow AGENTS.md. Start by asking me for my provider-issued PayNow QR and intended payment-page domain. Do not change Xero until I approve the exact payment-service change.

Codex should first audit the prerequisites and ask which hosting path to use. It should then decode the QR, prepare .env.local, test the generated payload, deploy the page and guide the final Xero setup.

Why decode the official QR?

PayNow receiving addresses are not interchangeable. A provider QR may use a mobile number, UEN or VPA. For example, a VPA can resemble a UEN while using a different PayNow proxy type. Guessing the proxy type can produce a valid-looking QR that banking apps report as unregistered.

The decoder extracts the provider's authoritative fields instead:

npm install
npm run decode-qr -- C:\path\to\provider-paynow.png

Keep the original QR and generated .env.local private.

Configure

Copy-Item .env.example .env.local
npm test
npm run build

Required settings:

Variable Source
VITE_PAYNOW_PROXY_TYPE PayNow merchant account field 01
VITE_PAYNOW_PROXY PayNow merchant account field 02
VITE_PAYNOW_RECIPIENT_DISPLAY Registered recipient name confirmed by a bank scan
VITE_PAYNOW_QR_MERCHANT_NAME Top-level merchant-name field 59
VITE_PAYNOW_MCC Merchant category field 52
VITE_PAYNOW_COUNTRY Country field 58
VITE_PAYNOW_CITY City field 60
VITE_PAYNOW_POSTAL_CODE Postal field 61, when present

The public URL accepts only transaction values:

https://pay.example.com/?invoiceNo=INV-1234&currency=SGD&amount=100.00

Merchant identity is compiled from trusted configuration and cannot be replaced through URL parameters.

Xero custom payment URL

After the page is deployed and a bank-app scan succeeds, configure Xero with:

https://pay.example.com/?invoiceNo=[INVOICENUMBER]&currency=[CURRENCY]&amount=[AMOUNTDUE]&shortCode=[SHORTCODE]

Attach the payment service only to the intended branding themes. This page does not create Xero payments or mark invoices paid.

Deploy

The project builds to dist/ and can be hosted on Netlify or another static host. A hardened Nginx container is also included:

docker compose up --build

The container binds to 127.0.0.1:18080; place it behind a TLS reverse proxy for public use.

Validation checklist

  • npm test and npm run build pass.
  • The official QR fields were decoded instead of guessed.
  • The live page rejects missing or non-SGD invoice parameters.
  • A banking app shows the expected recipient.
  • The amount and invoice reference are populated correctly.
  • The merchant confirms receipt from bank evidence, not from the payer's screenshot.

Credits

See NOTICE.md. This repository is not affiliated with Xero, PayNow, ABS, MAS or any bank/payment provider.

About

Codex-first template for invoice-specific Xero PayNow QR pages with provider QR decoding and VPA support

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages