I implemented authentication and authorization as JWT, Argon2id, RBAC; app/api/v1/auth.py; app/services/auth_service.py; tests/test_auth.py.
I implemented diagnostic centres as Centre service and CRUD APIs; app/api/v1/centres.py; app/services/centre_service.py; tests/test_centres.py.
I implemented diagnostic tests as Test catalogue and CRUD APIs; app/api/v1/tests.py; app/services/test_service.py; tests/test_tests.py.
I implemented centre-test pricing as CentreTest mapping with centre-specific prices; app/models/centre_test.py; app/api/v1/centres.py; tests/test_centre_tests.py.
I implemented the booking lifecycle as Transactional booking service; app/api/v1/bookings.py; app/services/booking_service.py; tests/test_bookings.py.
I implemented payment processing as Simulated payment service; app/api/v1/payments.py; app/services/payment_service.py; tests/test_payments.py.
I implemented webhook processing as HMAC verification + idempotency; app/api/v1/payments.py; app/services/webhook_service.py; tests/test_webhooks.py.
I implemented concurrency safety as Database constraints + row locking; uq_bookings_active_user_slot; with_for_update; tests/test_concurrency.py.
I implemented rate limiting as SlowAPI rate limiting middleware; app/core/rate_limit.py; tests/test_rate_limit.py.
I implemented health monitoring as Database and Redis readiness checks; app/api/v1/health.py; tests/test_health.py.
I implemented containerization as Dockerfile and Docker Compose orchestration; Dockerfile; docker-compose.yml; entrypoint.sh; Docker environment.
I implemented database versioning as Alembic migrations; alembic/versions/001_initial_schema.py; alembic/.
I implemented catalogue caching as Catalogue caching with automated pattern-based invalidation upon administrative updates; app/services/cache_service.py; graceful database fallback when Redis is offline.
I protect sensitive endpoints with Fixed-window SlowAPI protection on /api/v1/auth/login, /api/v1/auth/signup, /api/v1/payments, and /api/v1/payments/webhook via app/core/rate_limit.py.
I implemented observability as Structured JSON logging via structlog; app/logging_config.py; X-Request-ID; X-Process-Time; automatic secret redaction.
My container operations use Dockerfile and Docker Compose orchestration with automated health check dependencies, migrations, and seeder execution.
I provide Dual /health and /api/v1/health probes validating live PostgreSQL and Redis connectivity; app/api/v1/health.py.
I verify concurrency with Dedicated multi-threaded and asynchronous race tests validating slot collisions, payment double-spends, and replay idempotency; tests/test_concurrency.py.
I secure webhooks with Cryptographic payload verification via HMAC-SHA256; app/core/security.py.
I intentionally exclude Celery/ARQ background processing is intentionally not included because the current payment simulation does not require asynchronous job processing.
In DiagPay, all state transitions execute synchronously within ACID database transactions.
I test SlowAPI threshold enforcement on authentication and payment endpoints in tests/test_rate_limit.py (1 test).
I test database and cache readiness probes in tests/test_health.py (2 tests).
I chose a decoupled CentreTest junction instead of a flat price field on DiagnosticTest because diagnostic pricing varies by centre facility, operating costs, and location.
I chose immutable price snapshotting instead of dynamically joining prices when generating invoices to protect financial audit trails and prevent catalogue price updates from retroactively changing past bookings.
I chose pessimistic locking with with_for_update over optimistic locking via a version column because financial state transitions require immediate serialization without client retry loops.
I chose Argon2id password hashing over legacy bcrypt; the project describes Argon2id as the Password Hashing Competition winner with superior resistance against GPU and ASIC attacks.
I chose shared-secret HMAC-SHA256 webhook verification over unsigned or IP-whitelisted webhooks to verify payload integrity and authenticity and reject untrusted or tampered payloads.
I chose a PostgreSQL partial unique index over application-level SELECT checks to prevent booking-slot race conditions at the ACID storage-engine level.
I chose soft deletion with is_active = false over cascading SQL DELETE to preserve referential integrity and historical records for completed patient bookings.
I chose graceful cache degradation over a hard dependency on Redis so the application can continue serving read traffic from PostgreSQL if Redis fails.
I use a relational PostgreSQL schema for DiagPay, enforced with primary keys, foreign-key constraints, and partial unique indexes. Its six entities are User, DiagnosticCentre, DiagnosticTest, CentreTest, Booking, and Payment.
The schema describes these relationships: User has many Booking; DiagnosticCentre and DiagnosticTest each have many CentreTest; CentreTest has many Booking; Booking has many Payment.
My schema uses UUID entity IDs; NUMERIC(10, 2) monetary amounts mapped to Python Decimal; floating-point arithmetic strictly prohibited.
For centre-test pricing, UNIQUE(centre_id, test_id) prevents duplicate pricing definitions for the same centre-test pair.
CREATE UNIQUE INDEX uq_bookings_active_user_slot
ON bookings (user_id, centre_id, test_id, appointment_at)
WHERE status IN ('PENDING', 'CONFIRMED');
The partial index means PENDING and CONFIRMED bookings block duplicate same-user slots; CANCELLED and FAILED bookings allow re-booking.
The schema records User.email and Payment.event_id unique; Payment.raw_payload JSONB; schema includes timestamps, activity and availability booleans, centre contact details, test descriptions and preparation notes.
The schema status values are User.role: USER | ADMIN; Booking.status: PENDING|CONFIRMED|FAILED|CANCELLED; Payment.status: SUCCESS|FAILED.
I built DiagPay, a production-oriented backend for diagnostic healthcare appointment booking and simulated payments. It brings patient scheduling, centre-specific test pricing, administrative controls, and asynchronous payment updates together.
My DiagPay repository is Python; hosted on @AnirudhChhabra54's GitHub; started 2026-10, last updated 2026-10.
DiagPay provides authentication, diagnostic centre and test management, centre-specific pricing, appointment bookings, simulated payments, and secure webhook processing.
Patients discover diagnostic centres, inspect available laboratory tests with centre-specific pricing, schedule appointments for future time slots, and initiate payment simulations.
External payment gateways push transaction updates asynchronously via signed webhooks; strict deduplication prevents duplicate state transitions.
Healthcare administrators manage centres, catalogued tests, availability flags, and pricing schedules with automatic cache invalidation.
I keep HTTP transport separate from business logic: routers parse requests and return status codes, dependencies provide shared database and authorization handling, services own domain rules and transactions, and models define the data layer.
The routers in app/api/v1/ are pure controllers responsible only for HTTP parameter parsing, status codes, and invoking domain services.
The reusable injection layer in app/dependencies.py handles database sessions, JWT verification, and Role-Based Access Control (USER vs ADMIN).
My domain services in app/services/ encapsulate all business logic, validation invariants, transaction boundaries, and row-level locks.
My data layer in app/models/ uses SQLAlchemy 2.0 type-annotated declarative models with PostgreSQL-specific constraints.
Pricing is modeled through a CentreTest junction table rather than hardcoding price on DiagnosticTest; cost depends on diagnostic centre location, equipment, and overhead.
When I create a booking, the current CentreTest.price is permanently snapshotted into Booking.amount; later centre rate changes leave past and pending booking amounts unaffected.
My booking creation flow is POST /api/v1/bookings with centre_id, test_id, and future appointment_at; validates centre and test active and available; snapshots CentreTest.price into Booking.amount; inserts PENDING; returns 201 Created.
I validate appointments using appointment_at > utcnow() + 60s; 400 Bad Request.
I protect concurrent active booking slots with uq_bookings_active_user_slot; 409 Conflict.
My simulated payment flow is POST /api/v1/payments with booking_id and simulate_result; row lock; inserts payment; transitions booking to CONFIRMED or FAILED; returns 201 Created.
I authenticate webhooks with X-Webhook-Signature; constant-time HMAC-SHA256 comparison.
For an identical webhook replay, I return 200 OK; idempotent: true; no duplicate write.
For conflicting reuse of a webhook event ID, I return 409 Conflict; EVENT_PAYLOAD_MISMATCH; compares amount, status, payment_id.
My pending-payment transitions are PENDING to CONFIRMED on successful payment or webhook; PENDING to FAILED on failed payment or webhook; declined payment releases slot for rebooking.
My cancellation behavior is PENDING to CANCELLED before payment; CONFIRMED to CANCELLED subject to booking rules; refund processing is outside the current simulation.
I treat cancelled bookings as terminal: CANCELLED is terminal; confirmation rejected with HTTP 409.
I reject failed events against confirmed bookings: CONFIRMED to FAILED rejected; stale/out-of-order failed events will not overturn a confirmed booking.
My Redis fallback behavior is Cache calls fall back to PostgreSQL; 200 OK (Degraded).
My financial state transitions use SELECT ... FOR UPDATE row locks within atomic database transactions to prevent race conditions during simultaneous payment attempts or webhook replays.
The README reports 53/53 tests passing, 100% pass rate, 75% overall coverage.
I organize DiagPay’s verification around focused test modules, from authentication and catalogue behavior through payments, webhooks, concurrency, rate limiting, and health checks. The test suite complements my database constraints and transaction safeguards: it exercises both ordinary application flows and race-sensitive state changes.
I report 75% overall codebase coverage for DiagPay.
# Run the entire test suite
pytest -v
# Run with line-by-line coverage
pytest --cov=app --cov-report=term-missing
I group signup, lowercase normalization, password strength, Argon2 hashing, JWT login, profile retrieval, and duplicate-email rejection in tests/test_auth.py (6 tests).
I test centre listing, pagination, centre details, admin creation and updates, admin soft-deletion, and RBAC 403 checks in tests/test_centres.py (7 tests).
I test diagnostic catalogue retrieval, test creation, admin authorization guards, and soft deactivation in tests/test_tests.py (6 tests).
I test centre-test offering retrieval, price mapping, availability toggling, and unmapping in tests/test_centre_tests.py (7 tests).
I test appointment creation, future-date validation, inactive centre/test prevention, slot collisions, user-data isolation, and idempotent cancellation in tests/test_bookings.py (9 tests).
I test successful and failed payment simulations, double-confirmation prevention, and cancelled-booking payment protection in tests/test_payments.py (5 tests).
I test HMAC-SHA256 signature verification, event ingestion, duplicate replay idempotency, payload-tampering detection, and cancelled-booking protection in tests/test_webhooks.py (6 tests).
I test simultaneous booking-slot collisions, double-payment races, concurrent identical webhook delivery, and the state-machine transition matrix in tests/test_concurrency.py (4 tests).
The README says: Core application flows, concurrency edge cases, payment simulations, and webhook idempotency behaviors are verified through automated tests running against an async database.
The README says the entire stack boots with health checks, automated schema migrations (alembic upgrade head), and data seeding in a single command.
The README describes an OpenAPI 3.0 (Swagger UI) reference with 25 operations across 16 unique paths, including request and response schemas, bearer-token authentication, and status codes.
The supplied README excerpt describes the verification artifacts but does not include the artifacts themselves. It also leaves implementation specifics such as the database, payment gateway, webhook-signature scheme, cache technology, and concrete endpoint paths unspecified.
I recommend Docker; docker compose up --build -d starts API, PostgreSQL 16, and Redis 7 in background.
At startup, the API waits for database readiness; automatically executes migrations and seeder; launches Uvicorn on port 8000.
git clone https://github.com/AnirudhChhabra54/DiagPay.git
cd DiagPay
docker compose up --build -d
docker compose ps
curl -s http://localhost:8000/health | python3 -m json.tool
docker compose down
My interactive API documentation is available at http://localhost:8000/docs; http://localhost:8000/redoc; http://localhost:8000/openapi.json.
My centre and test listings are cached and paginated, and centre details are cached; centre updates invalidate cache.
My booking payment transition is PENDING to CONFIRMED on successful simulated payment.
My webhook uses X-Webhook-Signature HMAC-SHA256; replay returns HTTP 200 without duplicate records.
My health endpoints are described this way: /api/v1/health is system health; /health checks PostgreSQL and Redis readiness.
I document two payment paths: an in-app simulated payment and an external-provider webhook. The walkthrough creates a separate pending booking for each, signs the webhook payload, and replays the identical request to check idempotency.
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login -H "Content-Type: application/json" -d '{"email":"user@diagpay.com","password":"UserPass123!"}' | jq -r .access_token)
CENTRE_ID=$(curl -s -X GET http://localhost:8000/api/v1/centres -H "Authorization: Bearer $TOKEN" | jq -r '.items[0].id')
TEST_OFFERING=$(curl -s -X GET "http://localhost:8000/api/v1/centres/$CENTRE_ID/tests" -H "Authorization: Bearer $TOKEN" | jq -r '.items[0]')
TEST_ID=$(echo "$TEST_OFFERING" | jq -r .test_id)
PRICE=$(echo "$TEST_OFFERING" | jq -r .price)
For local setup, my prerequisites are Python 3.12+, PostgreSQL running locally, Redis optional.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
alembic upgrade head
python3 -m app.scripts.seed
uvicorn app.main:app --reload --port 8000
I configure application variables via .env; complete template provided in .env.example.
For production, my rule is: Production deployments must inject cryptographically secure secrets via secret managers.
The example token lifetime and rate limits are 120 minutes; default 100/minute; auth 15/minute; payment 30/minute; webhook 60/minute.
My seeded catalogue contains 3 diagnostic centres, 4 diagnostic tests, 12 centre-test mappings.
My seeded demo accounts are Administrator admin@diagpay.com / AdminPass123! with full CRUD on centres, tests, mappings and view all bookings; Regular User user@diagpay.com / UserPass123! can book appointments, simulate payments, and view own records.
My seeded centres and tests are Apex Diagnostic Hub, Metro PathLabs, Suburban Health Care; Complete Blood Count (CBC), Lipid Profile, Liver Function Test (LFT), HbA1c Diabetes Screen.
My example CBC prices are ₹350.00 at Apex, ₹400.00 at Metro, ₹320.00 at Suburban.
I need Docker Desktop installed and running before using the Docker setup.
My Docker setup starts PostgreSQL 16 and Redis 7 with container health checks.
My example configuration sets JWT_ALGORITHM to HS256, a symmetric signature algorithm for JWT.