# Внешние интеграции SFERA ## 🌐 Обзор Комплексная документация по всем внешним интеграциям платформы SFERA, включающая marketplace API, SMS-сервисы, проверку данных, аналитику и другие внешние сервисы. ## 📊 Архитектура интеграций ```mermaid graph TB A[SFERA Platform] --> B[Marketplace APIs] A --> C[SMS Services] A --> D[Data Validation] A --> E[Analytics & Tracking] A --> F[File Storage] A --> G[Payment Systems] B --> B1[Wildberries API] B --> B2[Ozon API] B --> B3[Яндекс.Маркет API] C --> C1[SMS Aero] C --> C2[SMS.ru] D --> D1[DaData API] D --> D2[ЕГРЮЛ API] E --> E1[Yandex.Metrica] E --> E2[Google Analytics] F --> F1[Yandex Cloud Storage] F --> F2[AWS S3] G --> G1[ЮKassa] G --> G2[Сбербанк Эквайринг] ``` ## 🛒 Marketplace API ### 1. Wildberries Integration #### Конфигурация API ```typescript // src/lib/integrations/wildberries.ts export class WildberriesAPI { private baseUrl = 'https://common-api.wildberries.ru' private suppliersUrl = 'https://suppliers-api.wildberries.ru' private statisticsUrl = 'https://statistics-api.wildberries.ru' constructor(private apiKey: string) {} // Получение информации о товарах async getProductCards(): Promise { try { const response = await fetch(`${this.suppliersUrl}/content/v1/cards/cursor/list`, { method: 'POST', headers: { Authorization: this.apiKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ sort: { cursor: { limit: 100, }, }, filter: { withPhoto: -1, }, }), }) if (!response.ok) { throw new WBAPIError(`HTTP ${response.status}: ${response.statusText}`) } const data = await response.json() return data.data?.cards || [] } catch (error) { console.error('Wildberries API error:', error) throw error } } // Получение остатков товаров async getStocks(): Promise { const response = await fetch(`${this.suppliersUrl}/api/v3/stocks`, { headers: { Authorization: this.apiKey, }, }) const data = await response.json() return data.stocks || [] } // Получение заказов async getOrders(dateFrom: string, flag: number = 0): Promise { const response = await fetch(`${this.suppliersUrl}/api/v3/orders?dateFrom=${dateFrom}&flag=${flag}`, { headers: { Authorization: this.apiKey, }, }) const data = await response.json() return data.orders || [] } // Получение продаж async getSales(dateFrom: string, flag: number = 0): Promise { const response = await fetch(`${this.suppliersUrl}/api/v3/sales?dateFrom=${dateFrom}&flag=${flag}`, { headers: { Authorization: this.apiKey, }, }) const data = await response.json() return data.sales || [] } // Получение складов async getWarehouses(): Promise { const response = await fetch(`${this.suppliersUrl}/api/v3/warehouses`, { headers: { Authorization: this.apiKey, }, }) const data = await response.json() return data.warehouses || [] } // Создание поставки async createSupply(name: string): Promise { const response = await fetch(`${this.suppliersUrl}/api/v3/supplies`, { method: 'POST', headers: { Authorization: this.apiKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ name }), }) const data = await response.json() return data.id } // Добавление товаров в поставку async addToSupply(supplyId: string, orders: WBOrderToSupply[]): Promise { await fetch(`${this.suppliersUrl}/api/v3/supplies/${supplyId}/orders/${orders[0].id}`, { method: 'PATCH', headers: { Authorization: this.apiKey, }, }) } // Получение статистики async getIncomes(dateFrom: string, dateTo: string): Promise { const response = await fetch( `${this.statisticsUrl}/api/v1/supplier/incomes?dateFrom=${dateFrom}&dateTo=${dateTo}`, { headers: { Authorization: this.apiKey, }, }, ) const data = await response.json() return data.incomes || [] } } // Типы данных Wildberries export interface WBProduct { nmID: number vendorCode: string brand: string title: string photos: WBPhoto[] dimensions: WBDimensions characteristics: WBCharacteristic[] sizes: WBSize[] } export interface WBStock { sku: string amount: number warehouse: number } export interface WBOrder { id: number rid: string createdAt: string officeName: string supplierArticle: string techSize: string barcode: string totalPrice: number discountPercent: number warehouseName: string oblast: string incomeID: number nmId: number subject: string category: string brand: string isCancel: boolean cancelDate?: string } export interface WBSale { gNumber: string date: string lastChangeDate: string supplierArticle: string techSize: string barcode: string totalPrice: number discountPercent: number isSupply: boolean isRealization: boolean promoCodeDiscount: number warehouseName: string countryName: string oblastOkrugName: string regionName: string incomeID: number saleID: string odid: number spp: number forPay: number finishedPrice: number priceWithDisc: number nmId: number subject: string category: string brand: string } export class WBAPIError extends Error { constructor(message: string) { super(message) this.name = 'WBAPIError' } } ``` #### Интеграция с базой данных ```typescript // src/services/wildberries-sync.ts import { PrismaClient } from '@prisma/client' import { WildberriesAPI } from '@/lib/integrations/wildberries' export class WildberriesSync { constructor( private prisma: PrismaClient, private wb: WildberriesAPI, private organizationId: string, ) {} // Синхронизация товаров async syncProducts(): Promise { console.log('Starting Wildberries products sync...') try { const products = await this.wb.getProductCards() for (const product of products) { await this.prisma.marketplaceProduct.upsert({ where: { organizationId_marketplaceId_externalId: { organizationId: this.organizationId, marketplaceId: 'WILDBERRIES', externalId: product.nmID.toString(), }, }, update: { title: product.title, brand: product.brand, vendorCode: product.vendorCode, photos: product.photos.map((p) => p.big), characteristics: product.characteristics, updatedAt: new Date(), }, create: { organizationId: this.organizationId, marketplaceId: 'WILDBERRIES', externalId: product.nmID.toString(), title: product.title, brand: product.brand, vendorCode: product.vendorCode, photos: product.photos.map((p) => p.big), characteristics: product.characteristics, }, }) } console.log(`Synced ${products.length} products from Wildberries`) } catch (error) { console.error('Wildberries products sync failed:', error) throw error } } // Синхронизация остатков async syncStocks(): Promise { console.log('Starting Wildberries stocks sync...') try { const stocks = await this.wb.getStocks() for (const stock of stocks) { await this.prisma.marketplaceStock.upsert({ where: { organizationId_marketplaceId_sku: { organizationId: this.organizationId, marketplaceId: 'WILDBERRIES', sku: stock.sku, }, }, update: { amount: stock.amount, warehouseId: stock.warehouse.toString(), updatedAt: new Date(), }, create: { organizationId: this.organizationId, marketplaceId: 'WILDBERRIES', sku: stock.sku, amount: stock.amount, warehouseId: stock.warehouse.toString(), }, }) } console.log(`Synced ${stocks.length} stock records from Wildberries`) } catch (error) { console.error('Wildberries stocks sync failed:', error) throw error } } // Синхронизация заказов и продаж async syncOrdersAndSales(dateFrom: string): Promise { console.log('Starting Wildberries orders and sales sync...') try { // Синхронизация заказов const orders = await this.wb.getOrders(dateFrom) for (const order of orders) { await this.prisma.marketplaceOrder.upsert({ where: { organizationId_marketplaceId_externalId: { organizationId: this.organizationId, marketplaceId: 'WILDBERRIES', externalId: order.id.toString(), }, }, update: { status: order.isCancel ? 'CANCELLED' : 'CONFIRMED', totalPrice: order.totalPrice, discountPercent: order.discountPercent, cancelDate: order.cancelDate ? new Date(order.cancelDate) : null, updatedAt: new Date(), }, create: { organizationId: this.organizationId, marketplaceId: 'WILDBERRIES', externalId: order.id.toString(), createdAt: new Date(order.createdAt), status: order.isCancel ? 'CANCELLED' : 'CONFIRMED', supplierArticle: order.supplierArticle, barcode: order.barcode, totalPrice: order.totalPrice, discountPercent: order.discountPercent, warehouseName: order.warehouseName, region: order.oblast, nmId: order.nmId, subject: order.subject, category: order.category, brand: order.brand, cancelDate: order.cancelDate ? new Date(order.cancelDate) : null, }, }) } // Синхронизация продаж const sales = await this.wb.getSales(dateFrom) for (const sale of sales) { await this.prisma.marketplaceSale.upsert({ where: { organizationId_marketplaceId_saleId: { organizationId: this.organizationId, marketplaceId: 'WILDBERRIES', saleId: sale.saleID, }, }, update: { totalPrice: sale.totalPrice, discountPercent: sale.discountPercent, forPay: sale.forPay, finishedPrice: sale.finishedPrice, priceWithDisc: sale.priceWithDisc, updatedAt: new Date(), }, create: { organizationId: this.organizationId, marketplaceId: 'WILDBERRIES', saleId: sale.saleID, gNumber: sale.gNumber, date: new Date(sale.date), supplierArticle: sale.supplierArticle, barcode: sale.barcode, totalPrice: sale.totalPrice, discountPercent: sale.discountPercent, isSupply: sale.isSupply, isRealization: sale.isRealization, promoCodeDiscount: sale.promoCodeDiscount, warehouseName: sale.warehouseName, region: sale.regionName, forPay: sale.forPay, finishedPrice: sale.finishedPrice, priceWithDisc: sale.priceWithDisc, nmId: sale.nmId, subject: sale.subject, category: sale.category, brand: sale.brand, }, }) } console.log(`Synced ${orders.length} orders and ${sales.length} sales from Wildberries`) } catch (error) { console.error('Wildberries orders/sales sync failed:', error) throw error } } } ``` ### 2. Ozon Integration ```typescript // src/lib/integrations/ozon.ts export class OzonAPI { private baseUrl = 'https://api-seller.ozon.ru' constructor( private apiKey: string, private clientId: string, ) {} private async makeRequest(endpoint: string, options: RequestInit = {}): Promise { const response = await fetch(`${this.baseUrl}${endpoint}`, { ...options, headers: { 'Client-Id': this.clientId, 'Api-Key': this.apiKey, 'Content-Type': 'application/json', ...options.headers, }, }) if (!response.ok) { throw new OzonAPIError(`HTTP ${response.status}: ${response.statusText}`) } return response.json() } // Получение товаров async getProducts(): Promise { const response = await this.makeRequest('/v2/product/list', { method: 'POST', body: JSON.stringify({ filter: { visibility: 'ALL', }, last_id: '', limit: 1000, }), }) return response.result.items } // Получение информации о товаре async getProductInfo(productId: number): Promise { const response = await this.makeRequest('/v2/product/info', { method: 'POST', body: JSON.stringify({ product_id: productId, }), }) return response.result } // Получение остатков async getStocks(): Promise { const response = await this.makeRequest('/v3/product/info/stocks', { method: 'POST', body: JSON.stringify({ filter: { visibility: 'ALL', }, last_id: '', limit: 1000, }), }) return response.result.items } // Получение заказов async getOrders(dateFrom: string, dateTo: string): Promise { const response = await this.makeRequest('/v3/posting/fbs/list', { method: 'POST', body: JSON.stringify({ dir: 'ASC', filter: { since: dateFrom, to: dateTo, status: '', }, limit: 1000, offset: 0, with: { analytics_data: true, financial_data: true, }, }), }) return response.result.postings } // Получение аналитики async getAnalytics(dateFrom: string, dateTo: string): Promise { const response = await this.makeRequest('/v1/analytics/data', { method: 'POST', body: JSON.stringify({ date_from: dateFrom, date_to: dateTo, metrics: ['revenue', 'ordered_units', 'cancel_rate', 'returns_rate'], dimension: ['sku'], filters: [], sort: [ { key: 'revenue', order: 'DESC', }, ], limit: 1000, offset: 0, }), }) return response.result } } // Типы данных Ozon export interface OzonProduct { product_id: number offer_id: string is_fbo_visible: boolean is_fbs_visible: boolean archived: boolean is_discounted: boolean } export interface OzonProductInfo { id: number name: string offer_id: string barcode: string category_id: number created_at: string images: OzonImage[] marketing_price: string min_price: string old_price: string premium_price: string price: string recommended_price: string sources: OzonSource[] state: string stocks: OzonStockInfo errors: OzonError[] vat: string visible: boolean visibility_details: OzonVisibilityDetails price_index: string images360: any[] color_image: string primary_image: string status: OzonStatus } export interface OzonStock { offer_id: string product_id: number stocks: OzonStockDetails[] } export interface OzonOrder { order_id: number order_number: string posting_number: string status: string cancel_reason_id: number created_at: string in_process_at: string products: OzonOrderProduct[] analytics_data: OzonAnalyticsData financial_data: OzonFinancialData } export class OzonAPIError extends Error { constructor(message: string) { super(message) this.name = 'OzonAPIError' } } ``` ## 📱 SMS Services ### 1. SMS Aero Integration ```typescript // src/lib/integrations/sms-aero.ts export class SMSAeroAPI { private baseUrl = 'https://gate.smsaero.ru/v2' constructor( private email: string, private apiKey: string, ) {} private getAuthHeader(): string { return 'Basic ' + Buffer.from(`${this.email}:${this.apiKey}`).toString('base64') } // Отправка SMS async sendSMS(phone: string, text: string, sign?: string): Promise { try { const response = await fetch(`${this.baseUrl}/sms/send`, { method: 'POST', headers: { Authorization: this.getAuthHeader(), 'Content-Type': 'application/json', }, body: JSON.stringify({ number: phone, text: text, sign: sign || 'SMS Aero', channel: 'DIRECT', }), }) if (!response.ok) { throw new SMSAeroError(`HTTP ${response.status}: ${response.statusText}`) } const data = await response.json() if (!data.success) { throw new SMSAeroError(`SMS sending failed: ${data.message}`) } return data } catch (error) { console.error('SMS Aero API error:', error) throw error } } // Проверка статуса SMS async checkStatus(smsId: number): Promise { const response = await fetch(`${this.baseUrl}/sms/${smsId}`, { headers: { Authorization: this.getAuthHeader(), }, }) const data = await response.json() return data.data } // Получение баланса async getBalance(): Promise { const response = await fetch(`${this.baseUrl}/balance`, { headers: { Authorization: this.getAuthHeader(), }, }) const data = await response.json() return data.data.balance } // Получение списка рассылок async getChannels(): Promise { const response = await fetch(`${this.baseUrl}/channels`, { headers: { Authorization: this.getAuthHeader(), }, }) const data = await response.json() return data.data } } // Типы для SMS Aero export interface SMSAeroResponse { success: boolean data?: { id: number from: string number: string text: string status: number extendStatus: string channel: string cost: number dateCreate: number dateSend: number } message?: string } export interface SMSStatus { id: number from: string number: string text: string status: number extendStatus: string channel: string cost: number dateCreate: number dateSend: number } export interface SMSChannel { id: string name: string tariff: string } export class SMSAeroError extends Error { constructor(message: string) { super(message) this.name = 'SMSAeroError' } } ``` ### 2. SMS Service Wrapper ```typescript // src/services/sms-service.ts import { SMSAeroAPI } from '@/lib/integrations/sms-aero' import { PrismaClient } from '@prisma/client' export class SMSService { private smsAero: SMSAeroAPI constructor( private prisma: PrismaClient, smsConfig: { email: string apiKey: string }, ) { this.smsAero = new SMSAeroAPI(smsConfig.email, smsConfig.apiKey) } // Отправка кода подтверждения async sendVerificationCode(phone: string): Promise<{ success: boolean messageId?: number message: string }> { try { // Генерация кода подтверждения const code = this.generateVerificationCode() // Проверка лимитов отправки (не более 3 SMS в час) const recentSMS = await this.prisma.smsLog.count({ where: { phone: phone, createdAt: { gte: new Date(Date.now() - 60 * 60 * 1000), // 1 час назад }, }, }) if (recentSMS >= 3) { return { success: false, message: 'Превышен лимит отправки SMS. Попробуйте через час.', } } let smsResult: any // В режиме разработки не отправляем реальные SMS if (process.env.SMS_DEV_MODE === 'true') { console.log(`[DEV MODE] SMS to ${phone}: Your verification code: ${code}`) smsResult = { success: true, data: { id: Date.now() } } } else { const text = `Ваш код подтверждения: ${code}. Никому не сообщайте этот код.` smsResult = await this.smsAero.sendSMS(phone, text) } if (smsResult.success) { // Сохранение кода в базу данных await this.prisma.verificationCode.create({ data: { phone: phone, code: code, expiresAt: new Date(Date.now() + 10 * 60 * 1000), // 10 минут attempts: 0, }, }) // Логирование отправки SMS await this.prisma.smsLog.create({ data: { phone: phone, messageId: smsResult.data?.id?.toString() || 'dev-mode', status: 'SENT', provider: 'SMS_AERO', cost: smsResult.data?.cost || 0, }, }) return { success: true, messageId: smsResult.data?.id, message: 'Код подтверждения отправлен', } } else { return { success: false, message: 'Ошибка отправки SMS', } } } catch (error) { console.error('SMS sending error:', error) return { success: false, message: 'Техническая ошибка при отправке SMS', } } } // Проверка кода подтверждения async verifyCode( phone: string, code: string, ): Promise<{ success: boolean message: string }> { try { const verification = await this.prisma.verificationCode.findFirst({ where: { phone: phone, code: code, expiresAt: { gt: new Date(), }, verified: false, }, }) if (!verification) { // Увеличиваем счетчик попыток await this.prisma.verificationCode.updateMany({ where: { phone: phone, verified: false, }, data: { attempts: { increment: 1, }, }, }) return { success: false, message: 'Неверный или истекший код подтверждения', } } // Проверка количества попыток if (verification.attempts >= 3) { return { success: false, message: 'Превышено количество попыток. Запросите новый код.', } } // Отмечаем код как использованный await this.prisma.verificationCode.update({ where: { id: verification.id, }, data: { verified: true, verifiedAt: new Date(), }, }) return { success: true, message: 'Код подтверждения верен', } } catch (error) { console.error('Code verification error:', error) return { success: false, message: 'Техническая ошибка при проверке кода', } } } private generateVerificationCode(): string { return Math.floor(100000 + Math.random() * 900000).toString() } // Очистка истекших кодов async cleanupExpiredCodes(): Promise { await this.prisma.verificationCode.deleteMany({ where: { expiresAt: { lt: new Date(), }, }, }) } } ``` ## 🔍 Data Validation Services ### 1. DaData Integration ```typescript // src/lib/integrations/dadata.ts export class DaDataAPI { private baseUrl = 'https://suggestions.dadata.ru/suggestions/api/4_1/rs' private cleanUrl = 'https://cleaner.dadata.ru/api/v1/clean' constructor(private apiKey: string) {} private getHeaders() { return { Authorization: `Token ${this.apiKey}`, 'Content-Type': 'application/json', } } // Поиск организации по ИНН async findByINN(inn: string): Promise { try { const response = await fetch(`${this.baseUrl}/findById/party`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify({ query: inn, count: 1, }), }) const data = await response.json() return data.suggestions[0]?.data || null } catch (error) { console.error('DaData findByINN error:', error) return null } } // Подсказки по организациям async suggestOrganizations(query: string): Promise { const response = await fetch(`${this.baseUrl}/suggest/party`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify({ query: query, count: 10, }), }) const data = await response.json() return data.suggestions.map((s: any) => s.data) } // Подсказки по адресам async suggestAddresses(query: string): Promise { const response = await fetch(`${this.baseUrl}/suggest/address`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify({ query: query, count: 10, }), }) const data = await response.json() return data.suggestions.map((s: any) => s.data) } // Подсказки по банкам async suggestBanks(query: string): Promise { const response = await fetch(`${this.baseUrl}/suggest/bank`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify({ query: query, count: 10, }), }) const data = await response.json() return data.suggestions.map((s: any) => s.data) } // Очистка и стандартизация данных async cleanPhone(phone: string): Promise { const response = await fetch(`${this.cleanUrl}/phone`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify([phone]), }) const data = await response.json() return data[0] } async cleanAddress(address: string): Promise { const response = await fetch(`${this.cleanUrl}/address`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify([address]), }) const data = await response.json() return data[0] } async cleanName(name: string): Promise { const response = await fetch(`${this.cleanUrl}/name`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify([name]), }) const data = await response.json() return data[0] } } // Типы для DaData export interface DaDataOrganization { kpp: string capital: { type: string value: number } management: { name: string post: string disqualified: boolean } founders: any[] managers: any[] predecessors: any[] successors: any[] branch_type: string branch_count: number source: string qc: number hid: string type: string state: { status: string code: number actuality_date: number registration_date: number liquidation_date: number } opf: { type: string code: string full: string short: string } name: { full_with_opf: string short_with_opf: string latin: string full: string short: string } inn: string ogrn: string okpo: string okato: string oktmo: string okogu: string okfs: string okved: string okveds: any[] authorities: any[] documents: any[] licenses: any[] finance: { tax_system: string income: number expense: number debt: number penalty: number year: number } address: { value: string unrestricted_value: string data: DaDataAddress } phones: any[] emails: any[] ogrn_date: number okved_type: string employee_count: number } export interface DaDataAddress { postal_code: string country: string country_iso_code: string federal_district: string region_fias_id: string region_kladr_id: string region_iso_code: string region_with_type: string region_type: string region_type_full: string region: string area_fias_id: string area_kladr_id: string area_with_type: string area_type: string area_type_full: string area: string city_fias_id: string city_kladr_id: string city_with_type: string city_type: string city_type_full: string city: string city_area: string city_district_fias_id: string city_district_kladr_id: string city_district_with_type: string city_district_type: string city_district_type_full: string city_district: string settlement_fias_id: string settlement_kladr_id: string settlement_with_type: string settlement_type: string settlement_type_full: string settlement: string street_fias_id: string street_kladr_id: string street_with_type: string street_type: string street_type_full: string street: string house_fias_id: string house_kladr_id: string house_type: string house_type_full: string house: string block_type: string block_type_full: string block: string entrance: string floor: string flat_fias_id: string flat_type: string flat_type_full: string flat: string flat_area: number square_meter_price: number flat_price: number postal_box: string fias_id: string fias_code: string fias_level: string fias_actuality_state: string kladr_id: string geoname_id: string capital_marker: string okato: string oktmo: string tax_office: string tax_office_legal: string timezone: string geo_lat: string geo_lon: string beltway_hit: string beltway_distance: string metro: any[] qc_geo: string qc_complete: string qc_house: string history_values: string[] unparsed_parts: string source: string qc: string } export interface DaDataBank { opf: { type: string full: string short: string } name: { payment: string full: string short: string } bic: string swift: string inn: string kpp: string registration_number: string correspondent_account: string address: { value: string unrestricted_value: string data: DaDataAddress } phone: string state: { status: string actuality_date: number registration_date: number liquidation_date: number } } export interface DaDataCleanedPhone { source: string type: string phone: string country_code: string city_code: string number: string extension: string provider: string country: string region: string timezone: string qc_conflict: number qc: number } export interface DaDataCleanedAddress { source: string result: string postal_code: string country: string region_with_type: string region: string city_with_type: string city: string street_with_type: string street: string house: string flat: string geo_lat: string geo_lon: string qc_geo: number qc_complete: number qc_house: number qc: number } export interface DaDataCleanedName { source: string result: string result_genitive: string result_dative: string result_ablative: string surname: string name: string patronymic: string gender: string qc: number } ``` ## 📊 Analytics Integration ### 1. Yandex.Metrica ```typescript // src/lib/integrations/yandex-metrica.ts export class YandexMetrica { private counterId: string constructor(counterId: string) { this.counterId = counterId } // Инициализация Яндекс.Метрики на клиенте init(): void { if (typeof window === 'undefined') return ;(function (m: any, e: any, t: any, r: any, i: any, k: any, a: any) { m[i] = m[i] || function () { ;(m[i].a = m[i].a || []).push(arguments) } m[i].l = 1 * new Date() k = e.createElement(t) a = e.getElementsByTagName(t)[0] k.async = 1 k.src = r a.parentNode.insertBefore(k, a) })(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js', 'ym') ;(window as any).ym(this.counterId, 'init', { clickmap: true, trackLinks: true, accurateTrackBounce: true, webvisor: true, ecommerce: 'dataLayer', }) } // Отправка пользовательских событий hit(url: string, options?: any): void { if (typeof window !== 'undefined' && (window as any).ym) { ;(window as any).ym(this.counterId, 'hit', url, options) } } // Отправка целей reachGoal(target: string, params?: any): void { if (typeof window !== 'undefined' && (window as any).ym) { ;(window as any).ym(this.counterId, 'reachGoal', target, params) } } // E-commerce события addToCart(item: EcommerceItem): void { this.ecommerce('add', { currency: 'RUB', value: item.price, items: [item], }) } removeFromCart(item: EcommerceItem): void { this.ecommerce('remove', { currency: 'RUB', value: item.price, items: [item], }) } purchase(orderId: string, items: EcommerceItem[], total: number): void { this.ecommerce('purchase', { transaction_id: orderId, currency: 'RUB', value: total, items: items, }) } private ecommerce(action: string, data: any): void { if (typeof window !== 'undefined' && (window as any).dataLayer) { ;(window as any).dataLayer.push({ ecommerce: { [action]: data, }, }) } } } export interface EcommerceItem { item_id: string item_name: string category: string quantity: number price: number currency?: string item_brand?: string item_variant?: string } ``` ### 2. Analytics Service ```typescript // src/services/analytics.ts import { YandexMetrica } from '@/lib/integrations/yandex-metrica' export class AnalyticsService { private ym: YandexMetrica constructor() { this.ym = new YandexMetrica(process.env.NEXT_PUBLIC_YANDEX_METRICA_ID!) } // Инициализация всех аналитических сервисов init(): void { this.ym.init() } // Отслеживание просмотров страниц trackPageView(url: string, title?: string): void { this.ym.hit(url, { title }) } // Отслеживание регистрации пользователя trackUserRegistration(organizationType: string): void { this.ym.reachGoal('user_registration', { organization_type: organizationType, }) } // Отслеживание входа пользователя trackUserLogin(organizationType: string): void { this.ym.reachGoal('user_login', { organization_type: organizationType, }) } // Отслеживание создания заказа trackOrderCreated(orderId: string, orderType: string, amount: number): void { this.ym.reachGoal('order_created', { order_id: orderId, order_type: orderType, amount: amount, }) } // Отслеживание принятия заказа trackOrderAccepted(orderId: string, fulfillmentId: string): void { this.ym.reachGoal('order_accepted', { order_id: orderId, fulfillment_id: fulfillmentId, }) } // Отслеживание использования мессенджера trackMessageSent(conversationType: string): void { this.ym.reachGoal('message_sent', { conversation_type: conversationType, }) } // Отслеживание партнерских запросов trackPartnershipRequest(requesterType: string, targetType: string): void { this.ym.reachGoal('partnership_request', { requester_type: requesterType, target_type: targetType, }) } // Отслеживание ошибок trackError(errorType: string, errorMessage: string, page: string): void { this.ym.reachGoal('error_occurred', { error_type: errorType, error_message: errorMessage, page: page, }) } // Отслеживание использования функций trackFeatureUsage(feature: string, organizationType: string): void { this.ym.reachGoal('feature_used', { feature: feature, organization_type: organizationType, }) } } // Экземпляр сервиса аналитики export const analytics = new AnalyticsService() // Хук для использования аналитики в React компонентах export const useAnalytics = () => { return { trackPageView: analytics.trackPageView.bind(analytics), trackUserRegistration: analytics.trackUserRegistration.bind(analytics), trackUserLogin: analytics.trackUserLogin.bind(analytics), trackOrderCreated: analytics.trackOrderCreated.bind(analytics), trackOrderAccepted: analytics.trackOrderAccepted.bind(analytics), trackMessageSent: analytics.trackMessageSent.bind(analytics), trackPartnershipRequest: analytics.trackPartnershipRequest.bind(analytics), trackError: analytics.trackError.bind(analytics), trackFeatureUsage: analytics.trackFeatureUsage.bind(analytics), } } ``` ## ☁️ Cloud Storage ### 1. Yandex Cloud Object Storage ```typescript // src/lib/integrations/yandex-storage.ts import AWS from 'aws-sdk' export class YandexCloudStorage { private s3: AWS.S3 private bucketName: string constructor(config: { accessKeyId: string; secretAccessKey: string; bucketName: string }) { this.bucketName = config.bucketName this.s3 = new AWS.S3({ endpoint: 'https://storage.yandexcloud.net', accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey, region: 'ru-central1', s3ForcePathStyle: true, signatureVersion: 'v4', }) } // Загрузка файла async uploadFile(key: string, file: Buffer, contentType: string): Promise { try { const result = await this.s3 .upload({ Bucket: this.bucketName, Key: key, Body: file, ContentType: contentType, ACL: 'public-read', }) .promise() return result.Location } catch (error) { console.error('Yandex Cloud Storage upload error:', error) throw error } } // Получение подписанного URL для загрузки getSignedUploadUrl(key: string, contentType: string, expiresIn: number = 3600): string { return this.s3.getSignedUrl('putObject', { Bucket: this.bucketName, Key: key, ContentType: contentType, Expires: expiresIn, ACL: 'public-read', }) } // Получение подписанного URL для скачивания getSignedDownloadUrl(key: string, expiresIn: number = 3600): string { return this.s3.getSignedUrl('getObject', { Bucket: this.bucketName, Key: key, Expires: expiresIn, }) } // Удаление файла async deleteFile(key: string): Promise { await this.s3 .deleteObject({ Bucket: this.bucketName, Key: key, }) .promise() } // Получение списка файлов async listFiles(prefix?: string): Promise { const result = await this.s3 .listObjectsV2({ Bucket: this.bucketName, Prefix: prefix, }) .promise() return result.Contents || [] } // Копирование файла async copyFile(sourceKey: string, destinationKey: string): Promise { await this.s3 .copyObject({ Bucket: this.bucketName, CopySource: `${this.bucketName}/${sourceKey}`, Key: destinationKey, }) .promise() } } ``` ## 🔄 Integration Management ### 1. Центральный менеджер интеграций ```typescript // src/services/integration-manager.ts import { WildberriesAPI } from '@/lib/integrations/wildberries' import { OzonAPI } from '@/lib/integrations/ozon' import { SMSService } from '@/services/sms-service' import { DaDataAPI } from '@/lib/integrations/dadata' import { YandexCloudStorage } from '@/lib/integrations/yandex-storage' import { PrismaClient } from '@prisma/client' export class IntegrationManager { private wb: Map = new Map() private ozon: Map = new Map() private sms: SMSService private dadata: DaDataAPI private storage: YandexCloudStorage constructor(private prisma: PrismaClient) { // Инициализация глобальных сервисов this.sms = new SMSService(prisma, { email: process.env.SMS_AERO_EMAIL!, apiKey: process.env.SMS_AERO_API_KEY!, }) this.dadata = new DaDataAPI(process.env.DADATA_API_KEY!) this.storage = new YandexCloudStorage({ accessKeyId: process.env.YANDEX_STORAGE_ACCESS_KEY!, secretAccessKey: process.env.YANDEX_STORAGE_SECRET_KEY!, bucketName: process.env.YANDEX_STORAGE_BUCKET!, }) } // Получение Wildberries API для организации async getWildberriesAPI(organizationId: string): Promise { if (this.wb.has(organizationId)) { return this.wb.get(organizationId)! } const apiKey = await this.prisma.organizationApiKey.findFirst({ where: { organizationId, marketplace: 'WILDBERRIES', isActive: true, }, }) if (!apiKey) return null const wbApi = new WildberriesAPI(apiKey.apiKey) this.wb.set(organizationId, wbApi) return wbApi } // Получение Ozon API для организации async getOzonAPI(organizationId: string): Promise { if (this.ozon.has(organizationId)) { return this.ozon.get(organizationId)! } const apiKey = await this.prisma.organizationApiKey.findFirst({ where: { organizationId, marketplace: 'OZON', isActive: true, }, }) if (!apiKey || !apiKey.clientId) return null const ozonApi = new OzonAPI(apiKey.apiKey, apiKey.clientId) this.ozon.set(organizationId, ozonApi) return ozonApi } // Получение SMS сервиса getSMSService(): SMSService { return this.sms } // Получение DaData API getDaDataAPI(): DaDataAPI { return this.dadata } // Получение облачного хранилища getCloudStorage(): YandexCloudStorage { return this.storage } // Синхронизация данных с маркетплейсами async syncMarketplaceData(organizationId: string): Promise { const org = await this.prisma.organization.findUnique({ where: { id: organizationId }, include: { apiKeys: true }, }) if (!org) throw new Error('Organization not found') // Синхронизация с Wildberries const wbApi = await this.getWildberriesAPI(organizationId) if (wbApi) { const wbSync = new (await import('@/services/wildberries-sync')).WildberriesSync( this.prisma, wbApi, organizationId, ) await wbSync.syncProducts() await wbSync.syncStocks() await wbSync.syncOrdersAndSales(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()) } // Синхронизация с Ozon const ozonApi = await this.getOzonAPI(organizationId) if (ozonApi) { // Реализация синхронизации с Ozon } // Обновление метки последней синхронизации await this.prisma.organization.update({ where: { id: organizationId }, data: { lastSyncAt: new Date() }, }) } // Проверка состояния интеграций async checkIntegrationsHealth(organizationId: string): Promise<{ wildberries: boolean ozon: boolean sms: boolean dadata: boolean storage: boolean }> { const health = { wildberries: false, ozon: false, sms: false, dadata: false, storage: false, } // Проверка Wildberries try { const wbApi = await this.getWildberriesAPI(organizationId) if (wbApi) { await wbApi.getWarehouses() health.wildberries = true } } catch (error) { console.error('Wildberries health check failed:', error) } // Проверка Ozon try { const ozonApi = await this.getOzonAPI(organizationId) if (ozonApi) { await ozonApi.getProducts() health.ozon = true } } catch (error) { console.error('Ozon health check failed:', error) } // Проверка SMS try { await this.sms.getSMSAero().getBalance() health.sms = true } catch (error) { console.error('SMS health check failed:', error) } // Проверка DaData try { await this.dadata.suggestOrganizations('test') health.dadata = true } catch (error) { console.error('DaData health check failed:', error) } // Проверка облачного хранилища try { await this.storage.listFiles() health.storage = true } catch (error) { console.error('Storage health check failed:', error) } return health } } // Глобальный экземпляр менеджера интеграций export const integrations = new IntegrationManager(new PrismaClient()) ``` ## 📋 Configuration Management ### 1. Конфигурация интеграций ```typescript // src/config/integrations.ts export const INTEGRATION_CONFIG = { wildberries: { baseUrl: process.env.WILDBERRIES_API_URL || 'https://common-api.wildberries.ru', suppliersUrl: 'https://suppliers-api.wildberries.ru', statisticsUrl: 'https://statistics-api.wildberries.ru', rateLimit: { requests: 100, window: 60000, // 1 минута }, timeout: 30000, retries: 3, }, ozon: { baseUrl: process.env.OZON_API_URL || 'https://api-seller.ozon.ru', rateLimit: { requests: 1000, window: 60000, // 1 минута }, timeout: 30000, retries: 3, }, sms: { provider: 'SMS_AERO', baseUrl: process.env.SMS_AERO_API_URL || 'https://gate.smsaero.ru/v2', devMode: process.env.SMS_DEV_MODE === 'true', rateLimit: { perPhone: 3, window: 3600000, // 1 час }, codeExpiry: 600000, // 10 минут maxAttempts: 3, }, dadata: { baseUrl: process.env.DADATA_API_URL || 'https://suggestions.dadata.ru/suggestions/api/4_1/rs', cleanUrl: 'https://cleaner.dadata.ru/api/v1/clean', rateLimit: { requests: 10000, window: 86400000, // 1 день }, timeout: 10000, }, storage: { provider: 'YANDEX_CLOUD', endpoint: 'https://storage.yandexcloud.net', region: 'ru-central1', bucket: process.env.YANDEX_STORAGE_BUCKET || 'sfera-storage', publicUrl: `https://${process.env.YANDEX_STORAGE_BUCKET}.storage.yandexcloud.net`, maxFileSize: 10 * 1024 * 1024, // 10MB allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'application/pdf', 'text/csv'], }, analytics: { yandexMetrica: { counterId: process.env.NEXT_PUBLIC_YANDEX_METRICA_ID, enabled: process.env.NODE_ENV === 'production', }, }, } // Валидация конфигурации export function validateIntegrationConfig(): boolean { const requiredVars = [ 'SMS_AERO_EMAIL', 'SMS_AERO_API_KEY', 'DADATA_API_KEY', 'YANDEX_STORAGE_ACCESS_KEY', 'YANDEX_STORAGE_SECRET_KEY', 'YANDEX_STORAGE_BUCKET', ] const missing = requiredVars.filter((varName) => !process.env[varName]) if (missing.length > 0) { console.error('Missing required environment variables:', missing) return false } return true } ``` ## 🎯 Заключение Система внешних интеграций SFERA обеспечивает: 1. **Marketplace Integration**: Полная интеграция с Wildberries и Ozon 2. **Communication**: SMS-сервисы для аутентификации и уведомлений 3. **Data Validation**: Проверка и очистка данных через DaData 4. **Analytics**: Отслеживание пользовательского поведения 5. **File Storage**: Надежное облачное хранение файлов 6. **Centralized Management**: Единый менеджер для всех интеграций Все интеграции включают обработку ошибок, rate limiting, логирование и мониторинг для обеспечения надежности и производительности системы.