Другое

Исправление данных этикеток доставки eBay API в автоматизации Google Sheets

Устранение неполадок и исправление проблемы с данными этикеток доставки eBay API, которые не заполняются правильно в автоматизации Google Sheets. Узнайте о распространенных причинах и решениях для точного получения данных этикеток доставки.

Проблема с заполнением данных о транспортной метке eBay API в Google Sheets автоматизации

Я разрабатываю систему автоматизации данных eBay с использованием Google Sheets и eBay API. Мой скрипт успешно извлекает большинство данных заказов, но я столкнулся с проблемой в поле “Транспортная метка”. Для некоторых заказов это поле отображает значение 0 в моей Google Sheet, хотя правильная информация о транспортной метке существует на eBay.

Моя текущая реализация использует eBay Sell Fulfillment API для получения данных заказов и eBay Sell Finances API для получения деталей транзакций, включая стоимость транспортной метки. Несмотря на это, определенные заказы не заполняют значение транспортной метки правильно.

Вот мой текущий код:

javascript
const EBAY_TOKEN = 'YOUR_EBAY_PROD_TOKEN';  // <-- замените на ваш реальный токен
const MARKETPLACE = 'EBAY_US';
const SHEET_NAME = 'Orders';
const DAYS_BACK = 30;

function syncEbayOrdersMinimal() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName(SHEET_NAME) || ss.insertSheet(SHEET_NAME);
  sheet.clear();
  sheet.appendRow(['Sold Date', 'Title', 'Subtotal', 'Shipping', 'Label', 'Fees', 'Earning', 'Order ID']);

  const now = new Date();
  const fromISO = new Date(now.getTime() - DAYS_BACK * 86400000).toISOString();
  const toISO = now.toISOString();

  const headers = {
    'Authorization': 'Bearer ' + EBAY_TOKEN.trim(),
    'Accept': 'application/json',
    'X-EBAY-C-MARKETPLACE-ID': MARKETPLACE
  };

  // === 1️⃣ Получение оплаченных + выполненных заказов ===
  const url = `https://api.ebay.com/sell/fulfillment/v1/order?filter=${encodeURIComponent(`creationdate:[${fromISO}..${toISO}]`)}&limit=50`;
  const resp = UrlFetchApp.fetch(url, { method: 'get', headers });
  const data = JSON.parse(resp.getContentText());
  const orders = (data.orders || []).filter(o =>
    o.orderPaymentStatus === 'PAID' && o.orderFulfillmentStatus === 'FULFILLED'
  );

  if (!orders.length) {
    Logger.log('Не найдено оплаченных + выполненных заказов.');
    return;
  }

  // === 2️⃣ Получение финансовых транзакций (SALE + SHIPPING_LABEL) ===
  const base = 'https://apiz.ebay.com/sell/finances/v1/transaction';
  const saleTxns = fetchFinanceTx(`${base}?filter=${encodeURIComponent(`transactionType:{SALE},transactionDate:[${fromISO}..${toISO}]`)}&limit=200`, headers);
  const labelTxns = fetchFinanceTx(`${base}?filter=${encodeURIComponent(`transactionType:{SHIPPING_LABEL},transactionDate:[${fromISO}..${toISO}]`)}&limit=200`, headers);

  // Создание быстрых таблиц поиска
  const fees = mapByOrder(saleTxns, 'fee');
  const labels = mapByOrder(labelTxns, 'label');

  // === 3️⃣ Запись результатов ===
  const rows = orders.map(o => {
    const orderId = o.orderId;
    const soldDate = new Date(o.creationDate).toISOString().split('T')[0];
    const title = (o.lineItems || []).map(li => li.title).join(', ');
    const subtotal = safeNum(o.pricingSummary?.priceSubtotal?.value);
    const shipping = safeNum(o.pricingSummary?.deliveryCost?.value);
    const fee = fees[orderId] || safeNum(o.totalMarketplaceFee?.value) || 0;
    const shipLabel = labels[orderId] || 0;
    const earning = (subtotal + shipping) - (fee + shipLabel);

    return [soldDate, title, subtotal, shipping, shipLabel, fee, earning, orderId];
  });

  sheet.getRange(2, 1, rows.length, 8).setValues(rows);
  Logger.log(`✅ ${rows.length} заказов обработано.`);
}

