Certification API - Управление сертификатами и документами

Certification API предоставляет полный функционал для работы с сертификатами соответствия и другими документами, необходимыми для продажи товаров на OZON, включая создание, привязку к товарам и управление жизненным циклом сертификатов.

Обзор API

Количество методов: 12
Основные функции: Управление сертификатами, привязка к товарам, контроль соответствия
Ключевая особенность: Автоматическая проверка соответствия товаров требованиям сертификации

⚠️ Важно: Метод getProductCertificationList (v1) устарел и будет отключён 14 апреля 2025 года. Используйте v2.

Основные методы управления сертификатами

1. Получение списка сертификатов

Метод: getCertificateList()
Эндпоинт: POST /v1/product/certificate/list

Возвращает список всех сертификатов продавца с возможностью фильтрации по статусу, типу и другим параметрам.

Параметры запроса

interface CertificateListRequest {
  page: number;                    // Номер страницы
  page_size: number;              // Количество элементов на странице
  status?: 'ACTIVE' | 'EXPIRED' | 'PENDING' | 'REJECTED';
  type?: string;                  // Тип сертификата
  name?: string;                  // Поиск по названию
}

Пример использования

import { OzonSellerApiClient } from '@spacechemical/ozon-seller-api';

const client = new OzonSellerApiClient({
  apiKey: 'your-api-key',
  clientId: 'your-client-id'
});

// Получить все активные сертификаты
const certificates = await client.certification.getCertificateList({
  page: 1,
  page_size: 100,
  status: 'ACTIVE'
});

console.log(`Найдено сертификатов: ${certificates.result?.total}`);
certificates.result?.certificates.forEach(cert => {
  console.log(`${cert.name} (${cert.status}) - истекает ${cert.expire_date}`);
  console.log(`  Тип: ${cert.type}, Номер: ${cert.number}`);
  console.log(`  Привязано товаров: ${cert.products_count || 0}`);
});

// Поиск сертификатов по названию
const searchResults = await client.certification.getCertificateList({
  page: 1,
  page_size: 50,
  name: 'ГОСТ'
});

console.log(`Найдено сертификатов с "ГОСТ": ${searchResults.result?.certificates.length || 0}`);

2. Создание нового сертификата

Метод: createCertificate()
Эндпоинт: POST /v1/product/certificate/create

Создает новый сертификат с загрузкой файлов и указанием всех необходимых параметров.

Параметры запроса

interface CertificateCreateRequest {
  name: string;                   // Название сертификата
  type: string;                   // Тип сертификата (из справочника)
  number: string;                 // Номер сертификата
  expire_date: string;            // Дата окончания действия (ISO 8601)
  file: string[];                 // Файлы в формате base64
  accordance_type_id?: number;    // ID типа соответствия
}

Пример использования

// Создать новый сертификат соответствия
const newCert = await client.certification.createCertificate({
  name: 'Сертификат соответствия ГОСТ Р на электронику',
  type: 'GOST_CERTIFICATE',
  number: 'РОСС RU.АИ37.H00124',
  expire_date: '2025-12-31T23:59:59Z',
  file: [
    'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIwAAAABJRU5ErkJggg==', // base64 файл 1
    'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIwAAAABJRU5ErkJggg==' // base64 файл 2
  ],
  accordance_type_id: 1
});

console.log(`Создан сертификат с ID: ${newCert.certificate_id}`);

// Функция для конвертации файла в base64
const fileToBase64 = async (file: File): Promise<string> => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => {
      const base64 = (reader.result as string).split(',')[1];
      resolve(base64);
    };
    reader.onerror = error => reject(error);
  });
};

// Создание сертификата с загрузкой файлов
const createCertificateWithFiles = async (files: File[]) => {
  const base64Files = await Promise.all(
    files.map(file => fileToBase64(file))
  );
  
  return client.certification.createCertificate({
    name: 'Декларация соответствия ТР ТС',
    type: 'DECLARATION_OF_CONFORMITY',
    number: 'ЕАЭС N RU Д-RU.РА01.В.49567/20',
    expire_date: '2026-06-15T23:59:59Z',
    file: base64Files
  });
};

