visual-game-admin-commerce.js 4.5 KB
import * as v from 'valibot'

export const VisualGameAdminBillingActionPolicy = v.object({
  action: v.string(),
  points: v.number(),
  period_kind: v.picklist(['daily', 'none']),
  timezone: v.nullable(v.string()),
  enabled: v.boolean(),
  updated_by: v.nullable(v.string()),
})

export const VisualGameAdminBillingProduct = v.object({
  id: v.string(),
  kind: v.literal('points_pack'),
  title: v.string(),
  points: v.number(),
  paid_points: v.number(),
  bonus_points: v.number(),
  price_cents: v.number(),
  price_usd: v.number(),
  currency: v.string(),
  stripe_price_id: v.nullable(v.string()),
  active: v.boolean(),
})

export const VisualGameAdminBillingOrder = v.object({
  id: v.string(),
  user_id: v.string(),
  product_id: v.string(),
  provider: v.string(),
  status: v.picklist(['pending', 'paid', 'canceled', 'failed', 'refunded', 'disputed']),
  amount_cents: v.number(),
  currency: v.string(),
  paid_points: v.number(),
  bonus_points: v.number(),
  total_points: v.number(),
  points_reversed: v.number(),
  reversal_shortfall_points: v.number(),
  created_at: v.string(),
})

export const VisualGameAdminBillingReference = v.object({
  label: v.string(),
  href: v.string(),
})

export const VisualGameAdminBillingLedgerEntry = v.object({
  id: v.string(),
  user_id: v.string(),
  type: v.picklist(['grant', 'deduct']),
  amount: v.number(),
  balance_after: v.number(),
  source: v.string(),
  credit_kind: v.nullable(v.string()),
  reason: v.string(),
  ref_type: v.nullable(v.string()),
  ref_id: v.nullable(v.string()),
  note: v.nullable(v.string()),
  failure_code: v.nullable(v.string()),
  reference: v.nullable(VisualGameAdminBillingReference),
  created_at: v.string(),
})

export const VisualGameAdminBillingPolicyList = v.object({
  items: v.array(VisualGameAdminBillingActionPolicy),
})

export const VisualGameAdminBillingProductList = v.object({
  items: v.array(VisualGameAdminBillingProduct),
})

export const VisualGameAdminBillingOrderPage = v.object({
  items: v.array(VisualGameAdminBillingOrder),
  next_cursor: v.nullable(v.string()),
})

export const VisualGameAdminBillingLedgerPage = v.object({
  items: v.array(VisualGameAdminBillingLedgerEntry),
  next_cursor: v.nullable(v.string()),
})

export const VisualGameAdminBillingDashboardDay = v.object({
  date: v.string(),
  start_at: v.string(),
  end_at: v.string(),
  revenue_cents: v.number(),
  paid_orders: v.number(),
  refund_orders: v.number(),
  points_granted: v.number(),
  points_spent: v.number(),
})

export const VisualGameAdminBillingDashboardProduct = v.object({
  product_id: v.string(),
  title: v.nullable(v.string()),
  orders: v.number(),
  revenue_cents: v.number(),
  points: v.number(),
})

/** 消耗按 reason + 业务类型分块:model_usage 这种按调用计费的流水靠 ref_type 才分得出 LLM / 语音 / 图片。 */
export const VisualGameAdminBillingDashboardSpend = v.object({
  reason: v.string(),
  ref_type: v.nullable(v.string()),
  points: v.number(),
})

/** 商业化看板:一个时间窗内的收入、积分、转化、风控汇总 + 按自然日的序列。比率分母为 0 时给 null。 */
export const VisualGameAdminBillingDashboard = v.object({
  since: v.string(),
  until: v.string(),
  start_at: v.string(),
  end_at: v.string(),
  timezone: v.string(),
  generated_at: v.string(),
  revenue: v.object({
    gross_cents: v.number(),
    refund_cents: v.number(),
    net_cents: v.number(),
    paid_orders: v.number(),
    paying_users: v.number(),
    first_time_buyers: v.number(),
  }),
  points: v.object({
    purchased_paid: v.number(),
    purchased_bonus: v.number(),
    daily_claim: v.number(),
    signup_reward: v.number(),
    admin_grant: v.number(),
    turn_spent: v.number(),
    video_spent: v.number(),
    other_spent: v.number(),
    turn_refunded: v.number(),
    video_refunded: v.number(),
  }),
  conversion: v.object({
    new_users: v.number(),
    new_users_paid: v.number(),
    signup_to_paid_rate: v.nullable(v.number()),
    claim_users: v.number(),
    active_users: v.number(),
    claim_rate: v.nullable(v.number()),
  }),
  risk: v.object({
    refunded_orders: v.number(),
    disputed_orders: v.number(),
    refund_rate: v.nullable(v.number()),
    dispute_rate: v.nullable(v.number()),
    turn_refunds: v.number(),
    video_refunds: v.number(),
    shortfall_orders: v.number(),
    shortfall_points: v.number(),
  }),
  daily: v.array(VisualGameAdminBillingDashboardDay),
  products: v.array(VisualGameAdminBillingDashboardProduct),
  spend_mix: v.array(VisualGameAdminBillingDashboardSpend),
})