Files
myeasycms-v2/docs/billing/paddle.mdoc
Giancarlo Buomprisco 7ebff31475 Next.js Supabase V3 (#463)
Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
2026-03-24 13:40:38 +08:00

475 lines
14 KiB
Plaintext

---
status: "published"
label: 'Paddle'
title: 'Configuring Paddle Billing | Next.js Supabase SaaS Kit Turbo'
order: 4
description: 'Complete guide to integrating Paddle billing with your Next.js Supabase SaaS application. Learn how to set up payment processing, webhooks, and subscription management with Paddle as your Merchant of Record.'
---
Paddle is a comprehensive billing solution that acts as a Merchant of Record (MoR), handling all payment processing, tax calculations, compliance, and regulatory requirements for your SaaS business.
This integration eliminates the complexity of managing global tax compliance, PCI requirements, and payment processing infrastructure.
## Overview
This guide will walk you through:
- Setting up Paddle for development and production
- Configuring webhooks for real-time billing events
- Creating and managing subscription products
- Testing the complete billing flow
- Deploying to production
## Limitations
Paddle currently supports flat and per-seat plans. Metered subscriptions are not supported with Paddle.
## Prerequisites
Before starting, ensure you have:
- A Paddle account (sandbox for development, live for production)
- Access to your application's environment configuration
- A method to expose your local development server (ngrok, LocalTunnel, Localcan, etc.)
## Step 0: Fetch the Paddle package from the plugins repository
The Paddle package is released as a plugin in the Plugins repository. You can fetch it by running the following command:
```bash
npx @makerkit/cli@latest plugins install
```
Please choose the Paddle plugin from the list of available plugins.
## Step 1: Registering Paddle
Now we need to register the services from the Paddle plugin.
### Install the Paddle package
Run the following command to add the Paddle package to our billing package:
```bash
pnpm --filter @kit/billing-gateway add "@kit/paddle@workspace:*"
```
### Registering the Checkout component
Update the function `loadCheckoutComponent` to include the `paddle` block,
which will dynamically import the Paddle checkout component:
```tsx {% title="packages/billing/gateway/src/components/embedded-checkout.tsx" %}
import { Suspense, lazy } from 'react';
import { Enums } from '@kit/supabase/database';
import { LoadingOverlay } from '@kit/ui/loading-overlay';
type BillingProvider = Enums<'billing_provider'>;
// Create lazy components at module level (not during render)
const StripeCheckoutLazy = lazy(async () => {
const { StripeCheckout } = await import('@kit/stripe/components');
return { default: StripeCheckout };
});
const LemonSqueezyCheckoutLazy = lazy(async () => {
const { LemonSqueezyEmbeddedCheckout } =
await import('@kit/lemon-squeezy/components');
return { default: LemonSqueezyEmbeddedCheckout };
});
const PaddleCheckoutLazy = lazy(async () => {
const { PaddleCheckout } = await import(
'@kit/paddle/components'
);
return { default: PaddleCheckout };
});
type CheckoutProps = {
onClose: (() => unknown) | undefined;
checkoutToken: string;
};
export function EmbeddedCheckout(
props: React.PropsWithChildren<{
checkoutToken: string;
provider: BillingProvider;
onClose?: () => void;
}>,
) {
return (
<>
<Suspense fallback={<LoadingOverlay fullPage={false} />}>
<CheckoutSelector
provider={props.provider}
onClose={props.onClose}
checkoutToken={props.checkoutToken}
/>
</Suspense>
<BlurryBackdrop />
</>
);
}
function CheckoutSelector(
props: CheckoutProps & { provider: BillingProvider },
) {
switch (props.provider) {
case 'stripe':
return (
<StripeCheckoutLazy
onClose={props.onClose}
checkoutToken={props.checkoutToken}
/>
);
case 'lemon-squeezy':
return (
<LemonSqueezyCheckoutLazy
onClose={props.onClose}
checkoutToken={props.checkoutToken}
/>
);
case 'paddle':
return (
<PaddleCheckoutLazy
onClose={props.onClose}
checkoutToken={props.checkoutToken}
/>
)
default:
throw new Error(`Unsupported provider: ${props.provider as string}`);
}
}
function BlurryBackdrop() {
return (
<div
className={
'bg-background/30 fixed top-0 left-0 w-full backdrop-blur-sm' +
' !m-0 h-full'
}
/>
);
}
```
### Registering the Webhook handler
At `packages/billing/gateway/src/server/services/billing-event-handler
/billing-event-handler-factory.service.ts`, add the snippet below at the
bottom of the file:
```tsx {% title="packages/billing/gateway/src/server/services/billing-event-handler/billing-event-handler-factory.service.ts" %}
// Register Paddle webhook handler
billingWebhookHandlerRegistry.register('paddle', async () => {
const { PaddleWebhookHandlerService } = await import('@kit/paddle');
return new PaddleWebhookHandlerService(planTypesMap);
});
```
### Registering the Billing service
Finally, at `packages/billing/gateway/src/server/services/billing-event-handler
/billing-gateway-registry.ts`, add the snippet below at the
bottom of the file:
```tsx {% title="packages/billing/gateway/src/server/services/billing-gateway/billing-gateway-registry.ts" %}
// Register Paddle billing strategy
billingStrategyRegistry.register('paddle', async () => {
const { PaddleBillingStrategyService } = await import('@kit/paddle');
return new PaddleBillingStrategyService();
});
```
## Step 2: Create Paddle Account
### Development Account (Sandbox)
1. Visit [Paddle Developer Console](https://sandbox-vendors.paddle.com/signup)
2. Complete the registration process
3. Verify your email address
4. Navigate to your sandbox dashboard
### Important Notes
- The sandbox environment allows unlimited testing without processing real payments
- All transactions in sandbox mode use test card numbers
- Webhooks and API calls work identically to production
- The Paddle payment provider currently only supports flat and per-seat plans (metered subscriptions are not supported)
## Step 3: Configure Billing Provider
### Database Configuration
Set Paddle as your billing provider in the database:
```sql
-- Update the billing provider in your configuration table
UPDATE public.config
set billing_provider = 'paddle';
```
### Environment Configuration
Add the following to your `.env.local` file:
```bash
# Set Paddle as the active billing provider
NEXT_PUBLIC_BILLING_PROVIDER=paddle
```
This environment variable tells your application to use Paddle-specific components and API endpoints for billing operations.
## Step 4: API Key Configuration
Paddle requires two types of API keys for complete integration:
### Server-Side API Key (Required)
1. In your Paddle dashboard, navigate to **Developer Tools** → **Authentication**
2. Click **Generate New API Key**
3. Give it a descriptive name (e.g., "Production API Key" or "Development API Key")
4. **Configure the required permissions** for your API key:
- **Write Customer Portal Sessions** - For managing customer billing portals
- **Read Customers** - For retrieving customer information
- **Read Prices** - For displaying pricing information
- **Read Products** - For product catalog access
- **Read/Write Subscriptions** - For subscription management
- **Read Transactions** - For payment and transaction tracking
5. Copy the generated key immediately (it won't be shown again)
6. Add to your `.env.local`:
```bash
PADDLE_API_KEY=your_server_api_key_here
```
**Security Note**: This key has access to the specified Paddle account permissions and should never be exposed to the client-side code. Only grant the minimum permissions required for your integration.
### Client-Side Token (Required)
1. In the same **Authentication** section, look for **Client-side tokens**
2. Click **New Client-Side Token**
3. Copy the client token
4. Add to your `.env.local`:
```bash
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=your_client_token_here
```
**Important**: This token is safe to expose in client-side code but should be restricted to your specific domains.
## Step 5: Webhook Configuration
Webhooks enable real-time synchronization between Paddle and your application for events like successful payments, subscription changes, and cancellations.
### Set Up Local Development Tunnel
First, expose your local development server to the internet:
#### Using ngrok (Recommended)
```bash
# Install ngrok if not already installed
npm install -g ngrok
# Expose port 3000 (default Next.js port)
ngrok http 3000
```
#### Using LocalTunnel
```bash
# Install localtunnel
npm install -g localtunnel
# Expose port 3000
lt --port 3000
```
### Configure Webhook Destination
1. In Paddle dashboard, go to **Developer Tools** → **Notifications**
2. Click **New Destination**
3. Configure the destination:
- **Destination URL**: `https://your-tunnel-url.ngrok.io/api/billing/webhook`
- **Description**: "Local Development Webhook"
- **Active**: ✅ Checked
### Select Webhook Events
Enable these essential events for proper billing integration:
### Retrieve Webhook Secret
1. After creating the destination, click on it to view details
2. Copy the **Endpoint Secret** (used to verify webhook authenticity)
3. Add to your `.env.local`:
```bash
PADDLE_WEBHOOK_SECRET_KEY=your_webhook_secret_here
```
### Test Webhook Connection
You can test the webhook endpoint by making a GET request to verify it's accessible:
```bash
curl https://your-tunnel-url.ngrok.io/api/billing/webhook
```
Expected response: `200 OK` with a message indicating the webhook endpoint is active.
## Step 6: Product and Pricing Configuration
### Create Products in Paddle
1. Navigate to **Catalog** → **Products** in your Paddle dashboard
2. Click **Create Product**
3. Configure your product:
**Basic Information:**
- **Product Name**: "Starter Plan", "Pro Plan", etc.
- **Description**: Detailed description of the plan features
- **Tax Category**: Select appropriate category (usually "Software")
**Pricing Configuration:**
- **Billing Interval**: Monthly, Yearly, or Custom
- **Price**: Set in your primary currency
- **Trial Period**: Optional free trial duration
### Configure Billing Settings
Update your billing configuration file with the Paddle product IDs:
```typescript
// apps/web/config/billing.config.ts
export const billingConfig = {
provider: 'paddle',
products: [
{
id: 'starter',
name: 'Starter Plan',
description: 'Perfect for individuals and small teams',
badge: 'Most Popular',
features: [
'Up to 5 projects',
'Basic support',
'1GB storage'
],
plans: [
{
name: 'Starter Monthly',
id: 'starter-monthly',
paymentType: 'recurring',
interval: 'month',
lineItems: [
{
id: 'pri_starter_monthly_001', // Paddle Price ID
name: 'Starter',
cost: 9.99,
type: 'flat' as const,
},
],
}
]
}
// Add more products...
]
};
```
## Step 7: Checkout Configuration
### Default Payment Link Configuration
1. Go to **Checkout** → **Checkout Settings** in Paddle dashboard
2. Configure **Default Payment Link**: use`http://localhost:3000` - but when deploying to production, you should use your production domain.
3. Save the configuration
## Step 8: Testing the Integration
### Development Testing Checklist
**Environment Verification:**
- [ ] All environment variables are set correctly
- [ ] Webhook tunnel is active and accessible
- [ ] Destination was defined using the correct URL
- [ ] Database billing provider is set to 'paddle' in both DB and ENV
**Subscription Flow Testing:**
1. Navigate to your billing/pricing page (`/home/billing` or equivalent)
2. Click on a subscription plan
3. Complete the checkout flow using Paddle test cards
4. Verify successful redirect to success page
5. Check that subscription appears in user dashboard
6. Verify webhook events are received in your application logs
You can also test cancellation flows:
- cancel the subscription from the billing portal
- delete the account and verify the subscription is cancelled as well
### Test Card Numbers
[Follow this link to get the test card numbers](https://developer.paddle.com/concepts/payment-methods/credit-debit-card#test-payment-method)
### Webhook Testing
Monitor webhook delivery in your application logs:
```bash
# Watch your development logs
pnpm dev
# In another terminal, monitor webhook requests
tail -f logs/webhook.log
```
## Step 9: Production Deployment
### Apply for Live Paddle Account
1. In your Paddle dashboard, click **Go Live**
2. Complete the application process:
- Business information and verification
- Tax information and documentation
- Banking details for payouts
- Identity verification for key personnel
**Timeline**: Live account approval typically takes 1-3 business days.
### Production Environment Setup
Create production-specific configuration:
```bash
# Production environment variables
NEXT_PUBLIC_BILLING_PROVIDER=paddle
PADDLE_API_KEY=your_production_api_key
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=your_production_client_token
PADDLE_WEBHOOK_SECRET_KEY=your_production_webhook_secret
```
### Production Webhook Configuration
1. Create a new webhook destination for production
2. Set the destination URL to your production domain:
```
https://yourdomain.com/api/billing/webhook
```
3. Enable the same events as configured for development
4. Update your production environment with the new webhook secret
### Production Products and Pricing
1. Create production versions of your products in the live environment
2. Update your production billing configuration with live Price IDs
3. Test the complete flow on production with small-amount transactions
### Support Resources
Refer to the [Paddle Documentation](https://developer.paddle.com) for more information:
- **Paddle Documentation**: [https://developer.paddle.com](https://developer.paddle.com)
- **Status Page**: [https://status.paddle.com](https://status.paddle.com)