3. Привязка товаров к сертификату

Метод: bindCertificate()
Эндпоинт: POST /v1/product/certificate/bind

Привязывает один или несколько товаров к существующему сертификату.

Параметры запроса

interface CertificateBindRequest {
  certificate_id: number;         // ID сертификата
  product_id: string[];          // Массив ID товаров
}

Пример использования

// Привязать товары к сертификату
const bindResult = await client.certification.bindCertificate({
  certificate_id: 12345,
  product_id: ['product-1', 'product-2', 'product-3']
});

// Обработать результаты привязки
let successCount = 0;
let errorCount = 0;

bindResult.result?.forEach(item => {
  if (item.status === 'success') {
    console.log(`✅ Товар ${item.product_id} успешно привязан к сертификату`);
    successCount++;
  } else {
    console.error(`❌ Ошибка привязки товара ${item.product_id}: ${item.error}`);
    errorCount++;
  }
});

console.log(`Итого: успешно привязано ${successCount}, ошибок ${errorCount}`);

4. Отвязка товаров от сертификата

Метод: unbindCertificate()
Эндпоинт: POST /v1/product/certificate/unbind

Отвязывает товары от сертификата, например, при изменении требований или замене сертификата.

Пример использования

// Отвязать товары от сертификата
const unbindResult = await client.certification.unbindCertificate({
  certificate_id: 12345,
  product_id: ['product-1', 'product-2']
});

unbindResult.result?.forEach(item => {
  if (item.status === 'success') {
    console.log(`✅ Товар ${item.product_id} успешно отвязан от сертификата`);
  } else {
    console.error(`❌ Ошибка отвязки товара ${item.product_id}: ${item.error}`);
  }
});

5. Удаление сертификатов

Метод: deleteCertificates()
Эндпоинт: POST /v1/product/certificate/delete

Удаляет один или несколько сертификатов. Операция необратима.

Пример использования

// Удалить сертификаты
const deleteResult = await client.certification.deleteCertificates({
  certificate_id: [12345, 12346, 12347]
});

deleteResult.result?.forEach(item => {
  if (item.status === 'success') {
    console.log(`✅ Сертификат ${item.certificate_id} успешно удален`);
  } else {
    console.error(`❌ Ошибка удаления сертификата ${item.certificate_id}: ${item.error}`);
  }
});

Информационные методы

6. Получение товаров, привязанных к сертификату

Метод: getCertificateProducts()
Эндпоинт: POST /v1/product/certificate/info/from_list

Возвращает список всех товаров, привязанных к конкретному сертификату.

Пример использования

// Получить товары, привязанные к сертификату
const products = await client.certification.getCertificateProducts({
  certificate_id: 12345,
  page: 1,
  page_size: 50
});

console.log(`К сертификату привязано товаров: ${products.result?.total}`);
products.result?.products.forEach(product => {
  console.log(`${product.name} (Offer ID: ${product.offer_id})`);
  console.log(`  SKU: ${product.sku}, Статус: ${product.status}`);
  console.log(`  Категория: ${product.category_name}`);
});

7. Справочник типов сертификатов

Метод: getCertificateTypes()
Эндпоинт: GET /v1/product/certificate/types

Возвращает все доступные типы сертификатов и документов.

Пример использования

// Получить типы сертификатов
const types = await client.certification.getCertificateTypes();

console.log('Доступные типы сертификатов:');
types.result?.forEach(type => {
  console.log(`${type.code}: ${type.name}`);
  if (type.description) {
    console.log(`  Описание: ${type.description}`);
  }
  console.log(`  Обязательные поля: ${type.required_fields?.join(', ') || 'нет'}`);
});

8. Справочник статусов сертификатов

Метод: getCertificateStatuses()
Эндпоинт: POST /v1/product/certificate/status/list

Возвращает все возможные статусы сертификатов.

Пример использования

// Получить статусы сертификатов
const statuses = await client.certification.getCertificateStatuses();

