diff --git a/apps/dokploy/components/dashboard/settings/billing/show-billing.tsx b/apps/dokploy/components/dashboard/settings/billing/show-billing.tsx
index ac211a1c5..faa5bcdcc 100644
--- a/apps/dokploy/components/dashboard/settings/billing/show-billing.tsx
+++ b/apps/dokploy/components/dashboard/settings/billing/show-billing.tsx
@@ -24,6 +24,7 @@ import { Progress } from "@/components/ui/progress";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
+import { ShowInvoices } from "./show-invoices";
const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
@@ -75,8 +76,8 @@ export const ShowBilling = () => {
const safePercentage = Math.min(percentage, 100);
return (
-
-
+
+
@@ -319,6 +320,8 @@ export const ShowBilling = () => {
+
+ {admin?.user.stripeCustomerId &&
}
);
};
diff --git a/apps/dokploy/components/dashboard/settings/billing/show-invoices.tsx b/apps/dokploy/components/dashboard/settings/billing/show-invoices.tsx
new file mode 100644
index 000000000..513bb3dc8
--- /dev/null
+++ b/apps/dokploy/components/dashboard/settings/billing/show-invoices.tsx
@@ -0,0 +1,159 @@
+import { Download, ExternalLink, FileText, Loader2 } from "lucide-react";
+import type Stripe from "stripe";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { api } from "@/utils/api";
+
+const formatDate = (timestamp: number | null) => {
+ if (!timestamp) return "-";
+ return new Date(timestamp * 1000).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ });
+};
+
+const formatAmount = (amount: number, currency: string) => {
+ return new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: currency.toUpperCase(),
+ }).format(amount / 100);
+};
+
+const getStatusBadge = (status: Stripe.Invoice.Status | null) => {
+ const statusConfig: Record<
+ Stripe.Invoice.Status,
+ { label: string; variant: "default" | "secondary" | "destructive" }
+ > = {
+ paid: { label: "Paid", variant: "default" },
+ open: { label: "Open", variant: "secondary" },
+ draft: { label: "Draft", variant: "secondary" },
+ void: { label: "Void", variant: "destructive" },
+ uncollectible: { label: "Uncollectible", variant: "destructive" },
+ };
+
+ if (!status) {
+ return Unknown;
+ }
+
+ const config = statusConfig[status] || {
+ label: status,
+ variant: "secondary" as const,
+ };
+
+ return {config.label};
+};
+
+export const ShowInvoices = () => {
+ const { data: invoices, isLoading } = api.stripe.getInvoices.useQuery();
+
+ return (
+
+
+
+
+
+ Invoices
+
+
+ View and download your billing invoices
+
+
+
+ {isLoading ? (
+
+
+ Loading invoices...
+
+
+
+ ) : invoices && invoices.length > 0 ? (
+
+
+
+
+ Invoice
+ Date
+ Due Date
+ Amount
+ Status
+ Actions
+
+
+
+ {invoices.map((invoice) => (
+
+
+ {invoice.number || invoice.id.slice(0, 12)}
+
+ {formatDate(invoice.created)}
+ {formatDate(invoice.dueDate)}
+
+ {formatAmount(invoice.amountDue, invoice.currency)}
+
+ {getStatusBadge(invoice.status)}
+
+
+ {invoice.hostedInvoiceUrl && (
+
+ )}
+ {invoice.invoicePdf && (
+
+ )}
+
+
+
+ ))}
+
+
+
+ ) : (
+
+
+
+ No invoices found
+
+
+ Your invoices will appear here once you have a subscription
+
+
+ )}
+
+
+
+ );
+};
diff --git a/apps/dokploy/server/api/routers/stripe.ts b/apps/dokploy/server/api/routers/stripe.ts
index 2d1556a27..3354c3311 100644
--- a/apps/dokploy/server/api/routers/stripe.ts
+++ b/apps/dokploy/server/api/routers/stripe.ts
@@ -129,4 +129,39 @@ export const stripeRouter = createTRPCRouter({
return servers.length < user.serversQuantity;
}),
+
+ getInvoices: adminProcedure.query(async ({ ctx }) => {
+ const user = await findUserById(ctx.user.ownerId);
+ const stripeCustomerId = user.stripeCustomerId;
+
+ if (!stripeCustomerId) {
+ return [];
+ }
+
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
+ apiVersion: "2024-09-30.acacia",
+ });
+
+ try {
+ const invoices = await stripe.invoices.list({
+ customer: stripeCustomerId,
+ limit: 100,
+ });
+
+ return invoices.data.map((invoice) => ({
+ id: invoice.id,
+ number: invoice.number,
+ status: invoice.status,
+ amountDue: invoice.amount_due,
+ amountPaid: invoice.amount_paid,
+ currency: invoice.currency,
+ created: invoice.created,
+ dueDate: invoice.due_date,
+ hostedInvoiceUrl: invoice.hosted_invoice_url,
+ invoicePdf: invoice.invoice_pdf,
+ }));
+ } catch (_) {
+ return [];
+ }
+ }),
});