Skip to content

bx0518/reservation-payment-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Reservation & Payment Hold API

A backend system that demonstrates atomic resource reservation, payment authorization (hold), automatice expiration, and idempotent payment handling using Stripe.


Features

  • Atomic Reservation + Payment Hold
    • A resource is only reserved if a payment hold succeeds.
    • No unpaid or dangling reservations.
  • Stripe Manual Capture Workflow
    • PaymentIntent created with capture_method: manual.
    • Payment is captured only after reservation confirmation.
  • Automatic Reservation Expiry
    • Pending reservations expire after a configurable TTL.
    • Expired reservations automatically release the resource and payment hold.
  • Idempotent Payment Creation
    • Stripe idempotency keys prevent duplicate PaymentIntents.
  • Database Concurrency Safety
    • Uses row-level locking and partial indexes to prevent double booking.

Tech Stack

  • Node.js
  • Express.js
  • PostgreSQL
  • Stripe Payment Integration
  • Node-cron
  • Docker + docker-compose
  • Zod Validation

Project Structure

src/
├── app.js
├── server.js
├── db/
│   ├── 001_migrations.sql
│   ├── seed.sql
│   └── index.js
├── routes/
│   └── reservations.routes.js
├── controllers/
│   └── reservations.controller.js
├── services/
│   ├── reservations.service.js
│   └── payments.service.js
├── jobs/
│   ├── index.js
│   └── expireReservations.job.js
├── middleware/
│   ├── validate.js
│   ├── errorHandler.js
│   └── notFound.js
├── validators/
│   └── reservations.schema.js
└── utils/
    └── time.js

Setup

  1. Clone & Install
git clone <repo-url>
cd reservation-api
npm install
  1. Environment Variables

Create a .env file based on .env.example and configure the required environment variables with your own values.

  1. Start PostgreSQL via Docker
docker compose up --build
  1. Copy file into DB container
docker cp src/db/001_migrations.sql reservation-db:/001_migrations.sql
  1. Run Database Migrations
docker compose exec db psql -U postgres -d reservation_db -f 001_migrations.sql

API Endpoints

Create Reservation (with Payment Hold)

POST /reservations

{
  "userId": "uuid",
  "resourceId": "uuid"
}

Response

{
  "reservation": {
    "id": "uuid",
    "userId": "uuid",
    "resourceId": "uuid",
    "status": "pending",
    "expiresAt": "2026-01-30T12:00:00Z"
  },
  "payment": {
    "status": "on_hold",
    "provider": "stripe",
    "providerRef": "pi_xxx"
  }
}

Confirm Reservation (Capture Payment)

POST /reservations/:id/confirm

  • Captures the Stripe PaymentIntent.
  • Updates reservation status to confirmed.

Cancel Reservation (Release Payment Hold)

POST /reservations/:id/delete

  • Cancels the reservation.
  • Releases the Stripe payment hold.

Reservation Lifecycle

                  PENDING   →   CONFIRMED
                    ↓
(cron job)    →   EXPIRED
                    ↓
                  CANCELLED

Rules:

  • A reservation starts as pending only after a payment hold succeeds.
  • Pending reservations expire automatically after TTL.
  • Only pending reservations can be confirmed or cancelled.

Background Job: Expire Reservations

Runs every minutes using node-cron.

cron.schedule("* * * * *", async () => {
  await expireReservations();
});

The jobs:

  1. Finds expired pending reservations.
  2. Releases Stripe payment holds.
  3. Marks reservations as expired.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors