Skip to content

Commit ed58b61

Browse files
committed
Monthly revenue script
1 parent e7fefd9 commit ed58b61

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed

scripts/fetch-monthly-revenue.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { stripeServer } from '@codebuff/internal/util/stripe'
2+
import { env } from '@codebuff/internal/env'
3+
4+
import type Stripe from 'stripe'
5+
6+
interface MonthlyRevenue {
7+
month: string
8+
grossRevenue: number
9+
fees: number
10+
refunds: number
11+
netRevenue: number
12+
transactionCount: number
13+
}
14+
15+
async function fetchMonthlyRevenue() {
16+
// Define the months we want to fetch (Sept, Oct, Nov, Dec 2025)
17+
const months = [
18+
{
19+
name: 'September 2025',
20+
start: new Date('2025-09-01'),
21+
end: new Date('2025-10-01'),
22+
},
23+
{
24+
name: 'October 2025',
25+
start: new Date('2025-10-01'),
26+
end: new Date('2025-11-01'),
27+
},
28+
{
29+
name: 'November 2025',
30+
start: new Date('2025-11-01'),
31+
end: new Date('2025-12-01'),
32+
},
33+
{
34+
name: 'December 2025',
35+
start: new Date('2025-12-01'),
36+
end: new Date('2026-01-01'),
37+
},
38+
]
39+
40+
// Check if we're in test or live mode
41+
const isTestMode = env.STRIPE_SECRET_KEY.startsWith('sk_test_')
42+
console.log(`Stripe mode: ${isTestMode ? '⚠️ TEST MODE' : '✅ LIVE MODE'}`)
43+
console.log('Fetching monthly revenue from Stripe (using Balance Transactions)...\n')
44+
45+
const results: MonthlyRevenue[] = []
46+
47+
for (const month of months) {
48+
let hasMore = true
49+
let startingAfter: string | undefined = undefined
50+
let grossRevenue = 0
51+
let fees = 0
52+
let refunds = 0
53+
let transactionCount = 0
54+
let batchCount = 0
55+
const MAX_BATCHES = 1000 // Safety limit
56+
57+
console.log(`Fetching ${month.name}...`)
58+
59+
try {
60+
while (hasMore && batchCount < MAX_BATCHES) {
61+
batchCount++
62+
const transactions: Stripe.Response<Stripe.ApiList<Stripe.BalanceTransaction>> =
63+
await stripeServer.balanceTransactions.list({
64+
starting_after: startingAfter,
65+
created: {
66+
gte: Math.floor(month.start.getTime() / 1000),
67+
lt: Math.floor(month.end.getTime() / 1000),
68+
},
69+
// Don't filter by type - get all transactions to match Stripe dashboard
70+
limit: 100,
71+
})
72+
73+
for (const txn of transactions.data) {
74+
if (txn.type === 'charge' || txn.type === 'payment') {
75+
grossRevenue += txn.amount
76+
fees += txn.fee
77+
transactionCount++
78+
} else if (txn.type === 'refund') {
79+
refunds += Math.abs(txn.amount) // refunds are negative, make positive for display
80+
}
81+
}
82+
83+
if (batchCount % 10 === 0) {
84+
console.log(
85+
` Batch ${batchCount}: ${transactionCount} charges, $${(grossRevenue / 100).toFixed(2)} gross`,
86+
)
87+
}
88+
89+
hasMore = transactions.has_more
90+
if (hasMore && transactions.data.length > 0) {
91+
startingAfter = transactions.data[transactions.data.length - 1].id
92+
}
93+
}
94+
95+
if (batchCount >= MAX_BATCHES) {
96+
console.warn(`Warning: Hit max batch limit for ${month.name}`)
97+
}
98+
99+
const netRevenue = grossRevenue - fees - refunds
100+
results.push({
101+
month: month.name,
102+
grossRevenue,
103+
fees,
104+
refunds,
105+
netRevenue,
106+
transactionCount,
107+
})
108+
109+
console.log(
110+
`${month.name}: $${(grossRevenue / 100).toFixed(2)} gross, $${(refunds / 100).toFixed(2)} refunds, $${(netRevenue / 100).toFixed(2)} net (${transactionCount} charges)\n`,
111+
)
112+
} catch (error) {
113+
console.error(`Error fetching ${month.name}:`, error)
114+
}
115+
}
116+
117+
// Print summary
118+
console.log('='.repeat(70))
119+
console.log('Summary')
120+
console.log('='.repeat(70))
121+
122+
const totalGross = results.reduce((sum, r) => sum + r.grossRevenue, 0)
123+
const totalFees = results.reduce((sum, r) => sum + r.fees, 0)
124+
const totalRefunds = results.reduce((sum, r) => sum + r.refunds, 0)
125+
const totalNet = results.reduce((sum, r) => sum + r.netRevenue, 0)
126+
const totalCharges = results.reduce((sum, r) => sum + r.transactionCount, 0)
127+
128+
console.log(
129+
`${'Month'.padEnd(18)} ${'Gross'.padStart(12)} ${'Refunds'.padStart(12)} ${'Net'.padStart(12)} ${'Charges'.padStart(10)}`,
130+
)
131+
console.log('-'.repeat(70))
132+
133+
for (const result of results) {
134+
console.log(
135+
`${result.month.padEnd(18)} $${(result.grossRevenue / 100).toFixed(2).padStart(11)} $${(result.refunds / 100).toFixed(2).padStart(11)} $${(result.netRevenue / 100).toFixed(2).padStart(11)} ${result.transactionCount.toString().padStart(10)}`,
136+
)
137+
}
138+
139+
console.log('-'.repeat(70))
140+
console.log(
141+
`${'Total'.padEnd(18)} $${(totalGross / 100).toFixed(2).padStart(11)} $${(totalRefunds / 100).toFixed(2).padStart(11)} $${(totalNet / 100).toFixed(2).padStart(11)} ${totalCharges.toString().padStart(10)}`,
142+
)
143+
if (totalFees > 0) {
144+
console.log(`\nStripe fees: $${(totalFees / 100).toFixed(2)}`)
145+
}
146+
}
147+
148+
// Run the script
149+
fetchMonthlyRevenue()

0 commit comments

Comments
 (0)