Bridge Protocol v1
ARCHIVE
Архівне дослідження / чернетка. Не є актуальним контрактом реалізації. Дивіться REST 1.0 і актуальну концепцію.
ARCHIVE
Архівна дослідницька чернетка. Не визначає актуальний протокол чи вимоги сумісності. Для реалізації використовуйте REST 1.0.
Прийнята архітектура · 1 жовтня 2026, варіант 2
Agentfy містить harness; API Store Deputy надає єдиний MCP; модуль OpenCart — захищений REST/JSON API. Store Deputy перевіряє підключення, контекст працівника/покупця, права, погодження й аудит, не створюючи другого harness. Connect → вхід/реєстрація → безпечне підключення → автоматичне створення агента в Agentfy з MCP Store Deputy → повернення в модуль. В app: команди, агенти, акаунт, білінг і чат із кожним агентом.
Це замінює попереднє рішення того самого дня про MCP безпосередньо в модулі. Код і прототипи нижче ще не реалізують нову інтеграцію. Сесії покупців і справжній кошик OpenCart мають окрему перевірену прив'язку; cookie залишається в магазині. Див. рішення про єдиний MCP: межі, етапи та подальша робота над контрактом.
Запропоновано наступника · 2 жовтня 2026. Контракт шлюзу v2 — чернетка REST-контракту, яка замінить цей RPC-протокол після погодження. Доти код говорить v1.
Чернетка специфікації · 28 вересня 2026 · частково збудовано, лише на тестових магазинах. Міст для OpenCart 3 відповідає на
connection.pair,connection.revoke,store.describe,catalog.searchіcatalog.get(міст 0.4.0, 30 вересня 2026), а технічний експеримент 29 вересня 2026 перевіривprice.apply(див. що робить запис). Усе це працювало лише на двох локальних тестових магазинах, на реальному магазині — ніколи. Рішення, позначені як прийняті, ухвалив засновник; усе інше — пропозиції.
Store Deputy спілкується з кожним підтримуваним магазином однією власною мовою; для кожної платформи встановлюється невеликий міст, який її перекладає. Щоб додати версію OpenCart, форк чи іншу платформу, достатньо написати ще один міст, що проходить ті самі тести. Бекенд, агент і правила схвалення лишаються незмінними.
Власник магазину ніколи не бачить цього протоколу — він бачить наслідки. Ціну, яку змінили вже після того, як він схвалив список, буде пропущено, а не перезаписано. Завдання, перерване збоєм мережі, ніколи не застосується двічі. Кнопка Пауза у власній адмінпанелі зупиняє агента незалежно від того, що відбувається на нашому боці.
Прийняті межі
| Рішення | Наслідок для протоколу |
|---|---|
| MCP працює в бекенді Store Deputy | Агент ніколи не звертається до магазину. До мосту звертається лише бекенд, після перевірки схвалення |
| Перша платформа: OpenCart 3.0.3.5 і новіші | Код мосту розрахований на PHP 7.3 і Twig 2/3; див. нотатки щодо OpenCart 3 |
| Лише прямі запити | Бекенд звертається до магазину через HTTPS. Магазин має бути доступним з інтернету; це перевіряється під час підключення |
| Записується лише базова ціна | Одна операція запису. Спецціни, знижки за кількість, ціни опцій і залишки читаються або ігноруються, але ніколи не записуються |
Чому міст, а не власний API кожної платформи: контракт виконання вимагає трьох гарантій, яких нативні API магазинів разом не дають. Ціну можна записати лише тоді, коли вона досі дорівнює значенню, яке схвалив власник. Повторний запит не може застосувати зміну двічі. Запис має бути прочитаний назад. Власний код у магазині нам потрібен у будь-якому разі, а один спільний протокол дозволяє реалізувати конектор у бекенді лише один раз.
Agent ── MCP tools ──▶ Store Deputy backend ── approvals, audit, jobs
│
│ Bridge Protocol v1 (HTTPS, signed)
▼
Bridge in the shop ──▶ OpenCart 3.0.3.5+ databaseТранспорт
- Одна точка входу на магазин, яку міст повідомляє під час підключення. Лише
POST,Content-Type: application/json, UTF-8, тіло запиту не більше 1 МБ. - HTTPS обов'язковий. Міст відхиляє звичайний HTTP, якщо в його конфігурації не ввімкнено режим розробки.
- Стиль RPC, а не REST. Назва операції передається в тілі. Одну адресу легко розмістити на будь-якій платформі (маршрут OpenCart, маршрут WordPress REST), а все тіло покривається одним підписом.
- Статус 200 означає, що відповів міст — з результатом чи з помилкою. Будь-який інший статус або 200 без дійсного підпису відповіді означає, що відповів хтось між нами й мостом: файрвол, перевірка CDN, сторінка техобслуговування. Бекенд показує це як «магазин недоступний», а не як помилку магазину. Вирішує підпис, а не статус: на тестових магазинах фатальна помилка OpenCart теж повертається зі статусом 200.
Запит
{
"protocol": "1.0",
"id": "01J8Z6Q7T3M2K4X9V0B1C2D3E4",
"op": "catalog.get",
"params": { "product_ids": [42, 43] }
}Відповідь
{
"protocol": "1.0",
"id": "01J8Z6Q7T3M2K4X9V0B1C2D3E4",
"ok": true,
"result": { "products": [] }
}У разі помилки ok дорівнює false, а замість result повертається "error": { "code": "…", "message": "…", "retryable": false, "details": {} }. id — це ULID, який обирає бекенд. Він унікальний для кожного запиту й повертається у відповіді.
Автентифікація
Щоденні запити: HMAC в обох напрямках
Кожне підключення має connection_id і 32-байтовий секрет, відомі бекенду та мосту. Кожен запит і кожна відповідь містять:
| Заголовок | Значення |
|---|---|
X-SD-Connection | connection_id |
X-SD-Timestamp | Секунди Unix |
X-SD-Nonce | Випадкове значення, від 16 байтів, base64url |
X-SD-Signature | v1= + hex HMAC-SHA256 від v1\n{timestamp}\n{nonce}\n{sha256_hex(body)} |
Міст відхиляє запити, час яких відрізняється від його годинника більш ніж на 300 секунд, або з nonce, який уже траплявся за останні 10 хвилин. Підписи порівнюються за сталий час. Бекенд перевіряє підпис відповіді — саме так він відрізняє міст від сторінки проксі.
Заголовок Authorization навмисно не використовується: поширені конфігурації CGI/FastCGI не передають його в PHP.
Підключення: з'єднати магазин може лише наш бекенд
Підключення має бути неможливим для того, хто просто побачив адресу. Міст зберігає набір публічних ключів підпису Store Deputy, кожен з ідентифікатором, і приймає запит на підключення лише тоді, коли його підписано одним із них. Початковий набір постачається з модулем; його можна оновити без оновлення модуля (див. керування ключами).
- Власник натискає Підключити в модулі. Міст створює одноразовий
pairing_code(діє 10 хвилин, прив'язаний до цього адміністратора) і переспрямовує на сайт акаунтів Store Deputy з кодом, адресою мосту й адресою повернення. - Після входу власника бекенд викликає
connection.pairна мосту, підписаний приватним ключем бекенду (ECDSA P-256 через OpenSSL). Він передає код, новийconnection_idі секрет. - Міст перевіряє підпис і код, зберігає облікові дані, знищує код і повертає маніфест
store.describe, підписаний HMAC новим секретом. Цей виклик також доводить, що бекенд може дістатися магазину. Якщо він не вдасться, власник дізнається про це під час налаштування, а не під час першого завдання з цінами. - Бекенд повертає власника в модуль. Адреса повернення має бути на тому самому хості, з якого почалося підключення.
Секрет ніколи не з'являється в адресі, HTML-сторінці чи журналі.
Як реалізовано в ітерації 3 (29 вересня 2026) на локальних тестових магазинах. Запит підключення містить X-SD-Key-Id, X-SD-Timestamp, X-SD-Nonce і X-SD-Pairing-Signature: v1=<base64url DER ECDSA-SHA256> над тим самим канонічним рядком, що й HMAC. Параметри: pairing_code, connection_id, secret і display (назва команди та email людини, що підключає, — показуються на сторінці модуля). Міст підписує відповідь запропонованим секретом навіть тоді, коли відмовляє, тож Store Deputy завжди бачить, що відповів саме міст. Код зберігається лише як хеш SHA-256 і використовується умовним оновленням, тому два запити з тим самим кодом не можуть обидва пройти. Адреса повернення містить user_token адмінки OpenCart; сайт не передає referrer, а Store Deputy його не зберігає.
Керування ключами
Прийнято: ключі мають замінюватися через інтерфейс, а не лише оновленням модуля. Описаний нижче механізм — пропозиція, ще не реалізована: у цій версії модуль довіряє одному ключу бекенду, який вписується в модуль під час збирання (рішення від 29 вересня 2026; ротація — окремою ітерацією).
Є два види ключів, і кожен можна замінити окремо.
| Ключ | Для чого | Як замінюється |
|---|---|---|
| Ключі підключення бекенду (публічні, у мосту) | Перевірка connection.pair | trust.update з бекенду або вручну в модулі |
| Секрет підключення (спільний) | Підпис щоденних запитів і відповідей | connection.rotate_secret з бекенду або Перепідключити в модулі |
Два рівні ключів бекенду. Кореневий ключ зберігається офлайн і підписує лише набори ключів. Ключі підключення бекенд використовує щодня. Міст приймає новий набір ключів підключення лише тоді, коли його підписано кореневим ключем, якому він уже довіряє. Тож викрадений ключ підключення можна вивести з обігу, і ним неможливо встановити ключі зловмисника. Самі кореневі ключі змінюються лише набором, підписаним поточним кореневим ключем, або оновленням модуля.
Параметри trust.update: keyset (ідентифікатори ключів, публічні ключі, not_before, not_after), keyset_version (має бути більшою за збережену, щоб старий набір не можна було надіслати повторно) і root_signature. Міст зберігає попередній набір до not_before нового, тому немає моменту, коли підключення не працює.
connection.rotate_secret підписується HMAC поточним секретом і містить новий. Протягом 10 хвилин приймаються обидва секрети, тож запити, що вже в дорозі, завершаться успішно. Після цього діє лише новий.
У модулі вкладка «Підключення» показує ідентифікатор підключення, відбитки й ідентифікатори довірених ключів, версію набору ключів і час останньої зміни кожного ключа. Там доступні три дії:
- Перепідключити — повторно запускає підключення й видає новий секрет.
- Імпортувати набір ключів — приймає підписаний файл набору ключів, опублікований Store Deputy, наприклад коли бекенд не може дістатися магазину. Підпис перевіряється так само, як для
trust.update. - Скинути до ключів модуля — відновлює ключі, що постачалися з установленою версією модуля, і відключає магазин.
Кожна зміна ключів записується в журнал операцій із зазначенням, хто її зробив: бекенд чи адміністратор в OpenCart.
Відключення й пауза
- Відключення (у модулі або
connection.revokeз бекенду) видаляє секрет. Усі подальші запити завершуються помилкоюnot_paired. - Пауза — власний перемикач власника в адмінці OpenCart. Під час паузи міст відповідає на всі операції, крім
store.describeіconnection.revoke, помилкоюpaused: відключення лише зменшує доступ, тому працює завжди. Це не залежить від того, чи правильно поводиться бекенд.
Операції
| Операція | Можливість | Тип |
|---|---|---|
connection.pair | — | Налаштування, асиметричний підпис |
connection.revoke | — | Налаштування |
connection.rotate_secret | — | Налаштування |
trust.update | — | Налаштування, підпис кореневим ключем |
store.describe | — | Читання |
catalog.list | catalog.read | Читання |
catalog.search | catalog.read | Читання |
catalog.get | catalog.read | Читання |
price.apply | price.base.write | Запис |
operation.get | price.base.write | Читання |
Перегляд замовлень, запис залишків, спецціни й опції не входять у v1 (перегляд замовлень відкладено рішенням від 28 вересня 2026). Кожна з цих функцій, якщо з'явиться, буде новою можливістю в мінорній версії.
store.describe
Повертає маніфест, за яким бекенд вирішує, які інструменти агента ввімкнено для цього магазину. Агентові ніколи не пропонується інструмент, який магазин не заявив.
{
"bridge": { "version": "1.0.0", "protocols": ["1.0"] },
"platform": {
"name": "opencart",
"reported_version": "3.0.3.8",
"detected_version": "3.0.3.9",
"fork": null,
"php": "7.4.33",
"db": "MariaDB 10.6"
},
"store": {
"stores": [{ "store_id": 0, "name": "Example Store", "url": "https://shop.example.com/" }],
"default_currency": "EUR",
"prices_entered_without_tax": true,
"display_prices_with_tax": true,
"admin_language": "en-gb",
"catalog_collation": "utf8_general_ci",
"timezone": "Europe/Kyiv"
},
"limits": { "max_ids_per_read": 500, "max_items_per_write": 50, "time_budget_ms": 20000 },
"capabilities": [
{ "name": "catalog.read", "version": 1 },
{ "name": "price.base.write", "version": 1 }
],
"warnings": [
{ "code": "product_edit_listener", "details": { "event_code": "example_feed", "trigger": "admin/model/catalog/product/editProduct/after" } }
],
"paused": false
}Це ілюстративний приклад, а не відповідь реального магазину.
detected_versionвизначається за ознаками в коді, а не лише за константоюVERSION. Реліз 3.0.3.9 досі оголошує себе як3.0.3.8.warningsперелічує те, що може поводитися інакше після нашого запису. Наприклад, розширення, підписані на події редагування товару, і модифікації OCMOD, що змінюють модель товару; див. чому.- Кілька вітрин варто показати попередженням у таблиці перевірки. В OpenCart базова ціна спільна для всіх вітрин однієї установки.
catalog.list
Посторінково перебирає товари за зростанням product_id, щоб бекенд побудував індекс для зіставлення. Параметри: after_id (за замовчуванням 0), limit (≤ max_ids_per_read), необов'язковий modified_since (ISO 8601). Повертає products (у тому ж форматі, що й catalog.get) і next_after_id або null.
catalog.search
Шукає товари за ідентифікатором. Повертає всі збіги й ніколи не обирає один. Як розв'язати дублікат, вирішує власник, і це відбувається в бекенді.
{
"identifiers": [
{ "field": "sku", "value": "AB-100" },
{ "field": "ean", "value": "4006381333931" }
]
}field — одне з product_id, model, sku, upc, ean, jan, isbn, mpn. Результат зберігає порядок вхідних даних: [{ "input": …, "product_ids": [ … ] }]. Порівняння використовує колацію бази даних. У стандартній установці OpenCart це означає, що регістр не враховується, а пробіли в кінці ігноруються. Маніфест повідомляє колацію, щоб бекенд міг пояснити несподіваний збіг.
catalog.get
Параметри: product_ids (≤ max_ids_per_read). Для кожного товару повертає:
| Поле | Примітки |
|---|---|
product_id, model, sku, upc, ean, jan, isbn, mpn | Значення без обробки |
name | Мовою адмінки |
status, quantity, store_ids, date_modified | Контекст лише для читання |
price | Десятковий рядок, 4 знаки, напр. "19.9900", у валюті за замовчуванням, без податку |
tax_class_id | Ціна зберігається без податку; податок додається під час показу |
active_specials | Спецціни, що діють зараз: група покупців, ціна, дати, пріоритет. Лише читання |
has_discounts, has_option_prices | Чи є інші механізми ціноутворення |
Суми завжди передаються десятковими рядками, ніколи не числами JSON. Числа з плаваючою комою для грошей неприпустимі.
ID, для яких товару немає, повертаються в missing, а не спричиняють помилку запиту.
active_specials потрібне, щоб таблиця перевірки могла попередити власника. Коли діє спецціна, покупці бачать її, а не нову базову ціну.
price.apply
Єдина операція запису у v1. Бекенд надсилає її лише для схваленої версії завдання; агент не може викликати її з довільною ціною.
{
"job_id": "job_01J8Z7…",
"approval_ref": "apr_01J8Z7…",
"items": [
{
"operation_key": "opk_01J8Z7…",
"product_id": 42,
"expected": { "price": "19.9900" },
"set": { "price": "21.5000" }
}
]
}Не більше max_items_per_write позицій. Для кожної позиції по черзі міст:
- Повертає збережений результат, якщо
operation_keyуже завершено в його журналі. Ніщо не виконується двічі. - Записує ключ у журнал зі статусом
started. - Виконує одну умовну команду:
UPDATE product SET price = :set, date_modified = NOW() WHERE product_id = :id AND price = :expected. - Якщо жоден рядок не змінився, читає товар. Відсутній товар — це
not_found. Інша ціна —conflictз поточним значенням. - Читає ціну назад і порівнює її з
set. - Записує результат у журнал.
Після пакета міст очищує кеш product в OpenCart, як це робить адмінка після збереження товару. Інакше кешовані списки, наприклад «Бестселери», показуватимуть стару ціну.
Результати позицій:
| Результат | Значення | Дія бекенду |
|---|---|---|
applied | Записано й прочитано назад як set | Готово |
conflict | Ціна не дорівнювала expected; нічого не записано | Пропустити; нова пропозиція потребує нового схвалення |
not_found | Товару більше не існує | Пропустити й повідомити |
rejected | Некоректна позиція: неправильне десяткове число, від'ємна ціна, expected = set | Виправити завдання; повторно не надсилається |
not_attempted | Час вичерпано до цієї позиції | Надіслати повторно з тим самим operation_key |
failed | Помилка бази даних; нічого не підтверджено | Перевірити через operation.get, потім вирішити |
Пакет не атомарний. Таблиці OpenCart 3 використовують MyISAM без транзакцій, тому кожна позиція має власний результат. Міст перестає починати нові позиції, коли вичерпано time_budget_ms. Спільний хостинг часто обриває PHP-запит через 30 секунд, а напівзавершений запит без відповіді — найгірший випадок.
operation.get
Параметри: operation_keys (≤ 500). Повертає запис журналу для кожного ключа або unknown. Після тайм-ауту бекенд викликає цю операцію, а не надсилає запит повторно наосліп:
unknown— запит не дійшов до мосту; можна безпечно надіслати повторно з тим самим ключем.- Завершений результат — зафіксувати його; повторно не надсилати.
startedбез результату (запит обірвався посеред позиції) — надіслати повторно з тим самим ключем. Це безпечно, бо запис умовний: якщо його вже застосовано, ціна більше не дорівнюєexpected. Позиція повернеться як конфлікт, бекенд побачить, що поточне значення дорівнюєset, тобто зміну застосовано, і звірить стан.
Що запис робить і чого не робить
Міст не викликає editProduct з OpenCart. Ця функція видаляє й заново вставляє спецціни, знижки, опції, зображення й описи товару з повної відправки форми і не має умови щодо старої ціни. Використати її для зміни одного числа — означає ризикувати всім іншим у товарі й втратити гарантію від конфліктів.
Іншим розширенням усе одно треба дізнатися, що ціна змінилася: фідам маркетплейсів, пошуковим індексам, синхронізації з ERP. В OpenCart 3 вони дізнаються про це одним із трьох способів, і кожен обробляється по-своєму.
| Як розширення помічає зміну | Приклад | Чи побачить наш запис? |
|---|---|---|
Читає базу даних під час роботи або шукає новіший date_modified | Фід, що будується на запит чи за cron; вивантаження змінених товарів | Так. Запис оновлює date_modified, кеш очищується |
Підписане на подію admin/model/catalog/product/editProduct/after | Модуль, що надсилає кожен збережений товар на маркетплейс | Так на тестових магазинах; міст сам генерує подію, див. нижче |
| Модифікація OCMOD чи vQmod змінює модель або контролер товару | Додатковий код усередині editProduct | Ні. Цей код виконується лише всередині editProduct |
Пропозиція: генерувати подію «після збереження» після запису. Коли ціну застосовано й прочитано назад, міст викликає model/catalog/product/editProduct/after у контексті адмінки OpenCart з тими самими аргументами, що й звичайне збереження: ID товару й повні дані товару, зібрані власними методами моделі адмінки так, як їх завантажує форма товару. Для слухача це виглядає так, ніби власник натиснув Зберегти. Подія генерується один раз для кожної застосованої позиції і ніколи — для conflict чи not_found.
Результат експерименту, 29 вересня 2026. На локальних тестових магазинах з OpenCart 3.0.3.5 (PHP 7.3) і 3.0.5.1 (PHP 8.1, з перейменованим каталогом адмінки) тестове розширення, що слухає збереження товару, отримало рівно одну подію на кожну застосовану ціну — з описами, вітринами, спеццінами, знижками й опціями товару. Ці пов'язані записи після запису лишилися незмінними байт у байт. Конфліктний запис не згенерував нічого. На реальному магазині це ще не перевіряли.
Обмеження цього підходу:
- Слухачі «перед збереженням» не викликаються. Вони працюють до запису й можуть змінити чи замінити його. Наш запис уже виконано, і він умовний.
- Поля, які модифікація додала до форми товару, стандартні методи не повертають, тож слухач може побачити їх порожніми. Міст повідомляє про такі модифікації в
warnings. - Потрібен контекст адмінки. Слухачі для тригерів
admin/завантажує лише застосунок адмінки. Він відправляє кожен запит без сесії адміністратора на сторінку входу, а застосунок вітрини не може завантажити модель товару адмінки, бо обидва класи називаютьсяModelCatalogProduct. Тому мосту потрібна власна точка входу, яка запускає застосунок адмінки з нашою перевіркою підпису замість входу. Якщо на реальних магазинах це виявиться ненадійним, запасний варіант — маршрут на боці вітрини без сповіщення подіями. - Точка входу має замінювати й маршрутизатор. Стандартний маршрутизатор OpenCart виконує той контролер адмінки, який названо в
?route=. Без входу це дозволило б будь-кому виконати будь-яку дію адмінки через міст. Маршрутизатор мосту виконує лише міст. У контрольному прогоні на тестовому магазині повернення стандартного маршрутизатора призвело до падіння відповідного сценарію, як і має бути.
Міст перелічує виявлених слухачів подій і пов'язані з товаром модифікації в warnings, тож власник ще до підключення знає, які розширення побачать зміни, а які ні.
Помилки
| Код | Можна повторити | Коли |
|---|---|---|
signature_invalid | ні | Неправильний або відсутній підпис |
timestamp_skew | ні | Відхилення від годинника мосту понад 300 с |
replayed | ні | Nonce уже використовувався |
not_paired | ні | Облікових даних немає або їх відкликано |
paused | ні | Власник поставив агента на паузу в адмінпанелі |
unsupported_protocol | ні | Мажорна версія не підтримується; details.supported перелічує версії |
unknown_op | ні | Операцію не реалізовано в цьому мосту |
capability_unavailable | ні | Реалізовано, але недоступно в цьому магазині |
invalid_params | ні | Помилка перевірки; details називає поле |
limit_exceeded | ні | Забагато ID чи позицій або завелике тіло |
insecure_transport | ні | Звичайний HTTP до релізної збірки модуля |
unknown_key | ні | connection.pair підписано ключем, якому модуль не довіряє |
pairing_code_invalid | ні | Такого коду підключення немає |
pairing_code_used | ні | Код уже використано |
pairing_code_expired | ні | Коду понад 10 хвилин |
internal | так | Неочікуваний збій мосту; message ніколи не містить секретів чи SQL |
Версіонування
protocolмає форматmajor.minor. Мінорна версія лише додає: нові операції, нові необов'язкові поля, нові можливості. Обидві сторони ігнорують невідомі поля. Мажорна версія може ламати сумісність, і міст може підтримувати кілька.- Бекенд підтримує поточну й попередню мажорні версії, тож власникам не доведеться оновлювати модуль у день нашого релізу.
- Можливість має власну версію. Вона змінюється, коли на платформі змінюється значення операції, а протокол лишається тим самим.
Як додається нова платформа чи версія
- Реалізувати операції на новій платформі, повторно використовуючи портативне ядро мосту: конверт, підписи, сховище nonce, журнал, маніфест.
- Додати образ магазину цієї версії до тестової матриці.
- Пройти набір тестів на відповідність. Доти версія не підтримується, хоч би як виглядав її код.
Запропоновані сценарії перевірки: читання збігається з базою даних; пошук за ідентифікатором повертає всі дублікати; запис застосовується й читається назад; одночасне редагування в адмінці дає conflict; повторний operation_key не записує вдруге; запит, обірваний посеред позиції, звіряється через operation.get; вичерпаний ліміт часу дає not_attempted і чисте повторне надсилання; неправильний підпис, старий час, повторний nonce, відкликаний секрет і пауза відхиляються; облікові дані іншого підключення відхиляються; файл постачальника з інструкціями не впливає на дії мосту.
Нотатки щодо реалізації для OpenCart 3
Ці нотатки отримано з порівняння тегів релізів OpenCart. На працюючому магазині їх не перевіряли.
- Власна точка входу, що запускає застосунок адмінки (спрацювала в експерименті на обох тестових магазинах). Інсталятор розширень OpenCart не пише в корінь магазину, тому архів кладе точку входу в
system/library/storedeputy/, а крок Install модуля копіює її в корінь, вписуючи назву каталогу адмінки; Uninstall її прибирає. Якщо корінь закритий на запис, сторінка модуля пояснює, який файл завантажити вручну. Точка входу — з перевіркою підпису замість входу OpenCart; див. що робить запис. Магазини часто перейменовують каталогadmin, тому його розташування фіксується під час встановлення. Запасний варіант — маршрут на боці вітрини, який не може генерувати події адмінки. - Власні таблиці: журнал операцій (унікальний
operation_key), який за рішенням зберігається безстроково, і сховище nonce. Nonce потрібні лише для 10-хвилинного вікна захисту від повторів і видаляються після нього. Облікові дані й довірений набір ключів зберігаються в налаштуваннях OpenCart. Будь-хто з доступом до бази даних може їх прочитати — це стосується секретів будь-якого розширення OpenCart. - Мінімальний синтаксис PHP 7.3: без стрілкових функцій, типізованих властивостей,
matchі іменованих аргументів. 3.0.5.x потребує PHP 8.1, тож код має коректно працювати й на 8.x. - Twig 2 і 3: 3.0.3.5–3.0.3.8 постачаються з Twig 2.13, 3.0.3.9 і новіші — з Twig 3. Шаблони адмінки використовують лише синтаксис, дійсний в обох версіях. Наприклад, без
{% spaceless %}і безfor … if. - Колація й кодування відрізняються в старих установках (utf8) і нових 3.0.5.x (utf8mb4). Таблиці мосту використовують те саме кодування, що й таблиця товарів.
Відкриті питання
- Чи працюватиме точка входу, що спрацювала на тестових магазинах, і на реальних магазинах із розширеннями безпеки та модифікаціями OCMOD?
- Необов'язковий список дозволених IP-адрес нашого бекенду, коли буде обрано хостинг.
- Які форки підтримувати — вирішують лише реальні пілотні магазини.
Див. також: повноваження й відновлення, план реалізації, модуль OpenCart.