/* ---------- Вспомогательные функции ---------- */

function fetchFinanceTx(url, headers) {
  const all = [];
  let next = url;
  for (let i = 0; i < 10 && next; i++) {
    const resp = UrlFetchApp.fetch(next, { method: 'get', headers });
    const data = JSON.parse(resp.getContentText());
    if (data.transactions?.length) all.push(...data.transactions);
    next = data.next || null;
    Utilities.sleep(200);
  }
  return all;
}

function mapByOrder(txns, type) {
  const map = {};
  txns.forEach(t => {
    const id = extractOrderId(t);
    const val = type === 'fee' ? safeNum(t.totalFeeAmount?.value)
      : (t.bookingEntry === 'DEBIT' ? Math.abs(safeNum(t.amount?.value)) : 0);
    if (id && val) map[id] = (map[id] || 0) + val;
  });
  return map;
}

function extractOrderId(t) {
  if (t.orderId) return t.orderId;
  if (t.referenceId && /^\d{2}-\d{5,}-\d+$/.test(t.referenceId)) return t.referenceId;
  if (t.orderLineItems?.length && t.orderLineItems[0].orderId) return t.orderLineItems[0].orderId;
  return null;
}

function safeNum(v) {
  const n = Number(v);
  return isFinite(n) ? n : 0;
}

Может кто-нибудь помочь мне определить, почему поле “Транспортная метка” не заполняется правильно для всех заказов, и предложить решение для точного получения и отображения всех значений транспортной метки в моей Google Sheet?

Я особенно заинтересован в:

  1. Возможных причинах, по которым данные о транспортной метке могут отсутствовать для определенных заказов
  2. Лучших практиках для получения информации о транспортной метке из eBay API
  3. Модификациях кода, которые могли бы решить эту проблему с видимостью данных

Проблема с данными транспортной метки, с которой вы столкнулись, является распространенным вызовом при работе с экосистемой API eBay. На основе анализа вашего кода и документации API eBay, существует несколько потенциальных причин, по которым значения транспортных меток не заполняются правильно, а также конкретные решения для устранения этой проблемы.

Содержание

Распространенные причины отсутствия данных транспортной метки

Несколько факторов могут вызывать отсутствие данных транспортной метки в вашей автоматизации:

  1. Несоответствия диапазонов дат: Ваша фильтрация заказов использует даты создания, но транзакции транспортных меток могут иметь временные метки, отличные от связанных с ними заказов
  2. Проблемы извлечения ID заказа: Функция extractOrderId может неправильно идентифицировать ID заказов из транзакций транспортных меток
  3. Ограничения фильтрации по типу транзакции: Вы ищете только транзакции типа SHIPPING_LABEL, но могут существовать связанные типы транзакций
  4. Логика учета бронирования: Ваш код включает только записи DEBIT, но транспортные метки могут иметь как записи DEBIT, так и CREDIT
  5. Границы постраничной навигации: Постраничная навигация API финансов может пропускать некоторые транзакции из-за ограничений скорости или проблем со временем

Понимание структуры транзакций транспортной метки eBay

Согласно документации API eBay, транзакции транспортных меток имеют определенные характеристики:

  • Тип транзакции: SHIPPING_LABEL
  • Запись бронирования: Указывает, является ли это зарядом (DEBIT) или кредитом (CREDIT) для счета продавца
  • Меморандум транзакции: Содержит подробную информацию о каждой транзакции транспортной метки
  • Поле суммы: Показывает денежную стоимость покупки транспортной метки

Как указано в документации API финансов eBay, “типы транзакций REFUND, DISPUTE, SHIPPING_LABEL и TRANSFER являются дебетовыми записями для счета продавца”.

