Command Palette

Search for a command to run...

Миграция провайдера на модуль интеграций

Сменить токен или пароль у провайдера значит изменять код и деплоить заново, если передавать эти опции через . Модуль интеграций убирает этот воркфлоу: настройки переезжают в Medusa Admin, секреты шифруются, провайдер получает готовый конфиг без единой строки кода.

В этом гайде вы узнаете, как перенести существующий провайдер на на примере реальной миграции провайдера ApiShip.


Что такое модуль интеграций

Модуль интеграций позволяет любому плагину описывать свои опции и даёт администраторам магазина настраивать их как интеграции прямо в админке, без правок и передеплоя. Он генерирует admin CRUD API и валидацию, поэтому вам не нужно писать модели данных, роуты или формы.

Дескриптор подробно разобран в разделе Концепции. Здесь рассказано только то, что нужно для переноса существующего провайдера.

Миграция

До миграции настройки ApiShip хранились в : токен, подключения к курьерским службам, адрес отправителя по умолчанию. Эти данные обслуживал собственный Admin UI, на каждую CRUD-операцию был отдельный API-роут. Модуль интеграций сводит всё это в один раздел НастройкиИнтеграции.

Шаг 1: Опишите дескриптор

Рядом с существующим провайдером доставки создайте новый провайдер интеграции: . Опишите в нём дескриптор: тот же список настроек, что и раньше, но каждое поле теперь объявлено явно: тип, обязательность, секретность и способ отображения в форме.

Structure
1src/providers/
2├── integration-apiship/ # новый: дескриптор настроек для модуля интеграций
3└── fulfillment-apiship/ # существующий: провайдер доставки
providers/integration-apiship/services/apiship-integration.ts
1import { AbstractIntegrationProvider, defineIntegration, z } from "@gorgo/medusa-integration"
2import { ProviderKeys } from "../../../types"
3import { APISHIP_ICON } from "../../icon.ts"
4
5const descriptor = defineIntegration({
6 category: "fulfillment",
7 displayName: "apiship.name",
8 icon: APISHIP_ICON,
9 preferredLayoutId: "core:two-column",
10 supportsMultipleInstances: true,
11
12 options: {
13 token: {
14 type: "string",
15 required: true,
16 secret: true,
17 control: "secret",
18 label: "apiship.fields.token"
19 },
20 is_test: {
21 type: "boolean",
22 default: false,
23 control: "switch",
24 label: "apiship.fields.isTest"
25 },
26 // За подключения к курьерским службам, адрес отправителя и параметры товара отвечает свой Admin UI (см. шаг 4),
27 // но они всё равно часть схемы: шифруются и валидируются наравне с остальными полями.
28 settings: {
29 type: "json",
30 default: {
31 connections: []
32 // ...
33 },
34 control: "json",
35 label: "apiship.fields.settings"
36 },
37 },
38
39 sections: [
40 {
41 id: "credentials",
42 title: "apiship.sections.credentials",
43 options: ["token", "is_test"]
44 },
45 ],
46
47 testConnection: async ({ options }) => {
48 // Лёгкий read-only вызов к API ApiShip, подтверждающий валидность токена.
49 // Возвращает { status, message }, исключений не бросает.
50 },
51})
52
53export type ApishipIntegrationOptions = z.infer<typeof descriptor.optionsSchema>
54
55export class ApishipIntegrationProvider extends AbstractIntegrationProvider {
56 static identifier = ProviderKeys.APISHIP
57
58 get descriptor() {
59 return descriptor
60 }
61}
62
63export default ApishipIntegrationProvider
у опции зашифрует это поле перед сохранением. Для вложенных структур вроде списка подключений к курьерским службам есть со своим .

Шаг 2: Подключите провайдер в

