2026-03-25 13:46:26 +08:00
|
|
|
|
import { http } from './http';
|
2026-03-19 01:39:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* [MOCK] 黑名单管理数据源
|
|
|
|
|
|
* AI注意: 这是Mock实现,不是真实业务逻辑
|
2026-03-23 12:41:35 +08:00
|
|
|
|
* 仅在USE_MOCK=true时启<EFBFBD>? */
|
2026-03-19 01:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
export interface BlacklistRecord {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
tenant_id: string;
|
2026-03-20 17:53:46 +08:00
|
|
|
|
shop_id: string;
|
|
|
|
|
|
task_id?: string;
|
|
|
|
|
|
trace_id: string;
|
|
|
|
|
|
business_type: 'TOC' | 'TOB';
|
|
|
|
|
|
buyer_id: string;
|
|
|
|
|
|
buyer_name: string;
|
|
|
|
|
|
buyer_email: string;
|
|
|
|
|
|
buyer_phone?: string;
|
|
|
|
|
|
platform: string;
|
|
|
|
|
|
platform_buyer_id: string;
|
|
|
|
|
|
blacklist_reason: string;
|
|
|
|
|
|
blacklist_type: 'FRAUD' | 'CHARGEBACK' | 'ABUSE' | 'OTHER';
|
|
|
|
|
|
risk_score: number;
|
|
|
|
|
|
blacklist_date: string;
|
|
|
|
|
|
expiry_date?: string;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
status: 'ACTIVE' | 'INACTIVE' | 'EXPIRED';
|
2026-03-20 17:53:46 +08:00
|
|
|
|
evidence?: string;
|
|
|
|
|
|
created_by: string;
|
|
|
|
|
|
created_at: string;
|
|
|
|
|
|
updated_at: string;
|
|
|
|
|
|
type?: 'CUSTOMER' | 'ADDRESS' | 'IP' | 'EMAIL';
|
|
|
|
|
|
value?: string;
|
|
|
|
|
|
reason?: string;
|
|
|
|
|
|
severity?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
|
|
|
|
source?: 'MANUAL' | 'AUTO';
|
|
|
|
|
|
createdBy?: string;
|
|
|
|
|
|
updatedAt?: string;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 12:41:35 +08:00
|
|
|
|
export interface BlacklistRule {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
condition: string;
|
|
|
|
|
|
condition_type?: 'TRANSACTION' | 'BEHAVIOR' | 'COMPLIANCE';
|
|
|
|
|
|
blacklist_type?: 'FRAUD' | 'CHARGEBACK' | 'ABUSE' | 'OTHER';
|
|
|
|
|
|
risk_score?: number;
|
|
|
|
|
|
auto_blacklist?: boolean;
|
|
|
|
|
|
expiry_days?: number;
|
|
|
|
|
|
platforms?: string[];
|
|
|
|
|
|
threshold: number;
|
|
|
|
|
|
action: 'BLOCK' | 'ALERT' | 'MONITOR';
|
|
|
|
|
|
status: 'ACTIVE' | 'INACTIVE';
|
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
created_at?: string;
|
|
|
|
|
|
updated_at?: string;
|
|
|
|
|
|
createdBy: string;
|
|
|
|
|
|
created_by?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
export interface RiskAssessment {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
orderId: string;
|
|
|
|
|
|
customerId: string;
|
|
|
|
|
|
customerName: string;
|
2026-03-21 15:04:06 +08:00
|
|
|
|
platform?: string;
|
|
|
|
|
|
platform_buyer_id?: string;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
riskScore: number;
|
2026-03-21 15:04:06 +08:00
|
|
|
|
risk_score?: number;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
2026-03-21 15:04:06 +08:00
|
|
|
|
risk_level?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
|
|
|
|
assessment_date?: string;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
riskFactors: string[];
|
|
|
|
|
|
status: 'PENDING' | 'REVIEWING' | 'APPROVED' | 'REJECTED';
|
|
|
|
|
|
reviewedBy?: string;
|
|
|
|
|
|
reviewedAt?: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface IBlacklistDataSource {
|
2026-03-20 17:53:46 +08:00
|
|
|
|
fetchBlacklist(params?: { platform?: string; status?: string; search?: string }): Promise<BlacklistRecord[]>;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
addBlacklist(data: Partial<BlacklistRecord>): Promise<BlacklistRecord>;
|
|
|
|
|
|
updateBlacklist(id: string, data: Partial<BlacklistRecord>): Promise<BlacklistRecord>;
|
|
|
|
|
|
removeBlacklist(id: string): Promise<void>;
|
|
|
|
|
|
|
|
|
|
|
|
fetchRiskAssessments(params?: { status?: string; riskLevel?: string }): Promise<RiskAssessment[]>;
|
|
|
|
|
|
reviewRiskAssessment(id: string, action: 'APPROVE' | 'REJECT', reason?: string): Promise<RiskAssessment>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class MockBlacklistDataSource implements IBlacklistDataSource {
|
|
|
|
|
|
private blacklist: BlacklistRecord[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '1',
|
|
|
|
|
|
tenant_id: 'tenant_001',
|
2026-03-20 17:53:46 +08:00
|
|
|
|
shop_id: 'shop_001',
|
|
|
|
|
|
trace_id: 'trace_001',
|
|
|
|
|
|
business_type: 'TOC',
|
|
|
|
|
|
buyer_id: 'buyer_001',
|
|
|
|
|
|
buyer_name: 'John Doe',
|
|
|
|
|
|
buyer_email: 'john.doe@example.com',
|
|
|
|
|
|
platform: 'AMAZON',
|
|
|
|
|
|
platform_buyer_id: 'amazon_001',
|
2026-03-23 12:41:35 +08:00
|
|
|
|
blacklist_reason: '多次恶意退货',
|
2026-03-20 17:53:46 +08:00
|
|
|
|
blacklist_type: 'FRAUD',
|
|
|
|
|
|
risk_score: 85,
|
|
|
|
|
|
blacklist_date: '2026-03-01',
|
|
|
|
|
|
status: 'ACTIVE',
|
|
|
|
|
|
created_by: 'admin',
|
|
|
|
|
|
created_at: '2026-03-01',
|
|
|
|
|
|
updated_at: '2026-03-01',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
type: 'CUSTOMER',
|
|
|
|
|
|
value: 'John Doe',
|
2026-03-23 12:41:35 +08:00
|
|
|
|
reason: '多次恶意退货',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
severity: 'HIGH',
|
|
|
|
|
|
source: 'MANUAL',
|
|
|
|
|
|
createdBy: 'admin',
|
2026-03-20 17:53:46 +08:00
|
|
|
|
updatedAt: '2026-03-01',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '2',
|
|
|
|
|
|
tenant_id: 'tenant_001',
|
2026-03-20 17:53:46 +08:00
|
|
|
|
shop_id: 'shop_001',
|
|
|
|
|
|
trace_id: 'trace_002',
|
|
|
|
|
|
business_type: 'TOC',
|
|
|
|
|
|
buyer_id: 'buyer_002',
|
|
|
|
|
|
buyer_name: 'Jane Smith',
|
|
|
|
|
|
buyer_email: 'jane.smith@example.com',
|
|
|
|
|
|
platform: 'EBAY',
|
|
|
|
|
|
platform_buyer_id: 'ebay_001',
|
|
|
|
|
|
blacklist_reason: '虚假地址',
|
|
|
|
|
|
blacklist_type: 'ABUSE',
|
|
|
|
|
|
risk_score: 65,
|
|
|
|
|
|
blacklist_date: '2026-03-05',
|
|
|
|
|
|
status: 'ACTIVE',
|
|
|
|
|
|
created_by: 'system',
|
|
|
|
|
|
created_at: '2026-03-05',
|
|
|
|
|
|
updated_at: '2026-03-05',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
type: 'ADDRESS',
|
|
|
|
|
|
value: '123 Fraud St, Scam City',
|
|
|
|
|
|
reason: '虚假地址',
|
|
|
|
|
|
severity: 'MEDIUM',
|
|
|
|
|
|
source: 'AUTO',
|
|
|
|
|
|
createdBy: 'system',
|
2026-03-20 17:53:46 +08:00
|
|
|
|
updatedAt: '2026-03-05',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
private riskAssessments: RiskAssessment[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '1',
|
|
|
|
|
|
orderId: 'ORD-2026-001',
|
|
|
|
|
|
customerId: 'CUST-001',
|
|
|
|
|
|
customerName: 'Suspicious Customer',
|
|
|
|
|
|
riskScore: 85,
|
|
|
|
|
|
riskLevel: 'HIGH',
|
|
|
|
|
|
riskFactors: ['新账户', '高额订单', '异常地址'],
|
|
|
|
|
|
status: 'PENDING',
|
|
|
|
|
|
createdAt: '2026-03-15',
|
|
|
|
|
|
updatedAt: '2026-03-15',
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
async fetchBlacklist(params?: { type?: string; status?: string; search?: string }): Promise<BlacklistRecord[]> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
let result = [...this.blacklist];
|
|
|
|
|
|
if (params?.type) {
|
2026-03-20 17:53:46 +08:00
|
|
|
|
// 移除type过滤,因为type属性不存在
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (params?.status) {
|
|
|
|
|
|
result = result.filter(r => r.status === params.status);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (params?.search) {
|
2026-03-20 17:53:46 +08:00
|
|
|
|
result = result.filter(r => r.buyer_name.toLowerCase().includes(params.search!.toLowerCase()));
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async addBlacklist(data: Partial<BlacklistRecord>): Promise<BlacklistRecord> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
const newRecord: BlacklistRecord = {
|
|
|
|
|
|
id: `${Date.now()}`,
|
|
|
|
|
|
tenant_id: 'tenant_001',
|
2026-03-20 17:53:46 +08:00
|
|
|
|
shop_id: data.shop_id || 'shop_001',
|
|
|
|
|
|
trace_id: data.trace_id || `trace_${Date.now()}`,
|
|
|
|
|
|
business_type: data.business_type || 'TOC',
|
|
|
|
|
|
buyer_id: data.buyer_id || `buyer_${Date.now()}`,
|
|
|
|
|
|
buyer_name: data.buyer_name || '',
|
|
|
|
|
|
buyer_email: data.buyer_email || '',
|
|
|
|
|
|
buyer_phone: data.buyer_phone,
|
|
|
|
|
|
platform: data.platform || '',
|
|
|
|
|
|
platform_buyer_id: data.platform_buyer_id || '',
|
|
|
|
|
|
blacklist_reason: data.blacklist_reason || '',
|
|
|
|
|
|
blacklist_type: data.blacklist_type || 'OTHER',
|
|
|
|
|
|
risk_score: data.risk_score || 50,
|
|
|
|
|
|
blacklist_date: data.blacklist_date || new Date().toISOString().split('T')[0],
|
|
|
|
|
|
expiry_date: data.expiry_date,
|
2026-03-19 01:39:34 +08:00
|
|
|
|
status: 'ACTIVE',
|
2026-03-20 17:53:46 +08:00
|
|
|
|
evidence: data.evidence,
|
|
|
|
|
|
created_by: data.created_by || 'current_user',
|
|
|
|
|
|
created_at: new Date().toISOString().split('T')[0],
|
|
|
|
|
|
updated_at: new Date().toISOString().split('T')[0],
|
2026-03-19 01:39:34 +08:00
|
|
|
|
...data,
|
|
|
|
|
|
};
|
|
|
|
|
|
this.blacklist.push(newRecord);
|
|
|
|
|
|
return newRecord;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateBlacklist(id: string, data: Partial<BlacklistRecord>): Promise<BlacklistRecord> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
const index = this.blacklist.findIndex(r => r.id === id);
|
|
|
|
|
|
if (index === -1) throw new Error('Record not found');
|
2026-03-20 17:53:46 +08:00
|
|
|
|
this.blacklist[index] = { ...this.blacklist[index], ...data, updated_at: new Date().toISOString().split('T')[0] };
|
2026-03-19 01:39:34 +08:00
|
|
|
|
return this.blacklist[index];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async removeBlacklist(id: string): Promise<void> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
const index = this.blacklist.findIndex(r => r.id === id);
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
this.blacklist.splice(index, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchRiskAssessments(params?: { status?: string; riskLevel?: string }): Promise<RiskAssessment[]> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
let result = [...this.riskAssessments];
|
|
|
|
|
|
if (params?.status) {
|
|
|
|
|
|
result = result.filter(r => r.status === params.status);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (params?.riskLevel) {
|
|
|
|
|
|
result = result.filter(r => r.riskLevel === params.riskLevel);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async reviewRiskAssessment(id: string, action: 'APPROVE' | 'REJECT', reason?: string): Promise<RiskAssessment> {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
|
|
const index = this.riskAssessments.findIndex(r => r.id === id);
|
|
|
|
|
|
if (index === -1) throw new Error('Assessment not found');
|
|
|
|
|
|
this.riskAssessments[index] = {
|
|
|
|
|
|
...this.riskAssessments[index],
|
|
|
|
|
|
status: action === 'APPROVE' ? 'APPROVED' : 'REJECTED',
|
|
|
|
|
|
reviewedBy: 'current_user',
|
|
|
|
|
|
reviewedAt: new Date().toISOString(),
|
|
|
|
|
|
updatedAt: new Date().toISOString(),
|
|
|
|
|
|
};
|
|
|
|
|
|
return this.riskAssessments[index];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class ApiBlacklistDataSource implements IBlacklistDataSource {
|
|
|
|
|
|
private baseUrl = '/api/blacklist';
|
|
|
|
|
|
|
|
|
|
|
|
async fetchBlacklist(params?: { type?: string; status?: string; search?: string }): Promise<BlacklistRecord[]> {
|
2026-03-25 13:46:26 +08:00
|
|
|
|
const response = await http.post(`${this.baseUrl}?${new URLSearchParams(params as any)}`);
|
|
|
|
|
|
|
|
|
|
|
|
return response.data;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async addBlacklist(data: Partial<BlacklistRecord>): Promise<BlacklistRecord> {
|
2026-03-25 13:46:26 +08:00
|
|
|
|
const response = await http.post(this.baseUrl, data);
|
|
|
|
|
|
|
|
|
|
|
|
return response.data;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateBlacklist(id: string, data: Partial<BlacklistRecord>): Promise<BlacklistRecord> {
|
2026-03-25 13:46:26 +08:00
|
|
|
|
const response = await http.put(`${this.baseUrl}/${id}`, data);
|
|
|
|
|
|
|
|
|
|
|
|
return response.data;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async removeBlacklist(id: string): Promise<void> {
|
2026-03-25 13:46:26 +08:00
|
|
|
|
const response = await http.delete(`${this.baseUrl}/${id}`);
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async fetchRiskAssessments(params?: { status?: string; riskLevel?: string }): Promise<RiskAssessment[]> {
|
2026-03-25 13:46:26 +08:00
|
|
|
|
const response = await http.get(`${this.baseUrl}/risk-assessments?${new URLSearchParams(params as any)}`);
|
|
|
|
|
|
|
|
|
|
|
|
return response.data;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async reviewRiskAssessment(id: string, action: 'APPROVE' | 'REJECT', reason?: string): Promise<RiskAssessment> {
|
2026-03-25 13:46:26 +08:00
|
|
|
|
const response = await http.post(`${this.baseUrl}/risk-assessments/${id}/review`, { action, reason });
|
|
|
|
|
|
|
|
|
|
|
|
return response.data;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 12:41:35 +08:00
|
|
|
|
const useMock = process.env.NODE_ENV === 'development' || process.env.REACT_APP_USE_MOCK === 'true';
|
2026-03-19 01:39:34 +08:00
|
|
|
|
export const blacklistDataSource: IBlacklistDataSource = useMock
|
|
|
|
|
|
? new MockBlacklistDataSource()
|
|
|
|
|
|
: new ApiBlacklistDataSource();
|