console.log('Возможные статусы сертификатов:');
statuses.result?.forEach(status => {
  console.log(`${status.code}: ${status.name}`);
  if (status.description) {
    console.log(`  Описание: ${status.description}`);
  }
});

9. Причины отклонения сертификатов

Метод: getRejectionReasons()
Эндпоинт: POST /v1/product/certificate/rejection_reasons/list

Возвращает справочник причин отклонения сертификатов.

Пример использования

// Получить причины отклонения
const reasons = await client.certification.getRejectionReasons();

console.log('Возможные причины отклонения сертификатов:');
reasons.result?.forEach(reason => {
  console.log(`${reason.code}: ${reason.name}`);
  if (reason.description) {
    console.log(`  Описание: ${reason.description}`);
  }
});

Методы работы с категориями сертификации

10. Типы соответствия требованиям (v2)

Метод: getCertificateAccordanceTypes()
Эндпоинт: GET /v2/product/certificate/accordance-types/list

Возвращает актуальные типы соответствия требованиям для сертификации.

Пример использования

// Получить типы соответствия
const accordanceTypes = await client.certification.getCertificateAccordanceTypes();

console.log('Доступные типы соответствия:');
accordanceTypes.result?.accordance_types.forEach(type => {
  console.log(`${type.code}: ${type.name} (ID: ${type.id})`);
  if (type.description) {
    console.log(`  Описание: ${type.description}`);
  }
});

11. Список сертифицируемых категорий (v2)

Метод: getProductCertificationListV2()
Эндпоинт: POST /v2/product/certification/list

Возвращает список категорий товаров, которые требуют сертификацию.

Пример использования

// Получить категории, требующие сертификацию
const certifications = await client.certification.getProductCertificationListV2({
  page: 1,
  page_size: 100
});

console.log(`Всего категорий, требующих сертификацию: ${certifications.total}`);
certifications.certification?.forEach(category => {
  if (category.has_certificate) {
    console.log(`${category.category_name} (ID: ${category.category_id})`);
    console.log(`  Требуется сертификат типа: ${category.certificate_type}`);
    console.log(`  Тип соответствия: ${category.accordance_type}`);
  }
});

12. Список сертифицируемых категорий (v1) - DEPRECATED

Метод: getProductCertificationList()
⚠️ Устарел: Будет отключён 14 апреля 2025 года. Используйте v2.

Практические сценарии использования

1. Система управления жизненным циклом сертификатов

class CertificationManager {
  constructor(private client: OzonSellerApiClient) {}

  // Проверить истекающие сертификаты
  async checkExpiringCertificates(daysBeforeExpiry: number = 30): Promise<Certificate[]> {
    const certificates = await this.client.certification.getCertificateList({
      page: 1,
      page_size: 1000,
      status: 'ACTIVE'
    });

    const expiringCerts: Certificate[] = [];
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() + daysBeforeExpiry);

    certificates.result?.certificates.forEach(cert => {
      if (cert.expire_date) {
        const expireDate = new Date(cert.expire_date);
        if (expireDate <= cutoffDate) {
          expiringCerts.push(cert);
        }
      }
    });

