PRODUCTO SAAS v1.0

SaaS Booking Platform

Online booking system with integrated payments for professionals and small service businesses.

Timeline

3 Weeks

Role

Full Stack Developer

Slide 1
1 / 19

The Problem

Many service professionals (classes, consultations, creative sessions) manage their bookings manually via WhatsApp, email, or unsynchronized calendars, creating errors, wasted time, and missed appointments.

  • Disorganized bookings across multiple channels.
  • Uncontrolled cancellations and no-shows.
  • Lack of upfront payments or deposits.
  • Generic tools poorly adapted to their real workflow.

The Solution

I designed and developed a SaaS platform that centralizes the management of bookings, clients, and payments in a single system, allowing professionals to automate their schedule and reduce operational friction.

Authentication and Access Control

Flexible and secure authentication system supporting multiple access methods and clearly separating Admin, Provider, and Client roles.

  • Login with Google OAuth, Email/Password, and Magic Links.
  • OTP verification for email registrations.
  • RBAC with differentiated portals.
  • Active abuse protections: rate limiting, CAPTCHA, and honeypots.

Advanced Booking Engine

Configurable booking engine allowing professionals to define services, availability, and complex rules without friction for the end client.

  • Flexible services: duration, price, capacity (1-on-1 or groups).
  • Hybrid availability with weekly rules and date exceptions.
  • Automatic buffers between appointments to avoid overlaps.
  • Concurrency management per service and global limits.

Google Calendar Sync

Two-way synchronization with Google Calendar to avoid double bookings and keep the schedule always updated.

  • Automatic import of external events.
  • Real-time blocking of busy times.
  • Automatic update upon cancellations or rescheduling.

Integrated Payments and Monetization

Complete payment system with Stripe allowing upfront charges, automatic payouts, and a scalable SaaS model.

  • Upfront payments to confirm bookings.
  • Stripe Connect Express for direct provider payouts.
  • Support for platform fees per transaction.
  • Monthly or annual subscription plans for providers.

Recurring Bookings Management

Native support for periodic bookings, ideal for classes, therapies, and continuous services.

  • Weekly or monthly configuration.
  • Automatic generation of future sessions.
  • Centralized management of cancellations and changes.

Internationalization (i18n)

Platform ready for international audiences with full multi-language support.

  • Interface fully translated to English and Spanish.
  • Automatic language detection via middleware.
  • Localized emails and error messages.

Automation and Notifications

Automation system reducing manual tasks and keeping clients and providers informed.

  • Automatic reminders 24h and 1h before the appointment.
  • Transactional emails for confirmations and cancellations.
  • Serverless cron jobs.

Analytics and Dashboard

Interactive dashboard for providers to understand their business performance.

  • Revenue and booking volume metrics.
  • Most popular services.
  • Clear visualizations using interactive charts.

Tech Stack

Next.js 14

Framework

TypeScript

Language

PostgreSQL

Database

Prisma

ORM

Auth.js

Auth

Stripe Connect

Payments

Resend

Email

TailwindCSS

Styling

Shadcn/UI

UI

Zod

Validation

React Hook Form

Forms

next-intl

i18n

Technical Challenges

Handling Webhook Idempotency

Ensuring Stripe webhooks are processed exactly once. Network retries could cause duplicate records if not handled correctly.

webhook-handler.ts
const handleWebhook = async (req, res) => {
  const event = req.body;
  
  // 1. Check Redis for Idempotency Key
  const isProcessed = await redis.get(event.id);
  if (isProcessed) {
    return res.status(200).send("Already processed");
  }

  // 2. Process Business Logic
  try {
    if (event.type === 'invoice.paid') {
      await updateInvoiceStatus(event.data.object);
    }
    
    // 3. Mark as processed for 24h
    await redis.set(event.id, 'processed', 'EX', 86400);
    res.json({ received: true });
  } catch (err) {
    console.error(err);
    res.status(500).send();
  }
};