diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/success/page.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/success/page.tsx index 53b7351a7..182ffdffd 100644 --- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/success/page.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/billing/success/page.tsx @@ -3,10 +3,11 @@ import { Button } from '@tuturuuu/ui/button'; import { ArrowLeft, CheckCircle, + Clock, CreditCard, Download, } from '@tuturuuu/ui/icons'; -import { format } from 'date-fns'; +import { addHours, format, isAfter } from 'date-fns'; import Link from 'next/link'; const fetchWorkspaceSubscription = async ( @@ -17,7 +18,7 @@ const fetchWorkspaceSubscription = async ( const { data: subscription, error } = await supabase .from('workspace_subscription') - .select('*') + .select('*, workspace_subscription_products(price)') .eq('ws_id', wsId) .single(); @@ -41,15 +42,20 @@ export default async function SuccessPage({ }) { const { wsId } = await params; - // Fetch the workspace subscription data const subscription = await fetchWorkspaceSubscription(wsId); - // Create paymentDetails from the subscription data, or use defaults if not found + // Calculate link expiration + const createdAt = subscription?.created_at + ? new Date(subscription.created_at) + : new Date(); + const expiresAt = addHours(createdAt, 24); + const isLinkExpired = isAfter(new Date(), expiresAt); + const paymentDetails = subscription ? { planName: subscription.plan_name || 'Pro Plan', - amount: subscription.price - ? `$${(subscription.price / 100).toFixed(2)}` + amount: subscription.workspace_subscription_products.price + ? `$${subscription.workspace_subscription_products.price.toFixed(2)}` : '--', invoiceId: subscription.id || 'N/A', date: subscription.created_at @@ -66,41 +72,40 @@ export default async function SuccessPage({ }; return ( -
- {/* Header */} -
-
+
+
+
-

+

Payment Successful!

-

+

Thank you for your subscription. Your payment has been processed successfully.

{/* Payment Summary Card */} -
+

Payment Summary

-
+
Plan: {paymentDetails.planName}
-
+
Amount: {paymentDetails.amount}
-
+
Invoice ID: {paymentDetails.invoiceId} @@ -108,21 +113,21 @@ export default async function SuccessPage({
-
+
Date: {paymentDetails.date}
-
+
Payment Method: {paymentDetails.paymentMethod}
-
+
Status: - + Paid
@@ -130,14 +135,36 @@ export default async function SuccessPage({
+ {/* Download Link Expiration Notice */} +
+
+ +

+ Download Receipt Availability +

+
+ {isLinkExpired ? ( +

+ The download link has expired (24 hours after payment). Please + contact support for a new receipt. +

+ ) : ( +

+ Download link expires on{' '} + {format(expiresAt, "MMMM d, yyyy 'at' h:mm a")} (24 hours after + payment) +

+ )} +
+ {/* What's Next Card */} -
+

What's Next?

-
- +
+

Your subscription is now active @@ -147,8 +174,8 @@ export default async function SuccessPage({

-
- +
+

Confirmation email sent @@ -158,8 +185,8 @@ export default async function SuccessPage({

-
- +
+

Billing cycle started @@ -173,39 +200,63 @@ export default async function SuccessPage({

{/* Action Buttons */} -
- - - + ) : ( + + )} +
{/* Support Information */} -
+

Need help? Contact our{' '} support team {' '} or visit our{' '} help center diff --git a/apps/web/src/app/api/billing/[wsId]/invoice/route.ts b/apps/web/src/app/api/billing/[wsId]/invoice/route.ts new file mode 100644 index 000000000..697b1d9e9 --- /dev/null +++ b/apps/web/src/app/api/billing/[wsId]/invoice/route.ts @@ -0,0 +1,470 @@ +import { createClient } from '@tuturuuu/supabase/next/server'; +import { format } from 'date-fns'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET( + _request: NextRequest, + { params }: { params: Promise<{ wsId: string }> } +) { + try { + const { wsId } = await params; + const supabase = await createClient(); + + // Fetch subscription data + const { data: subscription, error } = await supabase + .from('workspace_subscription') + .select('*, workspace_subscription_products(price)') + .eq('ws_id', wsId) + .single(); + + if (error || !subscription) { + return NextResponse.json( + { error: 'Subscription not found' }, + { status: 404 } + ); + } + + const invoiceHtml = generateInvoiceHtml(subscription); + + return new NextResponse(invoiceHtml, { + headers: { + 'Content-Type': 'text/html', + 'Content-Disposition': `attachment; filename="invoice-${subscription.id}.html"`, + }, + }); + } catch (error) { + console.error('Failed to generate invoice:', error); + return NextResponse.json( + { error: 'Failed to generate invoice' }, + { status: 500 } + ); + } +} + +function generateInvoiceHtml(subscription: any): string { + const price = subscription.workspace_subscription_products?.price; + const amount = price ? `$${price.toFixed(2)}` : '--'; + const date = subscription.created_at + ? format(new Date(subscription.created_at), 'MMMM d, yyyy') + : format(new Date(), 'MMMM d, yyyy'); + + return ` + + + + + + Invoice - ${subscription.id} + + + +

+
+
+
+

Payment Receipt

+

Your subscription has been confirmed successfully

+
+
+ +
+
+

Payment Summary

+
+
+ Plan: + ${subscription.plan_name || 'Pro Plan'} +
+
+ Amount: + ${subscription.workspace_subscription_products.price.toFixed(2)} +
+
+ Invoice ID: + #${subscription.id} +
+
+ Date: + ${date} +
+
+ Payment Method: + Credit Card +
+
+ Status: + Paid +
+
+
+ + + + + + + + + + + + + + + + + + + + +
DescriptionBilling PeriodAmount
+ ${subscription.plan_name || 'Pro Plan'}
+ Premium subscription with full access +
Monthly Subscription${amount}
TOTAL AMOUNT${amount}
+
+ + +
+ + + `; +}