    return expiringCerts;
  }

  // Автоматическое продление сертификатов
  async renewCertificate(oldCertificateId: number, newCertificateData: CertificateCreateRequest) {
    console.log(`Начинаем процесс продления сертификата ${oldCertificateId}`);

    // 1. Получить товары, привязанные к старому сертификату
    const products = await this.client.certification.getCertificateProducts({
      certificate_id: oldCertificateId,
      page: 1,
      page_size: 1000
    });

    const productIds = products.result?.products.map(p => p.offer_id || p.product_id).filter(Boolean) || [];
    console.log(`Найдено привязанных товаров: ${productIds.length}`);

    // 2. Создать новый сертификат
    const newCert = await this.client.certification.createCertificate(newCertificateData);
    console.log(`Создан новый сертификат с ID: ${newCert.certificate_id}`);

    // 3. Привязать товары к новому сертификату
    if (productIds.length > 0) {
      const bindResult = await this.client.certification.bindCertificate({
        certificate_id: newCert.certificate_id!,
        product_id: productIds
      });

      const successfulBinds = bindResult.result?.filter(r => r.status === 'success').length || 0;
      console.log(`Успешно привязано к новому сертификату: ${successfulBinds} товаров`);
    }

    // 4. Удалить старый сертификат (опционально)
    // await this.client.certification.deleteCertificates({
    //   certificate_id: [oldCertificateId]
    // });

    return {
      oldCertificateId,
      newCertificateId: newCert.certificate_id,
      transferredProductsCount: productIds.length
    };
  }

  // Массовая привязка товаров по категориям
  async bindProductsByCategory(certificateId: number, categoryIds: number[]) {
    // Здесь должна быть логика получения товаров по категориям
    // В реальном приложении это может быть отдельный API или база данных
    console.log(`Привязка товаров из категорий ${categoryIds.join(', ')} к сертификату ${certificateId}`);
    
    // Пример реализации
    const allProductIds: string[] = [];
    
    // В реальном приложении здесь был бы цикл по категориям
    // и получение товаров через Product API
    
    if (allProductIds.length > 0) {
      const bindResult = await this.client.certification.bindCertificate({
        certificate_id: certificateId,
        product_id: allProductIds
      });
      
      return bindResult;
    }
    
    return null;
  }

  // Аудит соответствия сертификации
  async auditCertificationCompliance() {
    const report = {
      totalCertificates: 0,
      activeCertificates: 0,
      expiredCertificates: 0,
      expiringCertificates: 0,
      certificatesWithoutProducts: 0,
      productsWithoutCertificates: 0
    };

    // Получить все сертификаты
    const allCertificates = await this.client.certification.getCertificateList({
      page: 1,
      page_size: 1000
    });

    report.totalCertificates = allCertificates.result?.total || 0;

    for (const cert of allCertificates.result?.certificates || []) {
      // Подсчет по статусам
      switch (cert.status) {
        case 'ACTIVE':
          report.activeCertificates++;
          break;
        case 'EXPIRED':
          report.expiredCertificates++;
          break;
      }

      // Проверка истекающих
      if (cert.expire_date) {
        const expireDate = new Date(cert.expire_date);
        const thirtyDaysFromNow = new Date();
        thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30);
        
        if (expireDate <= thirtyDaysFromNow && cert.status === 'ACTIVE') {
          report.expiringCertificates++;
        }
      }

      // Проверка привязанных товаров
      const products = await this.client.certification.getCertificateProducts({
        certificate_id: cert.id!,
        page: 1,
        page_size: 1
      });

      if ((products.result?.total || 0) === 0) {
        report.certificatesWithoutProducts++;
      }
    }

    return report;
  }
}

const certManager = new CertificationManager(client);

// Проверить истекающие сертификаты
const expiringCerts = await certManager.checkExpiringCertificates(30);
console.log(`Сертификатов истекает в ближайшие 30 дней: ${expiringCerts.length}`);

// Провести аудит соответствия
const auditReport = await certManager.auditCertificationCompliance();
console.log('Отчет по сертификации:', auditReport);

2. Автоматизация создания сертификатов

class AutoCertificationSystem {
  constructor(private client: OzonSellerApiClient) {}

  async createCertificateFromTemplate(template: CertificationTemplate, files: File[]) {
    try {
      // Конвертировать файлы в base64
      const base64Files = await this.convertFilesToBase64(files);
      
      // Создать сертификат
      const newCert = await this.client.certification.createCertificate({
        name: template.name,
        type: template.type,
        number: template.number,
        expire_date: template.expireDate,
        file: base64Files,
        accordance_type_id: template.accordanceTypeId
      });

      console.log(`✅ Сертификат создан: ${newCert.certificate_id}`);
      
      // Автоматически привязать товары, если указаны
      if (template.productIds && template.productIds.length > 0) {
        await this.bindProductsToCertificate(newCert.certificate_id!, template.productIds);
      }

      return newCert;
      
    } catch (error) {
      console.error('❌ Ошибка создания сертификата:', error);
      throw error;
    }
  }

