Типовий FulfillmentProvider у Medusa.js — manual. Він лише створює записи в базі, не вміючи розраховувати тарифи, створювати відправлення або відстежувати статуси. Це призводить до ручної роботи операторів, помилок у вартості та затримок. Ми вирішуємо цю проблему кастомними провайдерами, які автоматизують весь цикл доставки.
Наприклад, при інтеграції з DHL Express ми реалізували динамічний розрахунок вартості за вагою та зоною доставки, створення накладної та отримання трек-номера через API. Обробка замовлення прискорилася в 10 разів порівняно з manual провайдером. Економія на операційних витратах — 30–50% у середньому, а повернення обробляються повністю автоматично, знижуючи витрати. Згідно з документацією Medusa.js, розробка кастомного провайдера рекомендується для проектів з нестандартною логістикою.
Чому варто кастомізувати FulfillmentProvider?
Manual провайдер підходить лише для тестових проектів. У production він не витримує навантаження: відсутність розрахунку тарифів веде до недоотримання прибутку, а відсутність трекінгу — до втрачених посилок. Кастомний провайдер обробляє 100% сценаріїв, включаючи повернення та часткові скасування. Зниження витрат на повернення досягає 50%.
Проблеми, які вирішує інтеграція
- Несумісність одиниць: API перевізника може вимагати вагу в кг, а Medusa зберігає в грамах. Адаптатор конвертує дані автоматично.
- Відсутність webhook'ів: багато регіональних служб не надсилають сповіщення — ми реалізуємо polling з частотою 5 хвилин.
- Підтримка двох версій Medusa: v1 та v2 використовують різну архітектуру. Ми пишемо провайдер із загальним ядром та плагінами для кожної версії.
- Обробка повернень: створюємо кастомні обробники для cancelFulfillment та сповіщаємо клієнта по email.
- Множинні перевізники: для глобальної логістики провайдер може перемикатися між DHL, FedEx та локальними службами залежно від регіону.
50% інтеграцій стикаються з несумісністю одиниць, 30% — з відсутністю вебхуків, 20% — з підтримкою двох версій. Ми вирішуємо кожну задачу через адаптери, fallback polling та модульну архітектуру.
Як автоматизація повернень знижує операційні витрати?
Повернення — один із найдорожчих етапів в e-commerce. Ручна обробка потребує часу, а помилки призводять до повторних відправок. Кастомний провайдер автоматично створює заявку на повернення, генерує транспортну накладну та повідомляє всіх учасників. Це скорочує час обробки з годин до хвилин — у 24 рази швидше за ручний процес. У результаті операційні витрати на повернення падають на 30–50%, а середня економія на кожному поверненні становить до 500 грн за рахунок автоматизації.
Як ми це робимо
Використовуємо TypeScript, Medusa.js (v1 та v2), Axios для HTTP, Express для webhook'ів. Базовий клас провайдера:
import { AbstractFulfillmentService } from '@medusajs/medusa';
class CustomFulfillmentService extends AbstractFulfillmentService {
static identifier = 'custom-courier';
async getFulfillmentOptions() {
return [
{ id: 'standard', name: 'Стандарт' },
{ id: 'express', name: 'Експрес' },
];
}
async calculatePrice(optionData, data, cart) {
const weight = cart.items.reduce((sum, item) => sum + (item.variant?.weight ?? 100) * item.quantity, 0);
return await this.apiClient.getRate(optionData.id, weight, cart.shipping_address.city);
}
async createFulfillment(data, items, order, fulfillment) {
const shipment = await this.apiClient.createShipment({
service: data.id,
recipient: order.shipping_address,
items: items.map(i => ({ sku: i.variant?.sku, qty: i.quantity })),
order_ref: order.display_id.toString(),
});
return { tracking_number: shipment.tracking, shipment_id: shipment.id };
}
async cancelFulfillment(fulfillment) {
await this.apiClient.cancelShipment(fulfillment.data.shipment_id);
return {};
}
}
export default CustomFulfillmentService;
HTTP-клієнт для API перевізника інкапсулює запити, обробку помилок і трансформацію даних:
import axios from 'axios';
class CourierApiClient {
private client;
constructor(apiKey: string) {
this.client = axios.create({
baseURL: 'https://api.courier.ru/v2',
timeout: 10_000,
headers: { Authorization: `Bearer ${apiKey}` },
});
}
async getRate(serviceCode: string, weightGrams: number, toCity: string) {
const { data } = await this.client.post('/calculate', {
service: serviceCode,
weight: Math.max(0.1, weightGrams / 1000),
to_city: toCity,
});
return Math.round(data.price * 100); // в копійках
}
async createShipment(payload) {
const { data } = await this.client.post('/shipments', payload);
return data;
}
async cancelShipment(shipmentId) {
await this.client.delete(`/shipments/${shipmentId}`);
}
}
Webhook-роут обробляє події від перевізника та оновлює статус замовлення через EventBus:
import { Router } from 'express';
const router = Router();
router.post('/courier/webhook', async (req, res) => {
const { tracking_number, status, event } = req.body;
const fulfillmentRepo = req.scope.resolve('fulfillmentRepository');
const fulfillment = await fulfillmentRepo.findOne({ where: { data: { tracking_number } } });
if (!fulfillment) return res.sendStatus(404);
const eventBus = req.scope.resolve('eventBusService');
await eventBus.emit('fulfillment.tracking_updated', { fulfillment_id: fulfillment.id, tracking_number, status });
res.sendStatus(200);
});
export default router;
Поетапний процес створення кастомного провайдера:
- Розібрати API перевізника і скласти схему запитів.
- Створити клас-клієнт з методами для кожного endpoint.
- Реалізувати AbstractFulfillmentService з перевизначенням ключових методів.
- Налаштувати webhook-роути для отримання подій.
- Написати unit-тести і протестувати інтеграцію на staging.
Приклад архітектури провайдера
Архітектура включає три шари: шар інтеграції (API клієнт), шар бізнес-логіки (сервіс), шар представлення (webhook роути). Кожен шар тестується окремо, а інтеграційні тести покривають усі сценарії роботи з перевізником.Для production ми додаємо моніторинг через Grafana та алерти в Telegram при падінні API перевізника або помилках конвертації даних.
Порівняння: manual vs кастомний провайдер
| Параметр | Manual провайдер | Кастомний провайдер |
|---|---|---|
| Розрахунок тарифів | Тільки фіксована ціна | Динамічний розрахунок по API перевізника |
| Створення відправлення | Вручну в адмінці | Автоматично при замовленні |
| Відстеження | Немає | Webhook + оновлення статусів |
| Повернення | Немає | Повна підтримка скасування |
Етапи та терміни реалізації
| Етап | Опис | Тривалість |
|---|---|---|
| Аналіз API | Вивчення документації, тестування endpoints | 0,5 дня |
| Проєктування | Схема класів, обробка помилок, підтримка v1/v2 | 1 день |
| Реалізація | Пакет з unit-тестами та інтеграційними тестами | 2–3 дні |
| Тестування | На staging з реальними замовленнями | 1 день |
| Деплой та моніторинг | Розгортання в production, алерти | 0,5 дня |
- Базова інтеграція (один перевізник, без повернень): 3–5 днів.
- Додавання webhook та трекінгу: +1–2 дні.
- Повноцінний npm-пакет з підтримкою Medusa v1 та v2: 5–7 днів.
Що входить у роботу
- Вихідний код провайдера (TypeScript).
- Конфігурація для
medusa-config.js. - Документація по встановленню та налаштуванню.
- Навчання команди (1 година).
- Гарантійна підтримка 1 місяць.
Замовте інтеграцію, і ми автоматизуємо вашу логістику за тиждень. Отримайте консультацію інженера та точну оцінку вартості. Зв'яжіться з нами для безкоштовного аудиту вашого проекту — ми оцінимо складність та запропонуємо оптимальне рішення.







