financeops-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/assets/banner.svg +104 -0
- package/.github/pull_request_template.md +25 -0
- package/CODE_OF_CONDUCT.md +40 -0
- package/CONTRIBUTING.md +71 -0
- package/LICENSE +21 -0
- package/README.md +390 -0
- package/SECURITY.md +30 -0
- package/dist/adapters/stripe.d.ts +15 -0
- package/dist/adapters/stripe.js +175 -0
- package/dist/adapters/types.d.ts +30 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/xero.d.ts +19 -0
- package/dist/adapters/xero.js +261 -0
- package/dist/analysis/anomaly.d.ts +12 -0
- package/dist/analysis/anomaly.js +103 -0
- package/dist/analysis/cashflow.d.ts +10 -0
- package/dist/analysis/cashflow.js +134 -0
- package/dist/analysis/pnl.d.ts +8 -0
- package/dist/analysis/pnl.js +56 -0
- package/dist/analysis/reconciliation.d.ts +14 -0
- package/dist/analysis/reconciliation.js +98 -0
- package/dist/analysis/tax.d.ts +11 -0
- package/dist/analysis/tax.js +81 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +565 -0
- package/dist/lib/audit.d.ts +17 -0
- package/dist/lib/audit.js +70 -0
- package/dist/lib/providers.d.ts +6 -0
- package/dist/lib/providers.js +25 -0
- package/dist/premium/gate.d.ts +12 -0
- package/dist/premium/gate.js +22 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.js +7 -0
- package/mcpize.yaml +10 -0
- package/package.json +35 -0
- package/src/adapters/stripe.ts +190 -0
- package/src/adapters/types.ts +34 -0
- package/src/adapters/xero.ts +317 -0
- package/src/analysis/anomaly.ts +119 -0
- package/src/analysis/cashflow.ts +158 -0
- package/src/analysis/pnl.ts +80 -0
- package/src/analysis/reconciliation.ts +117 -0
- package/src/analysis/tax.ts +98 -0
- package/src/index.ts +649 -0
- package/src/lib/audit.ts +92 -0
- package/src/lib/providers.ts +29 -0
- package/src/premium/gate.ts +24 -0
- package/src/types.ts +153 -0
- package/tests/adapters/stripe.test.ts +150 -0
- package/tests/adapters/xero.test.ts +188 -0
- package/tests/analysis/anomaly.test.ts +137 -0
- package/tests/analysis/cashflow.test.ts +112 -0
- package/tests/analysis/pnl.test.ts +95 -0
- package/tests/analysis/reconciliation.test.ts +121 -0
- package/tests/analysis/tax.test.ts +163 -0
- package/tests/helpers/mock-data.ts +135 -0
- package/tests/lib/audit.test.ts +89 -0
- package/tests/lib/providers.test.ts +129 -0
- package/tests/premium/cash_flow_forecast.test.ts +157 -0
- package/tests/premium/detect_anomalies.test.ts +189 -0
- package/tests/premium/gate.test.ts +59 -0
- package/tests/premium/generate_pnl.test.ts +155 -0
- package/tests/premium/multi_currency_report.test.ts +141 -0
- package/tests/premium/reconcile.test.ts +174 -0
- package/tests/premium/tax_summary.test.ts +166 -0
- package/tests/tools/expense_tracker.test.ts +181 -0
- package/tests/tools/financial_health.test.ts +196 -0
- package/tests/tools/get_balances.test.ts +160 -0
- package/tests/tools/list_invoices.test.ts +191 -0
- package/tests/tools/list_transactions.test.ts +210 -0
- package/tests/tools/revenue_summary.test.ts +188 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Premium gate — reads PRO_LICENSE dynamically from environment.
|
|
3
|
+
* CRITICAL: Do NOT cache the env variable at module load time.
|
|
4
|
+
* Must re-read process.env.PRO_LICENSE on every call to support
|
|
5
|
+
* runtime license changes (e.g., in tests or when env is updated).
|
|
6
|
+
*/
|
|
7
|
+
export declare class ProRequiredError extends Error {
|
|
8
|
+
constructor(tool: string);
|
|
9
|
+
}
|
|
10
|
+
export declare function isPro(): boolean;
|
|
11
|
+
export declare function requirePro(tool: string): void;
|
|
12
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Premium gate — reads PRO_LICENSE dynamically from environment.
|
|
3
|
+
* CRITICAL: Do NOT cache the env variable at module load time.
|
|
4
|
+
* Must re-read process.env.PRO_LICENSE on every call to support
|
|
5
|
+
* runtime license changes (e.g., in tests or when env is updated).
|
|
6
|
+
*/
|
|
7
|
+
export class ProRequiredError extends Error {
|
|
8
|
+
constructor(tool) {
|
|
9
|
+
super(`Tool "${tool}" requires a PRO_LICENSE. Set the PRO_LICENSE environment variable.`);
|
|
10
|
+
this.name = 'ProRequiredError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function isPro() {
|
|
14
|
+
// Dynamic read — NOT cached
|
|
15
|
+
return Boolean(process.env.PRO_LICENSE);
|
|
16
|
+
}
|
|
17
|
+
export function requirePro(tool) {
|
|
18
|
+
if (!isPro()) {
|
|
19
|
+
throw new ProRequiredError(tool);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=gate.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core domain types for FinanceOps MCP.
|
|
3
|
+
* All monetary amounts are in the smallest currency unit (cents).
|
|
4
|
+
* Example: 15000 = €150.00
|
|
5
|
+
*/
|
|
6
|
+
export interface Transaction {
|
|
7
|
+
id: string;
|
|
8
|
+
date: string;
|
|
9
|
+
amount: number;
|
|
10
|
+
currency: string;
|
|
11
|
+
description: string;
|
|
12
|
+
category?: string;
|
|
13
|
+
type: 'income' | 'expense' | 'transfer';
|
|
14
|
+
provider: 'stripe' | 'xero';
|
|
15
|
+
metadata?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
export interface Invoice {
|
|
18
|
+
id: string;
|
|
19
|
+
number: string;
|
|
20
|
+
customer_name: string;
|
|
21
|
+
customer_email?: string;
|
|
22
|
+
amount: number;
|
|
23
|
+
currency: string;
|
|
24
|
+
status: 'paid' | 'pending' | 'overdue' | 'draft' | 'voided';
|
|
25
|
+
due_date: string;
|
|
26
|
+
issued_date: string;
|
|
27
|
+
paid_date?: string;
|
|
28
|
+
provider: 'stripe' | 'xero';
|
|
29
|
+
readonly amount_due?: number;
|
|
30
|
+
readonly amount_paid?: number;
|
|
31
|
+
readonly amount_credited?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface Balance {
|
|
34
|
+
currency: string;
|
|
35
|
+
available: number;
|
|
36
|
+
pending: number;
|
|
37
|
+
provider: 'stripe' | 'xero';
|
|
38
|
+
}
|
|
39
|
+
export interface Expense {
|
|
40
|
+
id: string;
|
|
41
|
+
date: string;
|
|
42
|
+
amount: number;
|
|
43
|
+
currency: string;
|
|
44
|
+
description: string;
|
|
45
|
+
category: string;
|
|
46
|
+
provider: 'stripe' | 'xero';
|
|
47
|
+
}
|
|
48
|
+
export interface Subscription {
|
|
49
|
+
id: string;
|
|
50
|
+
customer_name: string;
|
|
51
|
+
amount: number;
|
|
52
|
+
currency: string;
|
|
53
|
+
interval: 'month' | 'year';
|
|
54
|
+
status: 'active' | 'canceled' | 'past_due' | 'trialing';
|
|
55
|
+
current_period_start: string;
|
|
56
|
+
current_period_end: string;
|
|
57
|
+
provider: 'stripe' | 'xero';
|
|
58
|
+
}
|
|
59
|
+
export interface PaginatedResult<T> {
|
|
60
|
+
data: T[];
|
|
61
|
+
has_more: boolean;
|
|
62
|
+
next_cursor?: string;
|
|
63
|
+
}
|
|
64
|
+
export interface PnLReport {
|
|
65
|
+
period: string;
|
|
66
|
+
date_from: string;
|
|
67
|
+
date_to: string;
|
|
68
|
+
currency: string;
|
|
69
|
+
revenue: number;
|
|
70
|
+
cogs: number;
|
|
71
|
+
gross_profit: number;
|
|
72
|
+
gross_margin_pct: number;
|
|
73
|
+
operating_expenses: number;
|
|
74
|
+
net_income: number;
|
|
75
|
+
by_category: Record<string, number>;
|
|
76
|
+
}
|
|
77
|
+
export interface CashFlowForecast {
|
|
78
|
+
period_months: number;
|
|
79
|
+
generated_at: string;
|
|
80
|
+
historical_monthly_income: number;
|
|
81
|
+
historical_monthly_expenses: number;
|
|
82
|
+
trend_pct: number;
|
|
83
|
+
scenarios: {
|
|
84
|
+
conservative: MonthlyForecast[];
|
|
85
|
+
realistic: MonthlyForecast[];
|
|
86
|
+
optimistic: MonthlyForecast[];
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export interface MonthlyForecast {
|
|
90
|
+
month: string;
|
|
91
|
+
income: number;
|
|
92
|
+
expenses: number;
|
|
93
|
+
net: number;
|
|
94
|
+
cumulative_net: number;
|
|
95
|
+
}
|
|
96
|
+
export interface ReconciliationResult {
|
|
97
|
+
matched: MatchedPair[];
|
|
98
|
+
unmatched_stripe: Transaction[];
|
|
99
|
+
unmatched_xero: Transaction[];
|
|
100
|
+
summary: {
|
|
101
|
+
total_stripe: number;
|
|
102
|
+
total_xero: number;
|
|
103
|
+
matched_count: number;
|
|
104
|
+
unmatched_stripe_count: number;
|
|
105
|
+
unmatched_xero_count: number;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export interface MatchedPair {
|
|
109
|
+
stripe: Transaction;
|
|
110
|
+
xero: Transaction;
|
|
111
|
+
confidence: number;
|
|
112
|
+
}
|
|
113
|
+
export interface Anomaly {
|
|
114
|
+
id: string;
|
|
115
|
+
type: 'duplicate' | 'large_variance' | 'missed_recurring' | 'unusual_timing';
|
|
116
|
+
severity: 'low' | 'medium' | 'high';
|
|
117
|
+
transaction?: Transaction;
|
|
118
|
+
description: string;
|
|
119
|
+
amount?: number;
|
|
120
|
+
date?: string;
|
|
121
|
+
}
|
|
122
|
+
export interface TaxSummary {
|
|
123
|
+
provider: 'stripe' | 'xero';
|
|
124
|
+
period: 'quarter' | 'year';
|
|
125
|
+
jurisdiction?: string;
|
|
126
|
+
total_taxable_amount: number;
|
|
127
|
+
total_tax_collected: number;
|
|
128
|
+
total_tax_owed: number;
|
|
129
|
+
by_rate: TaxByRate[];
|
|
130
|
+
filing_period: string;
|
|
131
|
+
}
|
|
132
|
+
export interface TaxByRate {
|
|
133
|
+
rate: number;
|
|
134
|
+
rate_label: string;
|
|
135
|
+
taxable_amount: number;
|
|
136
|
+
tax_amount: number;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
package/mcpize.yaml
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "financeops-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unified financial intelligence MCP server — Stripe + Xero",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"financeops-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
20
|
+
"better-sqlite3": "^9.4.3",
|
|
21
|
+
"stripe": "^14.21.0",
|
|
22
|
+
"zod": "^3.22.4"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/better-sqlite3": "^7.6.8",
|
|
26
|
+
"@types/node": "^20.11.5",
|
|
27
|
+
"typescript": "^5.3.3",
|
|
28
|
+
"vitest": "^1.2.2"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": ["mcp", "finance", "stripe", "xero", "accounting"],
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import Stripe from 'stripe';
|
|
2
|
+
import type { FinanceProvider, TransactionQuery, InvoiceQuery, ExpenseQuery } from './types.js';
|
|
3
|
+
import type { Transaction, Invoice, Balance, Expense, Subscription, PaginatedResult } from '../types.js';
|
|
4
|
+
|
|
5
|
+
export class StripeAdapter implements FinanceProvider {
|
|
6
|
+
private client: Stripe;
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
const key = process.env.STRIPE_SECRET_KEY;
|
|
10
|
+
if (!key) {
|
|
11
|
+
throw new Error('STRIPE_SECRET_KEY environment variable is required');
|
|
12
|
+
}
|
|
13
|
+
this.client = new Stripe(key, { apiVersion: '2023-10-16' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async getTransactions(opts: TransactionQuery): Promise<PaginatedResult<Transaction>> {
|
|
17
|
+
const params: Stripe.BalanceTransactionListParams = {
|
|
18
|
+
limit: Math.min(opts.limit ?? 100, 100),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (opts.cursor) params.starting_after = opts.cursor;
|
|
22
|
+
if (opts.date_from) params.created = { gte: Math.floor(new Date(opts.date_from).getTime() / 1000) };
|
|
23
|
+
if (opts.date_to) {
|
|
24
|
+
params.created = {
|
|
25
|
+
...(typeof params.created === 'object' && params.created !== null ? params.created : {}),
|
|
26
|
+
lte: Math.floor(new Date(opts.date_to).getTime() / 1000),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = await this.client.balanceTransactions.list(params);
|
|
31
|
+
|
|
32
|
+
const transactions: Transaction[] = result.data.map((bt) => ({
|
|
33
|
+
id: bt.id,
|
|
34
|
+
date: new Date(bt.created * 1000).toISOString(),
|
|
35
|
+
amount: bt.amount, // Stripe already returns amounts in cents
|
|
36
|
+
currency: bt.currency,
|
|
37
|
+
description: bt.description ?? bt.type,
|
|
38
|
+
category: bt.type,
|
|
39
|
+
type: this.mapStripeType(bt.type),
|
|
40
|
+
provider: 'stripe' as const,
|
|
41
|
+
metadata: undefined,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
data: transactions,
|
|
46
|
+
has_more: result.has_more,
|
|
47
|
+
next_cursor: result.has_more ? result.data[result.data.length - 1]?.id : undefined,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getBalances(): Promise<Balance[]> {
|
|
52
|
+
const balance = await this.client.balance.retrieve();
|
|
53
|
+
|
|
54
|
+
const balances: Balance[] = [];
|
|
55
|
+
|
|
56
|
+
for (const available of balance.available) {
|
|
57
|
+
const pending = balance.pending.find((p) => p.currency === available.currency);
|
|
58
|
+
balances.push({
|
|
59
|
+
currency: available.currency,
|
|
60
|
+
available: available.amount, // Already in cents
|
|
61
|
+
pending: pending?.amount ?? 0,
|
|
62
|
+
provider: 'stripe' as const,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return balances;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async getInvoices(opts: InvoiceQuery): Promise<PaginatedResult<Invoice>> {
|
|
70
|
+
const params: Stripe.InvoiceListParams = {
|
|
71
|
+
limit: 100,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (opts.cursor) params.starting_after = opts.cursor;
|
|
75
|
+
|
|
76
|
+
if (opts.status) {
|
|
77
|
+
if (opts.status === 'paid') params.status = 'paid';
|
|
78
|
+
else if (opts.status === 'pending') params.status = 'open';
|
|
79
|
+
// 'overdue' = open past due_date; we post-filter below
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const result = await this.client.invoices.list(params);
|
|
83
|
+
|
|
84
|
+
let invoices: Invoice[] = result.data.map((inv) => ({
|
|
85
|
+
id: inv.id,
|
|
86
|
+
number: inv.number ?? '',
|
|
87
|
+
customer_name: typeof inv.customer_name === 'string' ? inv.customer_name : '',
|
|
88
|
+
customer_email: typeof inv.customer_email === 'string' ? inv.customer_email : undefined,
|
|
89
|
+
amount: inv.amount_due, // Already in cents
|
|
90
|
+
currency: inv.currency,
|
|
91
|
+
status: this.mapStripeInvoiceStatus(inv),
|
|
92
|
+
due_date: inv.due_date ? new Date(inv.due_date * 1000).toISOString() : '',
|
|
93
|
+
issued_date: new Date(inv.created * 1000).toISOString(),
|
|
94
|
+
paid_date: inv.status_transitions?.paid_at
|
|
95
|
+
? new Date(inv.status_transitions.paid_at * 1000).toISOString()
|
|
96
|
+
: undefined,
|
|
97
|
+
provider: 'stripe' as const,
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
if (opts.date_from) {
|
|
101
|
+
const from = new Date(opts.date_from).getTime();
|
|
102
|
+
invoices = invoices.filter((inv) => new Date(inv.issued_date).getTime() >= from);
|
|
103
|
+
}
|
|
104
|
+
if (opts.date_to) {
|
|
105
|
+
const to = new Date(opts.date_to).getTime();
|
|
106
|
+
invoices = invoices.filter((inv) => new Date(inv.issued_date).getTime() <= to);
|
|
107
|
+
}
|
|
108
|
+
if (opts.status === 'overdue') {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
invoices = invoices.filter(
|
|
111
|
+
(inv) => inv.status !== 'paid' && inv.due_date && new Date(inv.due_date).getTime() < now
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
data: invoices,
|
|
117
|
+
has_more: result.has_more,
|
|
118
|
+
next_cursor: result.has_more ? result.data[result.data.length - 1]?.id : undefined,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async getExpenses(opts: ExpenseQuery): Promise<PaginatedResult<Expense>> {
|
|
123
|
+
// Stripe expenses = charges (fees), refunds, and payouts
|
|
124
|
+
const params: Stripe.BalanceTransactionListParams = {
|
|
125
|
+
limit: 100,
|
|
126
|
+
type: 'payout',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (opts.cursor) params.starting_after = opts.cursor;
|
|
130
|
+
|
|
131
|
+
const result = await this.client.balanceTransactions.list(params);
|
|
132
|
+
|
|
133
|
+
const expenses: Expense[] = result.data.map((bt) => ({
|
|
134
|
+
id: bt.id,
|
|
135
|
+
date: new Date(bt.created * 1000).toISOString(),
|
|
136
|
+
amount: Math.abs(bt.amount), // Cents
|
|
137
|
+
currency: bt.currency,
|
|
138
|
+
description: bt.description ?? bt.type,
|
|
139
|
+
category: bt.type === 'payout' ? 'Payouts' : bt.type,
|
|
140
|
+
provider: 'stripe' as const,
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
data: expenses,
|
|
145
|
+
has_more: result.has_more,
|
|
146
|
+
next_cursor: result.has_more ? result.data[result.data.length - 1]?.id : undefined,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async getSubscriptions(): Promise<Subscription[]> {
|
|
151
|
+
const result = await this.client.subscriptions.list({ limit: 100, status: 'all' });
|
|
152
|
+
|
|
153
|
+
return result.data.map((sub) => ({
|
|
154
|
+
id: sub.id,
|
|
155
|
+
customer_name: typeof sub.customer === 'string' ? sub.customer : (sub.customer as Stripe.Customer)?.name ?? '',
|
|
156
|
+
amount: sub.items.data.reduce((sum, item) => sum + (item.price.unit_amount ?? 0), 0),
|
|
157
|
+
currency: sub.currency,
|
|
158
|
+
interval: (sub.items.data[0]?.price.recurring?.interval ?? 'month') as 'month' | 'year',
|
|
159
|
+
status: this.mapStripeSubStatus(sub.status),
|
|
160
|
+
current_period_start: new Date(sub.current_period_start * 1000).toISOString(),
|
|
161
|
+
current_period_end: new Date(sub.current_period_end * 1000).toISOString(),
|
|
162
|
+
provider: 'stripe' as const,
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private mapStripeType(type: string): 'income' | 'expense' | 'transfer' {
|
|
167
|
+
if (['charge', 'payment', 'adjustment'].includes(type)) return 'income';
|
|
168
|
+
if (['refund', 'payout', 'stripe_fee', 'application_fee'].includes(type)) return 'expense';
|
|
169
|
+
return 'transfer';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private mapStripeInvoiceStatus(inv: Stripe.Invoice): 'paid' | 'pending' | 'overdue' | 'draft' | 'voided' {
|
|
173
|
+
if (inv.status === 'paid') return 'paid';
|
|
174
|
+
if (inv.status === 'draft') return 'draft';
|
|
175
|
+
if (inv.status === 'void') return 'voided';
|
|
176
|
+
if (inv.status === 'open') {
|
|
177
|
+
if (inv.due_date && Date.now() > inv.due_date * 1000) return 'overdue';
|
|
178
|
+
return 'pending';
|
|
179
|
+
}
|
|
180
|
+
return 'pending';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private mapStripeSubStatus(status: Stripe.Subscription.Status): 'active' | 'canceled' | 'past_due' | 'trialing' {
|
|
184
|
+
if (status === 'active') return 'active';
|
|
185
|
+
if (status === 'canceled') return 'canceled';
|
|
186
|
+
if (status === 'past_due' || status === 'unpaid') return 'past_due';
|
|
187
|
+
if (status === 'trialing') return 'trialing';
|
|
188
|
+
return 'active';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Transaction, Invoice, Balance, Expense, Subscription, PaginatedResult } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export type { Transaction, Invoice, Balance, Expense, Subscription, PaginatedResult };
|
|
4
|
+
|
|
5
|
+
export interface TransactionQuery {
|
|
6
|
+
date_from?: string;
|
|
7
|
+
date_to?: string;
|
|
8
|
+
limit?: number;
|
|
9
|
+
cursor?: string;
|
|
10
|
+
status?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface InvoiceQuery {
|
|
14
|
+
status?: 'paid' | 'pending' | 'overdue';
|
|
15
|
+
date_from?: string;
|
|
16
|
+
date_to?: string;
|
|
17
|
+
cursor?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ExpenseQuery {
|
|
21
|
+
period?: string;
|
|
22
|
+
categories?: string[];
|
|
23
|
+
date_from?: string;
|
|
24
|
+
date_to?: string;
|
|
25
|
+
cursor?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FinanceProvider {
|
|
29
|
+
getTransactions(opts: TransactionQuery): Promise<PaginatedResult<Transaction>>;
|
|
30
|
+
getBalances(): Promise<Balance[]>;
|
|
31
|
+
getInvoices(opts: InvoiceQuery): Promise<PaginatedResult<Invoice>>;
|
|
32
|
+
getExpenses(opts: ExpenseQuery): Promise<PaginatedResult<Expense>>;
|
|
33
|
+
getSubscriptions?(): Promise<Subscription[]>;
|
|
34
|
+
}
|