- 新增分销站点独立短信服务商配置(阿里云),登录验证码按 site_code/domain 解析专属通道,未启用或解析失败时回退主站默认通道 - 密钥经 crypto.Encryptor 加密落库,模型字段 json 序列化隔离,DTO 仅回传掩码 - provider 白名单校验,mock 仅在非生产环境放行;启用时强制校验必填凭据 - 配置读写/测试接口限管理员权限,测试发送增加 60s 频控(Redis,缺省进程内) - 解密失败记告警并优雅回退主站,避免把密文当明文密钥使用 - 两套前端新增短信配置入口、必填与手机号校验、发送冷却 - 新增迁移 000071 及加解密、白名单、频控、解析回退等单测
365 lines
9.0 KiB
TypeScript
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
|
|
}
|