feat(desktop): add billing frontend — plans, subscription, payment flow
Sprint 1: Desktop 计费闭环 - Add 7 billing types to saas-types.ts (BillingPlan, Subscription, UsageQuota, etc.) - Add 6 billing API methods to saas-billing.ts (listPlans, getSubscription, createPayment, etc.) - Extend saas-client.ts with interface merging for billing methods - Extend saasStore with billing state/actions (plans, subscription, payment polling) - Create PricingPage component with plan cards, usage bars, and checkout modal - Add billing page entry in SettingsLayout (CreditCard icon + route) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,20 @@
|
||||
/**
|
||||
* SaaS Billing Methods — Mixin
|
||||
*
|
||||
* Installs billing-related methods (usage increment, quota check) onto
|
||||
* SaaSClient.prototype. Uses the same mixin pattern as saas-telemetry.ts.
|
||||
* Installs billing-related methods onto SaaSClient.prototype.
|
||||
* Covers usage increment (fire-and-forget) and full billing CRUD
|
||||
* (plans, subscription, usage, payments).
|
||||
*/
|
||||
|
||||
import type {
|
||||
BillingPlan,
|
||||
SubscriptionInfo,
|
||||
UsageQuota,
|
||||
CreatePaymentRequest,
|
||||
PaymentResult,
|
||||
PaymentStatus,
|
||||
} from './saas-types';
|
||||
|
||||
export interface UsageIncrementResult {
|
||||
dimension: string;
|
||||
incremented: number;
|
||||
@@ -18,9 +28,13 @@ export interface UsageIncrementResult {
|
||||
};
|
||||
}
|
||||
|
||||
type RequestFn = <T>(method: string, path: string, body?: unknown) => Promise<T>;
|
||||
|
||||
export function installBillingMethods(ClientClass: { prototype: any }): void {
|
||||
const proto = ClientClass.prototype;
|
||||
|
||||
// --- Usage Increment ---
|
||||
|
||||
/**
|
||||
* Report a usage increment for a specific dimension.
|
||||
*
|
||||
@@ -52,9 +66,61 @@ export function installBillingMethods(ClientClass: { prototype: any }): void {
|
||||
count: number = 1,
|
||||
): void {
|
||||
this.incrementUsageDimension(dimension, count).catch((err: unknown) => {
|
||||
// Non-fatal: billing reporting failure must never block user operations
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.warn(`[Billing] Failed to report ${dimension} usage (+${count}): ${msg}`);
|
||||
});
|
||||
};
|
||||
|
||||
// --- Plans ---
|
||||
|
||||
/** List all active billing plans (public, no auth required) */
|
||||
proto.listPlans = async function (
|
||||
this: { request: RequestFn },
|
||||
): Promise<BillingPlan[]> {
|
||||
return this.request<BillingPlan[]>('GET', '/api/v1/billing/plans');
|
||||
};
|
||||
|
||||
/** Get a single plan by ID */
|
||||
proto.getPlan = async function (
|
||||
this: { request: RequestFn },
|
||||
planId: string,
|
||||
): Promise<BillingPlan> {
|
||||
return this.request<BillingPlan>('GET', `/api/v1/billing/plans/${planId}`);
|
||||
};
|
||||
|
||||
// --- Subscription ---
|
||||
|
||||
/** Get current subscription info (plan + subscription + usage) */
|
||||
proto.getSubscription = async function (
|
||||
this: { request: RequestFn },
|
||||
): Promise<SubscriptionInfo> {
|
||||
return this.request<SubscriptionInfo>('GET', '/api/v1/billing/subscription');
|
||||
};
|
||||
|
||||
// --- Usage ---
|
||||
|
||||
/** Get current month's usage quota */
|
||||
proto.getUsage = async function (
|
||||
this: { request: RequestFn },
|
||||
): Promise<UsageQuota> {
|
||||
return this.request<UsageQuota>('GET', '/api/v1/billing/usage');
|
||||
};
|
||||
|
||||
// --- Payments ---
|
||||
|
||||
/** Create a payment order for a plan upgrade */
|
||||
proto.createPayment = async function (
|
||||
this: { request: RequestFn },
|
||||
data: CreatePaymentRequest,
|
||||
): Promise<PaymentResult> {
|
||||
return this.request<PaymentResult>('POST', '/api/v1/billing/payments', data);
|
||||
};
|
||||
|
||||
/** Check payment status by payment ID */
|
||||
proto.getPaymentStatus = async function (
|
||||
this: { request: RequestFn },
|
||||
paymentId: string,
|
||||
): Promise<PaymentStatus> {
|
||||
return this.request<PaymentStatus>('GET', `/api/v1/billing/payments/${paymentId}`);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user