Go · htmx · PostgreSQL · PayFast & SnapScan · MIT
A small online store,
written in Go.
gostore is a catalog, a cart and a checkout for a shop that sells a few things well — books, apparel, digital downloads — in rand, paid through PayFast or SnapScan. Server-rendered pages with htmx, Postgres for storage, one static binary, and a deliberately tiny set of dependencies.
Get started Screenshots Source
Go has no maintained open-source store, and no PayFast integration at all. This aims to be good at one thing rather than be a platform.
What it looks like
What you get
Two gateways, both careful
PayFast and SnapScan behind one small interface. A payment counts only when the gateway's own notification survives every check — signature, source, a read-back from the gateway, the amount.
Variants, stock and downloads
Up to three named options per product, stock per variant, and digital products delivered by a per-buyer link from private storage.
Search that forgives typos
Postgres full-text for word forms and trigram matching for spelling, category filters, and pagination — all working without JavaScript.
An admin with roles
Owner, admin, manager and viewer, a permission on every route, and accounts that are disabled rather than deleted. No default password: the first owner is claimed with a one-time token.
Retheme without forking
Every colour and size is a custom property in one stylesheet. Templates override by path from a directory, and nothing is rebuilt.
Embed the catalog anywhere
The catalog is cookie-free HTML fragments, so another site can drop it in with one
hx-get. Buying stays on the store's own domain.Hardened by default
A strict CSP with no
'unsafe-inline', CSRF on every write, argon2id passwords, and rate limits on login, checkout and callbacks.Runs anywhere a container does
A static binary in a distroless image that reads
PORTandDATABASE_URL, logs JSON, and migrates itself on boot.
How a payment works
Checkout writes a pending order — a snapshot of what is being bought, totalled from the catalog inside the transaction — and hands the shopper to the gateway. The shopper coming back proves nothing, so the page they return to says the payment is being confirmed, not that it succeeded.
The gateway's notification is what counts, and only once it has been verified: then the order is marked paid, stock moves, the cart empties and the receipt goes out — in that order, so a mail server having a bad afternoon can never lose a sale.
Run it
make up starts Postgres, a mail catcher, S3-compatible
storage and the store, and applies the migrations. The stack ships
PayFast's published sandbox credentials, so a checkout
works on the first try and takes no money.
git clone https://github.com/17xande-dev/gostore
cd gostore
make up
make seed
gostore -check-config && gostore -migrate && exec gostore
Then open http://localhost:8080. There is no default admin
password: make up prints a one-time setup token, and
/admin/setup turns it into the first account.
Replace the sandbox credentials before anyone else can reach a
deployment, and set PAYFAST_SANDBOX explicitly wherever you
deploy. The server refuses to take real money with PayFast's published
sandbox merchant id.