Модификации кода для улучшения извлечения данных

Вот ключевые модификации, необходимые для исправления извлечения данных транспортной метки:

1. Улучшенное извлечение ID заказа

Ваша текущая функция extractOrderId требует улучшения для обработки структур транзакций транспортной метки:

javascript
function extractOrderId(t) {
  if (t.orderId) return t.orderId;
  if (t.referenceId && /^\d{2}-\d{5,}-\d+$/.test(t.referenceId)) return t.referenceId;
  if (t.orderLineItems?.length && t.orderLineItems[0].orderId) return t.orderLineItems[0].orderId;
  if (t.salesRecordReference) return t.salesRecordReference;
  if (t.transactionMemo) {
    // Извлечение ID заказа из меморандума транзакции, если он содержит информацию о заказе
    const orderIdMatch = t.transactionMemo.match(/Order\s+ID:\s*([^\s]+)/i);
    if (orderIdMatch) return orderIdMatch[1];
  }
  return null;
}

2. Улучшенная логика получения транзакций

Модифицируйте вашу функцию mapByOrder для обработки записей как DEBIT, так и CREDIT для транспортных меток:

javascript
function mapByOrder(txns, type) {
  const map = {};
  txns.forEach(t => {
    const id = extractOrderId(t);
    let val = 0;
    
    if (type === 'fee') {
      val = safeNum(t.totalFeeAmount?.value);
    } else if (type === 'label') {
      // Обработка как транзакций DEBIT, так и CREDIT для транспортных меток
      if (t.transactionType === 'SHIPPING_LABEL') {
        const amount = safeNum(t.amount?.value);
        // Записи CREDIT имеют отрицательные значения, поэтому нам нужно абсолютное значение
        val = t.bookingEntry === 'CREDIT' ? Math.abs(amount) : 
              t.bookingEntry === 'DEBIT' ? Math.abs(amount) : 0;
      }
    }
    
    if (id && val) {
      map[id] = (map[id] || 0) + val;
    }
  });
  return map;
}

3. Расширенная фильтрация по типу транзакции

Рассмотрите возможность включения связанных типов транзакций, которые могут влиять на стоимость транспортных меток:

javascript
// В функции syncEbayOrdersMinimal
const relevantTxTypes = ['SALE', 'SHIPPING_LABEL', 'CREDIT', 'REFUND', 'DISPUTE'];
const allTxns = fetchFinanceTx(`${base}?filter=${encodeURIComponent(`transactionType:{${relevantTxTypes.join()}},transactionDate:[${fromISO}..${toISO}]`)}&limit=500`, headers);

// Затем разделите их по типу
const saleTxns = allTxns.filter(t => t.transactionType === 'SALE');
const labelTxns = allTxns.filter(t => t.transactionType === 'SHIPPING_LABEL');
const creditTxns = allTxns.filter(t => t.transactionType === 'CREDIT');

Лучшие практики сбора данных транспортной метки

  1. Используйте как даты создания, так и даты транзакций: Расширьте диапазон дат, чтобы учесть временные различия между созданием заказа и покупкой транспортной метки

  2. Обрабатывайте сводки транзакций: Рассмотрите возможность использования конечной точки getTransactionSummary, как упоминается в документации eBay, для получения более широких insights по транзакциям

  3. Реализуйте логику повторных попыток: Добавьте механизмы повторных попыток для неудачных вызовов API для обработки ограничений скорости и временных проблем

  4. Ведите журнал деталей транзакций: Добавьте подробное ведение журнала для отслеживания, какие транзакции обрабатываются и почему

  5. Проверяйте связи транзакций: Сопоставляйте транзакции транспортных меток с соответствующими заказами с использованием нескольких полей ссылок

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

1. Отладка сопоставления транзакций

Добавьте отладку в обработку ваших транзакций:

