Quoted, approved, done, paid — before the ute left the street.
A phone-first product for pricing and invoicing anything — a tradie's job, a market stallholder's furniture — on the spot, with a payment link the customer opens on their own phone and pays in one tap.
Built for the Pinch Payments $50K Hackathon (spec settled 24 July 2026 · MVP demo 26 July · pitch night 31 July).
Invoicing apps are not scarce. The differentiator here is speed plus payment details captured at quote time, so the invoice is paid in one tap the moment the job is done. No chasing.
On top of that sits the second half of the pitch — a loyalty scheme funded by the payment rail itself:
- The Business is charged a card-equivalent rate whichever method the Customer picks, so it is cost-neutral between methods and keeps nothing from the difference.
- A Customer who pays by bank earns points out of that difference — the gap
between what we charge and what the bank rail actually cost, withheld by Pinch as an
applicationFeeand credited to the platform. - Points are pooled across every Business in the app and spend as a discount against any Document from any Business. 100 points = $1.
Nobody is out of pocket honouring points earned elsewhere: the cash was collected at earn time, from the payment that created them.
Business (installs the app)
- Describe — say what you did, in your own words. It's a text box; the phone keyboard's own mic button is the voice input, so there's no second speech vendor.
- Review — the description comes back as priced, itemised lines. Labour off the configured hourly rate, materials off the Business's own Price Book, anything else an AI estimate flagged for review. GST applied per the Business's registration.
- Send — as a Quote with an approve-up-to Ceiling, or straight to Invoice. The Customer gets a link.
- Work complete — confirm the final amount. At or under the Ceiling it invoices immediately; over it, the Customer re-approves first.
Customer (installs nothing, and there is never a wall in front of paying)
- Opens the link in their phone browser. Approves the Ceiling and captures payment details — bank or card, on their own device, never touching our server.
- Pay now — one tap, with a nudge showing what paying by bank earns.
- Payment confirmation — itemised, plus points earned. Deliberately not called a receipt: Paid means the money is committed, not that it reached the Business.
apps/
api/ .NET 10 minimal API — auth, documents, pricing, payments, rewards
app/ Expo / React Native app (iOS, Android, and the static web export)
docs/
adr/ eight architecture decision records — the reasoning
agents/ conventions for agents working in this repo
handover/ session handovers
specs/ the product spec and the v2 rewards build spec
.azure/ ARM template for the whole resource group
.ado/ Azure DevOps build/deploy pipelines
CONTEXT.md the domain glossary — read this first
| Concern | Choice |
|---|---|
| API | .NET 10, ASP.NET Core minimal APIs, EF Core 10, Azure SQL |
| Auth | ASP.NET Core Identity + JWT bearer (Businesses); one-time codes (Customers) |
| App | Expo 57 / React Native 0.86 / React 19, expo-router, Zustand |
| AI pricing | Anthropic Messages API (with a fixture pricer for tests and offline work) |
| Payments | Pinch — cards and BECS direct debit, Managed Merchants, webhooks |
| Hosting | Native iOS and Android builds from the one codebase; Azure App Service (API) + Storage static website (web export), Australia East |
Everything below runs against Pinch's sandbox. The integration lives in
apps/api/InvoiceAnything.Api/Payments and apps/app/src/features/payments.
CaptureJS — tokenisation in the Customer's browser. Card and BECS details are tokenised client-side and never touch our server. Pinch ships CaptureJS as a browser script with no npm package and no native SDK, so the native build deliberately refuses to collect payment details rather than falling back to posting them to our own API — which is fine, because the Customer always arrives by link in a browser and the Business's app never collects them.
Payers and sources. POST payers and POST payers/{id}/sources store the Customer's
payment method against the token, which is what makes the later invoice payable in one tap
with no retyping.
Payments — two rails, one Document. POST payments for a BECS direct debit, whose
outcome arrives days later; POST payments/realtime for a card, which is usually decided
in the same breath. Both pass through Processing on the way to Paid.
Managed Merchants — one sub-merchant per Business. Our Pinch account is the parent and
each Business is created underneath it via POST merchants/managed. Every call that
touches a Business's money carries the Current-Merchant header, so the payment is
created against that sub-merchant and settles to that Business. Getting this wrong is
quiet in a way worth naming: a wrong merchant is a 400, but an absent header falls
through and charges the parent account with no error anywhere — so the header is enforced
in the call signature rather than left to a caller to remember (ADR-0004).
applicationFee — the mechanism the whole rewards model rests on. Documented by Pinch
as Managed Merchants only, it withholds a share of a payment from the Business's
settlement and credits it to the parent. That withheld amount is what funds a Customer's
points. The figure is read back off the payment, never recomputed from a fee schedule
— recomputing it re-implements Pinch's own cap and rounding, and the Business drifts off
cost-neutral silently.
Webhooks — how a bank debit finishes on its own. Deliveries are HMAC-verified with a
replay window before anything is read, and Pinch's payment statuses are translated into
our own vocabulary at the edge: approved, settled and transferred all mean Paid;
dishonoured is terminal and carries its reason. Pinch publishes statuses that are
neither success nor refusal (cleared-pending-dispute, returned-without-settlement …) —
those leave the Document where it is for a human, rather than being guessed in either
direction.
GET /fees — the merchant's own schedule. Read per merchant and briefly cached, used
only to display what paying by bank would earn. The authoritative number for any single
payment is what Pinch itself returns for that amount and method. The cache is load-bearing
rather than an optimisation: the pay screen is deliberately reachable without a session,
so an uncached read would let anyone holding a link turn one page load into one Pinch
request.
GET transfers/items/{id} — settlement lines, paged, for reconciling what actually
landed.