Overview
The getInvoices function retrieves all payment records (invoices) for the currently authenticated user from the database. This includes successful payments, failed payments, and refunds.
Function Signature
export async function getInvoices(): ServerActionRes<SelectPayment[]>
Parameters
This function does not accept any parameters. It automatically retrieves invoices for the authenticated user.
Return Value
Indicates whether the operation was successful
An array of payment records:Unique payment identifier
Payment status (e.g., “succeeded”, “failed”, “refunded”)
Currency code (e.g., “USD”, “EUR”)
Type of payment method (e.g., “card”, “bank_transfer”)
Dodo Payments customer ID
ISO timestamp when payment was created
Associated subscription ID
Last 4 digits of card (if card payment)
Card network (e.g., “visa”, “mastercard”)
Refund information if applicable
Error code if payment failed
Error message if payment failed
And other payment fields…
Error message if the operation failed:
- “Subscription not found” - User subscription data could not be retrieved
- “Failed to get invoices” - Database query failed
Implementation Details
The function:
- Retrieves the user’s subscription data via
getUserSubscription()
- Extracts the user’s
dodoCustomerId from the subscription data
- Queries the database for all payments matching the customer ID
- Returns the complete list of payment records
- Handles errors at each step
Error Handling
- Returns
{ success: false, error: "Subscription not found" } if user data cannot be retrieved
- Returns
{ success: false, error: "Failed to get invoices" } if database query fails
- All exceptions are caught and converted to structured error responses
Usage Example
import { getInvoices } from '@/actions/get-invoices';
export default async function BillingHistoryPage() {
const result = await getInvoices();
if (!result.success) {
return <div>Error loading invoices: {result.error}</div>;
}
const invoices = result.data;
return (
<div>
<h1>Billing History</h1>
<table>
<thead>
<tr>
<th>Date</th>
<th>Amount</th>
<th>Status</th>
<th>Card</th>
</tr>
</thead>
<tbody>
{invoices.map((invoice) => (
<tr key={invoice.paymentId}>
<td>{new Date(invoice.createdAt).toLocaleDateString()}</td>
<td>{invoice.totalAmount} {invoice.currency}</td>
<td>{invoice.status}</td>
<td>
{invoice.cardLastFour
? `${invoice.cardNetwork} ****${invoice.cardLastFour}`
: invoice.paymentMethodType}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Filtering by Status
import { getInvoices } from '@/actions/get-invoices';
export async function getSuccessfulInvoices() {
const result = await getInvoices();
if (!result.success) {
return [];
}
return result.data.filter(invoice => invoice.status === 'succeeded');
}
export async function getFailedInvoices() {
const result = await getInvoices();
if (!result.success) {
return [];
}
return result.data.filter(invoice => invoice.status === 'failed');
}
Client Component Example
'use client';
import { getInvoices } from '@/actions/get-invoices';
import { useEffect, useState } from 'react';
export function InvoiceList() {
const [invoices, setInvoices] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
getInvoices().then(result => {
if (result.success) {
setInvoices(result.data);
} else {
setError(result.error);
}
setLoading(false);
});
}, []);
if (loading) return <div>Loading invoices...</div>;
if (error) return <div>Error: {error}</div>;
if (invoices.length === 0) return <div>No invoices yet</div>;
return (
<div>
{invoices.map((invoice) => (
<div key={invoice.paymentId} className="border p-4 mb-2 rounded">
<div className="flex justify-between">
<span>{new Date(invoice.createdAt).toLocaleDateString()}</span>
<span className="font-bold">
{invoice.totalAmount} {invoice.currency}
</span>
</div>
<div className="text-sm text-gray-600">
{invoice.status === 'succeeded' && (
<span className="text-green-600">Paid</span>
)}
{invoice.status === 'failed' && (
<span className="text-red-600">Failed</span>
)}
{invoice.cardLastFour && (
<span> - {invoice.cardNetwork} ****{invoice.cardLastFour}</span>
)}
</div>
</div>
))}
</div>
);
}
Calculating Total Spent
import { getInvoices } from '@/actions/get-invoices';
export async function calculateTotalSpent() {
const result = await getInvoices();
if (!result.success) {
return 0;
}
return result.data
.filter(invoice => invoice.status === 'succeeded')
.reduce((total, invoice) => total + invoice.totalAmount, 0);
}
Invoice Details with Download
import { getInvoices } from '@/actions/get-invoices';
export default async function InvoicesPage() {
const result = await getInvoices();
if (!result.success) {
return <div>Error: {result.error}</div>;
}
return (
<div>
<h1>Invoices</h1>
{result.data.map((invoice) => (
<div key={invoice.paymentId} className="border rounded p-4 mb-4">
<div className="flex justify-between items-start">
<div>
<h3 className="font-bold">
{invoice.totalAmount} {invoice.currency}
</h3>
<p className="text-sm text-gray-600">
{new Date(invoice.createdAt).toLocaleDateString()}
</p>
<p className="text-sm">
Status: <span className={invoice.status === 'succeeded' ? 'text-green-600' : 'text-red-600'}>
{invoice.status}
</span>
</p>
</div>
{invoice.paymentLink && (
<a
href={invoice.paymentLink}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 text-sm"
>
View Invoice
</a>
)}
</div>
{invoice.errorMessage && (
<div className="mt-2 text-red-600 text-sm">
Error: {invoice.errorMessage}
</div>
)}
</div>
))}
</div>
);
}
Source Code
Location: actions/get-invoices.ts:9