feat(auth): 接入 DCAuth v2 登录并完善成员管理

- 登录链路改为用户名 + 密码 + 短信验证码 + DCAuth v2 设备签名
- 迁移 014:新增 login_sms_codes / user_devices / device_challenges 与 users.phone
- 新增 /api/dcauth/authorize 与 challenge 状态轮询;旧密码登录仅放行未绑定手机号的账号
- 成员管理支持手机号绑定、设备重置、账号改名、昵称与重置密码
- 移除最后一个商户关系时软删除账号,释放用户名、手机号并撤销设备绑定
This commit is contained in:
yml2213
2026-10-21 22:37:49 +08:00
parent aa052d420a
commit bab522bcb3
29 changed files with 5490 additions and 1170 deletions
+537 -221
View File
@@ -1,4 +1,4 @@
import request from './request'
import request from "./request";
import type {
DashboardStats,
AlertChannelInput,
@@ -17,11 +17,11 @@ import type {
LoginResult,
LowBalanceAlertConfig,
Merchant,
MerchantMember,
MerchantMemberGroup,
MerchantOption,
MerchantOwnerAccount,
MerchantRole,
MerchantMember,
MerchantMemberGroup,
MerchantOption,
MerchantOwnerAccount,
MerchantRole,
MerchantProduct,
PageResult,
ProductCatalogItem,
@@ -32,328 +32,644 @@ import type {
LewanProductListResult,
UpdateProductRoutingInput,
SyncLewanProductInput,
} from '../types'
SendLoginSMSCodeResult,
PreparedDeviceLogin,
DCAuthChallengeStatus,
} from "../types";
export const authApi = {
login: async (username: string, password: string) => {
const res = await request.post('/auth/login', { username, password })
return res.data.data as LoginResult
const res = await request.post("/auth/login", { username, password });
return res.data.data as LoginResult;
},
sendLoginSmsCode: async (username: string, password: string) => {
const res = await request.post("/auth/login/sms-code", {
username,
password,
});
return res.data.data as SendLoginSMSCodeResult;
},
prepareLogin: async (data: {
username: string;
password: string;
smsCode: string;
apiBase?: string;
}) => {
const res = await request.post("/auth/login/prepare", data);
return res.data.data as PreparedDeviceLogin;
},
completeLogin: async (data: {
challengeId: string;
pollToken: string;
deviceTicket: string;
}) => {
const res = await request.post("/auth/login/complete", data);
return res.data.data as LoginResult;
},
deviceChallengeStatus: async (challengeId: string, pollToken: string) => {
const res = await request.get(
`/dcauth/challenge/${encodeURIComponent(challengeId)}/status`,
{
params: { pollToken },
},
);
return res.data.data as DCAuthChallengeStatus;
},
profile: async () => {
const res = await request.get('/auth/profile')
return res.data.data as User
const res = await request.get("/auth/profile");
return res.data.data as User;
},
changePassword: (data: { current_password: string; new_password: string }) =>
request.put('/auth/password', data).then((r) => r.data.data),
updateCurrentAccount: (data: { current_password: string; username: string; nickname?: string; new_password?: string }) =>
request.put('/auth/account', data).then((r) => r.data.data as User),
}
request.put("/auth/password", data).then((r) => r.data.data),
updateCurrentAccount: (data: {
current_password: string;
username: string;
nickname?: string;
new_password?: string;
}) => request.put("/auth/account", data).then((r) => r.data.data as User),
};
export const dashboardApi = {
stats: () =>
request.get('/dashboard').then((r) => r.data.data as DashboardStats),
}
request.get("/dashboard").then((r) => r.data.data as DashboardStats),
};
export const userApi = {
list: (params?: Record<string, unknown>) =>
request.get('/platform/admins', { params }).then((r) => r.data.data as PageResult<User>),
request
.get("/platform/admins", { params })
.then((r) => r.data.data as PageResult<User>),
create: (data: { username: string; password: string; nickname?: string }) =>
request.post('/platform/admins', data).then((r) => r.data.data as User),
update: (id: number, data: { username: string; nickname?: string; password?: string; status: number }) =>
request.patch(`/platform/admins/${id}`, data).then((r) => r.data.data as User),
request.post("/platform/admins", data).then((r) => r.data.data as User),
update: (
id: number,
data: {
username: string;
nickname?: string;
password?: string;
status: number;
},
) =>
request
.patch(`/platform/admins/${id}`, data)
.then((r) => r.data.data as User),
remove: (id: number) =>
request.delete(`/platform/admins/${id}`).then((r) => r.data.data),
}
bindPhone: (id: number, phone: string) =>
request
.post(`/admin/users/${id}/phone`, { phone })
.then((r) => r.data.data as User),
resetDevice: (id: number) =>
request
.post(`/admin/users/${id}/device/reset`)
.then((r) => r.data.data as { reset: boolean }),
};
export const merchantApi = {
current: () =>
request.get('/merchant').then((r) => r.data.data as { merchant: Merchant; role: string; permissions: string[] }),
request.get("/merchant").then(
(r) =>
r.data.data as {
merchant: Merchant;
role: string;
permissions: string[];
},
),
products: (params?: Record<string, unknown>) =>
request.get('/merchant/products', { params }).then((r) => r.data.data as PageResult<MerchantProduct>),
request
.get("/merchant/products", { params })
.then((r) => r.data.data as PageResult<MerchantProduct>),
updateProduct: (
id: number,
data: {
display_name?: string
price_amount?: number
cost_amount?: number
stock?: number
status?: 'active' | 'inactive'
}
display_name?: string;
price_amount?: number;
cost_amount?: number;
stock?: number;
status?: "active" | "inactive";
},
) => request.patch(`/merchant/products/${id}`, data).then((r) => r.data.data),
updateProductStatus: (id: number, status: MerchantProduct['status']) =>
request.patch(`/merchant/products/${id}/status`, { status }).then((r) => r.data.data),
updateProductStatus: (id: number, status: MerchantProduct["status"]) =>
request
.patch(`/merchant/products/${id}/status`, { status })
.then((r) => r.data.data),
deleteProduct: (id: number) =>
request.delete(`/merchant/products/${id}`).then((r) => r.data.data),
orders: (params?: Record<string, unknown>) =>
request.get('/merchant/orders', { params }).then((r) => r.data.data as PageResult<FulfillmentOrder>),
request
.get("/merchant/orders", { params })
.then((r) => r.data.data as PageResult<FulfillmentOrder>),
manualOrderProducts: () =>
request.get('/merchant/orders/products').then((r) => r.data.data as MerchantProduct[]),
request
.get("/merchant/orders/products")
.then((r) => r.data.data as MerchantProduct[]),
createTestOrder: (data: {
sku: string
buyer_reference?: string
note?: string
order_status?: 'paid' | 'ship_failed' | 'cancelled'
}) => request.post('/merchant/orders/test', data).then((r) => r.data.data as CreateTestOrderResult),
sku: string;
buyer_reference?: string;
note?: string;
order_status?: "paid" | "ship_failed" | "cancelled";
}) =>
request
.post("/merchant/orders/test", data)
.then((r) => r.data.data as CreateTestOrderResult),
createManualOrder: (data: {
client_order_no: string
sku: string
quantity: number
buyer_reference?: string
game_account?: string
note?: string
}) => request.post('/merchant/orders/manual', data).then((r) => r.data.data as CreateManualOrderResult),
client_order_no: string;
sku: string;
quantity: number;
buyer_reference?: string;
game_account?: string;
note?: string;
}) =>
request
.post("/merchant/orders/manual", data)
.then((r) => r.data.data as CreateManualOrderResult),
cancelOrder: (orderNo: string, reason: string) =>
request.post(`/merchant/orders/${encodeURIComponent(orderNo)}/cancel`, { reason }).then((r) => r.data.data as { order: FulfillmentOrder; refund_amount: number }),
request
.post(`/merchant/orders/${encodeURIComponent(orderNo)}/cancel`, {
reason,
})
.then(
(r) =>
r.data.data as { order: FulfillmentOrder; refund_amount: number },
),
getDeliveryLink: (orderNo: string) =>
request.get(`/merchant/orders/${encodeURIComponent(orderNo)}/delivery-link`).then((r) => r.data.data as DeliveryLinkResult),
request
.get(`/merchant/orders/${encodeURIComponent(orderNo)}/delivery-link`)
.then((r) => r.data.data as DeliveryLinkResult),
revokeDeliveryLink: (orderNo: string) =>
request.post(`/merchant/orders/${encodeURIComponent(orderNo)}/delivery-link/revoke`).then((r) => r.data.data),
request
.post(
`/merchant/orders/${encodeURIComponent(orderNo)}/delivery-link/revoke`,
)
.then((r) => r.data.data),
restoreDeliveryLink: (orderNo: string) =>
request.post(`/merchant/orders/${encodeURIComponent(orderNo)}/delivery-link/restore`).then((r) => r.data.data as DeliveryLinkResult),
request
.post(
`/merchant/orders/${encodeURIComponent(orderNo)}/delivery-link/restore`,
)
.then((r) => r.data.data as DeliveryLinkResult),
wallet: () =>
request.get('/merchant/wallet').then((r) => r.data.data as WalletAccount),
request.get("/merchant/wallet").then((r) => r.data.data as WalletAccount),
ledger: (params?: Record<string, unknown>) =>
request.get('/merchant/wallet/ledger', { params }).then((r) => r.data.data as PageResult<WalletLedgerEntry>),
request
.get("/merchant/wallet/ledger", { params })
.then((r) => r.data.data as PageResult<WalletLedgerEntry>),
apiClients: () =>
request.get('/merchant/api-clients').then((r) => r.data.data as ApiClient[]),
request
.get("/merchant/api-clients")
.then((r) => r.data.data as ApiClient[]),
createApiClient: (data: {
name: string
scopes: string
signature_version?: string
expires_at?: string
}) => request.post('/merchant/api-clients', data).then((r) => r.data.data as ApiCredential),
updateApiClientStatus: (id: number, status: ApiClient['status']) =>
request.patch(`/merchant/api-clients/${id}/status`, { status }).then((r) => r.data.data),
name: string;
scopes: string;
signature_version?: string;
expires_at?: string;
}) =>
request
.post("/merchant/api-clients", data)
.then((r) => r.data.data as ApiCredential),
updateApiClientStatus: (id: number, status: ApiClient["status"]) =>
request
.patch(`/merchant/api-clients/${id}/status`, { status })
.then((r) => r.data.data),
deleteApiClient: (id: number) =>
request.delete(`/merchant/api-clients/${id}`).then((r) => r.data.data),
callbacks: () =>
request.get('/merchant/callbacks').then((r) => r.data.data as CallbackSubscription | null),
saveCallback: (data: { url: string; events: string; status: CallbackSubscription['status']; rotate_secret?: boolean }) =>
request.post('/merchant/callbacks', data).then((r) => r.data.data as CallbackCredential),
request
.get("/merchant/callbacks")
.then((r) => r.data.data as CallbackSubscription | null),
saveCallback: (data: {
url: string;
events: string;
status: CallbackSubscription["status"];
rotate_secret?: boolean;
}) =>
request
.post("/merchant/callbacks", data)
.then((r) => r.data.data as CallbackCredential),
members: () =>
request.get('/merchant/members').then((r) => r.data.data as MerchantMember[]),
addMember: (data: { user_id?: number; username?: string; password?: string; nickname?: string; role: string; is_default?: boolean }) =>
request.post('/merchant/members', data).then((r) => r.data.data as MerchantMember),
updateMember: (id: number, data: { role: string; status: number; is_default: boolean }) =>
request.patch(`/merchant/members/${id}`, data).then((r) => r.data.data as MerchantMember),
request
.get("/merchant/members")
.then((r) => r.data.data as MerchantMember[]),
addMember: (data: {
user_id?: number;
username?: string;
password?: string;
nickname?: string;
role: string;
is_default?: boolean;
}) =>
request
.post("/merchant/members", data)
.then((r) => r.data.data as MerchantMember),
updateMember: (
id: number,
data: {
username?: string;
password?: string;
nickname?: string;
role: string;
status: number;
is_default: boolean;
},
) =>
request
.patch(`/merchant/members/${id}`, data)
.then((r) => r.data.data as MerchantMember),
removeMember: (id: number) =>
request.delete(`/merchant/members/${id}`).then((r) => r.data.data),
roles: () => request.get('/merchant/roles').then((r) => r.data.data as MerchantRole[]),
bindMemberPhone: (id: number, phone: string) =>
request
.post(`/merchant/members/${id}/phone`, { phone })
.then((r) => r.data.data as User),
resetMemberDevice: (id: number) =>
request
.post(`/merchant/members/${id}/device/reset`)
.then((r) => r.data.data as { reset: boolean }),
roles: () =>
request.get("/merchant/roles").then((r) => r.data.data as MerchantRole[]),
createRole: (data: { code: string; name: string; permissions: string[] }) =>
request.post('/merchant/roles', data).then((r) => r.data.data as MerchantRole),
request
.post("/merchant/roles", data)
.then((r) => r.data.data as MerchantRole),
updateRole: (id: number, data: { name: string; permissions: string[] }) =>
request.patch(`/merchant/roles/${id}`, data).then((r) => r.data.data as MerchantRole),
request
.patch(`/merchant/roles/${id}`, data)
.then((r) => r.data.data as MerchantRole),
rechargeApplications: (params?: Record<string, unknown>) =>
request.get('/merchant/recharge/applications', { params }).then((r) => r.data.data as PageResult<RechargeApplication>),
createRechargeApplication: (data: { amount_cny: number; vouchers: string[]; note?: string }) =>
request
.get("/merchant/recharge/applications", { params })
.then((r) => r.data.data as PageResult<RechargeApplication>),
createRechargeApplication: (data: {
amount_cny: number;
vouchers: string[];
note?: string;
}) =>
// amount_cny 单位:分(1元 = 100分),积分按 1元 = 100积分 折算
request.post('/merchant/recharge/applications', data).then((r) => r.data.data as RechargeApplication),
request
.post("/merchant/recharge/applications", data)
.then((r) => r.data.data as RechargeApplication),
alertConfig: () =>
request.get('/merchant/recharge/alert-config').then((r) => r.data.data as LowBalanceAlertConfig),
saveAlertConfig: (data: { enabled: boolean; threshold_points: number; notify_interval_minutes: number; max_notifications: number }) =>
request.put('/merchant/recharge/alert-config', data).then((r) => r.data.data as LowBalanceAlertConfig),
request
.get("/merchant/recharge/alert-config")
.then((r) => r.data.data as LowBalanceAlertConfig),
saveAlertConfig: (data: {
enabled: boolean;
threshold_points: number;
notify_interval_minutes: number;
max_notifications: number;
}) =>
request
.put("/merchant/recharge/alert-config", data)
.then((r) => r.data.data as LowBalanceAlertConfig),
alertChannels: () =>
request.get('/merchant/alert-channels').then((r) => r.data.data as AlertNotifyChannel[]),
request
.get("/merchant/alert-channels")
.then((r) => r.data.data as AlertNotifyChannel[]),
createAlertChannel: (data: AlertChannelInput) =>
request.post('/merchant/alert-channels', data).then((r) => r.data.data as AlertNotifyChannel),
request
.post("/merchant/alert-channels", data)
.then((r) => r.data.data as AlertNotifyChannel),
updateAlertChannel: (id: number, data: AlertChannelInput) =>
request.put(`/merchant/alert-channels/${id}`, data).then((r) => r.data.data as AlertNotifyChannel),
request
.put(`/merchant/alert-channels/${id}`, data)
.then((r) => r.data.data as AlertNotifyChannel),
deleteAlertChannel: (id: number) =>
request.delete(`/merchant/alert-channels/${id}`).then((r) => r.data.data),
testAlertChannel: (id: number) =>
request.post(`/merchant/alert-channels/${id}/test`).then((r) => r.data.data),
}
request
.post(`/merchant/alert-channels/${id}/test`)
.then((r) => r.data.data),
};
export const uploadApi = {
file: (file: File) => {
const form = new FormData()
form.append('file', file)
return request.post('/upload', form, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 60000,
}).then((r) => r.data.data as { url: string })
const form = new FormData();
form.append("file", file);
return request
.post("/upload", form, {
headers: { "Content-Type": "multipart/form-data" },
timeout: 60000,
})
.then((r) => r.data.data as { url: string });
},
}
};
export const deliveryApi = {
getOrder: (orderNo: string, auth?: { exp?: string; sign?: string }) =>
request.get(`/delivery/v1/orders/${encodeURIComponent(orderNo)}`, { params: auth }).then((r) => r.data.data as DeliveryOrderInfo),
bind: (orderNo: string, gameAccount: string, auth?: { exp?: string; sign?: string }) =>
request.post(
`/delivery/v1/orders/${encodeURIComponent(orderNo)}/bind`,
{
game_account: gameAccount,
},
{ params: auth },
).then((r) => r.data.data as DeliveryBindResult),
getBindResult: (orderNo: string, bindUUID: string, auth?: { exp?: string; sign?: string }) =>
request.get(`/delivery/v1/orders/${encodeURIComponent(orderNo)}/bind-result`, {
params: { ...auth, bind_uuid: bindUUID },
}).then((r) => r.data.data as DeliveryBindResult),
submit: (orderNo: string, gameAccount: string, bindUUID: string, auth?: { exp?: string; sign?: string }) =>
request.post(
`/delivery/v1/orders/${encodeURIComponent(orderNo)}/submit`,
{
game_account: gameAccount,
bind_uuid: bindUUID,
},
{ params: auth },
).then((r) => r.data.data as DeliverySubmitResult),
}
request
.get(`/delivery/v1/orders/${encodeURIComponent(orderNo)}`, {
params: auth,
})
.then((r) => r.data.data as DeliveryOrderInfo),
bind: (
orderNo: string,
gameAccount: string,
auth?: { exp?: string; sign?: string },
) =>
request
.post(
`/delivery/v1/orders/${encodeURIComponent(orderNo)}/bind`,
{
game_account: gameAccount,
},
{ params: auth },
)
.then((r) => r.data.data as DeliveryBindResult),
getBindResult: (
orderNo: string,
bindUUID: string,
auth?: { exp?: string; sign?: string },
) =>
request
.get(`/delivery/v1/orders/${encodeURIComponent(orderNo)}/bind-result`, {
params: { ...auth, bind_uuid: bindUUID },
})
.then((r) => r.data.data as DeliveryBindResult),
submit: (
orderNo: string,
gameAccount: string,
bindUUID: string,
auth?: { exp?: string; sign?: string },
) =>
request
.post(
`/delivery/v1/orders/${encodeURIComponent(orderNo)}/submit`,
{
game_account: gameAccount,
bind_uuid: bindUUID,
},
{ params: auth },
)
.then((r) => r.data.data as DeliverySubmitResult),
};
export const platformApi = {
merchantAccounts: (params?: Record<string, unknown>) =>
request.get('/platform/merchant-accounts', { params }).then((r) => r.data.data as PageResult<MerchantMemberGroup>),
request
.get("/platform/merchant-accounts", { params })
.then((r) => r.data.data as PageResult<MerchantMemberGroup>),
deleteMerchantAccount: (userId: number) =>
request.delete(`/platform/merchant-accounts/${userId}`).then((r) => r.data.data),
request
.delete(`/platform/merchant-accounts/${userId}`)
.then((r) => r.data.data),
merchants: (params?: Record<string, unknown>) =>
request.get('/platform/merchants', { params }).then((r) => r.data.data as PageResult<Merchant>),
request
.get("/platform/merchants", { params })
.then((r) => r.data.data as PageResult<Merchant>),
// 不分页的精简商户列表,供筛选下拉使用(/platform/merchants 的 size 上限是 100)
merchantOptions: () =>
request.get('/platform/merchant-options').then((r) => r.data.data as MerchantOption[]),
request
.get("/platform/merchant-options")
.then((r) => r.data.data as MerchantOption[]),
// 平台侧跨商户订单视图:不传 merchant_id 即全部商户
orders: (params?: Record<string, unknown>) =>
request.get('/platform/orders', { params }).then((r) => r.data.data as PageResult<FulfillmentOrder>),
request
.get("/platform/orders", { params })
.then((r) => r.data.data as PageResult<FulfillmentOrder>),
createMerchant: (data: {
code: string
name: string
contact_name?: string
contact_info?: string
owner_user_id?: number
owner_username?: string
owner_password?: string
owner_nickname?: string
features?: string
fee_type?: 'rate' | 'fixed'
fee_rate_bp?: number
fee_fixed_amount?: number
}) => request.post('/platform/merchants', data).then((r) => r.data.data as Merchant),
code: string;
name: string;
contact_name?: string;
contact_info?: string;
owner_user_id?: number;
owner_username?: string;
owner_password?: string;
owner_nickname?: string;
features?: string;
fee_type?: "rate" | "fixed";
fee_rate_bp?: number;
fee_fixed_amount?: number;
}) =>
request
.post("/platform/merchants", data)
.then((r) => r.data.data as Merchant),
updateMerchant: (id: number, data: Partial<Merchant>) =>
request.patch(`/platform/merchants/${id}`, data).then((r) => r.data.data),
members: (merchantId: number) =>
request.get(`/platform/merchants/${merchantId}/members`).then((r) => r.data.data as MerchantMember[]),
request
.get(`/platform/merchants/${merchantId}/members`)
.then((r) => r.data.data as MerchantMember[]),
addMember: (
merchantId: number,
data: { user_id?: number; username?: string; password?: string; nickname?: string; role: string; is_default?: boolean },
) => request.post(`/platform/merchants/${merchantId}/members`, data).then((r) => r.data.data as MerchantMember),
data: {
user_id?: number;
username?: string;
password?: string;
nickname?: string;
role: string;
is_default?: boolean;
},
) =>
request
.post(`/platform/merchants/${merchantId}/members`, data)
.then((r) => r.data.data as MerchantMember),
updateMember: (
merchantId: number,
memberId: number,
data: { role: string; status: number; is_default: boolean },
) => request.patch(`/platform/merchants/${merchantId}/members/${memberId}`, data).then((r) => r.data.data as MerchantMember),
) =>
request
.patch(`/platform/merchants/${merchantId}/members/${memberId}`, data)
.then((r) => r.data.data as MerchantMember),
removeMember: (merchantId: number, memberId: number) =>
request.delete(`/platform/merchants/${merchantId}/members/${memberId}`).then((r) => r.data.data),
request
.delete(`/platform/merchants/${merchantId}/members/${memberId}`)
.then((r) => r.data.data),
roles: (merchantId: number) =>
request.get(`/platform/merchants/${merchantId}/roles`).then((r) => r.data.data as MerchantRole[]),
request
.get(`/platform/merchants/${merchantId}/roles`)
.then((r) => r.data.data as MerchantRole[]),
productCatalog: () =>
request.get('/platform/product-catalog').then((r) => r.data.data as ProductCatalogItem[]),
request
.get("/platform/product-catalog")
.then((r) => r.data.data as ProductCatalogItem[]),
merchantProducts: (merchantId: number) =>
request.get(`/platform/merchants/${merchantId}/products`).then((r) => r.data.data as ProductCatalogItem[]),
updateMerchantProduct: (merchantId: number, productId: number, data: Partial<MerchantProduct>) =>
request.patch(`/platform/merchants/${merchantId}/products/${productId}`, data).then((r) => r.data.data),
request
.get(`/platform/merchants/${merchantId}/products`)
.then((r) => r.data.data as ProductCatalogItem[]),
updateMerchantProduct: (
merchantId: number,
productId: number,
data: Partial<MerchantProduct>,
) =>
request
.patch(`/platform/merchants/${merchantId}/products/${productId}`, data)
.then((r) => r.data.data),
assignProducts: (merchantId: number, catalogIds: number[]) =>
request.post(`/platform/merchants/${merchantId}/products/assign`, { catalog_ids: catalogIds }).then((r) => r.data.data as { assigned: number }),
request
.post(`/platform/merchants/${merchantId}/products/assign`, {
catalog_ids: catalogIds,
})
.then((r) => r.data.data as { assigned: number }),
wallet: (merchantId: number) =>
request.get(`/platform/merchants/${merchantId}/wallet`).then((r) => r.data.data as WalletAccount),
request
.get(`/platform/merchants/${merchantId}/wallet`)
.then((r) => r.data.data as WalletAccount),
merchantAccount: (merchantId: number) =>
request.get(`/platform/merchants/${merchantId}/account`).then((r) => r.data.data as MerchantOwnerAccount[]),
updateMerchantAccount: (merchantId: number, data: { user_id: number; username: string; nickname?: string; password?: string; status?: number }) =>
request.patch(`/platform/merchants/${merchantId}/account`, data).then((r) => r.data.data as MerchantOwnerAccount),
request
.get(`/platform/merchants/${merchantId}/account`)
.then((r) => r.data.data as MerchantOwnerAccount[]),
updateMerchantAccount: (
merchantId: number,
data: {
user_id: number;
username: string;
nickname?: string;
password?: string;
status?: number;
},
) =>
request
.patch(`/platform/merchants/${merchantId}/account`, data)
.then((r) => r.data.data as MerchantOwnerAccount),
walletLedger: (merchantId: number, params?: Record<string, unknown>) =>
request.get(`/platform/merchants/${merchantId}/wallet/ledger`, { params }).then((r) => r.data.data as PageResult<WalletLedgerEntry>),
adjustWallet: (merchantId: number, data: { amount: number; idempotency_key: string; note?: string }) =>
request.post(`/platform/merchants/${merchantId}/wallet/adjust`, data).then((r) => r.data.data as WalletAccount),
request
.get(`/platform/merchants/${merchantId}/wallet/ledger`, { params })
.then((r) => r.data.data as PageResult<WalletLedgerEntry>),
adjustWallet: (
merchantId: number,
data: { amount: number; idempotency_key: string; note?: string },
) =>
request
.post(`/platform/merchants/${merchantId}/wallet/adjust`, data)
.then((r) => r.data.data as WalletAccount),
rechargeApplications: (params?: Record<string, unknown>) =>
request.get('/platform/recharge/applications', { params }).then((r) => r.data.data as PageResult<RechargeApplication>),
reviewRechargeApplication: (id: number, data: { approved: boolean; review_note?: string }) =>
request.post(`/platform/recharge/applications/${id}/review`, data).then((r) => r.data.data as RechargeApplication),
}
request
.get("/platform/recharge/applications", { params })
.then((r) => r.data.data as PageResult<RechargeApplication>),
reviewRechargeApplication: (
id: number,
data: { approved: boolean; review_note?: string },
) =>
request
.post(`/platform/recharge/applications/${id}/review`, data)
.then((r) => r.data.data as RechargeApplication),
};
export const lewanApi = {
listProducts: (params?: { page?: number; size?: number; keyword?: string }) =>
request.get('/platform/lewan/products', { params }).then((r) => r.data.data as LewanProductListResult),
request
.get("/platform/lewan/products", { params })
.then((r) => r.data.data as LewanProductListResult),
syncCatalog: (data: SyncLewanProductInput) =>
request.post('/platform/lewan/sync-catalog', data).then((r) => r.data.data as ProductCatalogItem),
request
.post("/platform/lewan/sync-catalog", data)
.then((r) => r.data.data as ProductCatalogItem),
batchSyncCatalog: (products: SyncLewanProductInput[]) =>
request.post('/platform/lewan/batch-sync-catalog', { products }).then((r) => r.data.data as { synced_count: number }),
request
.post("/platform/lewan/batch-sync-catalog", { products })
.then((r) => r.data.data as { synced_count: number }),
updateRouting: (id: number, data: UpdateProductRoutingInput) =>
request.put(`/platform/products/${id}/routing`, data).then((r) => r.data.data as { updated: boolean }),
}
request
.put(`/platform/products/${id}/routing`, data)
.then((r) => r.data.data as { updated: boolean }),
};
export interface RockConfig {
id: number
auto_delivery_enabled: boolean
base_url: string
bearer_token: string
username: string
points: number
min_points_warning: number
token_warning_stock: number
auto_purchase_enabled: boolean
auto_purchase_count: number
purchase_cooldown_minutes: number
last_purchased_at?: string
last_heartbeat_at?: string
id: number;
auto_delivery_enabled: boolean;
base_url: string;
bearer_token: string;
username: string;
points: number;
min_points_warning: number;
token_warning_stock: number;
auto_purchase_enabled: boolean;
auto_purchase_count: number;
purchase_cooldown_minutes: number;
last_purchased_at?: string;
last_heartbeat_at?: string;
}
export interface RockStats {
available_stock: number
using_stock: number
bound_stock: number
redeemed_stock: number
mismatch_stock: number
abnormal_stock: number
upstream_points: number
auto_delivery_enabled: boolean
token_warning_stock: number
auto_purchase_enabled: boolean
username: string
last_heartbeat_at?: string
available_stock: number;
using_stock: number;
bound_stock: number;
redeemed_stock: number;
mismatch_stock: number;
abnormal_stock: number;
upstream_points: number;
auto_delivery_enabled: boolean;
token_warning_stock: number;
auto_purchase_enabled: boolean;
username: string;
last_heartbeat_at?: string;
}
export interface RockTokenItem {
id: number
upstream_token_id: string
redeem_code: string
color: string
region: string
status: string
assigned_order_no?: string
client_order_no?: string
expected_uid?: string
actual_role_name?: string
actual_role_id?: string
actual_channel?: string
upstream_fulfill_id?: number
target_skin?: string
target_currency?: string
target_currency_quantity?: number
mismatch_at?: string
mismatch_reason?: string
remark?: string
claimed_at?: string
bound_at?: string
redeemed_at?: string
created_at: string
id: number;
upstream_token_id: string;
redeem_code: string;
color: string;
region: string;
status: string;
assigned_order_no?: string;
client_order_no?: string;
expected_uid?: string;
actual_role_name?: string;
actual_role_id?: string;
actual_channel?: string;
upstream_fulfill_id?: number;
target_skin?: string;
target_currency?: string;
target_currency_quantity?: number;
mismatch_at?: string;
mismatch_reason?: string;
remark?: string;
claimed_at?: string;
bound_at?: string;
redeemed_at?: string;
created_at: string;
}
export const rockApi = {
getConfig: () => request.get('/platform/rock/config').then((r) => r.data.data as RockConfig),
updateConfig: (data: Partial<RockConfig>) => request.put('/platform/rock/config', data).then((r) => r.data.data as RockConfig),
testConnection: (bearer_token?: string) => request.post('/platform/rock/test-conn', { bearer_token }).then((r) => r.data.data),
getStats: () => request.get('/platform/rock/stats').then((r) => r.data.data as RockStats),
purchaseTokens: (count = 50) => request.post('/platform/rock/purchase', { count }).then((r) => r.data.data),
listTokens: (params?: { page?: number; page_size?: number; status?: string; search?: string }) =>
request.get('/platform/rock/tokens', { params }).then((r) => r.data.data as { list: RockTokenItem[]; total: number; page: number; page_size: number }),
getConfig: () =>
request.get("/platform/rock/config").then((r) => r.data.data as RockConfig),
updateConfig: (data: Partial<RockConfig>) =>
request
.put("/platform/rock/config", data)
.then((r) => r.data.data as RockConfig),
testConnection: (bearer_token?: string) =>
request
.post("/platform/rock/test-conn", { bearer_token })
.then((r) => r.data.data),
getStats: () =>
request.get("/platform/rock/stats").then((r) => r.data.data as RockStats),
purchaseTokens: (count = 50) =>
request.post("/platform/rock/purchase", { count }).then((r) => r.data.data),
listTokens: (params?: {
page?: number;
page_size?: number;
status?: string;
search?: string;
}) =>
request.get("/platform/rock/tokens", { params }).then(
(r) =>
r.data.data as {
list: RockTokenItem[];
total: number;
page: number;
page_size: number;
},
),
correctExpectedUid: (id: number, expectedUid: string) =>
request.post(`/platform/rock/tokens/${id}/expected-uid`, { expected_uid: expectedUid }).then((r) => r.data.data as { message?: string; status?: string }),
resetToken: (id: number) => request.post(`/platform/rock/tokens/${id}/reset`).then((r) => r.data.data),
releaseToken: (id: number) => request.post(`/platform/rock/tokens/${id}/release`).then((r) => r.data.data),
request
.post(`/platform/rock/tokens/${id}/expected-uid`, {
expected_uid: expectedUid,
})
.then((r) => r.data.data as { message?: string; status?: string }),
resetToken: (id: number) =>
request.post(`/platform/rock/tokens/${id}/reset`).then((r) => r.data.data),
releaseToken: (id: number) =>
request
.post(`/platform/rock/tokens/${id}/release`)
.then((r) => r.data.data),
updateRemark: (id: number, data: { remark: string; status?: string }) =>
request.post(`/platform/rock/tokens/${id}/remark`, data).then((r) => r.data.data as { message: string; token: RockTokenItem }),
request
.post(`/platform/rock/tokens/${id}/remark`, data)
.then((r) => r.data.data as { message: string; token: RockTokenItem }),
markAbnormal: (id: number, remark?: string) =>
request.post(`/platform/rock/tokens/${id}/abnormal`, { remark }).then((r) => r.data.data as { message: string; token: RockTokenItem }),
}
request
.post(`/platform/rock/tokens/${id}/abnormal`, { remark })
.then((r) => r.data.data as { message: string; token: RockTokenItem }),
};