Files
hfb_sys/frontend/src/features/admin/api/adminDistribution.ts
T
yml2213 dc13b5dc82 feat(distribution): 支持分站独立短信验证码通道
- 新增分销站点独立短信服务商配置(阿里云),登录验证码按 site_code/domain
  解析专属通道,未启用或解析失败时回退主站默认通道
- 密钥经 crypto.Encryptor 加密落库,模型字段 json 序列化隔离,DTO 仅回传掩码
- provider 白名单校验,mock 仅在非生产环境放行;启用时强制校验必填凭据
- 配置读写/测试接口限管理员权限,测试发送增加 60s 频控(Redis,缺省进程内)
- 解密失败记告警并优雅回退主站,避免把密文当明文密钥使用
- 两套前端新增短信配置入口、必填与手机号校验、发送冷却
- 新增迁移 000071 及加解密、白名单、频控、解析回退等单测
2026-09-16 14:06:01 +08:00

365 lines
9.0 KiB
TypeScript

import { apiClient } from '@/shared/api/client'
import type { ApiResponse } from '@/shared/types/types'
export interface DistributionSite {
id: number
site_code: string
site_name: string
domain: string
status: string
display_account_enabled: boolean
tax_enabled: boolean
tax_rate_bps: number
settlement_hold_days: number
listing_qrcode_url?: string
receive_ratio_delta?: number
created_at: string
}
export interface DistributionRule {
id: number
site_id?: number
mode: string
version: number
status: string
platform_receive_ratio: number
platform_sell_ratio: number
distributor_receive_ratio: number
distributor_sell_ratio: number
distributor_a_ratio: number
distributor_b_ratio: number
service_fee_value: number
service_fee_unit: string
payment_fee_bps: number
tax_enabled: boolean
tax_rate_bps: number
effective_from: string
effective_to?: string
}
export interface DistributionOrder {
order_id: number
order_no: string
site_id: number
status: string
refund_status: string
settlement_status: string
distributor_settlement_status?: string
release_at?: string
distribution_mode: string
gross_paid_amount_cent: number
refund_amount_cent: number
net_paid_amount_cent: number
payment_fee_cent: number
service_fee_cent: number
markup_amount_cent: number
tax_amount_cent: number
split_base_amount_cent: number
rounding_adjustment_cent: number
created_at: string
}
export interface DistributionOrderPage {
items: DistributionOrder[]
total: number
page: number
page_size: number
}
export interface DistributionSettlementBatch {
id: number
site_id: number
batch_no: string
period_start: string
period_end: string
total_amount_cent: number
status: string
}
export interface DistributionSettlementItem {
id: number
order_id: number
party_role: string
amount_cent: number
status: string
}
export interface OnboardDistributionSitePayload {
site_code: string
site_name: string
domain: string
admin_username: string
admin_password: string
admin_nickname?: string
member_type?: string
display_account_enabled?: boolean
tax_enabled?: boolean
tax_rate_bps?: number
settlement_hold_days?: number
}
export interface OnboardDistributionSiteResult {
site: DistributionSite
rule: DistributionRule
member: {
admin_id: number
username: string
nickname: string
is_superadmin: boolean
site_id?: number
site_code?: string
site_name?: string
domain?: string
member_type?: string
}
}
export async function fetchDistributionSites() {
const { data } = await apiClient.get<ApiResponse<{ items: DistributionSite[] }>>(
'/admin/distribution/sites'
)
return data.data.items ?? []
}
export interface DistributionSiteDefaults {
default_settlement_hold_days: number
}
export async function fetchDistributionSiteDefaults() {
const { data } = await apiClient.get<ApiResponse<DistributionSiteDefaults>>(
'/admin/distribution/site-defaults'
)
return data.data
}
export async function onboardDistributionSite(payload: OnboardDistributionSitePayload) {
const { data } = await apiClient.post<ApiResponse<OnboardDistributionSiteResult>>(
'/admin/distribution/sites/onboard',
payload
)
return data.data
}
export async function updateDistributionSite(
id: number,
payload: Partial<
Pick<
DistributionSite,
| 'site_name'
| 'domain'
| 'status'
| 'display_account_enabled'
| 'tax_enabled'
| 'tax_rate_bps'
| 'settlement_hold_days'
| 'listing_qrcode_url'
| 'receive_ratio_delta'
>
>
) {
const { data } = await apiClient.put<ApiResponse<DistributionSite>>(
`/admin/distribution/sites/${id}`,
payload
)
return data.data
}
export async function fetchDistributionRules(siteId?: number, mode?: string) {
const { data } = await apiClient.get<ApiResponse<{ items: DistributionRule[] }>>(
'/admin/distribution/rules',
{
params: { site_id: siteId || undefined, mode: mode || undefined },
}
)
return data.data.items ?? []
}
export interface CreateRulePayload {
site_id?: number
mode: string
distributor_receive_ratio?: number
platform_sell_ratio?: number
distributor_sell_ratio?: number
platform_receive_ratio?: number
distributor_a_ratio?: number
distributor_b_ratio?: number
service_fee_value: number
service_fee_unit: string
payment_fee_bps: number
tax_enabled: boolean
tax_rate_bps: number
effective_from?: string
}
export async function createDistributionRule(payload: CreateRulePayload) {
const { data } = await apiClient.post<ApiResponse<DistributionRule>>(
'/admin/distribution/rules',
payload
)
return data.data
}
export async function updateDistributionRuleStatus(id: number, status: string) {
const { data } = await apiClient.put<ApiResponse<DistributionRule>>(
`/admin/distribution/rules/${id}/status`,
{ status }
)
return data.data
}
export async function fetchDistributionOrders(params: {
kind?: string
status?: string
mode?: string
financialStatus?: string
siteID?: number
page?: number
pageSize?: number
}) {
const { data } = await apiClient.get<ApiResponse<DistributionOrderPage>>(
'/admin/distribution/orders',
{
params: {
kind: params.kind || undefined,
status: params.status || undefined,
mode: params.mode || undefined,
financial_status: params.financialStatus || undefined,
site_id: params.siteID || undefined,
page: params.page || 1,
page_size: params.pageSize || 20,
},
}
)
return data.data
}
export async function fetchDistributionSettlementBatches(siteId: number) {
const { data } = await apiClient.get<ApiResponse<{ items: DistributionSettlementBatch[] }>>(
'/admin/distribution/settlement-batches',
{
params: { site_id: siteId },
}
)
return data.data.items ?? []
}
export async function createDistributionSettlementBatch(
siteId: number,
periodStart: string,
periodEnd: string
) {
const { data } = await apiClient.post<ApiResponse<DistributionSettlementBatch>>(
'/admin/distribution/settlement-batches',
{
site_id: siteId,
period_start: periodStart,
period_end: periodEnd,
}
)
return data.data
}
export async function settleDistributionBatch(id: number) {
const { data } = await apiClient.post<ApiResponse<DistributionSettlementBatch>>(
`/admin/distribution/settlement-batches/${id}/settle`
)
return data.data
}
export async function cancelDistributionBatch(id: number) {
const { data } = await apiClient.post<ApiResponse<DistributionSettlementBatch>>(
`/admin/distribution/settlement-batches/${id}/cancel`
)
return data.data
}
export async function fetchDistributionSettlementItems(batchId: number) {
const { data } = await apiClient.get<ApiResponse<{ items: DistributionSettlementItem[] }>>(
`/admin/distribution/settlement-batches/${batchId}/items`
)
return data.data.items ?? []
}
export interface SyncSiteAccountsRequest {
listing_ids?: number[]
clean_stale_listings?: boolean
default_shelf_ratio_delta?: number
}
export interface SyncSiteAccountsResult {
synced_count: number
skipped_count: number
updated_count?: number
disabled_stale_count?: number
total_active: number
}
export async function syncDistributionSiteAccounts(siteId: number, req?: SyncSiteAccountsRequest) {
const { data } = await apiClient.post<ApiResponse<SyncSiteAccountsResult>>(
`/admin/distribution/sites/${siteId}/sync-accounts`,
req ?? {}
)
return data.data
}
export interface DistributionSiteSMSConfig {
enabled: boolean
provider: string
access_key_id: string
masked_secret: string
has_secret: boolean
endpoint: string
sign_name: string
login_template_code: string
}
export interface UpdateDistributionSiteSMSConfigPayload {
enabled?: boolean
provider?: string
access_key_id?: string
access_key_secret?: string
endpoint?: string
sign_name?: string
login_template_code?: string
}
export interface TestDistributionSiteSMSConfigPayload {
phone: string
enabled?: boolean
provider?: string
access_key_id?: string
access_key_secret?: string
endpoint?: string
sign_name?: string
login_template_code?: string
}
export async function fetchDistributionSiteSMSConfig(siteId: number) {
const { data } = await apiClient.get<ApiResponse<DistributionSiteSMSConfig>>(
`/admin/distribution/sites/${siteId}/sms-config`
)
return data.data
}
export async function updateDistributionSiteSMSConfig(
siteId: number,
payload: UpdateDistributionSiteSMSConfigPayload
) {
const { data } = await apiClient.put<ApiResponse<DistributionSiteSMSConfig>>(
`/admin/distribution/sites/${siteId}/sms-config`,
payload
)
return data.data
}
export async function testDistributionSiteSMSConfig(
siteId: number,
payload: TestDistributionSiteSMSConfigPayload
) {
const { data } = await apiClient.post<ApiResponse<{ message: string }>>(
`/admin/distribution/sites/${siteId}/sms-config/test`,
payload
)
return data.data
}