Skip to content

Instantly share code, notes, and snippets.

@wmakeev
Last active June 2, 2022 04:01
Show Gist options
  • Save wmakeev/b8b8db87f8a03cc760204e936f2e3f24 to your computer and use it in GitHub Desktop.
Save wmakeev/b8b8db87f8a03cc760204e936f2e3f24 to your computer and use it in GitHub Desktop.
[OZON API wrapper] #ozon #api #marketplace
/*
# CHANGELOG:
- 2022-06-02 initial
*/
import type { fetch, FetchOptions } from "../tools/fetch-adapter";
export interface OzonOptions {
clientId: string;
apiKey: string;
endpoint?: string;
fetch: typeof fetch;
}
const OZON_API_ENDPOINT = "api-seller.ozon.ru";
export interface OzonCategory {
category_id: number;
title: string;
children: OzonCategory[];
}
export interface OzonCategoryTreeResponse {
result: OzonCategory[];
}
export interface OzonProductInfo {
id: number;
name: string;
offer_id: string;
barcode: string;
buybox_price: string;
category_id: number;
created_at: string;
images: string[];
marketing_price: string;
min_ozon_price: string;
old_price: string;
premium_price: string;
price: string;
recommended_price: string;
min_price: string;
sources: {
is_enabled: boolean;
sku: number;
source: "fbo" | "fbs";
}[];
stocks: {
coming: number;
present: number;
reserved: number;
};
errors: [];
vat: string;
visible: boolean;
visibility_details: {
has_price: boolean;
has_stock: boolean;
active_product: boolean;
reasons: {};
};
price_index: string;
images360: string[];
color_image: string;
primary_image: string;
status: {
state: "price_sent";
state_failed: string;
moderate_status: "approved";
decline_reasons: [];
validation_state: "success";
state_name: "Продается";
state_description: string;
is_failed: boolean;
is_created: boolean;
state_tooltip: string;
item_errors: [];
state_updated_at: string;
};
state: string;
service_type: "IS_CODE_SERVICE";
fbo_sku: number;
fbs_sku: number;
}
export const ProductVisibility = {
/** все товары. */
ALL: "ALL",
/** товары, которые видны покупателям. */
VISIBLE: "VISIBLE",
/** товары, которые по какой-то из причин не видны покупателям. */
INVISIBLE: "INVISIBLE",
/** товары, у которых не указано наличие. */
EMPTY_STOCK: "EMPTY_STOCK",
/** товары, которые не прошли модерацию. */
NOT_MODERATED: "NOT_MODERATED",
/** товары, которые прошли модерацию. */
MODERATED: "MODERATED",
/** товары, которые видны покупателям, но недоступны к покупке. */
DISABLED: "DISABLED",
/** товары, создание которых завершилось ошибĸой. */
STATE_FAILED: "STATE_FAILED",
/** товары, готовые к поставке. */
READY_TO_SUPPLY: "READY_TO_SUPPLY",
/** товары, которые проходят проверку на премодерации (валидатором). */
VALIDATION_STATE_PENDING: "VALIDATION_STATE_PENDING",
/** товары, которые не прошли проверку на премодерации (валидатором). */
VALIDATION_STATE_FAIL: "VALIDATION_STATE_FAIL",
/** товары, которые прошли проверку на премодерации (валидатором). */
VALIDATION_STATE_SUCCESS: "VALIDATION_STATE_SUCCESS",
/** товары, готовые к продаже. */
TO_SUPPLY: "TO_SUPPLY",
/** товары в продаже. */
IN_SALE: "IN_SALE",
/** товары, скрытые от покупателей. */
REMOVED_FROM_SALE: "REMOVED_FROM_SALE",
/** заблокированные товары. */
BANNED: "BANNED",
/** товары с завышенной ценой. */
OVERPRICED: "OVERPRICED",
/** товары со слишком завышенной ценой. */
CRITICALLY_OVERPRICED: "CRITICALLY_OVERPRICED",
/** товары без штрихкода. */
EMPTY_BARCODE: "EMPTY_BARCODE",
/** товары со штрихкодом. */
BARCODE_EXISTS: "BARCODE_EXISTS",
/** товары на карантине после изменения цены более чем на 50%. */
QUARANTINE: "QUARANTINE",
/** товары в архиве. */
ARCHIVED: "ARCHIVED",
/** товары в продаже со стоимостью выше, чем у конкурентов. */
OVERPRICED_WITH_STOCK: "OVERPRICED_WITH_STOCK",
/** товары в продаже с пустым или неполным описанием. */
PARTIAL_APPROVED: "PARTIAL_APPROVED",
/** товары без изображений. */
IMAGE_ABSENT: "IMAGE_ABSENT",
/** товары, для которых заблокирована модерация. */
MODERATION_BLOCK: "MODERATION_BLOCK",
};
export interface ProductV2GetProductListResponseItem {
offer_id: string;
product_id: number;
}
export interface ProductV2GetProductListResponseResult {
items: ProductV2GetProductListResponseItem[];
last_id: string;
total: number;
}
export const CategoryAttributesRequestAttributeType = {
ALL: "ALL",
REQUIRED: "REQUIRED",
OPTIONAL: "OPTIONAL",
};
export const Language = {
DEFAULT: "DEFAULT",
RU: "RU",
EN: "EN",
};
export interface Attribute {
/** Идентификатор справочника */
id: number;
/** Название */
name: string;
/** Тип характеристики */
type: string;
/** Описание характеристики. */
description: string;
/** Признак, что характеристика — набор значений */
is_collection: boolean;
/** Признак обязательной характеристики */
is_required: boolean;
dictionary_id: number;
/** Идентификатор группы характеристик */
group_id: number;
/** Название группы характеристик */
group_name: string;
}
export interface CategoryAttributes {
category_id: number;
attributes: Attribute[];
}
export interface CategoryAttributesResponse {
result: CategoryAttributes[];
}
export interface DictionaryValue {
id: number;
info: string;
picture: string;
value: string;
}
export class Ozon {
constructor(private options: OzonOptions) {}
async fetchApi(
method: FetchOptions["method"],
path: string,
// params: Record<string, string>,
body: FetchOptions["body"]
): Promise<unknown> {
const { apiKey, clientId, fetch, endpoint } = this.options;
const safePath = path[0] !== "/" ? `/${path}` : path;
const url = `https://${endpoint ?? OZON_API_ENDPOINT}${safePath}`;
const resp = await fetch(url, {
method,
headers: {
"Client-Id": clientId,
"Api-Key": apiKey,
},
body,
});
const data = await resp.json();
if (resp.status !== 200) {
throw new Error(data.message || `Ошибка запроса ${resp.status}`);
}
return data;
}
async POST(path: string, body: FetchOptions["body"]) {
return await this.fetchApi("POST", path, body);
}
async categoryTreeV1(params?: { language: "RU" }) {
const resp = (await this.POST(
"/v1/category/tree",
JSON.stringify(
params ?? {
language: "RU",
}
)
)) as OzonCategoryTreeResponse;
return resp;
}
/**
* Список товаров
* @link https://docs.ozon.ru/api/seller/#operation/ProductAPI_GetProductList
*/
async getProductListV2(params: {
filter?: {
offer_id?: string[];
product_id?: number[];
visibility?: keyof typeof ProductVisibility;
};
last_id?: string;
limit?: number;
}) {
const resp = (await this.POST(
"/v2/product/list",
JSON.stringify(params)
)) as {
result: ProductV2GetProductListResponseResult;
};
return resp.result;
}
/**
* Получить список товаров по идентификаторам
* @link https://docs.ozon.ru/api/seller/#operation/ProductAPI_GetProductInfoListV2
*/
async getProductInfoListV2(params?: {
offer_id?: string[];
product_id?: number[];
sku?: number[];
}) {
const resp = (await this.POST(
"/v2/product/info/list",
JSON.stringify(params)
)) as {
result: {
items: OzonProductInfo[];
};
};
return resp.result;
}
async getCategoryAttributesV3(params?: {
/** default: `ALL` */
attribute_type?: keyof typeof CategoryAttributesRequestAttributeType;
category_id?: number[];
language?: keyof typeof Language;
}) {
const resp = (await this.POST(
"/v3/category/attribute",
JSON.stringify(params)
)) as CategoryAttributesResponse;
return resp.result;
}
async getCategoryAttributeValuesV2(params?: {
attribute_id: number;
category_id: number;
language?: keyof typeof Language;
last_value_id?: number;
limit?: number;
}) {
const resp = (await this.POST(
"/v2/category/attribute/values",
JSON.stringify(params)
)) as {
has_next: boolean;
result: DictionaryValue[];
};
return resp;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment