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 PORT and DATABASE_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.

Shopper any browser gostore one Go binary PayFast or SnapScan PostgreSQL orders, stock, carts browse, check out pay, on the gateway notification ── the shopper's browser ── server to server only a verified notification marks an order paid

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.

Setting up payments

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.

Locally In a deploy
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.

Deploying