265 lines
7.6 KiB
TypeScript
265 lines
7.6 KiB
TypeScript
import axios from 'axios'
|
||
|
||
export interface MarketplaceValidationResult {
|
||
isValid: boolean
|
||
message: string
|
||
data?: {
|
||
sellerId?: string
|
||
sellerName?: string
|
||
tradeMark?: string
|
||
[key: string]: unknown
|
||
}
|
||
}
|
||
|
||
export interface WildberriesSellerInfo {
|
||
id: number
|
||
name: string
|
||
inn: string
|
||
kpp?: string
|
||
}
|
||
|
||
export interface OzonSellerInfo {
|
||
id: number
|
||
name: string
|
||
status: string
|
||
}
|
||
|
||
export class MarketplaceService {
|
||
private wbApiUrl: string
|
||
private ozonApiUrl: string
|
||
|
||
constructor() {
|
||
this.wbApiUrl = process.env.WILDBERRIES_API_URL || 'https://common-api.wildberries.ru'
|
||
this.ozonApiUrl = process.env.OZON_API_URL || 'https://api-seller.ozon.ru'
|
||
}
|
||
|
||
/**
|
||
* Валидирует API ключ Wildberries
|
||
*/
|
||
async validateWildberriesApiKey(apiKey: string): Promise<MarketplaceValidationResult> {
|
||
try {
|
||
console.log('🔵 Starting Wildberries validation for key:', apiKey.substring(0, 20) + '...');
|
||
|
||
// Сначала проверяем валидность ключа через ping (быстрее)
|
||
console.log('📡 Making ping request to:', `${this.wbApiUrl}/ping`);
|
||
const pingResponse = await axios.get(
|
||
`${this.wbApiUrl}/ping`,
|
||
{
|
||
headers: {
|
||
'Authorization': `Bearer ${apiKey}`
|
||
},
|
||
timeout: 5000
|
||
}
|
||
)
|
||
|
||
console.log('📡 Ping response:', {
|
||
status: pingResponse.status,
|
||
data: pingResponse.data
|
||
});
|
||
|
||
if (pingResponse.status !== 200 || pingResponse.data?.Status !== 'OK') {
|
||
return {
|
||
isValid: false,
|
||
message: 'API ключ Wildberries невалиден'
|
||
}
|
||
}
|
||
|
||
// Если ping прошёл, получаем информацию о продавце
|
||
const response = await axios.get(
|
||
`${this.wbApiUrl}/api/v1/seller-info`,
|
||
{
|
||
headers: {
|
||
'Authorization': `Bearer ${apiKey}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
timeout: 10000
|
||
}
|
||
)
|
||
|
||
if (response.status === 200 && response.data) {
|
||
const sellerData = response.data
|
||
|
||
return {
|
||
isValid: true,
|
||
message: 'API ключ Wildberries валиден',
|
||
data: {
|
||
sellerId: sellerData.sid, // sid - это уникальный ID продавца
|
||
sellerName: sellerData.name, // обычное наименование продавца
|
||
tradeMark: sellerData.tradeMark // торговое наименование продавца
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
isValid: false,
|
||
message: 'Не удалось получить информацию о продавце Wildberries'
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('🔴 Wildberries API validation error:', error)
|
||
|
||
if (axios.isAxiosError(error)) {
|
||
console.log('🔴 Axios error details:', {
|
||
status: error.response?.status,
|
||
statusText: error.response?.statusText,
|
||
data: error.response?.data,
|
||
message: error.message,
|
||
code: error.code
|
||
});
|
||
|
||
if (error.response?.status === 401) {
|
||
return {
|
||
isValid: false,
|
||
message: 'Неверный API ключ Wildberries'
|
||
}
|
||
}
|
||
|
||
if (error.response?.status === 403) {
|
||
return {
|
||
isValid: false,
|
||
message: 'Доступ запрещён. Проверьте права API ключа Wildberries'
|
||
}
|
||
}
|
||
|
||
if (error.response?.status === 429) {
|
||
return {
|
||
isValid: false,
|
||
message: 'Слишком много запросов к Wildberries API. Попробуйте позже'
|
||
}
|
||
}
|
||
|
||
if (error.code === 'ECONNABORTED') {
|
||
return {
|
||
isValid: false,
|
||
message: 'Превышено время ожидания ответа от Wildberries API'
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
isValid: false,
|
||
message: 'Ошибка при проверке API ключа Wildberries'
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Валидирует API ключ Ozon
|
||
*/
|
||
async validateOzonApiKey(apiKey: string, clientId?: string): Promise<MarketplaceValidationResult> {
|
||
try {
|
||
// Для Ozon нужен Client-Id
|
||
if (!clientId) {
|
||
return {
|
||
isValid: false,
|
||
message: 'Для Ozon API требуется Client-Id'
|
||
}
|
||
}
|
||
|
||
// Пытаемся получить информацию о продавце
|
||
const response = await axios.post(
|
||
`${this.ozonApiUrl}/v1/seller/info`,
|
||
{},
|
||
{
|
||
headers: {
|
||
'Api-Key': apiKey,
|
||
'Client-Id': clientId,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
timeout: 10000
|
||
}
|
||
)
|
||
|
||
if (response.status === 200 && response.data?.result) {
|
||
const sellerData = response.data.result as OzonSellerInfo
|
||
|
||
return {
|
||
isValid: true,
|
||
message: 'API ключ Ozon валиден',
|
||
data: {
|
||
sellerId: sellerData.id?.toString(),
|
||
sellerName: sellerData.name,
|
||
status: sellerData.status
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
isValid: false,
|
||
message: 'Не удалось получить информацию о продавце Ozon'
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Ozon API validation error:', error)
|
||
|
||
if (axios.isAxiosError(error)) {
|
||
if (error.response?.status === 401) {
|
||
return {
|
||
isValid: false,
|
||
message: 'Неверный API ключ или Client-Id для Ozon'
|
||
}
|
||
}
|
||
|
||
if (error.response?.status === 403) {
|
||
return {
|
||
isValid: false,
|
||
message: 'Доступ запрещён. Проверьте права API ключа Ozon'
|
||
}
|
||
}
|
||
|
||
if (error.code === 'ECONNABORTED') {
|
||
return {
|
||
isValid: false,
|
||
message: 'Превышено время ожидания ответа от Ozon API'
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
isValid: false,
|
||
message: 'Ошибка при проверке API ключа Ozon'
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Общий метод валидации API ключа по типу маркетплейса
|
||
*/
|
||
async validateApiKey(
|
||
marketplace: 'WILDBERRIES' | 'OZON',
|
||
apiKey: string,
|
||
clientId?: string
|
||
): Promise<MarketplaceValidationResult> {
|
||
switch (marketplace) {
|
||
case 'WILDBERRIES':
|
||
return this.validateWildberriesApiKey(apiKey)
|
||
case 'OZON':
|
||
return this.validateOzonApiKey(apiKey, clientId)
|
||
default:
|
||
return {
|
||
isValid: false,
|
||
message: 'Неподдерживаемый тип маркетплейса'
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Проверяет формат API ключа перед отправкой запроса
|
||
*/
|
||
validateApiKeyFormat(marketplace: 'WILDBERRIES' | 'OZON', apiKey: string): boolean {
|
||
if (!apiKey || typeof apiKey !== 'string') {
|
||
return false
|
||
}
|
||
|
||
switch (marketplace) {
|
||
case 'WILDBERRIES':
|
||
// Wildberries API ключи (JWT токены) содержат буквы, цифры, дефисы, подчёркивания и точки
|
||
return /^[a-zA-Z0-9\-_.]{10,}$/.test(apiKey)
|
||
case 'OZON':
|
||
// Ozon API ключи обычно содержат буквы, цифры и дефисы
|
||
return /^[a-zA-Z0-9\-_]{10,}$/.test(apiKey)
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
}
|