Skip to content

Architecture

Architecture

QZPay follows a layered architecture designed for flexibility and maintainability.

Overview

┌─────────────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Hono Routes │ │ NestJS Module │ │ React Components│ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
└───────────┼─────────────────────┼─────────────────────┼──────────┘
│ │ │
┌───────────▼─────────────────────▼─────────────────────▼──────────┐
│ Core Services │
│ ┌──────────────────────────────────────────────────────────────┐│
│ │ QZPayBilling ││
│ │ ┌─────────┐ ┌─────────────┐ ┌─────────┐ ┌──────────────────┐││
│ │ │Customers│ │Subscriptions│ │Payments │ │Entitlements/Usage│││
│ │ └─────────┘ └─────────────┘ └─────────┘ └──────────────────┘││
│ └──────────────────────────────────────────────────────────────┘│
└───────────────────────────┬────────────────────────┬─────────────┘
│ │
┌───────────────────────────▼────────┐ ┌─────────────▼─────────────┐
│ Provider Adapters │ │ Storage Adapters │
│ ┌──────────────┐ ┌──────────────┐ │ │ ┌────────────────────────┐│
│ │ Stripe │ │ MercadoPago │ │ │ │ Drizzle ││
│ └──────────────┘ └──────────────┘ │ │ └────────────────────────┘│
└────────────────────────────────────┘ └───────────────────────────┘

Core Components

QZPayBilling

The central orchestrator that coordinates all billing operations:

const billing = createQZPayBilling({
paymentAdapter: stripeAdapter,
storage: drizzleStorage,
// Optional configuration
defaultCurrency: 'usd',
gracePeriodDays: 7
});

Responsibilities:

  • Coordinates between provider and storage
  • Emits billing events
  • Handles error recovery
  • Manages configuration

Provider Adapters

Provider adapters implement a common interface for payment providers:

interface PaymentProvider {
customers: CustomerAdapter;
subscriptions: SubscriptionAdapter;
payments: PaymentAdapter;
webhooks: WebhookAdapter;
// ...
}

Each adapter translates QZPay operations to provider-specific API calls.

Storage Adapters

Storage adapters persist billing data:

interface BillingStorage {
customers: CustomerRepository;
subscriptions: SubscriptionRepository;
payments: PaymentRepository;
// ...
}

Data Flow

Creating a Subscription

1. Application calls billing.subscriptions.create()
2. QZPayBilling validates input
3. Provider adapter creates subscription in Stripe/MP
4. Storage adapter persists subscription record
5. Event emitter fires 'subscription.created'
6. Application receives subscription object

Processing a Webhook

1. Webhook endpoint receives POST from provider
2. Webhook adapter verifies signature
3. Webhook adapter parses event type
4. QZPayBilling updates local state
5. Event emitter fires corresponding event
6. Application handlers react to event

Event System

QZPay uses an event-driven architecture:

// Built-in events
billing.on('customer.created', handler);
billing.on('subscription.created', handler);
billing.on('subscription.updated', handler);
billing.on('subscription.canceled', handler);
billing.on('payment.succeeded', handler);
billing.on('payment.failed', handler);
billing.on('invoice.paid', handler);

Error Handling

QZPay uses typed errors for precise error handling:

import {
QZPayError,
CustomerNotFoundError,
SubscriptionNotFoundError,
PaymentFailedError
} from '@qazuor/qzpay-core';
try {
await billing.subscriptions.create({ ... });
} catch (error) {
if (error instanceof CustomerNotFoundError) {
// Handle missing customer
} else if (error instanceof PaymentFailedError) {
// Handle payment failure
}
}

Configuration

Global configuration options:

const billing = createQZPayBilling({
paymentAdapter,
storage,
// Default currency for operations
defaultCurrency: 'usd',
// Production mode flag
livemode: process.env.NODE_ENV === 'production',
// Grace period before hard cancellation
gracePeriodDays: 7,
// Custom logger
logger: customLogger
});