Skip to content

Commit 05e52b5

Browse files
chore: wip
1 parent f620620 commit 05e52b5

File tree

6 files changed

+259
-51
lines changed

6 files changed

+259
-51
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import { db, sql } from '@stacksjs/database'
2+
3+
/**
4+
* Payment statistics response interface
5+
*/
6+
export interface PaymentStats {
7+
total_transactions: number
8+
total_revenue: number
9+
average_transaction: number
10+
successful_rate: number
11+
comparison: {
12+
transactions: {
13+
difference: number
14+
percentage: number
15+
is_increase: boolean
16+
}
17+
revenue: {
18+
difference: number
19+
percentage: number
20+
is_increase: boolean
21+
}
22+
average: {
23+
difference: number
24+
percentage: number
25+
is_increase: boolean
26+
}
27+
}
28+
}
29+
30+
/**
31+
* Fetch payment statistics for a specific time period
32+
*
33+
* @param daysRange Number of days to look back (7, 30, 60, etc.)
34+
*/
35+
export async function fetchPaymentStats(daysRange: number = 30): Promise<PaymentStats> {
36+
const today = new Date()
37+
38+
// Current period (last N days)
39+
const currentPeriodStart = new Date(today)
40+
currentPeriodStart.setDate(today.getDate() - daysRange)
41+
42+
// Previous period (N days before the current period)
43+
const previousPeriodEnd = new Date(currentPeriodStart)
44+
previousPeriodEnd.setDate(previousPeriodEnd.getDate() - 1)
45+
46+
const previousPeriodStart = new Date(previousPeriodEnd)
47+
previousPeriodStart.setDate(previousPeriodEnd.getDate() - daysRange)
48+
49+
// Get current period stats for completed payments
50+
const currentStats = await db
51+
.selectFrom('payments')
52+
.select([
53+
db.fn.count('id').as('transaction_count'),
54+
db.fn.sum('amount').as('total_revenue'),
55+
])
56+
.where('date', '>=', currentPeriodStart)
57+
.where('date', '<=', today)
58+
.where('status', '=', 'completed')
59+
.executeTakeFirst()
60+
61+
// Get previous period stats for completed payments
62+
const previousStats = await db
63+
.selectFrom('payments')
64+
.select([
65+
db.fn.count('id').as('transaction_count'),
66+
db.fn.sum('amount').as('total_revenue'),
67+
])
68+
.where('date', '>=', previousPeriodStart)
69+
.where('date', '<=', previousPeriodEnd)
70+
.where('status', '=', 'completed')
71+
.executeTakeFirst()
72+
73+
// Get total transactions count (including non-completed ones) for calculating success rate
74+
const totalTransactions = await db
75+
.selectFrom('payments')
76+
.select(db.fn.count('id').as('count'))
77+
.where('date', '>=', currentPeriodStart)
78+
.where('date', '<=', today)
79+
.executeTakeFirst()
80+
81+
// Calculate current period stats
82+
const currentTransactions = Number(currentStats?.transaction_count || 0)
83+
const currentRevenue = Number(currentStats?.total_revenue || 0)
84+
const currentAverage = currentTransactions > 0 ? currentRevenue / currentTransactions : 0
85+
86+
// Calculate previous period stats
87+
const previousTransactions = Number(previousStats?.transaction_count || 0)
88+
const previousRevenue = Number(previousStats?.total_revenue || 0)
89+
const previousAverage = previousTransactions > 0 ? previousRevenue / previousTransactions : 0
90+
91+
// Calculate differences
92+
const transactionDifference = currentTransactions - previousTransactions
93+
const revenueDifference = currentRevenue - previousRevenue
94+
const averageDifference = currentAverage - previousAverage
95+
96+
// Calculate percentage changes
97+
const transactionPercentage = previousTransactions > 0
98+
? (transactionDifference / previousTransactions) * 100
99+
: (currentTransactions > 0 ? 100 : 0)
100+
101+
const revenuePercentage = previousRevenue > 0
102+
? (revenueDifference / previousRevenue) * 100
103+
: (currentRevenue > 0 ? 100 : 0)
104+
105+
const averagePercentage = previousAverage > 0
106+
? (averageDifference / previousAverage) * 100
107+
: (currentAverage > 0 ? 100 : 0)
108+
109+
// Calculate success rate
110+
const allTransactions = Number(totalTransactions?.count || 0)
111+
const successRate = allTransactions > 0
112+
? (currentTransactions / allTransactions) * 100
113+
: 0
114+
115+
return {
116+
total_transactions: currentTransactions,
117+
total_revenue: currentRevenue,
118+
average_transaction: currentAverage,
119+
successful_rate: successRate,
120+
comparison: {
121+
transactions: {
122+
difference: transactionDifference,
123+
percentage: Math.abs(transactionPercentage),
124+
is_increase: transactionDifference >= 0,
125+
},
126+
revenue: {
127+
difference: revenueDifference,
128+
percentage: Math.abs(revenuePercentage),
129+
is_increase: revenueDifference >= 0,
130+
},
131+
average: {
132+
difference: averageDifference,
133+
percentage: Math.abs(averagePercentage),
134+
is_increase: averageDifference >= 0,
135+
},
136+
},
137+
}
138+
}
139+
140+
/**
141+
* Fetch payment statistics by payment method
142+
*
143+
* @param daysRange Number of days to look back
144+
*/
145+
export async function fetchPaymentStatsByMethod(daysRange: number = 30): Promise<Record<string, {
146+
count: number
147+
revenue: number
148+
percentage_of_total: number
149+
}>> {
150+
const today = new Date()
151+
const startDate = new Date(today)
152+
startDate.setDate(today.getDate() - daysRange)
153+
154+
// Get total stats for the period
155+
const totalStats = await db
156+
.selectFrom('payments')
157+
.select([
158+
db.fn.count('id').as('total_count'),
159+
db.fn.sum('amount').as('total_revenue'),
160+
])
161+
.where('date', '>=', startDate)
162+
.where('date', '<=', today)
163+
.where('status', '=', 'completed')
164+
.executeTakeFirst()
165+
166+
const totalCount = Number(totalStats?.total_count || 0)
167+
const totalRevenue = Number(totalStats?.total_revenue || 0)
168+
169+
// Get stats grouped by payment method
170+
const methodStats = await db
171+
.selectFrom('payments')
172+
.select([
173+
'method',
174+
db.fn.count('id').as('count'),
175+
db.fn.sum('amount').as('revenue'),
176+
])
177+
.where('date', '>=', startDate)
178+
.where('date', '<=', today)
179+
.where('status', '=', 'completed')
180+
.groupBy('method')
181+
.execute()
182+
183+
// Format the results
184+
const result: Record<string, {
185+
count: number
186+
revenue: number
187+
percentage_of_total: number
188+
}> = {}
189+
190+
methodStats.forEach((item) => {
191+
const count = Number(item.count || 0)
192+
const revenue = Number(item.revenue || 0)
193+
const percentageOfTotal = totalCount > 0 ? (count / totalCount) * 100 : 0
194+
195+
result[item.method] = {
196+
count,
197+
revenue,
198+
percentage_of_total: percentageOfTotal,
199+
}
200+
})
201+
202+
return result
203+
}
204+
205+
/**
206+
* Fetch monthly payment trends for the last 12 months
207+
*/
208+
export async function fetchMonthlyPaymentTrends(): Promise<Array<{
209+
month: string
210+
year: number
211+
transactions: number
212+
revenue: number
213+
average: number
214+
}>> {
215+
// Calculate date 12 months ago
216+
const today = new Date()
217+
const twelveMonthsAgo = new Date(today)
218+
twelveMonthsAgo.setMonth(today.getMonth() - 11)
219+
220+
// Set to first day of that month
221+
twelveMonthsAgo.setDate(1)
222+
223+
// Use the query builder with expressions instead of raw SQL
224+
const monthlyData = await db
225+
.selectFrom('payments')
226+
.select([
227+
sql`EXTRACT(YEAR FROM date)`.as('year'),
228+
sql`EXTRACT(MONTH FROM date)`.as('month'),
229+
db.fn.count('id').as('transactions'),
230+
db.fn.sum('amount').as('revenue'),
231+
])
232+
.where('date', '>=', twelveMonthsAgo)
233+
.where('status', '=', 'completed')
234+
.groupBy(sql`year`)
235+
.groupBy(sql`month`)
236+
.orderBy('year', 'asc')
237+
.orderBy('month', 'asc')
238+
.execute()
239+
240+
// Format the results
241+
return monthlyData.map((item) => {
242+
const transactions = Number(item.transactions || 0)
243+
const revenue = Number(item.revenue || 0)
244+
const average = transactions > 0 ? revenue / transactions : 0
245+
246+
// Format month name
247+
const monthDate = new Date(Number(item.year), Number(item.month) - 1, 1)
248+
const monthName = monthDate.toLocaleString('default', { month: 'short' })
249+
250+
return {
251+
month: monthName,
252+
year: Number(item.year),
253+
transactions,
254+
revenue,
255+
average,
256+
}
257+
})
258+
}

