Skip to content

ci: add CI; fix the Docker build, the live annual price, and Paystack initialize - #33

Merged
EOEboh merged 4 commits into
mainfrom
chore/ci-and-currency-guard
Jul 28, 2026
Merged

ci: add CI; fix the Docker build, the live annual price, and Paystack initialize#33
EOEboh merged 4 commits into
mainfrom
chore/ci-and-currency-guard

Conversation

@EOEboh

@EOEboh EOEboh commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Opened after investigating the live 500 on USD checkout. The 500 itself was a deployment gap — production had 11.9 days uptime and predated all five merged PRs, so hookdrop.app was serving the new frontend against the original untested backend. Production is now deployed from main plus these commits, and all four checkout paths return 200.

Four changes, two of which are mistakes of mine.

The Docker build excluded half of package main

RUN CGO_ENABLED=1 GOOS=linux go build -o hookdrop ./main.go

That compiles one file, not the package. Adding repair.go in #32 therefore broke the production image while go build ./... still passed — main was not deployable and nothing reported it until the deploy failed. Builds . now.

The annual price fix in #32 was wrong

I changed the annual price to ₦33,500 based on the test-mode plan. The live plan PLN_pqaarazyxu6np2o charges 3360000, so production was quoting ₦100 less than customers are charged — the opposite of the problem it meant to fix, and worse. Restored to ₦33,600 (₦2,800/mo, which divides exactly, as the original figures did). The live plan codes and amounts are now recorded beside the constants with a note that test-mode prices differ.

Paystack initialize never worked

transaction/initialize rejects a request with no amount"Invalid Amount Sent" — even though passing a plan supersedes it. The amount was never sent, so this path failed every time it was reached. Unnoticed because the NGN UI uses the inline popup, which supplies its own amount; /billing/checkout?currency=ngn reaches it. The amount now comes from the plan, which is what the customer is actually charged. The error also reported only "paystack initialization failed", discarding the message explaining why.

CI

Nothing ran the Go tests — the only PR check was Cloudflare building the UI, so every test written for the billing work gated nothing. Four jobs: main module (build, vet, gofmt, tests, race), CLI module, UI, and the production Docker image. That last one exists because the Go job cannot catch the Dockerfile bug above: it compiles the package correctly, so only the image build exposes it.

The UI job runs pnpm run build, not tsc --noEmitui/tsconfig.json is solution-style, so --noEmit checks nothing and exits 0.

Currency guard — recorded, not blocked

NGN pricing is much cheaper than USD and the currency is chosen client-side (localStorage.hookdrop_currency is enough). The verified transaction's authorization.country_code and payer IP are now recorded, and a NGN payment on a card issued outside Paystack's markets is logged for review.

Deliberately not blocked: a Nigerian customer paying with a foreign-issued card is a real case, and refusing them after they have been charged is a worse failure than the mispricing. This makes the exposure measurable before any policy is written against real customers.

Verified in production

usd month -> 200  hookdroptool.lemonsqueezy.com
usd year  -> 200  hookdroptool.lemonsqueezy.com
ngn month -> 200  checkout.paystack.com
ngn year  -> 200  checkout.paystack.com

DB backed up before and after shutdown; -repair-paystack run and reports nothing to do — production has 0 subscriptions.

EOEboh added 4 commits July 28, 2026 12:20
Nothing ran the Go tests. The only check on a PR was Cloudflare building
the UI, so a change could break billing outright and still merge green —
every test written for the billing work gated nothing.

Three jobs: the main module (build, vet, gofmt, tests, and tests under
the race detector), the CLI module, and the UI.

Two details that matter:

The UI job runs `pnpm run build`, which is `tsc -b && vite build`, not
`tsc --noEmit`. ui/tsconfig.json is solution-style — "files": [] with
only references — so --noEmit type-checks nothing and exits 0. That is
exactly how a type error reached the Cloudflare build and failed there
instead of in review.

Go tests run with -count=1. A cached pass says nothing about the commit
under test.

Every step verified locally against this tree before committing.
The Dockerfile ran `go build -o hookdrop ./main.go`, which compiles that
single file rather than package main. Adding repair.go therefore broke
the production image while `go build ./...` still passed locally — main
was not deployable and nothing said so until the deploy itself failed.

Builds `.` instead, and adds a CI job that builds the production image
and runs the binary. The Go job alone cannot catch this: it compiles the
package correctly, so only the image build exposes the difference.
I got this wrong in #32. The annual price was changed to ₦33,500 on the
strength of the test-mode plan; the LIVE plan PLN_pqaarazyxu6np2o charges
3360000, so production has been quoting ₦100 less than customers are
actually charged — worse than the discrepancy the change meant to fix.

₦33,600 ÷ 12 is exactly ₦2,800, which is why the original figures were
what they were.

Records both live plan codes and their amounts next to the constants, and
notes that the test-mode plans are priced differently, so these must be
checked against live rather than whatever the test keys report.
Two things, both found against the live API.

transaction/initialize rejects a request with no amount — "Invalid Amount
Sent" — even though passing a plan supersedes it. CreateCheckout never
sent one, so the Paystack redirect path failed every time. It went
unnoticed because the NGN UI uses the inline popup, which supplies its
own amount; reaching /billing/checkout with currency=ngn exposes it. The
amount now comes from the plan itself, which is the source of truth for
what the customer is charged.

The failure also reported only "paystack initialization failed",
discarding the message that says why. It now includes it.

Separately, the verified transaction's authorization.country_code and
payer IP are recorded, and a NGN payment made with a card issued outside
Paystack's markets is logged for review. NGN pricing is much cheaper than
USD and the currency is picked client-side — localStorage is enough — so
the card's country is the only server-side evidence of where the payer
is.

Flagged rather than blocked, deliberately: a Nigerian customer paying
with a foreign-issued card is a real case, and refusing them after they
have been charged is a worse failure than the mispricing. This makes the
exposure measurable before any policy is written against real customers.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Deploying hookdrop-frontend with  Cloudflare Pages  Cloudflare Pages

Latest commit: a7937d1
Status: ✅  Deploy successful!
Preview URL: https://8175968e.hookdrop-frontend.pages.dev
Branch Preview URL: https://chore-ci-and-currency-guard.hookdrop-frontend.pages.dev

View logs

@cloudflare-workers-and-pages

Copy link
Copy Markdown
Contributor

Deploying hookdrop with  Cloudflare Pages  Cloudflare Pages

Latest commit: a7937d1
Status: ✅  Deploy successful!
Preview URL: https://9cbf60b4.hookdrop.pages.dev
Branch Preview URL: https://chore-ci-and-currency-guard.hookdrop.pages.dev

View logs

@EOEboh
EOEboh merged commit 18fa760 into main Jul 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant