Payment Integration Overview
This guide provides a hands-on walkthrough of the Ever Works payment system. It covers the provider abstraction layer, how to configure each provider, the checkout and subscription lifecycle, feature gating, and webhook handling.
Provider Architecture at a Glance
The payment system is built on a provider-agnostic abstraction. Every payment provider implements the same PaymentProviderInterface, and a factory pattern lets you switch providers without changing application code.
lib/payment/
index.ts # Public API exports
config/
provider-configs.ts # Provider configuration factory
payment-provider-manager.ts # Singleton manager + ConfigManager
validation.ts # Input validation utilities
guards/
feature.guard.tsx # Plan-based feature gating
hooks/
use-payment.tsx # React context + usePayment hook
lib/
payment-provider-factory.ts # Factory for creating providers
payment-service.ts # Service wrapping the active provider
payment-service-manager.ts # Singleton for service lifecycle
providers/
stripe-provider.ts
lemonsqueezy-provider.ts
polar-provider.ts
solidgate-provider.ts
client/
payment-account-client.ts # Client-side account API
utils/
prices.ts # Price formatting utilities
polar-subscription-helpers.ts
services/
payment-email.service.ts # Email notifications on payment events
types/
payment-types.ts # Core type definitions
payment.ts # Payment flow and submission types
ui/
stripe/stripe-elements.tsx
lemonsqueezy/lemonsqueezy-elements.tsx
polar/polar-elements.tsx
solidgate/solidgate-elements.tsx
Supported Providers
| Provider | One-Time Payments | Subscriptions | Trials | Webhooks | Merchant of Record |
|---|---|---|---|---|---|
| Stripe | Yes | Yes | Yes | Yes | No |
| LemonSqueezy | Yes | Yes | Yes | Yes | Yes |
| Polar | Yes | Yes | Yes | Yes | No |
| Solidgate | Yes | Yes | No | Yes | No |
Core Interfaces
PaymentProviderInterface
Every provider implements this interface, defined in lib/payment/types/payment-types.ts:
// lib/payment/types/payment-types.ts
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
handleWebhook(payload: any, signature: string, rawBody?: string,
timestamp?: string, webhookId?: string): Promise<WebhookResult>;
// Refunds
refundPayment(paymentId: string, amount?: number): Promise<any>;
// Client-side configuration
getClientConfig(): ClientConfig;
getUIComponents(): UIComponents;
}
Key Data Types
// Subscription statuses
export enum SubscriptionStatus {
INCOMPLETE = 'incomplete',
INCOMPLETE_EXPIRED = 'incomplete_expired',
TRIALING = 'trialing',
ACTIVE = 'active',
PAST_DUE = 'past_due',
CANCELED = 'canceled',
UNPAID = 'unpaid',
}
// Payment types
export enum PaymentType {
ONE_TIME = 'one_time',
SUBSCRIPTION = 'subscription',
FREE = 'free',
}
// Supported providers
export type SupportedProvider = 'stripe' | 'solidgate' | 'lemonsqueezy' | 'polar';
Quick Setup
Step 1: Set Environment Variables
Each provider requires API keys and webhook secrets. Add them to .env.local:
# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
# LemonSqueezy
LEMONSQUEEZY_API_KEY=...
LEMONSQUEEZY_WEBHOOK_SECRET=...
LEMONSQUEEZY_STORE_ID=...
# Polar
POLAR_ACCESS_TOKEN=...
POLAR_WEBHOOK_SECRET=...
POLAR_ORGANIZATION_ID=...
# Solidgate
SOLIDGATE_API_KEY=...
SOLIDGATE_SECRET_KEY=...
SOLIDGATE_WEBHOOK_SECRET=...
SOLIDGATE_MERCHANT_ID=...
NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY=...
Step 2: Configure Pricing Plans
Pricing plans are defined in your .content/.works/works.yml:
pricing:
provider: stripe # Default provider
currency: USD
plans:
FREE:
id: free
name: Free
description: Basic access
price: 0
features:
- "List your product"
- "Basic analytics"
STANDARD:
id: standard
name: Standard
description: Enhanced features
price: 9
stripePriceId: price_xxx
annualDiscount: 20
features:
- "Everything in Free"
- "Priority listing"
- "Advanced analytics"
PREMIUM:
id: premium
name: Premium
description: Full access
price: 29
stripePriceId: price_yyy
annualDiscount: 25
isPremium: true
features:
- "Everything in Standard"
- "Featured placement"
- "API access"
Step 3: Set Up Webhooks
Each provider has a dedicated webhook endpoint:
| Provider | Webhook URL |
|---|---|
| Stripe | /api/stripe/webhook |
| LemonSqueezy | /api/lemonsqueezy/webhook |
| Polar | /api/polar/webhook |
| Solidgate | /api/solidgate/webhook |
Configure these URLs in each provider's dashboard, pointing to your deployed domain.