Lean payment orchestration API backed by a JSON-persisted store that mimics SQLite behaviour. The service exposes Stripe-inspired payment flows over HTTP with request logging, webhook verification, and a simple sweeper for stale authorisations.
Click here
The codebase is split into a handful of small packages:
| Package | Purpose |
|---|---|
internal/config |
Loads .env files and exposes runtime configuration such as bind address, Stripe keys, and environment mode. |
internal/endpoint |
HTTP router plus request handlers for intents, captures, refunds, checkout sessions, webhooks, and health checks. |
internal/payments |
Core domain service that orchestrates repository updates and calls into the payment gateway abstraction. Includes a fake gateway for local use and the Stripe-backed implementation for production. |
internal/store |
JSON-backed persistence layer that mirrors a subset of SQLite behaviour and stores transactions plus webhook payloads. |
internal/jobs |
Background checkout tracker that periodically polls Stripe (or the fake gateway) to keep transactions in sync after hosted checkout flows. |
internal/types |
Serializable data models shared across the service boundary. |
cmd/server/main.go wires everything together: it loads configuration, opens the store, picks the correct gateway (FakeGateway for non-production environments), and starts the HTTP server along with the authorization sweeper job.
Transactions move through a handful of statuses defined in internal/types: created → authorized → captured/refunded or voided/expired/failed. Metadata is stored as JSON on each transaction and webhook payloads are persisted for auditability.
| Variable | Required | Description |
|---|---|---|
STRIPE_SECRET_KEY |
❌ | Used for authenticating with Stripe's REST API. The server will exit if it is not provided. |
STRIPE_WEBHOOK_SECRET |
❌ | Optional secret used to verify incoming Stripe webhook signatures. |
APP_BASE_URL |
✅ | Base URL used to resolve default success/cancel URLs for checkout sessions and determine the listen port. Defaults to http://localhost:8080. |
APP_ENV |
✅ | Controls runtime behaviour (local, production). local enables the fake gateway and bypasses Stripe. |
Configuration values are loaded from a .env file at the repository root using github.com/joho/godotenv. Duplicate .env.example if you need to customise values locally. When APP_ENV=local the server automatically switches to the in-memory fake gateway so you can exercise the API without real Stripe credentials.
make format # gofmt -w -s .
make test-all # go test ./... inside a golang:1.25 container
make test-coverage # go test ./... inside a golang:1.25 container
make build # compile ./cmd/server into ./bin/app with the dockerised Go toolchain
make run # execute the built binary (requires prior make build)To run the service with Docker, build and start the containerised server:
docker compose up --buildThe application listens on port 8080 inside the container and is published to port 8004 on the host. Once the container is healthy you can access the API at http://localhost:8004.
- Copy
.env.exampleto.envand setAPP_ENV=local. Leave the Stripe keys blank to exercise the fake gateway. - Install Go 1.22 or newer.
- Run
make build && make run. - Issue HTTP requests against
http://localhost:8080using curl or your favourite API client.
All endpoints live under /v1 and accept/return JSON. Every JSON response includes a request_id field that can be used to correlate server logs with API calls.
| Method | Path | Description |
|---|---|---|
POST |
/v1/payments/intent |
Create a payment intent and transaction record. |
POST |
/v1/payments/confirm |
Confirm an intent with a payment method. |
POST |
/v1/payments/capture |
Capture an authorised payment (optionally partial). |
POST |
/v1/payments/refund |
Refund a captured payment (optionally partial). |
POST |
/v1/payments/void |
Void an uncaptured payment. |
POST |
/v1/payments |
List all transactions in chronological order. |
POST |
/v1/payments/{id} |
Retrieve a transaction by ID. |
POST |
/v1/checkout/session |
Create a Stripe Checkout session for a transaction. |
POST |
/v1/stripe/webhook |
Receive Stripe webhooks and sync transaction status. |
A simple health probe is available via POST /healthz.
The fake gateway (APP_ENV=local) lets you exercise the full lifecycle without touching real Stripe systems. The commands below use realistic but fake identifiers so you can copy/paste them as-is to understand request payloads. They target http://localhost:8080, which is the default when running the binary directly. If you're using Docker, replace the host and port with http://localhost:8004.
BASE_URL=${BASE_URL:-http://localhost:8080}
# python3 -m json.tool pretty-prints the JSON responses shown below.
# 1. Create a payment intent (returns transaction + client secret)
curl -sS -X POST "${BASE_URL}/v1/payments/intent" \
-H 'Content-Type: application/json' \
-d '{
"amount": 4200,
"currency": "usd",
"capture_method": "manual",
"merchant_order_id": "order-1001",
"metadata": {"customer_id": "cust_demo_42"}
}' | python3 -m json.tool
# 2. Confirm the intent with a fake payment method
curl -sS -X POST "${BASE_URL}/v1/payments/confirm" \
-H 'Content-Type: application/json' \
-d '{
"tx_id": "txn_demo_1001",
"payment_method": "pm_card_visa"
}' | python3 -m json.tool
# 3. Capture the authorised payment (omit amount to capture in full)
curl -sS -X POST "${BASE_URL}/v1/payments/capture" \
-H 'Content-Type: application/json' \
-d '{
"tx_id": "txn_demo_1001",
"amount": 2100
}' | python3 -m json.tool
# 4. Refund a portion of the captured payment
curl -sS -X POST "${BASE_URL}/v1/payments/refund" \
-H 'Content-Type: application/json' \
-d '{
"tx_id": "txn_demo_1001",
"amount": 1000
}' | python3 -m json.tool
# 5. Void another authorisation
curl -sS -X POST "${BASE_URL}/v1/payments/void" \
-H 'Content-Type: application/json' \
-d '{"tx_id": "txn_demo_2001"}' | python3 -m json.tool
# 6. Create a checkout session (returns hosted page URL)
curl -sS -X POST "${BASE_URL}/v1/checkout/session" \
-H 'Content-Type: application/json' \
-d '{
"tx_id": "txn_demo_1001",
"amount": 4200,
"currency": "usd",
"merchant_order_id": "order-1001",
"success_url": "https://merchant.example/success",
"cancel_url": "https://merchant.example/cancel"
}' | python3 -m json.tool
# 7. List every recorded transaction
curl -sS -X POST "${BASE_URL}/v1/payments" | python3 -m json.tool
# 8. Fetch a transaction to inspect its status
curl -sS -X POST "${BASE_URL}/v1/payments/txn_demo_1001" | python3 -m json.tool
# 9. Simulate a Stripe webhook callback (signature check disabled when secret is empty)
curl -sS -X POST "${BASE_URL}/v1/stripe/webhook" \
-H 'Content-Type: application/json' \
-d '{
"id": "evt_demo_1",
"type": "payment_intent.succeeded",
"data": {"object": {"id": "pi_fake_1"}}
}' | python3 -m json.toolThe repository also includes executable helpers under
ssh/that mirror each example (includinglist_transactions.sh) so you can run them without copy/pasting JSON payloads. OverrideBASE_URLwhen targeting a remote deployment, for exampleBASE_URL=http://localhost:8004 ./ssh/create_intent.shwhen hitting Docker. To run the full sequence with colour-coded output, executemake emulate-paymentor run the orchestrator directly viaBASE_URL=http://localhost:8004 ./ssh/init.sh.
Webhook testing against a real Stripe account can be performed with Stripe CLI by forwarding events to
/v1/stripe/webhookand ensuringSTRIPE_WEBHOOK_SECRETmatches your CLI signing secret.
Please feel free to fork this repository to contribute to it by submitting a functionalities/bugs-fixes pull request to enhance it.
There are many ways you would be able to support my open source work. There is not a right one to choose, so the choice is yours.
Nevertheless 😀, I would propose the following
- ⬆️ Follow me on Twitter.
- ⭐ Star the repository.
- 🤝 Open a pull request to fix/improve the codebase.
- ✍️ Open a pull request to improve the documentation.
- ☕ Buy me a coffee?
Thank you for reading this far. 😊