storage/framework/defaults/models/Payment.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,6 @@ export default {
3232
belongsTo: ['Order', 'User'], // For order_id and customer_id
3333

3434
attributes: {
35-
order_id: {
36-
required: true,
37-
order: 1,
38-
fillable: true,
39-
validation: {
40-
rule: schema.string(),
41-
},
42-
factory: faker => faker.string.uuid(),
43-
},
44-
45-
customer_id: {
46-
required: true,
47-
order: 2,
48-
fillable: true,
49-
validation: {
50-
rule: schema.string(),
51-
},
52-
factory: faker => faker.string.uuid(),
53-
},
54-
5535
amount: {
5636
required: true,
5737
order: 3,

storage/framework/orm/src/models/Payment.ts

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export interface PaymentsTable {
1818
id: Generated<number>
1919
order_id: number
2020
user_id: number
21-
order_id: string
2221
customer_id: string
2322
amount: number
2423
method: string
@@ -70,7 +69,7 @@ interface QueryOptions {
7069

7170
export class PaymentModel {
7271
private readonly hidden: Array<keyof PaymentJsonResponse> = []
73-
private readonly fillable: Array<keyof PaymentJsonResponse> = ['order_id', 'customer_id', 'amount', 'method', 'status', 'date', 'currency', 'reference_number', 'card_last_four', 'card_brand', 'billing_email', 'transaction_id', 'payment_provider', 'refund_amount', 'notes', 'uuid']
72+
private readonly fillable: Array<keyof PaymentJsonResponse> = ['customer_id', 'amount', 'method', 'status', 'date', 'currency', 'reference_number', 'card_last_four', 'card_brand', 'billing_email', 'transaction_id', 'payment_provider', 'refund_amount', 'notes', 'uuid']
7473
private readonly guarded: Array<keyof PaymentJsonResponse> = []
7574
protected attributes = {} as PaymentJsonResponse
7675
protected originalAttributes = {} as PaymentJsonResponse
@@ -172,10 +171,6 @@ export class PaymentModel {
172171
return this.attributes.uuid
173172
}
174173

175-
get order_id(): string {
176-
return this.attributes.order_id
177-
}
178-
179174
get customer_id(): string {
180175
return this.attributes.customer_id
181176
}
@@ -244,10 +239,6 @@ export class PaymentModel {
244239
this.attributes.uuid = value
245240
}
246241

247-
set order_id(value: string) {
248-
this.attributes.order_id = value
249-
}
250-
251242
set customer_id(value: string) {
252243
this.attributes.customer_id = value
253244
}
@@ -1251,14 +1242,6 @@ export class PaymentModel {
12511242
return instance
12521243
}
12531244

1254-
static whereOrderId(value: string): PaymentModel {
1255-
const instance = new PaymentModel(undefined)
1256-
1257-
instance.selectFromQuery = instance.selectFromQuery.where('order_id', '=', value)
1258-
1259-
return instance
1260-
}
1261-
12621245
static whereCustomerId(value: string): PaymentModel {
12631246
const instance = new PaymentModel(undefined)
12641247

@@ -1931,7 +1914,6 @@ export class PaymentModel {
19311914
uuid: this.uuid,
19321915

19331916
id: this.id,
1934-
order_id: this.order_id,
19351917
customer_id: this.customer_id,
19361918
amount: this.amount,
19371919
method: this.method,
@@ -2005,13 +1987,6 @@ export async function remove(id: number): Promise<void> {
20051987
.execute()
20061988
}
20071989

2008-
export async function whereOrderId(value: string): Promise<PaymentModel[]> {
2009-
const query = DB.instance.selectFrom('payments').where('order_id', '=', value)
2010-
const results: PaymentJsonResponse = await query.execute()
2011-
2012-
return results.map((modelItem: PaymentJsonResponse) => new PaymentModel(modelItem))
2013-
}
2014-
20151990
export async function whereCustomerId(value: string): Promise<PaymentModel[]> {
20161991
const query = DB.instance.selectFrom('payments').where('customer_id', '=', value)
20171992
const results: PaymentJsonResponse = await query.execute()

storage/framework/requests/PaymentRequest.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ interface CustomAttributes {
1313
}
1414
interface RequestDataPayment {
1515
id: number
16-
order_id: string
1716
customer_id: string
1817
amount: number
1918
method: string
@@ -33,7 +32,6 @@ interface RequestDataPayment {
3332
}
3433
export class PaymentRequest extends Request<RequestDataPayment> implements PaymentRequestType {
3534
public id = 1
36-
public order_id = ''
3735
public customer_id = ''
3836
public amount = 0
3937
public method = ''

storage/framework/types/attributes.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export interface Attributes {
2929
password: string
3030
title: string
3131
body: string
32-
order_id: string
3332
customer_id: string
3433
amount: number
3534
method: string

0 commit comments

Comments
 (0)