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