Expose gateway billing flows from the synced server baseline

The preserved local work adds PopiNewAPI billing checkout, gateway account binding, and matching web console/pricing surfaces on top of the current test-server baseline. During the merge, the start-end video helpers from the deployed baseline were kept alongside the actionGenerate prompt handling from the WIP.

Constraint: Current usable baseline is 7054436, already deployed on the test server.

Rejected: Commit the WIP before syncing the baseline | would have hidden conflicts with the deployed start-end-frame changes.

Confidence: medium

Scope-risk: broad

Directive: Do not deploy this commit to the test server without rechecking gateway credentials and billing checkout behavior in that environment.

Tested: go test ./...

Tested: make build

Tested: cd web && npm run build
This commit is contained in:
wtgoku
2026-05-21 16:53:07 +08:00
parent 7054436a99
commit baba209519
31 changed files with 6035 additions and 86 deletions
+213
View File
@@ -29,6 +29,9 @@ export type AuthSessionView = {
user: PopiartUser;
session_key?: string;
upstream_key_masked?: string;
gateway_user_id?: number;
gateway_access_token_masked?: string;
gateway_bound: boolean;
};
export type Skill = {
@@ -84,6 +87,149 @@ export type BudgetUsage = {
};
};
export type Project = {
id: string;
name: string;
};
export type ProjectListResponse = {
items: Project[];
total: number;
limit: number;
offset: number;
};
export type BillingPlan = {
id: string;
product_type: string;
badge: string;
name: string;
price: string;
cadence: string;
summary: string;
features: string[];
cta: string;
highlight?: boolean;
};
export type BillingCatalog = {
plans: BillingPlan[];
capabilities: {
subscription_status: boolean;
credit_balance: boolean;
invoices: boolean;
checkout: boolean;
};
source: string;
note?: string;
};
export type BillingSubscription = {
billing_preference: string;
subscriptions: Array<{
subscription?: {
id?: number;
status?: string;
amount_total?: number;
amount_used?: number;
start_time?: number;
end_time?: number;
next_reset_time?: number;
member_level?: string;
};
}>;
all_subscriptions: Array<Record<string, unknown>>;
subscription_points: Record<
string,
{
available_points?: number;
total_points?: number;
used_points?: number;
}
>;
};
export type BillingCredits = {
wallets: Array<{
id: number;
source_type: string;
points: number;
points_total: number;
expire_at: number;
status: string;
priority: number;
origin_currency: string;
origin_amount: number;
}>;
balance: number;
};
export type BillingSubscriptionPlan = {
plan: {
id: number;
title: string;
subtitle: string;
description: string;
price_amount: number;
currency: string;
price_amount_cny?: number;
price_amount_usd?: number;
points_amount: number;
duration_unit: string;
duration_value: number;
recommended?: boolean;
member_level?: string;
total_amount?: number;
quota_reset_period?: string;
};
};
export type BillingSubscriptionPlans = {
items: BillingSubscriptionPlan[];
};
export type BillingPointsPackage = {
id: number;
name: string;
currency: string;
price_amount: number;
points_amount: number;
bonus_points: number;
enabled: boolean;
};
export type BillingPointsPackages = {
items: BillingPointsPackage[];
};
export type BillingCheckoutResult = {
message?: string;
trade_no?: string;
url?: string;
code_url?: string;
data?: Record<string, unknown>;
};
export type BillingOrderPage = {
page: number;
page_size: number;
total: number;
items: Array<Record<string, unknown>>;
};
export type BillingInvoices = {
subscription_orders: BillingOrderPage;
point_orders: BillingOrderPage;
};
export type GatewayBindResponse = {
gateway_user_id: number;
gateway_username?: string;
gateway_display_name?: string;
gateway_email?: string;
gateway_bound: boolean;
};
export class PopiartApiError extends Error {
status: number;
code?: string;
@@ -221,3 +367,70 @@ export async function getBudgetUsage() {
}
return popiartRequest<BudgetUsage>("/budget/usage", { token });
}
export async function getProjects() {
const token = await getSessionToken();
if (!token) {
return null;
}
return popiartRequest<ProjectListResponse>("/projects", { token });
}
export async function getBillingCatalog() {
return popiartRequest<BillingCatalog>("/billing/catalog");
}
export async function getBillingSubscription() {
const token = await getSessionToken();
if (!token) {
return null;
}
return popiartRequest<BillingSubscription>("/billing/subscription", { token });
}
export async function getBillingCredits() {
const token = await getSessionToken();
if (!token) {
return null;
}
return popiartRequest<BillingCredits>("/billing/credits", { token });
}
export async function getBillingSubscriptionPlans() {
const token = await getSessionToken();
if (!token) {
return null;
}
return popiartRequest<BillingSubscriptionPlans>("/billing/subscription/plans", { token });
}
export async function getBillingPointsPackages() {
const token = await getSessionToken();
if (!token) {
return null;
}
return popiartRequest<BillingPointsPackages>("/billing/points/packages", { token });
}
export async function getBillingInvoices(options?: {
keyword?: string;
page?: number;
pageSize?: number;
}) {
const token = await getSessionToken();
if (!token) {
return null;
}
const query = new URLSearchParams();
if (options?.keyword) {
query.set("keyword", options.keyword);
}
if (options?.page && options.page > 0) {
query.set("p", String(options.page));
}
if (options?.pageSize && options.pageSize > 0) {
query.set("page_size", String(options.pageSize));
}
const suffix = query.toString();
return popiartRequest<BillingInvoices>(`/billing/invoices${suffix ? `?${suffix}` : ""}`, { token });
}