Payment Library
The template implements a multi-provider payment system using the Factory and Strategy patterns. It supports Stripe, LemonSqueezy, Solidgate, and Polar as payment providers, with a unified interface for payments, subscriptions, webhooks, and refunds.
Architecture Overview
Source Files
| File | Purpose |
|---|---|
lib/payment/index.ts | Public API exports |
lib/payment/lib/payment-provider-factory.ts | Factory for creating provider instances |
lib/payment/lib/payment-service.ts | Unified service facade |
lib/payment/lib/payment-service-manager.ts | Singleton manager for service lifecycle |
lib/payment/types/payment-types.ts | Core interfaces and enums |
lib/payment/types/payment.ts | Payment flow and submission types |
lib/payment/config/ | Provider configuration and validation |
lib/payment/lib/providers/ | Individual provider implementations |
lib/payment/hooks/ | React hooks for client-side payment flows |
lib/payment/ui/ | Payment form components |
Core Interfaces
PaymentProviderInterface
Every provider implements this comprehensive interface:
export interface PaymentProviderInterface {
// Payment operations
createPaymentIntent(params: CreatePaymentParams): Promise<PaymentIntent>;
confirmPayment(paymentId: string, paymentMethodId: string): Promise<PaymentIntent>;
verifyPayment(paymentId: string): Promise<PaymentVerificationResult>;
createSetupIntent(user: User | null): Promise<SetupIntent>;
// Subscription management
createCustomer(params: CreateCustomerParams): Promise<CustomerResult>;
createSubscription(params: CreateSubscriptionParams): Promise<SubscriptionInfo>;
cancelSubscription(subscriptionId: string, cancelAtPeriodEnd?: boolean): Promise<SubscriptionInfo>;
updateSubscription(params: UpdateSubscriptionParams): Promise<SubscriptionInfo>;
hasCustomerId(user: User | null): boolean;
getCustomerId(user: User | null): Promise<string | null>;
// Webhooks and refunds
handleWebhook(payload: any, signature: string, ...args: any[]): Promise<WebhookResult>;
refundPayment(paymentId: string, amount?: number): Promise<any>;
// Client configuration and UI
getClientConfig(): ClientConfig;
getUIComponents(): UIComponents;
}
PaymentProviderFactory
Creates provider instances based on configuration:
export type SupportedProvider = 'stripe' | 'solidgate' | 'lemonsqueezy' | 'polar';
export class PaymentProviderFactory {
static createProvider(
providerType: SupportedProvider,
config: PaymentProviderConfig
): PaymentProviderInterface {
switch (providerType) {
case 'stripe': return new StripeProvider(config);
case 'solidgate': return new SolidgateProvider(config);
case 'lemonsqueezy': return new LemonSqueezyProvider(config);
case 'polar': return new PolarProvider(config);
default: throw new Error(`Unsupported payment provider: ${providerType}`);
}
}
}
PaymentService
The PaymentService class provides a unified facade over all provider operations:
export class PaymentService {
private provider: PaymentProviderInterface;
constructor(config: PaymentServiceConfig) {
this.provider = PaymentProviderFactory.createProvider(config.provider, config.config);
}
// All methods delegate to the underlying provider
async createPaymentIntent(params: CreatePaymentParams): Promise<PaymentIntent> {
return this.provider.createPaymentIntent(params);
}
async createSubscription(params: CreateSubscriptionParams): Promise<SubscriptionInfo> {
return this.provider.createSubscription(params);
}
// ... additional delegated methods
}
Data Types
Payment Enums
export enum PaymentType {
ONE_TIME = 'one_time',
SUBSCRIPTION = 'subscription',
FREE = 'free',
}
export enum SubscriptionStatus {
INCOMPLETE = 'incomplete',
INCOMPLETE_EXPIRED = 'incomplete_expired',
TRIALING = 'trialing',
ACTIVE = 'active',
PAST_DUE = 'past_due',
CANCELED = 'canceled',
UNPAID = 'unpaid',
}
export enum PaymentFlow {
PAY_AT_START = "pay_at_start",
PAY_AT_END = "pay_at_end",
}
Webhook Events
export enum WebhookEventType {
PAYMENT_SUCCEEDED = 'payment_succeeded',
PAYMENT_FAILED = 'payment_failed',
SUBSCRIPTION_CREATED = 'subscription_created',
SUBSCRIPTION_UPDATED = 'subscription_updated',
SUBSCRIPTION_CANCELLED = 'subscription_cancelled',
INVOICE_PAID = 'invoice_paid',
REFUND_CREATED = 'refund_created',
// ... additional event types
}
Key Data Structures
| Type | Purpose |
|---|---|
PaymentIntent | Payment session with id, amount, currency, status, clientSecret |
SubscriptionInfo | Subscription details with status, period end, trial info |
CustomerResult | Created customer with id, email, name |
WebhookResult | Processed webhook with type, id, data |
ClientConfig | Frontend-safe config with publicKey and gateway type |
UIComponents | React components and visual assets for the provider |
Currency Utilities
The library includes helper functions for currency formatting:
// Format cents to display currency
export function formatCentsToCurrency(
cents: number, currency: string = 'USD', locale: string = 'en-US'
): string {
const amount = cents / 100;
return new Intl.NumberFormat(locale, {
style: 'currency', currency,
minimumFractionDigits: 2, maximumFractionDigits: 2,
}).format(amount);
}
// Convert between cents and decimal
export function convertCentsToDecimal(cents: number): number;
export function convertDecimalToCents(decimal: number): number;
// Convert timestamps to Date objects
export function convertNumberToDate(timestamp?: number): Date | null;
export function safeTimestampToDate(timestamp: number | null | undefined): Date | undefined;
Payment Flow Types
The system supports two submission payment flows:
| Flow | Enum | Description |
|---|---|---|
| Pay at Start | PAY_AT_START | Payment required before submission review |
| Pay at End | PAY_AT_END | Payment collected after admin approval |
Submission Status Lifecycle
UI Components Interface
Each provider exposes UI components for frontend integration:
export interface UIComponents {
PaymentForm: React.ComponentType<PaymentFormProps>;
logo: string;
cardBrands: CardBrandIcon[];
supportedPaymentMethods: string[];
translations: Record<string, Record<string, string>>;
}
Client-Side Integration
The usePayment hook and PaymentProvider context provide React integration:
import { usePayment, PaymentProvider } from '@/lib/payment';
// Wrap your app with the payment provider
<PaymentProvider>
<PaymentForm
amount={2999}
currency="usd"
isSubscription={false}
onSuccess={(paymentId) => console.log('Paid:', paymentId)}
onError={(error) => console.error('Failed:', error)}
/>
</PaymentProvider>
Provider Configuration
export interface PaymentProviderConfig {
apiKey: string;
webhookSecret?: string;
secretKey?: string;
options?: Record<string, any>;
}
Each provider requires at minimum an apiKey. Stripe and Solidgate also use webhookSecret for webhook signature verification.