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 objectProcessing 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 eventEvent System
QZPay uses an event-driven architecture:
// Built-in eventsbilling.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});