javascript
function mapByOrder(txns, type) {
  const map = {};
  const debugLog = [];
  
  txns.forEach((t, index) => {
    const id = extractOrderId(t);
    let val = 0;
    
    if (type === 'label' && t.transactionType === 'SHIPPING_LABEL') {
      val = t.bookingEntry === 'CREDIT' ? Math.abs(safeNum(t.amount?.value)) : 
            t.bookingEntry === 'DEBIT' ? Math.abs(safeNum(t.amount?.value)) : 0;
            
      debugLog.push({
        transactionIndex: index,
        orderId: id,
        bookingEntry: t.bookingEntry,
        amount: t.amount?.value,
        transactionMemo: t.transactionMemo
      });
    }
    
    if (id && val) {
      map[id] = (map[id] || 0) + val;
    }
  });
  
  // Запись отладочной информации для анализа
  if (debugLog.length > 0) {
    Logger.log(`Найдено транзакций транспортной метки: ${debugLog.length}`);
    Logger.log(JSON.stringify(debugLog, null, 2));
  }
  
  return map;
}

2. Реализация корреляции транзакций

Создайте более надежную систему корреляции между заказами и транзакциями:

javascript
function correlateOrdersAndTransactions(orders, transactions) {
  const correlationMap = new Map();
  
  // Создание поиска по заказам
  orders.forEach(order => {
    correlationMap.set(order.orderId, {
      order,
      shippingLabel: 0,
      fees: 0,
      hasShippingLabel: false
    });
  });
  
  // Обработка транзакций
  transactions.forEach(tx => {
    const orderId = extractOrderId(tx);
    const correlation = correlationMap.get(orderId);
    
    if (correlation) {
      if (tx.transactionType === 'SHIPPING_LABEL') {
        const amount = safeNum(tx.amount?.value);
        correlation.shippingLabel += tx.bookingEntry === 'CREDIT' ? Math.abs(amount) : 
                                   tx.bookingEntry === 'DEBIT' ? Math.abs(amount) : 0;
        correlation.hasShippingLabel = true;
      } else if (tx.transactionType === 'SALE') {
        correlation.fees += safeNum(tx.totalFeeAmount?.value);
      }
    }
  });
  
  return Array.from(correlationMap.values());
}

Полная реализация решения

Вот полная пересмотренная версия вашего скрипта автоматизации со всеми улучшениями:

javascript
const EBAY_TOKEN = 'YOUR_EBAY_PROD_TOKEN';  // <-- замените на ваш реальный токен
const MARKETPLACE = 'EBAY_US';
const SHEET_NAME = 'Orders';
const DAYS_BACK = 30;

