Исправление данных этикеток доставки 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 для получения деталей транзакций, включая стоимость транспортной метки. Несмотря на это, определенные заказы не заполняют значение транспортной метки правильно.
Вот мой текущий код:
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?
Я особенно заинтересован в:
- Возможных причинах, по которым данные о транспортной метке могут отсутствовать для определенных заказов
- Лучших практиках для получения информации о транспортной метке из eBay API
- Модификациях кода, которые могли бы решить эту проблему с видимостью данных
Проблема с данными транспортной метки, с которой вы столкнулись, является распространенным вызовом при работе с экосистемой API eBay. На основе анализа вашего кода и документации API eBay, существует несколько потенциальных причин, по которым значения транспортных меток не заполняются правильно, а также конкретные решения для устранения этой проблемы.
Содержание
- Распространенные причины отсутствия данных транспортной метки
- Понимание структуры транзакций транспортной метки eBay
- Модификации кода для улучшения извлечения данных
- Лучшие практики сбора данных транспортной метки
- Продвинутые методы устранения неполадок
- Полная реализация решения
Распространенные причины отсутствия данных транспортной метки
Несколько факторов могут вызывать отсутствие данных транспортной метки в вашей автоматизации:
- Несоответствия диапазонов дат: Ваша фильтрация заказов использует даты создания, но транзакции транспортных меток могут иметь временные метки, отличные от связанных с ними заказов
- Проблемы извлечения ID заказа: Функция
extractOrderIdможет неправильно идентифицировать ID заказов из транзакций транспортных меток - Ограничения фильтрации по типу транзакции: Вы ищете только транзакции типа
SHIPPING_LABEL, но могут существовать связанные типы транзакций - Логика учета бронирования: Ваш код включает только записи DEBIT, но транспортные метки могут иметь как записи DEBIT, так и CREDIT
- Границы постраничной навигации: Постраничная навигация API финансов может пропускать некоторые транзакции из-за ограничений скорости или проблем со временем
Понимание структуры транзакций транспортной метки eBay
Согласно документации API eBay, транзакции транспортных меток имеют определенные характеристики:
- Тип транзакции:
SHIPPING_LABEL - Запись бронирования: Указывает, является ли это зарядом (DEBIT) или кредитом (CREDIT) для счета продавца
- Меморандум транзакции: Содержит подробную информацию о каждой транзакции транспортной метки
- Поле суммы: Показывает денежную стоимость покупки транспортной метки
Как указано в документации API финансов eBay, “типы транзакций REFUND, DISPUTE, SHIPPING_LABEL и TRANSFER являются дебетовыми записями для счета продавца”.
Модификации кода для улучшения извлечения данных
Вот ключевые модификации, необходимые для исправления извлечения данных транспортной метки:
1. Улучшенное извлечение ID заказа
Ваша текущая функция extractOrderId требует улучшения для обработки структур транзакций транспортной метки:
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 для транспортных меток:
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. Расширенная фильтрация по типу транзакции
Рассмотрите возможность включения связанных типов транзакций, которые могут влиять на стоимость транспортных меток:
// В функции 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');
Лучшие практики сбора данных транспортной метки
-
Используйте как даты создания, так и даты транзакций: Расширьте диапазон дат, чтобы учесть временные различия между созданием заказа и покупкой транспортной метки
-
Обрабатывайте сводки транзакций: Рассмотрите возможность использования конечной точки
getTransactionSummary, как упоминается в документации eBay, для получения более широких insights по транзакциям -
Реализуйте логику повторных попыток: Добавьте механизмы повторных попыток для неудачных вызовов API для обработки ограничений скорости и временных проблем
-
Ведите журнал деталей транзакций: Добавьте подробное ведение журнала для отслеживания, какие транзакции обрабатываются и почему
-
Проверяйте связи транзакций: Сопоставляйте транзакции транспортных меток с соответствующими заказами с использованием нескольких полей ссылок
Продвинутые методы устранения неполадок
1. Отладка сопоставления транзакций
Добавьте отладку в обработку ваших транзакций:
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. Реализация корреляции транзакций
Создайте более надежную систему корреляции между заказами и транзакциями:
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());
}
Полная реализация решения
Вот полная пересмотренная версия вашего скрипта автоматизации со всеми улучшениями:
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;
}
Это улучшенное решение решает основные проблемы путем:
- Улучшения извлечения ID заказа с несколькими методами отката
- Обработки как транзакций DEBIT, так и CREDIT для транспортных меток
- Расширения покрытия типов транзакций для включения связанных типов транзакций
- Добавления подробного ведения журнала для целей отладки
- Реализации надежной обработки ошибок с логикой повторных попыток
- Создания системы корреляции, которая отслеживает обработку транзакций
Ключевое улучшение - переход от отдельных транзакций SALES и SHIPPING_LABEL к единой системе корреляции, которая обрабатывает все релевантные типы транзакций вместе, обеспечивая правильное сопоставление данных транспортной метки с соответствующими заказами.
Заключение
Проблема с видимостью данных транспортной метки, с которой вы столкнулись, обычно вызывается одним или несколькими из этих факторов: недостаточные методы извлечения ID заказа, неполная обработка типов транзакций или несоответствия диапазонов дат. Реализуя улучшенную систему корреляции и улучшенную логику обработки транзакций, описанную выше, вы должны иметь возможность точно извлекать и отображать информацию о транспортных метках для всех ваших заказов eBay в Google Sheets.
Для постоянного обслуживания рассмотрите возможность добавления периодических аудитов корреляции ваших транзакций и реализации предупреждений, когда отсутствующие данные транспортной метки превышают ожидаемые пороговые значения. Это поможет вам поддерживать точность данных по мере развития API eBay и роста вашего бизнеса.