From 04a64ef83a11291993f406027a183cefe067a764 Mon Sep 17 00:00:00 2001 From: Dries Augustyns Date: Fri, 5 Dec 2025 07:59:47 +0100 Subject: [PATCH] Added display of credits --- apps/api/src/controllers/Users.ts | 20 +++++++++++++ apps/api/src/controllers/Webhooks.ts | 5 ++++ .../web/src/components/BillingConsumption.tsx | 30 ++++++++++++++++++- .../src/lib/hooks/useBillingConsumption.ts | 8 +++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/apps/api/src/controllers/Users.ts b/apps/api/src/controllers/Users.ts index 7a7d156..a248975 100644 --- a/apps/api/src/controllers/Users.ts +++ b/apps/api/src/controllers/Users.ts @@ -476,6 +476,25 @@ export class Users { // This is normal for new subscriptions or if there's no usage yet } + // Get customer to retrieve balance (credits) + const customer = await stripe.customers.retrieve(project.customer); + let credits = null; + + if (!customer.deleted) { + // Stripe stores balance in cents as a negative number (credit) or positive (debit) + // A negative balance means the customer has credits + const balance = customer.balance || 0; + const hasCredits = balance < 0; + const creditAmount = hasCredits ? Math.abs(balance) : 0; + + credits = { + balance, + hasCredits, + creditAmount, + currency: customer.currency || 'usd', + }; + } + return res.status(200).json({ period: { start: startOfMonth.toISOString(), @@ -485,6 +504,7 @@ export class Users { total: totalUsage, records: [], // Deprecated in favor of invoice line items }, + credits, upcomingInvoice: upcomingInvoice ? { amountDue: upcomingInvoice.amount_due, diff --git a/apps/api/src/controllers/Webhooks.ts b/apps/api/src/controllers/Webhooks.ts index cbcd2cd..1936450 100644 --- a/apps/api/src/controllers/Webhooks.ts +++ b/apps/api/src/controllers/Webhooks.ts @@ -276,6 +276,11 @@ export class Webhooks { }, }); + // add 1 eur/usd in credit in return for onboarding fee + await stripe.customers.update(customerId, { + balance: -100, + }); + signale.success(`[WEBHOOK] Checkout completed for project ${projectId}`); // Send notification about subscription started diff --git a/apps/web/src/components/BillingConsumption.tsx b/apps/web/src/components/BillingConsumption.tsx index 3c23eca..c5bc4d6 100644 --- a/apps/web/src/components/BillingConsumption.tsx +++ b/apps/web/src/components/BillingConsumption.tsx @@ -1,5 +1,5 @@ import {Card, CardContent, CardDescription, CardHeader, CardTitle, Alert} from '@plunk/ui'; -import {AlertCircle, TrendingUp} from 'lucide-react'; +import {AlertCircle, TrendingUp, Coins} from 'lucide-react'; import {useBillingConsumption} from '../lib/hooks/useBillingConsumption'; import {useConfig} from '../lib/hooks/useConfig'; import {useCallback} from 'react'; @@ -119,6 +119,26 @@ export function BillingConsumption({projectId, hasSubscription}: BillingConsumpt + {/* Account Credits */} + {consumptionData.credits && consumptionData.credits.hasCredits && ( +
+
+
+ +
+
+

Account Credits

+

+ {formatCurrency(consumptionData.credits.creditAmount, consumptionData.credits.currency)} +

+
+
+

+ Credits will be automatically applied to your upcoming invoices +

+
+ )} + {/* Upcoming Invoice */} {consumptionData.upcomingInvoice && (
@@ -137,6 +157,14 @@ export function BillingConsumption({projectId, hasSubscription}: BillingConsumpt {formatCurrency(consumptionData.upcomingInvoice.subtotal, consumptionData.upcomingInvoice.currency)}
+ {consumptionData.credits && consumptionData.credits.hasCredits && ( +
+ Credits Applied + + -{formatCurrency(consumptionData.credits.creditAmount, consumptionData.credits.currency)} + +
+ )}
Amount Due diff --git a/apps/web/src/lib/hooks/useBillingConsumption.ts b/apps/web/src/lib/hooks/useBillingConsumption.ts index 2036a67..9cb8ae3 100644 --- a/apps/web/src/lib/hooks/useBillingConsumption.ts +++ b/apps/web/src/lib/hooks/useBillingConsumption.ts @@ -19,12 +19,20 @@ export interface UpcomingInvoice { total: number; } +export interface AccountCredits { + balance: number; + hasCredits: boolean; + creditAmount: number; + currency: string; +} + export interface BillingConsumptionData { period: BillingPeriod; usage: { total: number; records: UsageRecord[]; }; + credits: AccountCredits | null; upcomingInvoice: UpcomingInvoice | null; }