  private async convertFilesToBase64(files: File[]): Promise<string[]> {
    return Promise.all(files.map(file => {
      return new Promise<string>((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => {
          const base64 = (reader.result as string).split(',')[1];
          resolve(base64);
        };
        reader.onerror = reject;
        reader.readAsDataURL(file);
      });
    }));
  }

  private async bindProductsToCertificate(certificateId: number, productIds: string[]) {
    const BATCH_SIZE = 100; // Привязываем по 100 товаров за раз
    
    for (let i = 0; i < productIds.length; i += BATCH_SIZE) {
      const batch = productIds.slice(i, i + BATCH_SIZE);
      
      try {
        const result = await this.client.certification.bindCertificate({
          certificate_id: certificateId,
          product_id: batch
        });
        
        const successCount = result.result?.filter(r => r.status === 'success').length || 0;
        console.log(`Привязано товаров в батче ${Math.floor(i/BATCH_SIZE) + 1}: ${successCount}/${batch.length}`);
        
      } catch (error) {
        console.error(`Ошибка привязки батча ${Math.floor(i/BATCH_SIZE) + 1}:`, error);
      }
    }
  }

  // Создание сертификата на основе шаблона
  async createFromGovernmentTemplate(
    governmentCertData: GovernmentCertificateData,
    files: File[]
  ) {
    const template: CertificationTemplate = {
      name: `${governmentCertData.type} ${governmentCertData.productType}`,
      type: this.mapGovernmentTypeToOzon(governmentCertData.type),
      number: governmentCertData.registrationNumber,
      expireDate: governmentCertData.validUntil,
      accordanceTypeId: governmentCertData.accordanceTypeId,
      productIds: governmentCertData.applicableProducts
    };

    return this.createCertificateFromTemplate(template, files);
  }

  private mapGovernmentTypeToOzon(govType: string): string {
    const mapping: Record<string, string> = {
      'Сертификат соответствия ГОСТ Р': 'GOST_CERTIFICATE',
      'Декларация соответствия ТР ТС': 'DECLARATION_OF_CONFORMITY',
      'Сертификат пожарной безопасности': 'FIRE_SAFETY_CERTIFICATE',
      'Санитарно-эпидемиологическое заключение': 'SANITARY_CERTIFICATE'
    };
    
    return mapping[govType] || 'OTHER';
  }
}

interface CertificationTemplate {
  name: string;
  type: string;
  number: string;
  expireDate: string;
  accordanceTypeId?: number;
  productIds?: string[];
}

interface GovernmentCertificateData {
  type: string;
  productType: string;
  registrationNumber: string;
  validUntil: string;
  accordanceTypeId?: number;
  applicableProducts?: string[];
}

3. Система мониторинга и уведомлений

class CertificationMonitor {
  constructor(private client: OzonSellerApiClient) {}

  async setupMonitoring() {
    // Проверяем каждый день
    setInterval(async () => {
      await this.dailyCheck();
    }, 24 * 60 * 60 * 1000); // 24 часа

    // Немедленная проверка при запуске
    await this.dailyCheck();
  }

  private async dailyCheck() {
    console.log('🔍 Запуск ежедневной проверки сертификатов...');
    
    const alerts: CertificationAlert[] = [];
    
    // Проверка истекающих сертификатов
    const expiringAlerts = await this.checkExpiringCertificates();
    alerts.push(...expiringAlerts);
    
    // Проверка отклоненных сертификатов
    const rejectedAlerts = await this.checkRejectedCertificates();
    alerts.push(...rejectedAlerts);
    
    // Проверка сертификатов без товаров
    const unusedAlerts = await this.checkUnusedCertificates();
    alerts.push(...unusedAlerts);
    
    if (alerts.length > 0) {
      await this.sendAlerts(alerts);
    } else {
      console.log('✅ Все сертификаты в порядке');
    }
  }

