Fix PayPal billing flow and mobile admin UX
This commit is contained in:
@@ -571,6 +571,19 @@ export type LemonSqueezyOrderSummary = {
|
||||
tax?: number | null;
|
||||
};
|
||||
|
||||
export type TenantBillingTransactionSummary = {
|
||||
id: string | number | null;
|
||||
status: string | null;
|
||||
amount: number | null;
|
||||
currency: string | null;
|
||||
provider: string | null;
|
||||
provider_id: string | null;
|
||||
package_name: string | null;
|
||||
purchased_at: string | null;
|
||||
receipt_url?: string | null;
|
||||
tax?: number | null;
|
||||
};
|
||||
|
||||
export type TenantAddonEventSummary = {
|
||||
id: number;
|
||||
slug: string;
|
||||
@@ -1125,21 +1138,21 @@ export function normalizeTenantPackage(pkg: JsonValue): TenantPackageSummary {
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
function normalizeTenantBillingTransaction(entry: JsonValue): TenantBillingTransactionSummary {
|
||||
const idValue = (entry as { id?: unknown }).id;
|
||||
const amountValue = (entry as { amount?: unknown }).amount;
|
||||
const taxValue = (entry as { tax?: unknown }).tax;
|
||||
|
||||
return {
|
||||
id: typeof entry.id === 'string' ? entry.id : entry.id ? String(entry.id) : null,
|
||||
status: entry.status ?? null,
|
||||
id: typeof idValue === 'string' || typeof idValue === 'number' ? idValue : idValue ? String(idValue) : null,
|
||||
status: typeof (entry as { status?: unknown }).status === 'string' ? (entry as { status?: unknown }).status : null,
|
||||
amount: amountValue !== undefined && amountValue !== null ? Number(amountValue) : null,
|
||||
currency: entry.currency ?? entry.currency_code ?? 'EUR',
|
||||
origin: entry.origin ?? null,
|
||||
checkout_id: entry.checkout_id ?? (entry.details?.checkout_id ?? null),
|
||||
created_at: entry.created_at ?? null,
|
||||
updated_at: entry.updated_at ?? null,
|
||||
receipt_url: entry.receipt_url ?? entry.invoice_url ?? null,
|
||||
grand_total: entry.grand_total !== undefined && entry.grand_total !== null ? Number(entry.grand_total) : null,
|
||||
currency: typeof (entry as { currency?: unknown }).currency === 'string' ? (entry as { currency?: unknown }).currency : 'EUR',
|
||||
provider: typeof (entry as { provider?: unknown }).provider === 'string' ? (entry as { provider?: unknown }).provider : null,
|
||||
provider_id: typeof (entry as { provider_id?: unknown }).provider_id === 'string' ? (entry as { provider_id?: unknown }).provider_id : null,
|
||||
package_name: typeof (entry as { package_name?: unknown }).package_name === 'string' ? (entry as { package_name?: unknown }).package_name : null,
|
||||
purchased_at: typeof (entry as { purchased_at?: unknown }).purchased_at === 'string' ? (entry as { purchased_at?: unknown }).purchased_at : null,
|
||||
receipt_url: typeof (entry as { receipt_url?: unknown }).receipt_url === 'string' ? (entry as { receipt_url?: unknown }).receipt_url : null,
|
||||
tax: taxValue !== undefined && taxValue !== null ? Number(taxValue) : null,
|
||||
};
|
||||
}
|
||||
@@ -2731,32 +2744,43 @@ export async function downloadTenantDataExport(downloadUrl: string): Promise<Blo
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
export async function getTenantLemonSqueezyTransactions(cursor?: string): Promise<{
|
||||
data: LemonSqueezyOrderSummary[];
|
||||
nextCursor: string | null;
|
||||
hasMore: boolean;
|
||||
export async function downloadTenantBillingReceipt(receiptUrl: string): Promise<Blob> {
|
||||
const response = await authorizedFetch(receiptUrl, {
|
||||
headers: { 'Accept': 'application/pdf' },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const payload = await safeJson(response);
|
||||
console.error('[API] Failed to download billing receipt', response.status, payload);
|
||||
throw new Error('Failed to download billing receipt');
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
export async function getTenantBillingTransactions(page = 1): Promise<{
|
||||
data: TenantBillingTransactionSummary[];
|
||||
}> {
|
||||
const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : '';
|
||||
const response = await authorizedFetch(`/api/v1/tenant/billing/transactions${query}`);
|
||||
const params = new URLSearchParams({
|
||||
page: String(Math.max(1, page)),
|
||||
});
|
||||
const response = await authorizedFetch(`/api/v1/tenant/billing/transactions?${params.toString()}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
return { data: [], nextCursor: null, hasMore: false };
|
||||
return { data: [] };
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const payload = await safeJson(response);
|
||||
console.error('[API] Failed to load Lemon Squeezy transactions', response.status, payload);
|
||||
throw new Error('Failed to load Lemon Squeezy transactions');
|
||||
console.error('[API] Failed to load billing transactions', response.status, payload);
|
||||
throw new Error('Failed to load billing transactions');
|
||||
}
|
||||
|
||||
const payload = await safeJson(response) ?? {};
|
||||
const entries = Array.isArray(payload.data) ? payload.data : [];
|
||||
const meta = payload.meta ?? {};
|
||||
|
||||
return {
|
||||
data: entries.map(normalizeLemonSqueezyOrder),
|
||||
nextCursor: typeof meta.next === 'string' ? meta.next : null,
|
||||
hasMore: Boolean(meta.has_more),
|
||||
data: entries.map(normalizeTenantBillingTransaction),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user