medusa-config.ts
1// ...
2
3const APISHIP_INTEGRATION_ID = "apiship-1"
4
5module.exports = defineConfig({
6 // ...
7 plugins: [
8 {
9 resolve: "@gorgo/medusa-integration",
10 options: {
11 encryptionKey: process.env.INTEGRATION_ENCRYPTION_KEY,
12 providers: [
13 {
14 resolve: "@gorgo/medusa-fulfillment-apiship/providers/integration-apiship",
15 id: APISHIP_INTEGRATION_ID,
16 options: {},
17 },
18 ],
19 },
20 },
21 // ...
22 ],
23 modules: [
24 {
25 resolve: "@medusajs/medusa/fulfillment",
26 options: {
27 providers: [
28 {
29 resolve: "@gorgo/medusa-fulfillment-apiship/providers/fulfillment-apiship",
30 id: "apiship",
31 options: { id: APISHIP_INTEGRATION_ID },
32 },
33 ],
34 },
35 },
36 // ...
37 ],
38})
Внимание: Поле у интеграционного провайдера задаётся на верхнем уровне записи: именно из него собирается итоговый ключ инстанса . Если случайно положить его внутрь , провайдер молча зарегистрируется под дефолтным (безымянным) инстансом. Ошибки не будет, но конфиг из админки в провайдер не попадёт, и обнаружите вы это уже по факту. Учтите также, что здесь и на стороне фулфилмент-провайдера задаются в двух независимых местах конфигурации, поэтому синхронизировать их нужно вручную.

Добавьте секретный ключ шифрования в :

.env
INTEGRATION_ENCRYPTION_KEY=supersecret

Шаг 3: Переключите провайдер на чтение через

Раньше провайдер доставки читал настройки напрямую из . Теперь он получает их из модуля интеграций через , передавая свой и (тот самый из ). Модуль возвращает уже расшифрованный и провалидированный конфиг:

providers/fulfillment-apiship/core/apiship-base.ts
1// ...
2import { resolveIntegrationOptions } from "@gorgo/medusa-integration"
3import { ProviderKeys } from "../../../types"
4
5class ApishipBase extends AbstractFulfillmentProviderService {
6 protected instanceId_: string | null
7
8 constructor({ logger }, options?: Record<string, unknown>) {
9 super()
10 this.logger_ = logger
11 this.instanceId_ = (options?.id as string | undefined) ?? null
12 }
13
14 private async getApishipOptions_(): Promise<ApishipOptionsDTO> {
15 const options = await resolveIntegrationOptions<DeepPartial<ApishipOptionsDTO>>({
16 identifier: ProviderKeys.APISHIP,
17 instance_id: this.instanceId_,
18 })
19 return this.normalizeApishipOptions_(options)
20 }
21
22 // ...
23}

Шаг 4: Перенесите кастомный Admin UI

Раньше у ApiShip была отдельная страница в админке. Теперь страницу интеграции с формой из дескриптора рендерит сам модуль, а ваш кастомный UI (подключения к курьерским службам, адрес отправителя) встраивается в неё виджетом через зону расширения:

admin/widgets/apiship-integration-main.tsx
1// ...
2import { defineWidgetConfig } from "@medusajs/admin-sdk"
3import type { IntegrationSectionData } from "@gorgo/medusa-integration"
4import { ProviderKeys } from "../../../types"
5
6const ApishipIntegrationWidget = ({ data }: { data: IntegrationSectionData }) => {
7 const providerId = data.providerId // ключ инстанса от модуля: "int_apiship" или "int_apiship_<instance>"
8
9 const options = await resolveIntegrationOptions<DeepPartial<ApishipOptionsDTO>>({
10 identifier: ProviderKeys.APISHIP,
11 instance_id: providerId,
12 })
13
14 return (
15 <>
16 // ...существующие компоненты, providerId прокидывается дальше во все хуки и запросы
17 </>
18 )
19}
20
21export const config = defineWidgetConfig({
22 // зона расширения модуля: виджет встанет под формой на странице интеграции ApiShip
23 zone: "gorgo.integration.apiship.main.after",
24})
25
26export default ApishipIntegrationWidget

Модуль передаёт в виджет готовый ключ инстанса через , так что прокидывать его в свои хуки и запросы к собственным API-роутам можно напрямую. В итоге одна страница совмещает и общую форму от модуля (токен, ), и специфичный для провайдера UI (подключения к курьерским службам, адрес отправителя) вместо двух разрозненных экранов.


Итог

Миграция завершена. Теперь провайдер получает настройки из Medusa Admin без правок кода и редеплоя, хранит секреты зашифрованными, из коробки поддерживает несколько инстансов и проверяет соединение прямо из админки. Кастомный UI при этом остаётся вашим, но живёт на одной странице с формой модуля.

Полный код миграции доступен в открытом репозитории: .

Материалы

Изменено 31 июля 2026 г.·Редактировать страницу