  private async checkExpiringCertificates(): Promise<CertificationAlert[]> {
    const alerts: CertificationAlert[] = [];
    const certificates = await this.client.certification.getCertificateList({
      page: 1,
      page_size: 1000,
      status: 'ACTIVE'
    });

    const now = new Date();
    const thirtyDaysFromNow = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
    const sevenDaysFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);

    for (const cert of certificates.result?.certificates || []) {
      if (cert.expire_date) {
        const expireDate = new Date(cert.expire_date);
        
        if (expireDate <= sevenDaysFromNow) {
          alerts.push({
            type: 'CRITICAL_EXPIRY',
            certificateId: cert.id!,
            certificateName: cert.name!,
            message: `Сертификат "${cert.name}" истекает через ${Math.ceil((expireDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000))} дней`,
            severity: 'critical'
          });
        } else if (expireDate <= thirtyDaysFromNow) {
          alerts.push({
            type: 'WARNING_EXPIRY',
            certificateId: cert.id!,
            certificateName: cert.name!,
            message: `Сертификат "${cert.name}" истекает через ${Math.ceil((expireDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000))} дней`,
            severity: 'warning'
          });
        }
      }
    }

    return alerts;
  }

  private async checkRejectedCertificates(): Promise<CertificationAlert[]> {
    const alerts: CertificationAlert[] = [];
    const certificates = await this.client.certification.getCertificateList({
      page: 1,
      page_size: 1000,
      status: 'REJECTED'
    });

    for (const cert of certificates.result?.certificates || []) {
      alerts.push({
        type: 'REJECTED_CERTIFICATE',
        certificateId: cert.id!,
        certificateName: cert.name!,
        message: `Сертификат "${cert.name}" был отклонен. Причина: ${cert.rejection_reason || 'не указана'}`,
        severity: 'error'
      });
    }

    return alerts;
  }

  private async checkUnusedCertificates(): Promise<CertificationAlert[]> {
    const alerts: CertificationAlert[] = [];
    const certificates = await this.client.certification.getCertificateList({
      page: 1,
      page_size: 1000,
      status: 'ACTIVE'
    });

    for (const cert of certificates.result?.certificates || []) {
      const products = await this.client.certification.getCertificateProducts({
        certificate_id: cert.id!,
        page: 1,
        page_size: 1
      });

      if ((products.result?.total || 0) === 0) {
        alerts.push({
          type: 'UNUSED_CERTIFICATE',
          certificateId: cert.id!,
          certificateName: cert.name!,
          message: `К сертификату "${cert.name}" не привязано ни одного товара`,
          severity: 'info'
        });
      }
    }

    return alerts;
  }

  private async sendAlerts(alerts: CertificationAlert[]) {
    console.log(`🚨 Найдено ${alerts.length} предупреждений:`);
    
    const critical = alerts.filter(a => a.severity === 'critical');
    const errors = alerts.filter(a => a.severity === 'error');
    const warnings = alerts.filter(a => a.severity === 'warning');
    const info = alerts.filter(a => a.severity === 'info');

    if (critical.length > 0) {
      console.log('\n🔴 КРИТИЧЕСКИЕ:');
      critical.forEach(alert => console.log(`  - ${alert.message}`));
    }

    if (errors.length > 0) {
      console.log('\n🟠 ОШИБКИ:');
      errors.forEach(alert => console.log(`  - ${alert.message}`));
    }

    if (warnings.length > 0) {
      console.log('\n🟡 ПРЕДУПРЕЖДЕНИЯ:');
      warnings.forEach(alert => console.log(`  - ${alert.message}`));
    }

    if (info.length > 0) {
      console.log('\n🔵 ИНФОРМАЦИЯ:');
      info.forEach(alert => console.log(`  - ${alert.message}`));
    }

    // Здесь можно добавить отправку уведомлений через email, Slack, etc.
  }
}

interface CertificationAlert {
  type: string;
  certificateId: number;
  certificateName: string;
  message: string;
  severity: 'critical' | 'error' | 'warning' | 'info';
}

const monitor = new CertificationMonitor(client);
await monitor.setupMonitoring();

Обработка ошибок

Типичные ошибки и их обработка

