Migrate billing from Paddle to Lemon Squeezy

This commit is contained in:
Codex Agent
2026-02-03 10:59:54 +01:00
parent 2f4ebfefd4
commit a0ef90e13a
228 changed files with 4369 additions and 4067 deletions

View File

@@ -557,7 +557,7 @@ export type DataExportSummary = {
} | null;
};
export type PaddleTransactionSummary = {
export type LemonSqueezyOrderSummary = {
id: string | null;
status: string | null;
amount: number | null;
@@ -1125,7 +1125,7 @@ export function normalizeTenantPackage(pkg: JsonValue): TenantPackageSummary {
};
}
function normalizePaddleTransaction(entry: JsonValue): PaddleTransactionSummary {
function normalizeLemonSqueezyOrder(entry: JsonValue): LemonSqueezyOrderSummary {
const amountValue = entry.amount ?? entry.grand_total ?? (entry.totals && entry.totals.grand_total);
const taxValue = entry.tax ?? (entry.totals && entry.totals.tax_total);
@@ -2348,8 +2348,8 @@ export type Package = {
gallery_days: number | null;
max_events_per_year?: number | null;
included_package_slug?: string | null;
paddle_price_id?: string | null;
paddle_product_id?: string | null;
lemonsqueezy_variant_id?: string | null;
lemonsqueezy_product_id?: string | null;
branding_allowed?: boolean | null;
watermark_allowed?: boolean | null;
features: string[] | Record<string, boolean> | null;
@@ -2731,8 +2731,8 @@ export async function downloadTenantDataExport(downloadUrl: string): Promise<Blo
return response.blob();
}
export async function getTenantPaddleTransactions(cursor?: string): Promise<{
data: PaddleTransactionSummary[];
export async function getTenantLemonSqueezyTransactions(cursor?: string): Promise<{
data: LemonSqueezyOrderSummary[];
nextCursor: string | null;
hasMore: boolean;
}> {
@@ -2745,8 +2745,8 @@ export async function getTenantPaddleTransactions(cursor?: string): Promise<{
if (!response.ok) {
const payload = await safeJson(response);
console.error('[API] Failed to load Paddle transactions', response.status, payload);
throw new Error('Failed to load Paddle transactions');
console.error('[API] Failed to load Lemon Squeezy transactions', response.status, payload);
throw new Error('Failed to load Lemon Squeezy transactions');
}
const payload = await safeJson(response) ?? {};
@@ -2754,17 +2754,17 @@ export async function getTenantPaddleTransactions(cursor?: string): Promise<{
const meta = payload.meta ?? {};
return {
data: entries.map(normalizePaddleTransaction),
data: entries.map(normalizeLemonSqueezyOrder),
nextCursor: typeof meta.next === 'string' ? meta.next : null,
hasMore: Boolean(meta.has_more),
};
}
export async function createTenantPaddleCheckout(
export async function createTenantLemonSqueezyCheckout(
packageId: number,
urls?: { success_url?: string; return_url?: string }
): Promise<{ checkout_url: string; id: string; expires_at?: string; checkout_session_id?: string }> {
const response = await authorizedFetch('/api/v1/tenant/packages/paddle-checkout', {
const response = await authorizedFetch('/api/v1/tenant/packages/lemonsqueezy-checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -2796,15 +2796,15 @@ export async function createTenantBillingPortalSession(): Promise<{ url: string
if (!response.ok) {
const payload = await safeJson(response);
console.error('[API] Failed to create Paddle portal session', response.status, payload);
throw new Error('Failed to create Paddle portal session');
console.error('[API] Failed to create Lemon Squeezy portal session', response.status, payload);
throw new Error('Failed to create Lemon Squeezy portal session');
}
const payload = await safeJson(response);
const url = payload?.url;
if (typeof url !== 'string' || url.length === 0) {
throw new Error('Paddle portal session missing URL');
throw new Error('Lemon Squeezy portal session missing URL');
}
return { url };
@@ -2848,13 +2848,13 @@ export async function getTenantAddonHistory(page = 1, perPage = 25): Promise<{
export async function completeTenantPackagePurchase(params: {
packageId: number;
paddleTransactionId: string;
orderId: string;
}): Promise<void> {
const { packageId, paddleTransactionId } = params;
const { packageId, orderId } = params;
const payload: Record<string, unknown> = { package_id: packageId };
if (paddleTransactionId) {
payload.paddle_transaction_id = paddleTransactionId;
if (orderId) {
payload.lemonsqueezy_order_id = orderId;
}
const response = await authorizedFetch('/api/v1/tenant/packages/complete', {