Skip to main content

Payment Service Deep Dive

Overview

The Payment Service provides a provider-agnostic payment processing layer. It uses the Strategy pattern through PaymentProviderFactory to delegate all operations to a concrete provider (Stripe, Polar, etc.) while exposing a unified API to the rest of the application.

Source Files

FilePath
Payment Servicetemplate/lib/payment/lib/payment-service.ts
Provider Factorytemplate/lib/payment/lib/payment-provider-factory.ts
Service Managertemplate/lib/payment/lib/payment-service-manager.ts
Typestemplate/lib/payment/types/payment-types.ts
Stripe Providertemplate/lib/payment/lib/providers/stripe-provider.ts
Payment Hooktemplate/lib/payment/hooks/use-payment.tsx
Payment Email Servicetemplate/lib/payment/services/payment-email.service.ts

Architecture

React Components / API Routes
|
PaymentService (unified API)
|
PaymentProviderInterface (strategy interface)
|
┌─────┼──────────────┐
│ │ │
Stripe Polar LemonSqueezy
Provider Provider Provider

The PaymentService constructor accepts a provider name and config, then creates the appropriate provider via PaymentProviderFactory.

Configuration

interface PaymentServiceConfig {
provider: SupportedProvider; // 'stripe' | 'solidgate' | 'lemonsqueezy' | 'polar'
config: PaymentProviderConfig;
}

interface PaymentProviderConfig {
apiKey: string;
webhookSecret?: string;
secretKey?: string;
options?: Record<string, any>;
}

Payment Plans

enum PaymentPlanId {
FREE = "1",
ONE_TIME = "2",
SUBSCRIPTION = "3",
PREMIUM = "4",
}

interface PaymentPlan {
id: PaymentPlanId;
amount: number;
isSubscription: boolean;
features: string[];
}

Method Reference

Customer Management

hasCustomerId(user: User | null): boolean

Checks if the user object contains a customer ID for the active payment provider.

getCustomerId(user: User | null): Promise<string | null>

Retrieves the customer ID from the user's metadata.

createCustomer(params: CreateCustomerParams): Promise<CustomerResult>

Creates a new customer record in the payment provider.

Parameters:

{
email: string;
name?: string;
metadata?: Record<string, any>;
}

Returns: { id, email, name?, metadata? }

Payment Intents

createSetupIntent(user: User | null): Promise<SetupIntent>

Creates a setup intent for saving payment methods without charging.

createPaymentIntent(params: CreatePaymentParams): Promise<PaymentIntent>

Creates a payment intent for a one-time charge.

Parameters:

{
amount: number;
currency: string;
metadata?: Record<string, any>;
customerId?: string;
productId?: string;
successUrl?: string;
cancelUrl?: string;
}

Returns:

{
id: string;
amount: number;
currency: string;
status: string;
clientSecret?: string;
customerId?: string;
}

confirmPayment(paymentId: string, paymentMethodId: string): Promise<PaymentIntent>

Confirms a previously created payment intent with a specific payment method.

verifyPayment(paymentId: string): Promise<PaymentVerificationResult>

Checks the status of a payment.

Returns: { isValid: boolean; paymentId: string; status: string; details?: any }

Subscriptions

createSubscription(params: CreateSubscriptionParams): Promise<SubscriptionInfo>

Creates a recurring subscription.

Parameters:

{
customerId: string;
priceId: string;
paymentMethodId?: string;
trialPeriodDays?: number;
metadata?: Record<string, any>;
}

cancelSubscription(subscriptionId: string, cancelAtPeriodEnd?: boolean): Promise<SubscriptionInfo>

Cancels a subscription. If cancelAtPeriodEnd is true (default), the subscription remains active until the current period ends.

updateSubscription(params: UpdateSubscriptionParams): Promise<SubscriptionInfo>

Updates subscription parameters (price, cancellation schedule, metadata).

Webhooks

handleWebhook(payload, signature, rawBody?, timestamp?, webhookId?): Promise<WebhookResult>

Processes an incoming payment webhook. Signature verification is handled by the provider.

Returns: { received: boolean; type: string; id: string; data?: any }

Refunds

refundPayment(paymentId: string, amount?: number): Promise<any>

Issues a full or partial refund. If amount is omitted, the full payment is refunded.

Client Configuration

getClientConfig(): ClientConfig

Returns the public key and gateway identifier for frontend integration.

{
publicKey: string;
paymentGateway: 'stripe' | 'solidgate' | 'lemonsqueezy' | 'polar';
options?: Record<string, any>;
}

getUIComponents(): UIComponents

Returns provider-specific UI components including the payment form React component, logo, card brand icons, supported payment methods, and translations.

Subscription Statuses

enum SubscriptionStatus {
INCOMPLETE = 'incomplete',
INCOMPLETE_EXPIRED = 'incomplete_expired',
TRIALING = 'trialing',
ACTIVE = 'active',
PAST_DUE = 'past_due',
CANCELED = 'canceled',
UNPAID = 'unpaid',
}

Webhook Event Types

The service handles a comprehensive set of webhook events:

  • Payment events: payment_succeeded, payment_failed, payment_intent_succeeded, payment_intent_failed
  • Subscription events: subscription_created, subscription_updated, subscription_cancelled, subscription_trial_ending
  • Invoice events: invoice_paid, invoice_payment_failed
  • Refund events: refund_succeeded, refund_created
  • Billing portal events: session created, expired, updated, deleted

Utility Functions

formatCentsToCurrency(cents, currency?, locale?): string

Converts cents to a formatted currency string (e.g., 1999 to $19.99).

convertCentsToDecimal(cents): number

Converts cents to decimal (e.g., 1999 to 19.99).

convertDecimalToCents(decimal): number

Converts decimal to cents (e.g., 19.99 to 1999).

safeTimestampToDate(timestamp): Date | undefined

Safely converts a Unix timestamp (seconds or milliseconds) to a Date object. Handles null, undefined, and invalid values.

Error Handling

All methods delegate to the provider implementation. Errors from Stripe or other providers propagate to the caller. The service does not add additional error handling -- this is by design to let the API layer handle HTTP-specific error responses.

Usage Examples

import { PaymentService } from '@/lib/payment/lib/payment-service';

const paymentService = new PaymentService({
provider: 'stripe',
config: {
apiKey: process.env.STRIPE_SECRET_KEY!,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
},
});

// Create a customer
const customer = await paymentService.createCustomer({
email: 'user@example.com',
name: 'Jane Doe',
});

// One-time payment
const intent = await paymentService.createPaymentIntent({
amount: 2999,
currency: 'usd',
customerId: customer.id,
});

// Create subscription
const subscription = await paymentService.createSubscription({
customerId: customer.id,
priceId: 'price_xxx',
trialPeriodDays: 7,
});

// Handle webhook
const result = await paymentService.handleWebhook(
payload,
request.headers.get('stripe-signature')!
);