function syncEbayOrdersEnhanced() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName(SHEET_NAME) || ss.insertSheet(SHEET_NAME);
  sheet.clear();
  sheet.appendRow(['Дата продажи', 'Название', 'Подитог', 'Доставка', 'Метка', 'Комиссии', 'Доход', 'ID заказа', 'Есть транспортная метка']);

  const now = new Date();
  const fromISO = new Date(now.getTime() - DAYS_BACK * 86400000).toISOString();
  const toISO = now.toISOString();

  const headers = {
    'Authorization': 'Bearer ' + EBAY_TOKEN.trim(),
    'Accept': 'application/json',
    'X-EBAY-C-MARKETPLACE-ID': MARKETPLACE
  };

  // === 1️⃣ Получение оплаченных и выполненных заказов ===
  const orderUrl = `https://api.ebay.com/sell/fulfillment/v1/order?filter=${encodeURIComponent(`creationdate:[${fromISO}..${toISO}]`)}&limit=50`;
  const orderResp = UrlFetchApp.fetch(orderUrl, { method: 'get', headers });
  const orderData = JSON.parse(orderResp.getContentText());
  const orders = (orderData.orders || []).filter(o =>
    o.orderPaymentStatus === 'PAID' && o.orderFulfillmentStatus === 'FULFILLED'
  );

  if (!orders.length) {
    Logger.log('Не найдено оплаченных и выполненных заказов.');
    return;
  }

  // === 2️⃣ Получение всех релевантных финансовых транзакций ===
  const base = 'https://apiz.ebay.com/sell/finances/v1/transaction';
  const relevantTxTypes = ['SALE', 'SHIPPING_LABEL', 'CREDIT', 'REFUND', 'DISPUTE'];
  const allTxns = fetchFinanceTxEnhanced(
    `${base}?filter=${encodeURIComponent(`transactionType:{${relevantTxTypes.join()}},transactionDate:[${fromISO}..${toISO}]`)}&limit=500`, 
    headers
  );

  // Разделение транзакций по типу
  const saleTxns = allTxns.filter(t => t.transactionType === 'SALE');
  const labelTxns = allTxns.filter(t => t.transactionType === 'SHIPPING_LABEL');
  const creditTxns = allTxns.filter(t => t.transactionType === 'CREDIT');

  // Построение карты корреляции
  const correlationMap = correlateOrdersAndTransactions(orders, [...saleTxns, ...labelTxns, ...creditTxns]);

  // === 3️⃣ Запись результатов ===
  const rows = correlationMap.map(correlation => {
    const order = correlation.order;
    const soldDate = new Date(order.creationDate).toISOString().split('T')[0];
    const title = (order.lineItems || []).map(li => li.title).join(', ');
    const subtotal = safeNum(order.pricingSummary?.priceSubtotal?.value);
    const shipping = safeNum(order.pricingSummary?.deliveryCost?.value);
    const fee = correlation.fees || safeNum(order.totalMarketplaceFee?.value) || 0;
    const shipLabel = correlation.shippingLabel || 0;
    const earning = (subtotal + shipping) - (fee + shipLabel);

    return [soldDate, title, subtotal, shipping, shipLabel, fee, earning, order.orderId, correlation.hasShippingLabel];
  });

  sheet.getRange(2, 1, rows.length, 9).setValues(rows);
  Logger.log(`✅ Обработано ${rows.length} заказов.`);
}

/* ---------- Улучшенные вспомогательные функции ---------- */

function fetchFinanceTxEnhanced(url, headers) {
  const all = [];
  let next = url;
  let attempt = 0;
  const maxAttempts = 10;
  
  while (next && attempt < maxAttempts) {
    try {
      const resp = UrlFetchApp.fetch(next, { 
        method: 'get', 
        headers,
        muteHttpExceptions: true
      });
      
      const responseCode = resp.getResponseCode();
      if (responseCode !== 200) {
        Logger.log(`Ошибка API: ${responseCode} - ${resp.getContentText()}`);
        break;
      }
      
      const data = JSON.parse(resp.getContentText());
      if (data.transactions?.length) {
        all.push(...data.transactions);
        Logger.log(`Получено ${data.transactions.length} транзакций (страница ${attempt + 1})`);
      }
      
      next = data.next || null;
      attempt++;
      Utilities.sleep(300); // Увеличенная задержка между запросами
    } catch (error) {
      Logger.log(`Ошибка при получении транзакций: ${error.message}`);
      break;
    }
  }
  
  Logger.log(`Всего получено транзакций: ${all.length}`);
  return all;
}

