LOCAL_DEV_SETUP — payment-gateway-service
Sibling: TESTING_STRATEGY · DEPLOYMENT_TOPOLOGY
This guide gets a developer fully productive on payment-gateway-service locally without ever talking to live vendor APIs. Card data never enters the local environment under any circumstance.
1. Prerequisites
- Node.js 20.11+ (managed via
miseornvm). - pnpm 9.x (root monorepo uses pnpm workspaces).
- Docker + Docker Compose v2.
- Postgres client (
psql) for ad-hoc inspection. - gcloud CLI authenticated to your
gm-payments-devuser (for Secret Manager dry-runs only — local dev does not touch live secrets).
2. One-shot bootstrap
cd services/payment-gateway-service
pnpm install
cp .env.example .env.local
docker compose -f docker-compose.dev.yml up -d
pnpm db:migrate:central
pnpm db:provision-tenant tnt_dev_kabul
pnpm db:provision-tenant tnt_dev_dubai
pnpm dev
pnpm dev starts the API on http://localhost:7081 and the worker on http://localhost:7082 (metrics endpoint).
3. docker-compose.dev.yml services
| Service | Image | Port | Notes |
|---|---|---|---|
postgres | postgres:16-alpine | 5432 | payments DB, payments_app role |
pubsub-emulator | gcr.io/google.com/cloudsdktool/cloud-sdk:emulators | 8085 | Pre-seeded topics |
stripe-mock | stripe/stripe-mock:latest | 12111 | OpenAPI-driven mock |
paypal-mock | ghcr.io/melmastoon/paypal-mock:latest | 12112 | Internal Pact-driven mock |
hesabpay-mock | ghcr.io/melmastoon/hesabpay-mock:latest | 12113 | Internal mock; mirrors HesabPay docs |
webhook-simulator | ghcr.io/melmastoon/webhook-simulator:latest | 12114 | UI to send signed webhook envelopes |
redis | redis:7-alpine | 6379 | Idempotency cache (optional in local) |
4. Environment variables
# Server
PORT=7081
WORKER_PORT=7082
LOG_LEVEL=debug
NODE_ENV=development
# Postgres (per-tenant schemas resolved at request time)
DATABASE_URL=postgres://payments_app:dev@localhost:5432/payments
DB_POOL_MIN=2
DB_POOL_MAX=10
# Pub/Sub emulator
PUBSUB_EMULATOR_HOST=localhost:8085
GOOGLE_CLOUD_PROJECT=gm-payments-dev
# Vendor mocks
STRIPE_BASE_URL=http://localhost:12111
STRIPE_API_KEY=sk_test_local
STRIPE_WEBHOOK_SECRET=whsec_test_local
PAYPAL_BASE_URL=http://localhost:12112
PAYPAL_CLIENT_ID=local
PAYPAL_CLIENT_SECRET=local
HESABPAY_BASE_URL=http://localhost:12113
HESABPAY_API_KEY=local
HESABPAY_WEBHOOK_SECRET=local
# FX
FX_PROVIDER=mock
FX_MOCK_DEFAULT_RATE=71.50
# IAM (local stub)
IAM_BASE_URL=http://localhost:7001
IAM_DEV_TENANT=tnt_dev_kabul
IAM_DEV_USER=usr_dev_admin
A separate .env.test is auto-loaded by Vitest with the same shape but isolated DB/Pub/Sub.
5. Common scripts
pnpm dev # API + worker with hot reload
pnpm test # unit + integration
pnpm test:watch # Vitest watch
pnpm db:migrate:central # apply central migrations
pnpm db:provision-tenant <tenantId> # create tnt_<id> schema, run tenant migrations
pnpm db:reset # destructive: drop and recreate DB
pnpm db:seed # demo data (synthetic guests, properties, transactions)
pnpm webhooks:simulate stripe payment_intent.succeeded --paymentId pay_…
pnpm cli # opens payments-admin-cli REPL
pnpm pci:scan # PCI hygiene scan
pnpm openapi:diff # diff against committed openapi.json
6. Webhook simulator
The webhook-simulator UI at http://localhost:12114 lets you send signed webhook envelopes for any vendor. It uses the same secrets configured in .env.local. Typical flow:
- Run
pnpm cli→authorize --tenant tnt_dev_kabul --reservation rsv_dev_1→ returnspay_…. - In the simulator UI, choose vendor
stripe, eventpayment_intent.succeeded, pastepay_…into metadata. - Click Send signed. The receiver applies the transition; logs and metrics in the API tail.
7. Card capture in local development
You never type a card number locally. Tokenization in dev uses processor mocks that return synthetic tokens (pm_local_visa, pm_local_mastercard, etc.). The seed script creates pre-tokenized payment methods for the demo guests. If you need a "fresh" token, run pnpm cli mint-token --kind card --brand visa.
8. Cash-on-arrival local flow
pnpm cli cash:receipt \
--tenant tnt_dev_kabul \
--property ppt_dev_1 \
--reservation rsv_dev_1 \
--amount 56000 \
--currency AFN \
--operator usr_dev_admin \
--shift shf_dev_1
This runs RecordCashPaymentUseCase end-to-end and emits transaction.captured.v1 to the local Pub/Sub emulator.
9. Connecting the desktop
The Electron desktop app lives in apps/desktop/ and points at http://localhost:7081 when NODE_ENV=development. Use the dev-issued JWT printed by pnpm cli login --tenant tnt_dev_kabul --user usr_dev_admin. The local SQLite cache is stored at <userData>/payments-local.sqlite and is wiped by pnpm desktop:reset.
10. Troubleshooting
permission denied for schema tnt_…— re-runpnpm db:provision-tenant <id>(the per-tenant role grants may have been wiped on adb:reset).- Webhook signature mismatch — confirm
STRIPE_WEBHOOK_SECRETmatches what the simulator UI is using. - PCI scanner fails — search the diff for forbidden field names; fixtures are scanned too. Use generic identifiers like
processorToken(allowed) rather thancardNumber(forbidden). - Pub/Sub emulator not picked up — ensure
PUBSUB_EMULATOR_HOSTis set; restart the worker.