/** * SaaS Billing Methods — Mixin * * 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, CreatePaymentRequest, PaymentResult, PaymentStatus, } from './saas-types'; export interface UsageIncrementResult { dimension: string; incremented: number; usage: { relay_requests: number; hand_executions: number; pipeline_runs: number; input_tokens: number; output_tokens: number; [key: string]: unknown; }; } type RequestFn = (method: string, path: string, body?: unknown) => Promise; export function installBillingMethods(ClientClass: { prototype: any }): void { const proto = ClientClass.prototype; // --- Usage Increment --- /** * Report a usage increment for a specific dimension. * * Called after hand execution, pipeline run, or other metered operations. * Non-blocking — failures are logged but don't affect the caller. * * @param dimension - One of: hand_executions, pipeline_runs, relay_requests * @param count - How many units to increment (default 1, max 100) */ proto.incrementUsageDimension = async function ( this: { request(method: string, path: string, body?: unknown): Promise }, dimension: string, count: number = 1, ): Promise { return this.request( 'POST', '/api/v1/billing/usage/increment', { dimension, count }, ); }; /** * Fire-and-forget version of incrementUsageDimension. * Logs errors but never throws — safe to call in finally blocks. */ proto.reportUsageFireAndForget = function ( this: { incrementUsageDimension(dimension: string, count?: number): Promise }, dimension: string, count: number = 1, ): void { this.incrementUsageDimension(dimension, count).catch((err: unknown) => { 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 { return this.request('GET', '/api/v1/billing/plans'); }; // --- Subscription --- /** Get current subscription info (plan + subscription + usage) */ proto.getSubscription = async function ( this: { request: RequestFn }, ): Promise { return this.request('GET', '/api/v1/billing/subscription'); }; // --- Payments --- /** Create a payment order for a plan upgrade */ proto.createPayment = async function ( this: { request: RequestFn }, data: CreatePaymentRequest, ): Promise { return this.request('POST', '/api/v1/billing/payments', data); }; /** Check payment status by payment ID */ proto.getPaymentStatus = async function ( this: { request: RequestFn }, paymentId: string, ): Promise { return this.request('GET', `/api/v1/billing/payments/${paymentId}`); }; }