主播提现列表

This commit is contained in:
zhx 2026-07-02 14:21:32 +08:00
parent 8fe2efb344
commit 18ad13563b
13 changed files with 917 additions and 1 deletions

View File

@ -1650,6 +1650,71 @@
"x-permissions": ["coin-seller:exchange-rate"]
}
},
"/admin/host-withdrawals": {
"get": {
"operationId": "listHostWithdrawals",
"parameters": [
{
"$ref": "#/components/parameters/Page"
},
{
"$ref": "#/components/parameters/PageSize"
},
{
"in": "query",
"name": "app_code",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "record_type",
"schema": {
"type": "string",
"enum": ["salary_transfer", "withdrawal"]
}
},
{
"in": "query",
"name": "identity",
"schema": {
"type": "string",
"enum": ["host", "agency", "bd", "bd_leader_admin"]
}
},
{
"$ref": "#/components/parameters/Status"
},
{
"$ref": "#/components/parameters/Keyword"
},
{
"in": "query",
"name": "start_at_ms",
"schema": {
"type": "integer",
"format": "int64"
}
},
{
"in": "query",
"name": "end_at_ms",
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"$ref": "#/components/responses/HostWithdrawalPageResponse"
}
},
"x-permission": "host-withdrawal:view",
"x-permissions": ["host-withdrawal:view"]
}
},
"/admin/countries": {
"get": {
"operationId": "listCountries",
@ -5819,6 +5884,16 @@
}
}
},
"HostWithdrawalPageResponse": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiResponseHostWithdrawalPage"
}
}
}
},
"HostProfilePageResponse": {
"description": "OK",
"content": {
@ -6159,6 +6234,27 @@
}
}
},
"ApiPageHostWithdrawal": {
"type": "object",
"required": ["items", "page", "pageSize", "total"],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/HostWithdrawal"
}
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
},
"total": {
"type": "integer"
}
}
},
"ApiPageRechargeBill": {
"type": "object",
"required": ["items", "page", "pageSize", "total"],
@ -6349,6 +6445,21 @@
}
]
},
"ApiResponseHostWithdrawalPage": {
"allOf": [
{
"$ref": "#/components/schemas/Envelope"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/components/schemas/ApiPageHostWithdrawal"
}
}
}
]
},
"ApiResponseRechargeBillPage": {
"allOf": [
{
@ -6852,6 +6963,114 @@
}
}
},
"HostWithdrawalUser": {
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"displayUserId": {
"type": "string"
},
"username": {
"type": "string"
},
"avatar": {
"type": "string"
}
}
},
"HostWithdrawal": {
"type": "object",
"required": [
"id",
"recordType",
"identity",
"salaryAssetType",
"usdMinorAmount",
"coinAmount",
"status",
"createdAtMs",
"updatedAtMs"
],
"properties": {
"id": {
"type": "string"
},
"recordType": {
"type": "string",
"enum": ["salary_transfer", "withdrawal"]
},
"identity": {
"type": "string"
},
"salaryAssetType": {
"type": "string"
},
"sourceUserId": {
"type": "string"
},
"sourceUser": {
"$ref": "#/components/schemas/HostWithdrawalUser"
},
"sellerUserId": {
"type": "string"
},
"sellerUser": {
"$ref": "#/components/schemas/HostWithdrawalUser"
},
"usdMinorAmount": {
"type": "integer",
"format": "int64"
},
"coinAmount": {
"type": "integer",
"format": "int64"
},
"status": {
"type": "string"
},
"transactionId": {
"type": "string"
},
"commandId": {
"type": "string"
},
"applicationId": {
"type": "string"
},
"freezeTransactionId": {
"type": "string"
},
"auditTransactionId": {
"type": "string"
},
"withdrawMethod": {
"type": "string"
},
"withdrawAddress": {
"type": "string"
},
"auditRemark": {
"type": "string"
},
"auditImageUrl": {
"type": "string"
},
"metadata": {
"type": "object",
"additionalProperties": true
},
"createdAtMs": {
"type": "integer",
"format": "int64"
},
"updatedAtMs": {
"type": "integer",
"format": "int64"
}
}
},
"HumanRoomRobotCountryRule": {
"type": "object",
"required": ["countryCode", "maxRoomCount"],

View File

@ -1,7 +1,7 @@
import { API_OPERATIONS, apiEndpointPath } from "../../src/shared/api/generated/endpoints.ts";
import { API_BASE_URL } from "../../src/shared/config/env.js";
import { countryFlag, stripCountryFlagPrefix } from "./data/countryMeta.js";
const API_BASE_URL = import.meta.env?.VITE_API_BASE_URL || "/api";
const TOKEN_KEY = "hyapp-admin.access-token";
const APP_CODE_KEY = "hyapp-admin.app-code";
const APP_CODE_HEADER = "X-App-Code";

View File

@ -134,6 +134,7 @@ export const fallbackNavigation = [
routeNavItem("host-agency-policy", { icon: WalletOutlined }),
routeNavItem("host-salary-settlement", { icon: ReceiptLongOutlined }),
routeNavItem("host-org-coin-sellers", { icon: WalletOutlined }),
routeNavItem("host-withdrawals", { icon: ReceiptLongOutlined }),
],
},
{

View File

@ -41,6 +41,7 @@ export const PERMISSIONS = {
coinSellerUpdate: "coin-seller:update",
coinSellerStockCredit: "coin-seller:stock-credit",
coinSellerExchangeRate: "coin-seller:exchange-rate",
hostWithdrawalView: "host-withdrawal:view",
financeView: "finance:view",
financeApplicationCreate: "finance-application:create",
financeApplicationAudit: "finance-application:audit",
@ -272,6 +273,7 @@ export const MENU_CODES = {
hostAgencyPolicy: "host-agency-policy",
hostSalarySettlement: "host-salary-settlement",
hostOrgCoinSellers: "host-org-coin-sellers",
hostWithdrawals: "host-withdrawals",
payment: "payment",
paymentBillList: "payment-bill-list",
paymentRechargeProducts: "payment-recharge-products",

View File

@ -30,6 +30,7 @@ import type {
CreateManagerPayload,
EntityId,
HostCommandPayload,
HostWithdrawalDto,
HostProfileDto,
ManagerDto,
PageQuery,
@ -212,6 +213,14 @@ export function listCoinSellers(query: PageQuery = {}): Promise<ApiPage<CoinSell
});
}
export function listHostWithdrawals(query: PageQuery = {}): Promise<ApiPage<HostWithdrawalDto>> {
const endpoint = API_ENDPOINTS.listHostWithdrawals;
return apiRequest<ApiPage<HostWithdrawalDto>>(apiEndpointPath(API_OPERATIONS.listHostWithdrawals), {
method: endpoint.method,
query,
});
}
export function listSalaryWalletHistory(query: PageQuery = {}): Promise<ApiPage<SalaryWalletHistoryDto>> {
return apiRequest<ApiPage<SalaryWalletHistoryDto>>("/v1/admin/host-org/salary-wallets/history", {
method: "GET",

View File

@ -0,0 +1,90 @@
import { useMemo, useState } from "react";
import { listHostWithdrawals } from "@/features/host-org/api";
import { useHostWithdrawalAbilities } from "@/features/host-org/permissions.js";
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
const pageSize = 50;
const emptyData = { items: [], page: 1, pageSize, total: 0 };
const emptyTimeRange = { endMs: "", startMs: "" };
export function useHostWithdrawalsPage() {
const abilities = useHostWithdrawalAbilities();
const [page, setPage] = useState(1);
const [recordType, setRecordType] = useState("");
const [identity, setIdentity] = useState("");
const [status, setStatus] = useState("");
const [keyword, setKeyword] = useState("");
const [timeRange, setTimeRange] = useState(emptyTimeRange);
const filters = useMemo(
() => ({
end_at_ms: timeRange.endMs || "",
identity,
keyword,
record_type: recordType,
start_at_ms: timeRange.startMs || "",
status,
}),
[identity, keyword, recordType, status, timeRange.endMs, timeRange.startMs],
);
const query = usePaginatedQuery({
errorMessage: "加载主播提现列表失败",
fetcher: listHostWithdrawals,
filters,
page,
pageSize,
queryKey: ["host-org", "host-withdrawals", filters, page],
});
const data = query.data || emptyData;
const hasActiveFilters = Boolean(recordType || identity || status || keyword || timeRange.startMs || timeRange.endMs);
const changeRecordType = (value) => {
setRecordType(value);
setPage(1);
};
const changeIdentity = (value) => {
setIdentity(value);
setPage(1);
};
const changeStatus = (value) => {
setStatus(value);
setPage(1);
};
const changeKeyword = (value) => {
setKeyword(value);
setPage(1);
};
const changeTimeRange = (nextRange) => {
setTimeRange(nextRange);
setPage(1);
};
const resetFilters = () => {
setRecordType("");
setIdentity("");
setStatus("");
setKeyword("");
setTimeRange(emptyTimeRange);
setPage(1);
};
return {
abilities,
changeIdentity,
changeKeyword,
changeRecordType,
changeStatus,
changeTimeRange,
data,
error: query.error,
hasActiveFilters,
identity,
keyword,
loading: query.loading,
page,
recordType,
reload: query.reload,
resetFilters,
setPage,
status,
timeRange,
};
}

View File

@ -0,0 +1,276 @@
import {
AdminFilterResetButton,
AdminListBody,
AdminListPage,
AdminListToolbar,
} from "@/shared/ui/AdminListLayout.jsx";
import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx";
import { DataState } from "@/shared/ui/DataState.jsx";
import { DataTable } from "@/shared/ui/DataTable.jsx";
import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx";
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
import { formatMillis } from "@/shared/utils/time.js";
import { useHostWithdrawalsPage } from "@/features/host-org/hooks/useHostWithdrawalsPage.js";
import styles from "@/features/operations/operations.module.css";
const recordTypeOptions = [
["", "全部"],
["salary_transfer", "工资转币商"],
["withdrawal", "提现申请"],
];
const identityOptions = [
["", "全部"],
["host", "Host"],
["agency", "Agency"],
["bd", "BD"],
["bd_leader_admin", "BD Leader/Admin"],
];
const statusOptions = [
["", "全部"],
["completed", "已完成"],
["pending", "待审核"],
["approved", "已通过"],
["rejected", "已拒绝"],
];
const recordTypeLabels = Object.fromEntries(recordTypeOptions.filter(([value]) => value));
const identityLabels = Object.fromEntries(identityOptions.filter(([value]) => value));
const statusLabels = Object.fromEntries(statusOptions.filter(([value]) => value));
const baseColumns = [
{
key: "createdAtMs",
label: "时间",
render: (item) => formatMillis(item.createdAtMs),
width: "minmax(170px, 0.8fr)",
},
{
key: "recordType",
label: "类型",
render: (item) => recordTypeLabel(item.recordType),
width: "minmax(130px, 0.6fr)",
},
{
key: "identity",
label: "身份",
render: (item) => identityLabel(item.identity, item.salaryAssetType),
width: "minmax(150px, 0.65fr)",
},
{
key: "sourceUser",
label: "发起用户",
render: (item) => <AdminUserIdentity openInAppUserDetail user={item.sourceUser} userId={item.sourceUserId} />,
width: "minmax(230px, 1fr)",
},
{
key: "receiver",
label: "币商/收款",
render: (item) => <ReceiverCell item={item} />,
width: "minmax(240px, 1.05fr)",
},
{
key: "usdMinorAmount",
label: "USD金额",
render: (item) => <AmountText value={`$${formatUSDMinor(item.usdMinorAmount)}`} />,
width: "minmax(120px, 0.55fr)",
},
{
key: "coinAmount",
label: "币商金币",
render: (item) => formatCoinAmount(item),
width: "minmax(130px, 0.6fr)",
},
{
key: "status",
label: "状态",
render: (item) => <StatusBadge status={item.status} />,
width: "minmax(110px, 0.55fr)",
},
{
key: "recordId",
label: "交易/申请ID",
render: (item) => <RecordIdCell item={item} />,
width: "minmax(260px, 1.1fr)",
},
];
export function HostWithdrawalsPage() {
const page = useHostWithdrawalsPage();
const data = page.data || { items: [], page: 1, pageSize: 50, total: 0 };
const items = data.items || [];
const total = Number(data.total || 0);
const tableColumns = baseColumns.map((column) => {
if (column.key === "recordType") {
return {
...column,
filter: createOptionsColumnFilter({
emptyLabel: "全部",
options: recordTypeOptions,
placeholder: "类型",
value: page.recordType,
onChange: page.changeRecordType,
}),
};
}
if (column.key === "identity") {
return {
...column,
filter: createOptionsColumnFilter({
emptyLabel: "全部",
options: identityOptions,
placeholder: "身份",
value: page.identity,
onChange: page.changeIdentity,
}),
};
}
if (column.key === "sourceUser") {
return {
...column,
filter: createTextColumnFilter({
activeLabel: page.keyword ? `用户 ${page.keyword}` : "",
placeholder: "用户/交易/地址",
value: page.keyword,
onChange: page.changeKeyword,
}),
};
}
if (column.key === "status") {
return {
...column,
filter: createOptionsColumnFilter({
emptyLabel: "全部",
options: statusOptions,
placeholder: "状态",
value: page.status,
onChange: page.changeStatus,
}),
};
}
return column;
});
return (
<AdminListPage>
<AdminListToolbar
filters={
<>
<TimeRangeFilter value={page.timeRange} onChange={page.changeTimeRange} />
<AdminFilterResetButton disabled={!page.hasActiveFilters} onClick={page.resetFilters} />
</>
}
/>
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
<AdminListBody>
<DataTable
columns={tableColumns}
emptyLabel="当前无主播提现记录"
items={items}
minWidth="1510px"
pagination={
total > 0
? {
page: page.page,
pageSize: data.pageSize || 50,
total,
onPageChange: page.setPage,
}
: undefined
}
rowKey={(item) => item.id}
/>
</AdminListBody>
</DataState>
</AdminListPage>
);
}
function ReceiverCell({ item }) {
if (item.recordType === "salary_transfer") {
return <AdminUserIdentity openInAppUserDetail user={item.sellerUser} userId={item.sellerUserId} />;
}
const method = String(item.withdrawMethod || "").trim();
const address = String(item.withdrawAddress || "").trim();
if (!method && !address) {
return "-";
}
return (
<span className={styles.identityText}>
<span className={styles.primaryText}>{method || "-"}</span>
{address ? <span className={styles.meta}>{address}</span> : null}
</span>
);
}
function RecordIdCell({ item }) {
const primary = item.transactionId || item.applicationId || item.id || "-";
const secondary = item.commandId || item.freezeTransactionId || item.auditTransactionId || "";
return (
<span className={styles.identityText}>
<span className={styles.primaryText}>{primary}</span>
{secondary ? <span className={styles.meta}>{secondary}</span> : null}
</span>
);
}
function StatusBadge({ status }) {
const normalized = String(status || "").trim();
return (
<span className={`status-badge status-badge--${statusTone(normalized)}`}>
<span className="status-point" />
{statusLabel(normalized)}
</span>
);
}
function AmountText({ value }) {
return <span className={styles.amount}>{value}</span>;
}
function recordTypeLabel(value) {
return recordTypeLabels[value] || value || "-";
}
function identityLabel(identity, salaryAssetType) {
return identityLabels[identity] || salaryAssetLabel(salaryAssetType) || identity || "-";
}
function salaryAssetLabel(assetType) {
const labels = {
AGENCY_SALARY_USD: "Agency",
ADMIN_SALARY_USD: "BD Leader/Admin",
BD_SALARY_USD: "BD",
HOST_SALARY_USD: "Host",
};
return labels[assetType] || "";
}
function statusLabel(status) {
return statusLabels[status] || status || "-";
}
function statusTone(status) {
if (status === "completed" || status === "approved") {
return "succeeded";
}
if (status === "pending") {
return "warning";
}
if (status === "rejected") {
return "danger";
}
return "stopped";
}
function formatCoinAmount(item) {
if (item.recordType !== "salary_transfer") {
return "-";
}
return Number(item.coinAmount || 0).toLocaleString("zh-CN");
}
function formatUSDMinor(value) {
return (Number(value || 0) / 100).toFixed(2);
}

View File

@ -0,0 +1,175 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { afterEach, expect, test, vi } from "vitest";
import { HostWithdrawalsPage } from "./HostWithdrawalsPage.jsx";
import { useHostWithdrawalsPage } from "@/features/host-org/hooks/useHostWithdrawalsPage.js";
const mocks = vi.hoisted(() => ({
changeIdentity: vi.fn(),
changeKeyword: vi.fn(),
changeRecordType: vi.fn(),
changeStatus: vi.fn(),
changeTimeRange: vi.fn(),
dataTable: vi.fn(),
resetFilters: vi.fn(),
}));
vi.mock("@/features/host-org/hooks/useHostWithdrawalsPage.js", () => ({
useHostWithdrawalsPage: vi.fn(),
}));
vi.mock("@/shared/ui/DataTable.jsx", () => ({
DataTable: (props) => {
mocks.dataTable(props);
return (
<div data-testid="host-withdrawals-table">
{props.columns.map((column) => (
<div data-testid={`column-${column.key}`} key={column.key}>
{column.label}
</div>
))}
{props.items.map((item) => (
<div data-testid={`row-${item.id}`} key={item.id}>
{props.columns.map((column) => (
<div key={column.key}>{column.render ? column.render(item) : item[column.key]}</div>
))}
</div>
))}
</div>
);
},
}));
vi.mock("@/shared/ui/TimeRangeFilter.jsx", () => ({
TimeRangeFilter: ({ onChange }) => (
<button type="button" onClick={() => onChange({ endMs: "2000", startMs: "1000" })}>
时间
</button>
),
}));
afterEach(() => {
vi.clearAllMocks();
});
test("host withdrawals page renders salary transfer and withdrawal records", () => {
vi.mocked(useHostWithdrawalsPage).mockReturnValue(pageFixture());
render(<HostWithdrawalsPage />);
expect(screen.getByText("工资转币商")).toBeInTheDocument();
expect(screen.getByText("提现申请")).toBeInTheDocument();
expect(screen.getByText("Host")).toBeInTheDocument();
expect(screen.getByText("BD Leader/Admin")).toBeInTheDocument();
expect(screen.getByText("$123.45")).toBeInTheDocument();
expect(screen.getByText("1,112")).toBeInTheDocument();
expect(screen.getByText("已完成")).toBeInTheDocument();
expect(screen.getByText("待审核")).toBeInTheDocument();
expect(screen.getByText("USDT-TRC20")).toBeInTheDocument();
expect(screen.getByText("TRON9ADDRESS")).toBeInTheDocument();
expect(screen.getByText("tx-salary-1")).toBeInTheDocument();
expect(screen.getByText("9")).toBeInTheDocument();
});
test("host withdrawals filters are wired to the page hook", () => {
vi.mocked(useHostWithdrawalsPage).mockReturnValue(pageFixture({ hasActiveFilters: true }));
render(<HostWithdrawalsPage />);
const props = mocks.dataTable.mock.calls.at(-1)?.[0];
props.columns.find((column) => column.key === "recordType").filter.onChange("withdrawal");
props.columns.find((column) => column.key === "identity").filter.onChange("bd_leader_admin");
props.columns.find((column) => column.key === "sourceUser").filter.onChange("168557");
props.columns.find((column) => column.key === "status").filter.onChange("approved");
fireEvent.click(screen.getByRole("button", { name: "时间" }));
fireEvent.click(screen.getByRole("button", { name: "重置" }));
expect(mocks.changeRecordType).toHaveBeenCalledWith("withdrawal");
expect(mocks.changeIdentity).toHaveBeenCalledWith("bd_leader_admin");
expect(mocks.changeKeyword).toHaveBeenCalledWith("168557");
expect(mocks.changeStatus).toHaveBeenCalledWith("approved");
expect(mocks.changeTimeRange).toHaveBeenCalledWith({ endMs: "2000", startMs: "1000" });
expect(mocks.resetFilters).toHaveBeenCalled();
});
function pageFixture(patch = {}) {
return {
abilities: { canView: true },
changeIdentity: mocks.changeIdentity,
changeKeyword: mocks.changeKeyword,
changeRecordType: mocks.changeRecordType,
changeStatus: mocks.changeStatus,
changeTimeRange: mocks.changeTimeRange,
data: {
items: [salaryTransferFixture(), withdrawalFixture()],
page: 1,
pageSize: 50,
total: 2,
},
error: null,
hasActiveFilters: false,
identity: "",
keyword: "",
loading: false,
page: 1,
recordType: "",
reload: vi.fn(),
resetFilters: mocks.resetFilters,
setPage: vi.fn(),
status: "",
timeRange: { endMs: "", startMs: "" },
...patch,
};
}
function salaryTransferFixture() {
return {
coinAmount: 1112,
commandId: "cmd-salary-1",
createdAtMs: 1780000000000,
id: "salary_transfer:101",
identity: "host",
recordType: "salary_transfer",
salaryAssetType: "HOST_SALARY_USD",
sellerUser: {
displayUserId: "168556",
userId: "3001",
username: "Coin Seller",
},
sellerUserId: "3001",
sourceUser: {
displayUserId: "168577",
userId: "1001",
username: "Raj",
},
sourceUserId: "1001",
status: "completed",
transactionId: "tx-salary-1",
updatedAtMs: 1780000000000,
usdMinorAmount: 12345,
};
}
function withdrawalFixture() {
return {
applicationId: "9",
coinAmount: 0,
commandId: "freeze-cmd-9",
createdAtMs: 1780000001000,
freezeTransactionId: "freeze-tx-9",
id: "withdrawal:9",
identity: "bd_leader_admin",
recordType: "withdrawal",
salaryAssetType: "ADMIN_SALARY_USD",
sourceUser: {
displayUserId: "481479",
userId: "2002",
username: "Mao",
},
sourceUserId: "2002",
status: "pending",
updatedAtMs: 1780000001000,
usdMinorAmount: 5000,
withdrawAddress: "TRON9ADDRESS",
withdrawMethod: "USDT-TRC20",
};
}

View File

@ -67,3 +67,11 @@ export function useCoinSellerAbilities() {
canView: can(PERMISSIONS.coinSellerView),
};
}
export function useHostWithdrawalAbilities() {
const { can } = useAuth();
return {
canView: can(PERMISSIONS.hostWithdrawalView),
};
}

View File

@ -65,4 +65,12 @@ export const hostOrgRoutes = [
path: "/host/coin-sellers",
permission: PERMISSIONS.coinSellerView,
},
{
label: "主播提现",
loader: () => import("./pages/HostWithdrawalsPage.jsx").then((module) => module.HostWithdrawalsPage),
menuCode: MENU_CODES.hostWithdrawals,
pageKey: "host-withdrawals",
path: "/host/withdrawals",
permission: PERMISSIONS.hostWithdrawalView,
},
];

View File

@ -175,6 +175,7 @@ export const API_OPERATIONS = {
listHostAgencyPolicies: "listHostAgencyPolicies",
listHosts: "listHosts",
listHostSalarySettlements: "listHostSalarySettlements",
listHostWithdrawals: "listHostWithdrawals",
listInviteActivityRewardClaims: "listInviteActivityRewardClaims",
listLevelConfig: "listLevelConfig",
listLoginLogs: "listLoginLogs",
@ -1460,6 +1461,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
permission: "host-salary-settlement:view",
permissions: ["host-salary-settlement:view"]
},
listHostWithdrawals: {
method: "GET",
operationId: API_OPERATIONS.listHostWithdrawals,
path: "/v1/admin/host-withdrawals",
permission: "host-withdrawal:view",
permissions: ["host-withdrawal:view"]
},
listInviteActivityRewardClaims: {
method: "GET",
operationId: API_OPERATIONS.listInviteActivityRewardClaims,

View File

@ -1140,6 +1140,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/admin/host-withdrawals": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["listHostWithdrawals"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/countries": {
parameters: {
query?: never;
@ -3672,6 +3688,12 @@ export interface components {
pageSize: number;
total: number;
};
ApiPageHostWithdrawal: {
items: components["schemas"]["HostWithdrawal"][];
page: number;
pageSize: number;
total: number;
};
ApiPageRechargeBill: {
items: components["schemas"]["RechargeBill"][];
page: number;
@ -3714,6 +3736,9 @@ export interface components {
ApiResponseFinanceWithdrawalApplicationPage: components["schemas"]["Envelope"] & {
data?: components["schemas"]["ApiPageFinanceWithdrawalApplication"];
};
ApiResponseHostWithdrawalPage: components["schemas"]["Envelope"] & {
data?: components["schemas"]["ApiPageHostWithdrawal"];
};
ApiResponseRechargeBillPage: components["schemas"]["Envelope"] & {
data?: components["schemas"]["ApiPageRechargeBill"];
};
@ -3845,6 +3870,44 @@ export interface components {
updatedAtMs?: number;
userId: string;
};
HostWithdrawalUser: {
userId?: string;
displayUserId?: string;
username?: string;
avatar?: string;
};
HostWithdrawal: {
id: string;
/** @enum {string} */
recordType: "salary_transfer" | "withdrawal";
identity: string;
salaryAssetType: string;
sourceUserId?: string;
sourceUser?: components["schemas"]["HostWithdrawalUser"];
sellerUserId?: string;
sellerUser?: components["schemas"]["HostWithdrawalUser"];
/** Format: int64 */
usdMinorAmount: number;
/** Format: int64 */
coinAmount: number;
status: string;
transactionId?: string;
commandId?: string;
applicationId?: string;
freezeTransactionId?: string;
auditTransactionId?: string;
withdrawMethod?: string;
withdrawAddress?: string;
auditRemark?: string;
auditImageUrl?: string;
metadata?: {
[key: string]: unknown;
};
/** Format: int64 */
createdAtMs: number;
/** Format: int64 */
updatedAtMs: number;
};
HumanRoomRobotCountryRule: {
allowedOwnerIds?: string[];
countryCode: string;
@ -4137,6 +4200,15 @@ export interface components {
};
};
/** @description OK */
HostWithdrawalPageResponse: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiResponseHostWithdrawalPage"];
};
};
/** @description OK */
HostProfilePageResponse: {
headers: {
[name: string]: unknown;
@ -5802,6 +5874,28 @@ export interface operations {
200: components["responses"]["EmptyResponse"];
};
};
listHostWithdrawals: {
parameters: {
query?: {
page?: components["parameters"]["Page"];
page_size?: components["parameters"]["PageSize"];
app_code?: string;
record_type?: "salary_transfer" | "withdrawal";
identity?: "host" | "agency" | "bd" | "bd_leader_admin";
status?: components["parameters"]["Status"];
keyword?: components["parameters"]["Keyword"];
start_at_ms?: number;
end_at_ms?: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
200: components["responses"]["HostWithdrawalPageResponse"];
};
};
listCountries: {
parameters: {
query?: never;

View File

@ -273,6 +273,32 @@ export interface CoinSellerLedgerDto {
transferUsdMinor?: number;
}
export interface HostWithdrawalDto {
applicationId?: string;
auditImageUrl?: string;
auditRemark?: string;
auditTransactionId?: string;
coinAmount: number;
commandId?: string;
createdAtMs?: number;
freezeTransactionId?: string;
id: string;
identity: string;
metadata?: Record<string, unknown>;
recordType: string;
salaryAssetType: string;
sellerUser?: CoinLedgerUserDto;
sellerUserId?: string;
sourceUser?: CoinLedgerUserDto;
sourceUserId?: string;
status: string;
transactionId?: string;
updatedAtMs?: number;
usdMinorAmount: number;
withdrawAddress?: string;
withdrawMethod?: string;
}
export interface CoinAdjustmentOperatorDto {
adminId?: string;
name?: string;