Back To ProjectsCase Study / 2026
Multi-Tenant Invoicing And Payment Platform

Reliable payment infrastructure for multi-domain SaaS.

I designed and developed a multi-tenant invoicing and payment platform for businesses operating across transport, restaurants, online payments, employee billing, and general invoicing.

Multi-tenant
schema-isolated SaaS architecture
Stripe
payment intents, refunds, disputes
Redis
queues, retries, distributed locks
Django
DRF APIs and tenant-aware workers
Multi-tenant invoicing system interface
Core challenge

Keep invoice, booking, refund, dispute, and payment records consistent while Stripe sends related webhook events almost simultaneously across multiple workers.

Overview

What I Built

The platform enables each tenant to manage customers, invoices, installments, payments, refunds, disputes, and payment workflows while keeping its data fully isolated from other tenants.

I was responsible for the complete development of the system, including architecture, database design, APIs, payment integrations, webhook processing, background workers, tenant isolation, deployment, and production reliability.

Problem

More Than Invoice Generation

The business needed one invoicing platform capable of supporting multiple organisations, each with its own users, customers, payment settings, and operational workflows.

The largest technical challenge was maintaining data consistency when multiple Stripe webhook events arrived almost simultaneously for the same payment or invoice.

Multi-tenant data isolation
One-time and installment-based invoices
Saved cards and manual payments
Payment authorisation and capture
Refunds and partial refunds
Payment disputes and chargebacks
Transport booking payments
Restaurant-related transactions
Website and employee-generated invoices
Asynchronous Stripe webhook events
Reliable processing during high traffic and concurrent events
Architecture

Schema-Isolated Multi-Tenancy

The system uses schema-based multi-tenancy with PostgreSQL and django-tenants. Each organisation has its own isolated PostgreSQL schema while shared platform-level information remains available in the public schema.

Incoming Request
Tenant Identification
Tenant Schema Context
Invoice / Customer / Payment Data
Demonstration

Tenant Isolation Model

Public schema01
Customers
Invoices
Payments
Tenant A schema02
Customers
Invoices
Payments
Tenant B schema03
Customers
Invoices
Payments
Payments

Invoice Lifecycle

Invoices can be created for standard customer payments, taxi and dispatch bookings, restaurant bookings, website orders, employee-generated transactions, manual payments, and installment plans.

Open
Pending
Authorised
Partially paid
Paid
Failed
Cancelled
Voided
Refunded
Partially refunded
Disputed
Stripe

Verified Payment Flow

A payment request does not immediately mark an invoice as paid. The invoice is updated only after the relevant Stripe webhook confirms that the payment has successfully completed.

Customer initiates payment
Stripe Payment Intent created
Stripe sends webhook
Webhook placed in Redis queue
Worker updates invoice status
Verified payment flow demonstration
Verified payment flow
Reliability

Webhook Race Conditions And Worker Coordination

Stripe can send several related events for the same payment within a very short time, including payment intent, charge, invoice, refund, and dispute events. In a multi-worker environment, those events may be picked up by different workers at the same time.

payment_intent.succeeded
charge.succeeded
charge.updated
invoice.payment_succeeded
Without coordination
  • Duplicate invoice updates
  • Multiple booking creation attempts
  • Repeated notifications
  • Incorrect payment status
  • Refund or capture conflicts
With Redis locks
  • One worker owns a payment lock
  • Duplicate events are idempotently skipped
  • Retries are safe after worker failure
  • Invoice state remains consistent
  • Domain workflows run once
payment_lock:{payment_intent_id}
invoice_lock:{invoice_uid}
trip_lock:{trip_id}
Architecture Summary

Queue-Based Processing

Stripe
FastAPI Webhook API
Redis Queue
Django Workers
Tenant Context
Transport / Restaurant / Invoice Workflows
Status / Notification / Refund Actions
Disputes

Chargeback-Aware Records

The platform processes Stripe dispute events and links each dispute to the relevant invoice or invoice installment. Missing webhook fields do not overwrite existing dispute values, which protects previously stored information during partial webhook updates.

charge.dispute.created
charge.dispute.updated
charge.dispute.closed
charge.dispute.funds_withdrawn
charge.dispute.funds_reinstated
Stripe dispute ID, charge ID, amount, currency, status, reason, evidence due date, payment method, invoice, and installment references
Stack

Technology Surface Area

Backend
PythonDjangoDjango REST FrameworkFastAPI
Database
PostgreSQLSchema-based multi-tenancyDjango ORM
Payments
Stripe Payment IntentsStripe WebhooksSaved payment methodsRefundsDisputesPre-authorisation
Async Processing
RedisRedis queues and streamsBackground workersDistributed locksIdempotency handling
Infrastructure
DockerNginxGitHub ActionsKubernetes / AKSLinux VPS
Monitoring
Structured loggingError trackingWorker monitoringWebhook failure trackingPayment audit history
Decisions

Key Engineering Choices

Process payments asynchronously

Webhook events are queued and processed outside the HTTP request to avoid timeouts and improve reliability.

Trust verified webhooks

Invoice status changes are based on Stripe webhook confirmation rather than only client responses.

Use distributed locks

Redis locks prevent concurrent workers from updating the same payment, invoice, trip, or installment.

Preserve idempotency

Every event can be processed safely more than once without producing duplicate business actions.

Isolate tenant data

Each organisation operates inside its own PostgreSQL schema while shared platform data remains centralised.

Separate domain workflows

Payment processing is centralised while transport, restaurant, website, employee, and general invoice logic remains modular.

Results

Production Outcome

The final system provides a reliable payment foundation for multiple products and business workflows. The queue-based architecture significantly improved reliability by preventing long-running webhook requests and reducing race conditions during concurrent payment events.

Multiple organisations on one platform
Secure tenant-level data isolation
Consistent invoice and payment states
Reliable Stripe webhook processing
Safe concurrent worker execution
Installment-based payments
Refund and dispute handling
Integration with transport and restaurant systems
Scalable background processing
Recovery from duplicate or retried events
What this demonstrates

Production-grade SaaS payment engineering.

Back To Projects