Skip to main content

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 mise or nvm).
  • 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-dev user (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

ServiceImagePortNotes
postgrespostgres:16-alpine5432payments DB, payments_app role
pubsub-emulatorgcr.io/google.com/cloudsdktool/cloud-sdk:emulators8085Pre-seeded topics
stripe-mockstripe/stripe-mock:latest12111OpenAPI-driven mock
paypal-mockghcr.io/melmastoon/paypal-mock:latest12112Internal Pact-driven mock
hesabpay-mockghcr.io/melmastoon/hesabpay-mock:latest12113Internal mock; mirrors HesabPay docs
webhook-simulatorghcr.io/melmastoon/webhook-simulator:latest12114UI to send signed webhook envelopes
redisredis:7-alpine6379Idempotency 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:

  1. Run pnpm cliauthorize --tenant tnt_dev_kabul --reservation rsv_dev_1 → returns pay_….
  2. In the simulator UI, choose vendor stripe, event payment_intent.succeeded, paste pay_… into metadata.
  3. 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-run pnpm db:provision-tenant <id> (the per-tenant role grants may have been wiped on a db:reset).
  • Webhook signature mismatch — confirm STRIPE_WEBHOOK_SECRET matches 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 than cardNumber (forbidden).
  • Pub/Sub emulator not picked up — ensure PUBSUB_EMULATOR_HOST is set; restart the worker.