feat: replace desktop membership with permanent point wallet
This commit is contained in:
203
electron/api/routes/works-billing.ts
Normal file
203
electron/api/routes/works-billing.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import type { WorksBillingSummary, WorksPointHistory, WorksRechargeOrder, WorksRechargeProduct, WorksTokenPointBalance } from '../../../shared/works-billing';
|
||||
import { getValidWorksSquareAccessToken, getWorksSquareAccountBinding, isCurrentWorksSquareAccountBinding } from '../../services/works-square-session';
|
||||
import { proxyAwareFetch } from '../../utils/proxy-fetch';
|
||||
import { parseJsonBody, sendJson } from '../route-utils';
|
||||
import { hasRendererCapability } from '../renderer-capability';
|
||||
import { WORKS_SQUARE_CONFIG } from '../works-config';
|
||||
|
||||
const pointsPattern = /^(?:0|[1-9]\d*)\.\d{2}$/u;
|
||||
function record(value: unknown): Record<string, unknown> {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid billing response');
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
function text(value: unknown): string {
|
||||
if (typeof value !== 'string' || !value.trim()) throw new Error('Invalid billing text');
|
||||
return value;
|
||||
}
|
||||
function bool(value: unknown): boolean {
|
||||
if (typeof value !== 'boolean') throw new Error('Invalid billing flag');
|
||||
return value;
|
||||
}
|
||||
function point(value: unknown, signed = false): string {
|
||||
const result = text(value);
|
||||
if (!pointsPattern.test(signed ? result.replace(/^-/, '') : result)) throw new Error('Invalid points');
|
||||
return result;
|
||||
}
|
||||
function date(value: unknown): string {
|
||||
const result = text(value);
|
||||
if (!Number.isFinite(Date.parse(result))) throw new Error('Invalid billing date');
|
||||
return result;
|
||||
}
|
||||
function items(value: unknown): unknown[] {
|
||||
if (!Array.isArray(value)) throw new Error('Invalid billing page');
|
||||
return value;
|
||||
}
|
||||
function amount(value: unknown): number {
|
||||
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) throw new Error('Invalid amount');
|
||||
return value;
|
||||
}
|
||||
function currency(value: unknown): 'CNY' {
|
||||
if (value !== 'CNY') throw new Error('Invalid currency');
|
||||
return value;
|
||||
}
|
||||
|
||||
export function projectBillingBalance(input: unknown): WorksTokenPointBalance {
|
||||
const value = record(input);
|
||||
const source = value.entitlement_source;
|
||||
if (source !== 'self' && source !== 'family_owner' && source !== 'shared_group') throw new Error('Invalid funding source');
|
||||
const shared = bool(value.family_shared);
|
||||
// A family payer can use their own shared wallet. The API exposes coarse
|
||||
// availability only when the selected wallet belongs to someone else.
|
||||
const coarse = value.shared_available !== null;
|
||||
if (coarse) bool(value.shared_available);
|
||||
if (value.expires_at !== null) throw new Error('Invalid permanent wallet');
|
||||
return {
|
||||
total: coarse ? null : point(value.total),
|
||||
used: coarse ? null : point(value.used),
|
||||
reserved: coarse ? null : point(value.reserved),
|
||||
total_remaining: coarse ? null : point(value.total_remaining),
|
||||
entitlement_source: source,
|
||||
family_shared: shared,
|
||||
can_recharge: coarse ? false : bool(value.can_recharge),
|
||||
shared_available: coarse ? bool(value.shared_available) : null,
|
||||
expires_at: null,
|
||||
};
|
||||
}
|
||||
|
||||
function projectProduct(input: unknown): WorksRechargeProduct {
|
||||
const value = record(input);
|
||||
const cents = amount(value.amount_cents);
|
||||
const points = point(value.point_amount);
|
||||
if (BigInt(points.replace('.', '')) !== BigInt(cents) * 50n) throw new Error('Invalid recharge ratio');
|
||||
return { id: text(value.id), name: text(value.name), amount_cents: cents, point_amount: points, currency: currency(value.currency) };
|
||||
}
|
||||
|
||||
export function projectBillingSummary(input: unknown): WorksBillingSummary {
|
||||
const value = record(input);
|
||||
const own = projectBillingBalance(value.own_points);
|
||||
if (own.family_shared || value.points_per_yuan !== 50 || value.signup_bonus !== '100.00') throw new Error('Invalid wallet contract');
|
||||
const canRecharge = bool(value.can_recharge);
|
||||
if (own.can_recharge !== canRecharge) throw new Error('Invalid recharge permission');
|
||||
return {
|
||||
own_points: own,
|
||||
points: value.points === null ? null : projectBillingBalance(value.points),
|
||||
// Do not pass arbitrary upstream exception messages to Renderer.
|
||||
funding_error: value.funding_error ? 'AI 编程付款来源不可用,请在网页账户页选择或检查付款来源。' : null,
|
||||
recharge_products: items(value.recharge_products).map(projectProduct),
|
||||
points_per_yuan: 50, signup_bonus: '100.00',
|
||||
can_recharge: canRecharge, payment_configured: bool(value.payment_configured),
|
||||
};
|
||||
}
|
||||
|
||||
function paymentQr(input: unknown): string | null {
|
||||
if (!input || typeof input !== 'object') return null;
|
||||
const payload = record(input);
|
||||
for (const key of ['codeUrl', 'code_url', 'qrCode', 'qr_code', 'qrCodeUrl', 'qr_code_url', 'paymentQrCodeUrl', 'payment_qr_code_url', 'nativeUrl', 'native_url', 'payUrl', 'pay_url', 'mwebUrl', 'mweb_url', 'checkout_url']) {
|
||||
const value = payload[key];
|
||||
if (typeof value !== 'string') continue;
|
||||
try {
|
||||
const url = new URL(value);
|
||||
if ((url.protocol === 'weixin:' || url.protocol === 'https:') && !url.username && !url.password) return value;
|
||||
} catch { /* Not a supported desktop payment URL. */ }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function projectRechargeOrder(input: unknown, payment?: unknown): WorksRechargeOrder {
|
||||
const value = record(input);
|
||||
const status = value.status;
|
||||
if (status !== 'pending' && status !== 'succeeded' && status !== 'failed' && status !== 'canceled' && status !== 'manual_review') throw new Error('Invalid order state');
|
||||
const review = bool(value.manual_review_required);
|
||||
return {
|
||||
id: text(value.id), product_id: text(value.product_id), product_name: text(value.product_name),
|
||||
status: review ? 'manual_review' : status,
|
||||
amount_cents: amount(value.amount_cents), point_amount: point(value.point_amount), currency: currency(value.currency),
|
||||
created_at: date(value.created_at), paid_at: value.paid_at === null ? null : date(value.paid_at),
|
||||
manual_review_required: review,
|
||||
qr_payload: status === 'pending' && !review ? paymentQr(payment ?? value.provider_payload) : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function projectPointHistory(input: unknown): WorksPointHistory {
|
||||
const value = record(input);
|
||||
return { has_more: bool(value.has_more), items: items(value.items).map((item) => {
|
||||
const row = record(item);
|
||||
if (row.kind !== 'credit' && row.kind !== 'usage') throw new Error('Invalid transaction kind');
|
||||
return { id: text(row.id), kind: row.kind, description: text(row.description), status: text(row.status), points: point(row.points, true), reserved_points: point(row.reserved_points), created_at: date(row.created_at) };
|
||||
}) };
|
||||
}
|
||||
|
||||
export async function handleWorksBillingRoutes(req: IncomingMessage, res: ServerResponse, url: URL): Promise<boolean> {
|
||||
const prefix = '/api/works/billing';
|
||||
if (!url.pathname.startsWith(`${prefix}/`)) return false;
|
||||
const path = url.pathname.slice(prefix.length);
|
||||
const creating = path === '/recharge/orders' && req.method === 'POST';
|
||||
const orderMatch = /^\/recharge\/orders\/([^/]+)$/.exec(path);
|
||||
const reading = req.method === 'GET' && (['/me', '/points', '/points/transactions', '/recharge/orders'].includes(path) || orderMatch);
|
||||
if (!creating && !reading) {
|
||||
sendJson(res, 410, { success: false, code: 'membership_retired', error: '旧账户接口已停用,请使用词元点数充值。' });
|
||||
return true;
|
||||
}
|
||||
if (creating && !hasRendererCapability(req)) {
|
||||
sendJson(res, 403, { success: false, error: '请从应用内发起充值。', commandOutcome: 'definitive_failure' });
|
||||
return true;
|
||||
}
|
||||
const binding = getWorksSquareAccountBinding();
|
||||
let dispatched = false;
|
||||
try {
|
||||
const accessToken = await getValidWorksSquareAccessToken();
|
||||
if (!binding || !accessToken || !isCurrentWorksSquareAccountBinding(binding)) {
|
||||
sendJson(res, 401, { success: false, error: '请重新登录后查看账户。', commandOutcome: 'definitive_failure' });
|
||||
return true;
|
||||
}
|
||||
const target = new URL(`/api/billing${path}`, WORKS_SQUARE_CONFIG.apiBaseUrl);
|
||||
const headers: Record<string, string> = { Authorization: `Bearer ${accessToken}` };
|
||||
let body: string | undefined;
|
||||
if (creating) {
|
||||
const input = record(await parseJsonBody(req));
|
||||
const productId = text(input.productId);
|
||||
const requestId = text(input.requestId);
|
||||
if (!/^[a-zA-Z0-9_-]{1,100}$/.test(requestId) || !isCurrentWorksSquareAccountBinding(binding)) throw new Error('Invalid recharge request');
|
||||
headers['Content-Type'] = 'application/json';
|
||||
headers['Idempotency-Key'] = `makelore:${requestId}`;
|
||||
body = JSON.stringify({ product_id: productId });
|
||||
} else if (path === '/points/transactions' || path === '/recharge/orders') {
|
||||
const offset = Number(url.searchParams.get('offset') ?? 0);
|
||||
if (!Number.isSafeInteger(offset) || offset < 0) throw new Error('Invalid page');
|
||||
target.searchParams.set('limit', '20');
|
||||
target.searchParams.set('offset', String(offset));
|
||||
}
|
||||
dispatched = true;
|
||||
const response = await proxyAwareFetch(target.toString(), { method: creating ? 'POST' : 'GET', headers, body });
|
||||
const payload: unknown = await response.json();
|
||||
if (!isCurrentWorksSquareAccountBinding(binding)) {
|
||||
sendJson(res, 409, { success: false, error: '账户已切换,请重新打开词元点数。', commandOutcome: 'unknown' });
|
||||
return true;
|
||||
}
|
||||
if (!response.ok) {
|
||||
const detail = record(payload).detail;
|
||||
const rejection = detail && typeof detail === 'object' && record(detail).outcome === 'definitive_rejection';
|
||||
const messages: Record<number, string> = { 401: '登录已失效,请重新登录。', 403: '当前账户不能充值,请联系家长。', 404: '充值档位或订单已不可用,请刷新。', 409: '请先完成或核查已有充值订单;计费服务也可能正在切换。', 410: '账户服务版本不匹配,请更新客户端和服务端。' };
|
||||
sendJson(res, response.status >= 400 && response.status < 500 ? response.status : 502, {
|
||||
success: false, error: rejection ? '支付服务拒绝了本次请求,请稍后重试。' : messages[response.status] ?? '支付结果尚未确认,请刷新订单或联系运营核查,勿重复付款。',
|
||||
commandOutcome: rejection || [401, 403, 404, 422].includes(response.status) ? 'definitive_failure' : 'unknown',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
let data: unknown;
|
||||
if (path === '/me') data = projectBillingSummary(payload);
|
||||
else if (path === '/points') data = projectBillingBalance(payload);
|
||||
else if (path === '/points/transactions') data = projectPointHistory(payload);
|
||||
else if (creating) data = projectRechargeOrder(record(payload).order, record(payload).provider_payload);
|
||||
else if (orderMatch) data = projectRechargeOrder(payload);
|
||||
else data = { items: items(record(payload).items).map((item) => projectRechargeOrder(item)) };
|
||||
sendJson(res, 200, { success: true, data });
|
||||
} catch {
|
||||
sendJson(res, creating && !dispatched ? 400 : 502, {
|
||||
success: false, error: creating && dispatched ? '充值结果尚未确认,请刷新订单,重试时沿用本次请求。' : '词元点数服务暂时不可用,请刷新后重试。',
|
||||
commandOutcome: dispatched ? 'unknown' : 'definitive_failure',
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { handleWorksBillingRoutes } from './works-billing';
|
||||
import type { IncomingMessage, ServerResponse } from 'http';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { app } from 'electron';
|
||||
@@ -60,35 +61,6 @@ const AGENT_AVATAR_MIME_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp'
|
||||
const MAX_PROJECT_COVER_BYTES = 10 * 1024 * 1024;
|
||||
const PROJECT_COVER_MIME_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp']);
|
||||
const SAFE_PROJECT_STATUSES = new Set(['draft', 'published']);
|
||||
const TOKEN_POINT_FIELDS = [
|
||||
'weekly_allowance',
|
||||
'weekly_used',
|
||||
'weekly_reserved',
|
||||
'weekly_remaining',
|
||||
'permanent_total',
|
||||
'permanent_used',
|
||||
'permanent_reserved',
|
||||
'permanent_remaining',
|
||||
'total_remaining',
|
||||
] as const;
|
||||
const TOKEN_POINT_METADATA_FIELDS = [
|
||||
'plan_code',
|
||||
'plan_name',
|
||||
'cycle_start',
|
||||
'cycle_end',
|
||||
'next_refresh_at',
|
||||
] as const;
|
||||
const TOKEN_POINT_ENTITLEMENT_SOURCES = new Set(['self', 'family_owner', 'shared_group']);
|
||||
const TOKEN_POINT_UPGRADE_ACTIONS = new Set(['self_service', 'contact_family_owner']);
|
||||
const TOKEN_POINT_VALUE_PATTERN = /^(?:0|[1-9]\d*)(?:\.\d{1,2})?$/u;
|
||||
const RESET_CARD_STATUSES = new Set(['available', 'expired', 'redeemed']);
|
||||
const RESET_CARD_ERROR_MESSAGES: Record<string, string> = {
|
||||
reset_card_expired: '这张重置卡已过期。',
|
||||
token_point_wallet_owner_required: '当前额度由家庭管理员管理,无法使用这张重置卡。',
|
||||
billing_temporarily_paused: '计费服务正在切换,请稍后再试。',
|
||||
token_point_transaction_conflict: '重置卡状态刚刚发生变化,请刷新后重试。',
|
||||
};
|
||||
|
||||
function readRequiredString(value: unknown, field: string): string {
|
||||
if (typeof value !== 'string' || !value.trim()) {
|
||||
throw new Error(`Missing ${field}`);
|
||||
@@ -201,31 +173,6 @@ async function sendUpstreamError(
|
||||
});
|
||||
}
|
||||
|
||||
async function sendResetCardUpstreamError(
|
||||
res: ServerResponse,
|
||||
response: Response,
|
||||
): Promise<void> {
|
||||
const payload = await readResponsePayload(response);
|
||||
const upstreamCode = getPayloadErrorCode(payload);
|
||||
const code = upstreamCode && RESET_CARD_ERROR_MESSAGES[upstreamCode]
|
||||
? upstreamCode
|
||||
: undefined;
|
||||
const status = response.status >= 400 && response.status < 500 ? response.status : 502;
|
||||
const error = code
|
||||
? RESET_CARD_ERROR_MESSAGES[code]
|
||||
: response.status === 404
|
||||
? '重置卡不存在或不可用。'
|
||||
: response.status === 409
|
||||
? '当前有待处理的计费操作,暂时不能使用重置卡。'
|
||||
: '重置卡服务暂时不可用,请稍后重试。';
|
||||
sendJson(res, status, {
|
||||
success: false,
|
||||
status: response.status,
|
||||
...(code ? { code } : {}),
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
function sendPublishSourceFailure(
|
||||
res: ServerResponse,
|
||||
status: number,
|
||||
@@ -324,112 +271,6 @@ function readNullableStringField(
|
||||
return typeof value === 'string' ? value : undefined;
|
||||
}
|
||||
|
||||
function readNullablePointField(
|
||||
source: Record<string, unknown>,
|
||||
field: string,
|
||||
): string | null | undefined {
|
||||
const value = source[field];
|
||||
if (value === undefined || value === null) return null;
|
||||
if (typeof value !== 'string') return undefined;
|
||||
const normalized = value.trim();
|
||||
return TOKEN_POINT_VALUE_PATTERN.test(normalized) ? normalized : undefined;
|
||||
}
|
||||
|
||||
function projectSafeTokenPointBalance(value: unknown): Record<string, unknown> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
|
||||
const entitlementSource = readOptionalString(value.entitlement_source);
|
||||
const upgradeAction = readOptionalString(value.upgrade_action);
|
||||
if (
|
||||
!entitlementSource
|
||||
|| !TOKEN_POINT_ENTITLEMENT_SOURCES.has(entitlementSource)
|
||||
|| typeof value.family_shared !== 'boolean'
|
||||
|| typeof value.can_manage_membership !== 'boolean'
|
||||
|| !upgradeAction
|
||||
|| !TOKEN_POINT_UPGRADE_ACTIONS.has(upgradeAction)
|
||||
) return null;
|
||||
|
||||
if (
|
||||
value.shared_available !== undefined
|
||||
&& value.shared_available !== null
|
||||
&& typeof value.shared_available !== 'boolean'
|
||||
) return null;
|
||||
|
||||
const canManageMembership = value.can_manage_membership;
|
||||
if (!canManageMembership && typeof value.shared_available !== 'boolean') return null;
|
||||
|
||||
const projected: Record<string, unknown> = {};
|
||||
for (const field of TOKEN_POINT_METADATA_FIELDS) {
|
||||
const fieldValue = canManageMembership ? readNullableStringField(value, field) : null;
|
||||
if (fieldValue === undefined) return null;
|
||||
projected[field] = fieldValue;
|
||||
}
|
||||
for (const field of TOKEN_POINT_FIELDS) {
|
||||
const fieldValue = canManageMembership ? readNullablePointField(value, field) : null;
|
||||
if (fieldValue === undefined) return null;
|
||||
projected[field] = fieldValue;
|
||||
}
|
||||
|
||||
return {
|
||||
...projected,
|
||||
entitlement_source: entitlementSource,
|
||||
family_shared: value.family_shared,
|
||||
can_manage_membership: canManageMembership,
|
||||
upgrade_action: upgradeAction,
|
||||
shared_available: canManageMembership ? null : value.shared_available,
|
||||
};
|
||||
}
|
||||
|
||||
function readResetCardTimestamp(value: unknown): string | null {
|
||||
if (typeof value !== 'string' || !value.trim()) return null;
|
||||
const normalized = value.trim();
|
||||
return Number.isNaN(Date.parse(normalized)) ? null : normalized;
|
||||
}
|
||||
|
||||
function projectSafeResetCard(value: unknown): Record<string, unknown> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
const id = readOptionalString(value.id);
|
||||
const status = readOptionalString(value.status);
|
||||
const grantedAt = readResetCardTimestamp(value.granted_at);
|
||||
const expiresAt = readResetCardTimestamp(value.expires_at);
|
||||
const redeemedAt = value.redeemed_at === null
|
||||
? null
|
||||
: readResetCardTimestamp(value.redeemed_at);
|
||||
const redeemedCycleId = readNullableStringField(value, 'redeemed_cycle_id');
|
||||
if (
|
||||
!id
|
||||
|| !status
|
||||
|| !RESET_CARD_STATUSES.has(status)
|
||||
|| !grantedAt
|
||||
|| !expiresAt
|
||||
|| redeemedAt === null && value.redeemed_at !== null
|
||||
|| redeemedCycleId === undefined
|
||||
) return null;
|
||||
|
||||
if (status === 'redeemed') {
|
||||
if (!redeemedAt || !redeemedCycleId) return null;
|
||||
} else if (redeemedAt !== null || redeemedCycleId !== null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
status: status === 'available' && Date.parse(expiresAt) <= Date.now() ? 'expired' : status,
|
||||
granted_at: grantedAt,
|
||||
expires_at: expiresAt,
|
||||
redeemed_at: redeemedAt,
|
||||
redeemed_cycle_id: redeemedCycleId,
|
||||
};
|
||||
}
|
||||
|
||||
function projectSafeResetCardPage(value: unknown): Record<string, unknown>[] | null {
|
||||
if (!isRecord(value) || !Array.isArray(value.items)) return null;
|
||||
const cards = value.items.map(projectSafeResetCard);
|
||||
return cards.some((card) => card === null)
|
||||
? null
|
||||
: cards as Record<string, unknown>[];
|
||||
}
|
||||
|
||||
function projectSafeProject(
|
||||
value: unknown,
|
||||
options: { requireStatus?: boolean } = {},
|
||||
@@ -777,85 +618,6 @@ async function handleListMyProjects(
|
||||
sendJson(res, response.status, { success: true, page });
|
||||
}
|
||||
|
||||
async function handleGetBillingTokenPoints(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
): Promise<void> {
|
||||
const accessToken = readRequiredHeader(req, 'x-niancode-access-token');
|
||||
const response = await proxyAwareFetch(createWorksUrl('/api/billing/points').toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
await sendUpstreamError(res, response, `Works Square token points failed (${response.status})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const points = projectSafeTokenPointBalance(await readResponsePayload(response));
|
||||
if (!points) {
|
||||
sendJson(res, 502, { success: false, error: 'Works Square returned an invalid token point balance' });
|
||||
return;
|
||||
}
|
||||
sendJson(res, response.status, { success: true, points });
|
||||
}
|
||||
|
||||
async function handleListBillingResetCards(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
): Promise<void> {
|
||||
const accessToken = readRequiredHeader(req, 'x-niancode-access-token');
|
||||
const response = await proxyAwareFetch(createWorksUrl('/api/billing/reset-cards').toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
await sendResetCardUpstreamError(res, response);
|
||||
return;
|
||||
}
|
||||
|
||||
const cards = projectSafeResetCardPage(await readResponsePayload(response));
|
||||
if (!cards) {
|
||||
sendJson(res, 502, { success: false, error: 'Works Square returned an invalid reset-card list' });
|
||||
return;
|
||||
}
|
||||
sendJson(res, response.status, { success: true, cards });
|
||||
}
|
||||
|
||||
async function handleRedeemBillingResetCard(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
cardId: string,
|
||||
): Promise<void> {
|
||||
const accessToken = readRequiredHeader(req, 'x-niancode-access-token');
|
||||
const response = await proxyAwareFetch(
|
||||
createWorksUrl(`/api/billing/reset-cards/${encodeURIComponent(readRequiredString(cardId, 'card_id'))}/redeem`).toString(),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
await sendResetCardUpstreamError(res, response);
|
||||
return;
|
||||
}
|
||||
|
||||
const card = projectSafeResetCard(await readResponsePayload(response));
|
||||
if (!card) {
|
||||
sendJson(res, 502, { success: false, error: 'Works Square returned an invalid reset-card result' });
|
||||
return;
|
||||
}
|
||||
sendJson(res, response.status, { success: true, card });
|
||||
}
|
||||
|
||||
async function handleAgentProfile(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
@@ -1620,6 +1382,7 @@ export async function handleWorksRoutes(
|
||||
}
|
||||
|
||||
try {
|
||||
if (await handleWorksBillingRoutes(req, res, url)) return true;
|
||||
if (url.pathname === '/api/works/user/avatar' && (req.method === 'POST' || req.method === 'DELETE')) {
|
||||
await handleAgentAvatar(req, res);
|
||||
return true;
|
||||
@@ -1635,22 +1398,6 @@ export async function handleWorksRoutes(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/works/billing/points' && req.method === 'GET') {
|
||||
await handleGetBillingTokenPoints(req, res);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/works/billing/reset-cards' && req.method === 'GET') {
|
||||
await handleListBillingResetCards(req, res);
|
||||
return true;
|
||||
}
|
||||
|
||||
const resetCardRedeemMatch = url.pathname.match(/^\/api\/works\/billing\/reset-cards\/([^/]+)\/redeem$/);
|
||||
if (resetCardRedeemMatch && req.method === 'POST') {
|
||||
await handleRedeemBillingResetCard(req, res, decodeURIComponent(resetCardRedeemMatch[1]));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/works/ai-gateway/images/generations' && req.method === 'POST') {
|
||||
await handleSubmitImageGeneration(req, res);
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user