feat(web): add billing/subscription client stub for Pro tier
- billing-client.ts: getMySubscription, cancelSubscription, getUsageSummary - Calls platform-service /subscriptions + /usage endpoints
This commit is contained in:
parent
95f71f4625
commit
a5aec74e4d
83
web/src/lib/billing-client.ts
Normal file
83
web/src/lib/billing-client.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Billing / Subscription client stub for ChronoMind.
|
||||||
|
*
|
||||||
|
* Calls platform-service subscription + usage endpoints via the auth-api base URL.
|
||||||
|
* Client-side only — uses the stored auth token for authorization.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PRODUCT_ID, getBaseUrl } from './auth-api';
|
||||||
|
|
||||||
|
function getToken(): string | null {
|
||||||
|
if (typeof window === 'undefined') return null;
|
||||||
|
return localStorage.getItem('chronomind_access_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function billingFetch<T>(path: string, options?: RequestInit): Promise<T> {
|
||||||
|
const token = getToken();
|
||||||
|
const res = await fetch(`${getBaseUrl()}${path}`, {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-product-id': PRODUCT_ID,
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
...(options?.headers as Record<string, string>),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error(`Billing API error: ${res.status}`);
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Types ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface Subscription {
|
||||||
|
id: string;
|
||||||
|
productId: string;
|
||||||
|
userId: string;
|
||||||
|
plan: 'free' | 'pro';
|
||||||
|
status: 'active' | 'cancelled' | 'past_due' | 'trialing';
|
||||||
|
currentPeriodStart: string;
|
||||||
|
currentPeriodEnd: string;
|
||||||
|
cancelAtPeriodEnd: boolean;
|
||||||
|
stripeCustomerId?: string;
|
||||||
|
stripeSubscriptionId?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Subscription API ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
export async function getMySubscription(userId: string): Promise<Subscription | null> {
|
||||||
|
try {
|
||||||
|
return await billingFetch<Subscription>(`/subscriptions/${userId}`);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cancelSubscription(userId: string): Promise<Subscription | null> {
|
||||||
|
try {
|
||||||
|
return await billingFetch<Subscription>(`/subscriptions/${userId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({ cancelAtPeriodEnd: true }),
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Usage API ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface UsageSummary {
|
||||||
|
timersCreated: number;
|
||||||
|
routinesCompleted: number;
|
||||||
|
focusMinutes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUsageSummary(userId: string, days = 30): Promise<UsageSummary | null> {
|
||||||
|
try {
|
||||||
|
const data = await billingFetch<{ summary: UsageSummary }>(`/usage/summary?userId=${userId}&days=${days}`);
|
||||||
|
return data.summary as UsageSummary;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user