try {
  const result = await client.certification.createCertificate({
    name: 'Тестовый сертификат',
    type: 'GOST_CERTIFICATE',
    number: 'TEST-001',
    expire_date: '2025-12-31T23:59:59Z',
    file: ['invalid_base64']
  });
} catch (error) {
  if (error.response?.status === 400) {
    const errorData = error.response.data;
    
    switch (errorData.code) {
      case 'INVALID_FILE_FORMAT':
        console.error('Неверный формат файла. Загружайте только JPG, PNG или PDF');
        break;
      case 'FILE_TOO_LARGE':
        console.error('Размер файла превышает допустимый лимит (10 МБ)');
        break;
      case 'CERTIFICATE_NUMBER_EXISTS':
        console.error('Сертификат с таким номером уже существует');
        break;
      case 'INVALID_EXPIRE_DATE':
        console.error('Некорректная дата окончания действия');
        break;
      case 'INVALID_CERTIFICATE_TYPE':
        console.error('Неверный тип сертификата');
        break;
      default:
        console.error('Неизвестная ошибка:', errorData.message);
    }
  } else if (error.response?.status === 413) {
    console.error('Размер загружаемых файлов слишком велик');
  } else if (error.response?.status === 429) {
    console.error('Превышен лимит запросов. Повторите попытку позже.');
  } else {
    console.error('Произошла ошибка:', error.message);
  }
}

Лучшие практики

1. Управление файлами сертификатов

class CertificateFileManager {
  private readonly MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 МБ
  private readonly ALLOWED_FORMATS = ['image/jpeg', 'image/png', 'application/pdf'];

  validateFile(file: File): { valid: boolean; error?: string } {
    if (file.size > this.MAX_FILE_SIZE) {
      return { valid: false, error: 'Размер файла превышает 10 МБ' };
    }

    if (!this.ALLOWED_FORMATS.includes(file.type)) {
      return { valid: false, error: 'Поддерживаются только JPG, PNG и PDF файлы' };
    }

    return { valid: true };
  }

  async compressImage(file: File, quality: number = 0.8): Promise<File> {
    return new Promise((resolve) => {
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d')!;
      const img = new Image();
      
      img.onload = () => {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);
        
        canvas.toBlob((blob) => {
          resolve(new File([blob!], file.name, { type: file.type }));
        }, file.type, quality);
      };
      
      img.src = URL.createObjectURL(file);
    });
  }
}

2. Пакетная обработка операций

// Пакетная привязка товаров
async function batchBindProducts(
  client: OzonSellerApiClient,
  certificateId: number,
  productIds: string[],
  batchSize: number = 100
) {
  const results = [];
  
  for (let i = 0; i < productIds.length; i += batchSize) {
    const batch = productIds.slice(i, i + batchSize);
    
    try {
      const result = await client.certification.bindCertificate({
        certificate_id: certificateId,
        product_id: batch
      });
      
      results.push(result);
      
      // Пауза между запросами для соблюдения лимитов
      await new Promise(resolve => setTimeout(resolve, 1000));
      
    } catch (error) {
      console.error(`Ошибка в батче ${i / batchSize + 1}:`, error);
    }
  }
  
  return results;
}

3. Кэширование справочных данных

class CertificationDictionaries {
  private cache: Map<string, { data: any; timestamp: number }> = new Map();
  private cacheTimeout = 24 * 60 * 60 * 1000; // 24 часа

  async getCertificateTypes(client: OzonSellerApiClient, useCache = true) {
    const cacheKey = 'certificate-types';
    
    if (useCache && this.isCacheValid(cacheKey)) {
      return this.cache.get(cacheKey)!.data;
    }

    const data = await client.certification.getCertificateTypes();
    this.cache.set(cacheKey, { data, timestamp: Date.now() });
    
    return data;
  }

  private isCacheValid(key: string): boolean {
    const cached = this.cache.get(key);
    return cached ? Date.now() - cached.timestamp < this.cacheTimeout : false;
  }
}

Связанные API: Product API (создание товаров), Brand API (сертификация брендов), Category API (требования по категориям)