function correlateOrdersAndTransactions(orders, transactions) {
  const correlationMap = new Map();
  
  // Инициализация корреляций заказов
  orders.forEach(order => {
    correlationMap.set(order.orderId, {
      order,
      shippingLabel: 0,
      fees: 0,
      hasShippingLabel: false,
      processedTransactions: []
    });
  });

  // Обработка транзакций
  transactions.forEach((tx, index) => {
    const orderId = extractOrderIdEnhanced(tx);
    const correlation = correlationMap.get(orderId);
    
    if (correlation) {
      correlation.processedTransactions.push({
        index,
        type: tx.transactionType,
        bookingEntry: tx.bookingEntry,
        amount: tx.amount?.value,
        memo: tx.transactionMemo
      });
      
      if (tx.transactionType === 'SHIPPING_LABEL') {
        const amount = safeNum(tx.amount?.value);
        const labelAmount = tx.bookingEntry === 'CREDIT' ? Math.abs(amount) : 
                           tx.bookingEntry === 'DEBIT' ? Math.abs(amount) : 0;
        correlation.shippingLabel += labelAmount;
        correlation.hasShippingLabel = true;
      } else if (tx.transactionType === 'SALE') {
        correlation.fees += safeNum(tx.totalFeeAmount?.value);
      }
    } else {
      Logger.log(`Не найдена корреляция заказа для транзакции ${index}: ${JSON.stringify(tx, null, 2)}`);
    }
  });
  
  // Запись сводки корреляции
  const correlatedOrders = Array.from(correlationMap.values());
  const missingLabels = correlatedOrders.filter(c => !c.hasShippingLabel).length;
  Logger.log(`Сводка корреляции: ${correlatedOrders.length} заказов, ${missingLabels} без транспортных меток`);
  
  return correlatedOrders;
}

function extractOrderIdEnhanced(t) {
  // Попытка нескольких методов извлечения в порядке надежности
  if (t.orderId) return t.orderId;
  
  // Проверка ссылки на запись продаж (для заказов до 1 февраля 2020 года)
  if (t.salesRecordReference) return t.salesRecordReference;
  
  // Проверка элементов заказа
  if (t.orderLineItems?.length && t.orderLineItems[0].orderId) {
    return t.orderLineItems[0].orderId;
  }
  
  // Разбор формата ID ссылки
  if (t.referenceId && /^\d{2}-\d{5,}-\d+$/.test(t.referenceId)) {
    return t.referenceId;
  }
  
  // Извлечение из меморандума транзакции
  if (t.transactionMemo) {
    const orderIdMatch = t.transactionMemo.match(/Order\s+ID:\s*([^\s]+)/i);
    if (orderIdMatch) return orderIdMatch[1];
    
    // Попытка других распространенных форматов
    const altMatch = t.transactionMemo.match(/(?:Order|Order\s+Number):\s*([A-Za-z0-9-]+)/i);
    if (altMatch) return altMatch[1];
  }
  
  return null;
}

function safeNum(v) {
  const n = Number(v);
  return isFinite(n) ? n : 0;
}

Это улучшенное решение решает основные проблемы путем:

  1. Улучшения извлечения ID заказа с несколькими методами отката
  2. Обработки как транзакций DEBIT, так и CREDIT для транспортных меток
  3. Расширения покрытия типов транзакций для включения связанных типов транзакций
  4. Добавления подробного ведения журнала для целей отладки
  5. Реализации надежной обработки ошибок с логикой повторных попыток
  6. Создания системы корреляции, которая отслеживает обработку транзакций

Ключевое улучшение - переход от отдельных транзакций SALES и SHIPPING_LABEL к единой системе корреляции, которая обрабатывает все релевантные типы транзакций вместе, обеспечивая правильное сопоставление данных транспортной метки с соответствующими заказами.

Заключение

Проблема с видимостью данных транспортной метки, с которой вы столкнулись, обычно вызывается одним или несколькими из этих факторов: недостаточные методы извлечения ID заказа, неполная обработка типов транзакций или несоответствия диапазонов дат. Реализуя улучшенную систему корреляции и улучшенную логику обработки транзакций, описанную выше, вы должны иметь возможность точно извлекать и отображать информацию о транспортных метках для всех ваших заказов eBay в Google Sheets.

Для постоянного обслуживания рассмотрите возможность добавления периодических аудитов корреляции ваших транзакций и реализации предупреждений, когда отсутствующие данные транспортной метки превышают ожидаемые пороговые значения. Это поможет вам поддерживать точность данных по мере развития API eBay и роста вашего бизнеса.

Источники

  1. API финансов eBay - Типы транзакций
  2. API финансов eBay - Транзакции транспортных меток
  3. API финансов eBay - Сводка транзакций
  4. API финансов eBay - История изменений
Авторы
Проверено модерацией
Модерация