Nobody has started this yet — be first.
Business impact
Any client (a checkout page with a broken discount calculation, a batch import script with a bad row) that ever sends amount_minor: 0 gets back a 500 Internal Server Error. Most HTTP clients and retry middleware treat 5xx as "transient, safe to retry" and 4xx as "your request is wrong, fix it and don't retry blindly" -- so this single boundary bug turns one bad request into a retry storm against an endpoint that will never succeed for that payload, and it pollutes server-error-rate dashboards/alerts with noise that isn't actually a server problem. Anyone on call investigating an "error rate spike" alert will burn time confirming it's just a client sending zero-amount requests.
Problem
POST /v1/payments with {"amount_minor": 0, "currency": "INR"} returns 500 Internal Server Error with body {"error":"payment: amount_minor must be positive"}.
Current behavior
A zero-amount payment creation request returns HTTP 500 with the payment-service's own validation error message, instead of a clean 422.
Expected behavior
The same request should return 422 Unprocessable Entity with a body like {"error":"amount_minor must be positive"} -- a clean, actionable client error, not a fault that looks like our server broke.
Steps to reproduce
go run ./cmd/paystate # or use the ticket test below, no server needed
curl -i -X POST localhost:8080/v1/payments
-H 'Content-Type: application/json'
-d '{"amount_minor": 0, "currency": "INR"}'
Why this matters
createPayment has its own early boundary check meant to reject non-positive amounts before ever calling payment.Service.Create. If that check's boundary is off, a request slips through to Service.Create, which has its own, separate amount validation (errNonPositiveAmount in internal/payment/machine.go) and returns a plain Go error. createPayment's generic if err != nil { ... StatusInternalServerError ... } branch then maps any Service.Create error -- including this one -- to 500. Two independent validation layers existing is itself fine (defense in depth), but only if their boundaries actually agree; here they don't.
Suggested approach
Look at the comparison operator in createPayment's amount check in internal/api/payments_handler.go, and compare it against what the error message right next to it says the rule is ("must be positive"). Zero is not positive. You do not need to touch writeServiceError or payment.Service.Create -- the fix is a single comparison operator.
Acceptance criteria
Verification
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket01 -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/zero-amount-returns-500Fix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/zero-amount-returns-500Submit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign indocker composepaystate/paystate/go.modreplace reliable => ../reliabledocker compose up --buildpaystate/context: ..docker-compose.ymlCREATED_TIMEOUTAUTHORIZED_TIMEOUTWork the tickets in tickets/docs/ (ticket-01 through ticket-25); each names one Go test in tickets/tests/:
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket01 -v # a single ticket
TEST_DATABASE_URL=postgresql://... ./tickets/run_tickets.sh # all 25, clean pass/fail summary
Tickets that don't touch Postgres (02, 03, 04, 05, 06, 14, 15, 24) run fine without TEST_DATABASE_URL set; the rest report FAIL with a "TEST_DATABASE_URL not set" message if it's missing. make test-race runs the full go test -race -p 1 ./... suite the same way.
Level 1
Fix a bug
Read existing behaviour, correct it.