Місток
Документація

HTTP API

Швидкий старт новеАрхітектураШлюзиHTTP APIПередоплата і правилаФіскалізаціяСповіщення і CRMПосилання на оплатуАналітика новеПольщаНативний шлюз ShopifyБезпека нове

Машиночитана специфікація: /openapi.yaml (OpenAPI 3.1), колекція Postman: /postman_collection.json (змінні baseUrl, adminKey).

Покупець

МаршрутОпис
GET /pay/:orderId?c=<confirmation>&p=<provider>&lang=uk|pl|enСтворює (або перевикористовує) рахунок і редіректить на сторінку оплати. c — confirmation number замовлення (захист від перебору). Rate-limit 30/5 хв на IP.
GET /l/:tokenПосилання на оплату чернетки (draft order). Токен 96 біт.
GET /return/:orderId?c=…Повернення з PSP: синхронізація статусу → Order status page. Для «частинами mono» — сторінка очікування.
GET /api/order/:orderId?c=…JSON для Order-status блоку: {orderName, provider, status: paid|pending|failed|…, prepay, amount, currency, fiscalUrl, payUrl}. CORS *, лише читання.

Вебхуки

МаршрутПеревірка
POST /webhooks/mono[/<MONO_WEBHOOK_SECRET>]ECDSA X-Sign
POST /webhooks/monoparts · privatparts · liqpay · wayforpay · novapay · p24 · payu · stripeпідпис кожного шлюзу — див. сторінку шлюзу
POST /webhooks/shopifyHMAC-SHA256 X-Shopify-Hmac-Sha256; теми: orders/create, refunds/create, orders/cancelled, orders/edited, orders/fulfilled, orders/paid; дедуп за webhook id

Відповіді: 200 — прийнято; 400 — поганий підпис/тіло (шлюз не повторює); 500 — тимчасова помилка (шлюз повторить).

Відділення Нової Пошти на Thank-you page

Вибір відділення всередині Shopify Checkout доступний лише на Plus (Functions). Місток переносить його на сторінку «Дякуємо»: блок mistok-np-pickup показується, якщо спосіб доставки — Нова Пошта, покупець шукає місто й відділення/поштомат, вибір записується у замовлення як custom attributes (Нова Пошта: місто, Нова Пошта: відділення, np_warehouse_ref), тег np-branch, нотатка і подія np_branch в аналітиці — CRM/логістика забирає ref відділення для ТТН.

МаршрутОпис
GET /api/np/cities?q=Кипошук населених пунктів (Address.searchSettlements), кеш 10 хв
GET /api/np/warehouses?city=<ref>&q=12відділення й поштомати міста (Address.getWarehouses), фільтр за номером/вулицею
POST /api/order/:id/np {c, cityRef, cityName, warehouseRef, warehouseName, type}зберегти вибір (потрібен код підтвердження замовлення); CORS для extension

Змінна: NP_API_KEY (кабінет бізнес-клієнта НП). Ліміт 60 запитів/хв з IP. Якщо ключа немає — блок просто не показується.

Бейдж «від ₴X/міс» на сторінці товару

Покупець бачить розстрочку ще до кошика: блок показує мінімальний щомісячний платіж і, при розкритті, усі програми — 🐈‍⬛ «Покупка частинами» mono та 💚 «Оплата частинами» ПриватБанк, з кількістю частин і позначкою «без переплат» / «комісія покупця». Ціна береться з обраного варіанта і перераховується при зміні варіанта.

Як підключитиДля кого
Theme app extension extension/mistok-installments — у редакторі теми: Товар → Додати блок → Apps → «Mistok — pay in parts», вказати базову адресу Місткає Partner-застосунок (shopify app deploy)
Сніпет extension/installments-snippet.liquid → Themes → Edit code → Snippets, і в main-product.liquid: {% render 'mistok-installments', product: product, base: 'https://pay.example.com' %}без Partner-застосунку, 5 хвилин

Дані: GET /api/installments/quote?amount=<копійки>&currency=UAH → { best, offers[], providers[] } (CORS *, кеш 5 хв, 240 запитів/хв з IP). Пропозиції формуються лише з налаштованих провайдерів і в межах лімітів банків (mono 500–100 000 ₴, Приват 300–50 000 ₴); кожна частина округлюється вгору до гривні, остання — залишок. Якщо бекенд недоступний, блок показує резервну кількість частин із налаштувань або ховається.

Змінні: MONOPARTS_PARTS (напр. 3,4,6,9), PRIVATPARTS_WIDGET_PARTS (напр. 2,4,10; інакше PRIVATPARTS_PARTS), INSTALLMENTS_PARTS / INSTALLMENTS_PROVIDERS — обмежити, що показувати, INSTALLMENTS_OFF=1 — вимкнути, MONOPARTS_MIN_UAH/MONOPARTS_MAX_UAH — ваші ліміти з договору.

Адмінка власника

🧪 Пісочниця: на порожній базі Місток сам створює вигадані платежі й покупців (#D…), щоб дашборд і списки не були порожніми; вони нікуди не надсилаються. Вимкнути й видалити — Налаштування → «Вимкнути і видалити тестові дані» або DEMO_DATA=false.

Вхід: /admin/login з паролем ADMIN_PASSWORD (сесія-cookie на 12 год, HttpOnly, SameSite=Lax; 5 невдалих спроб — блокування на 15 хв). Опційно ADMIN_2FA=telegram — після пароля код приходить у Telegram власника. Для скриптів і API як і раніше працює ?key=ADMIN_KEY.

👥 Користувачі, ролі та журнал дій

Кілька людей в адмінці — кожен під своїм логіном (/admin/users, тільки власник). Паролі зберігаються як scrypt-хеші; вимкнення користувача миттєво закриває його сесію. 2FA: якщо ADMIN_2FA=telegram, код приходить на Telegram chat id користувача (або на TELEGRAM_CHAT_ID). Вхід owner + ADMIN_PASSWORD з .env залишається резервним (вимкнути: ADMIN_ENV_LOGIN=off — тоді потрібен хоча б один активний власник у базі). ?key=ADMIN_KEY = власник (у журналі api-key).

РольМоже
👑 власникусе: налаштування, користувачі, пісочниця, журнал
🛠️ менеджероперації: синк, нагадування, посилання на оплату, правила по телефонах, QR-каси, чеки, повтор подій; читає журнал; не бачить налаштувань і користувачів
👁️ переглядлише сторінки й експорт CSV; кнопки дій приховано, POST → 403

🧾 Журнал дій (/admin/audit, CSV, /admin/api/audit): кожна POST-дія, вхід, вихід, невдалий вхід і відмова в правах — з логіном, роллю, об’єктом (замовлення, користувач), безпечними полями форми (суми, телефони, ключі налаштувань — без паролів), IP і HTTP-статусом результату. Фільтри: текст, користувач, дія, дати. Зберігається AUDIT_KEEP_DAYS (365) днів.

СторінкаЩо є
📊 /admin/dashboard?days=7|30|90KPI, воронка, точки відмови з сумами, динаміка, країни 🇺🇦🇵🇱🇩🇪 / області / міста, провайдери з трендом, пристрої, частки, теплова карта «день × година», «зависли сьогодні», аномалії
📈 /admin/analytics?days=7|30|90модуль аналітики: KPI з порівнянням до попереднього періоду, 📣 джерела трафіку (UTM / реферер / Shopify source) з конверсією і середнім чеком, 🎯 кампанії, 🔁 нові vs повторні покупці, 🏆 топ-покупці, 📦 топ-товари й середній кошик, ⏱️ час від замовлення до оплати, 🔔 ефективність нагадувань, 📅 виручка за днями тижня, 🌍×💳 матриця країна × провайдер, 🧑‍🤝‍🧑 когорти утримання, висновки; /admin/analytics.csv, /admin/api/analytics
💳 /admin/paymentsпошук (№, телефон, email, інвойс), фільтри статус / оплата / PSP / країна / дати, пагінація, /admin/payments.csv експорт
🧾 /admin/order/:idкартка замовлення: платіж, покупець, таймлайн подій, фіскальні чеки (PDF), повернення, KeyCRM-зв’язок, вихідні події; дії: sync, посилання на оплату, нагадування зараз, сума на QR-касу, перевірити чек
🔗 /admin/links · 📵 /admin/phones · 📤 /admin/events · ▦ /admin/qr · ⚙️ /admin/settingsпосилання на оплату, правила по телефонах, журнал вихідних подій, QR-каси, зведення налаштувань

JSON та дії (?key=ADMIN_KEY або cookie)

МаршрутОпис
GET /admin/api/stats?days=N · /admin/api/payments?q=&status=&provider=&country=&from=&to=&paid= · /admin/api/events/:orderId · /admin/api/events · /admin/api/qrJSON
POST /admin/sync/:orderId · POST /admin/remind/:orderId · POST /admin/refund-retry/:refundId · POST /admin/fiscal/:orderId/poll · GET /admin/fiscal/:orderId.pdf?type=sale|returnдії
POST /admin/links (form або JSON: title, amount, email, phone, provider | draftId)створити посилання на оплату; з Accept: application/json повертає {url, token, draft, amount}
POST /admin/phone-rules (phone, mode=prepay|block|trusted|delete, note)правила по телефонах
POST /admin/qr/:qrId/show {orderId} · POST /admin/qr/:qrId/clear · POST /admin/events/:id/redeliverQR-каси, повтор доставки події
GET /status/:orderIdсирий запис

Вихідні події (CRM / Zapier / власний бекенд)

ON_EVENT_WEBHOOK_URL отримує POST application/json з заголовком X-Mistok-Signature = hex HMAC-SHA256(тіло, ON_EVENT_WEBHOOK_SECRET).

{
  "event": "paid",              // paid | prepaid | failed | expired | reversed | refunded | cancelled | reminder_sent | fiscalized
  "at": "2026-10-31T12:00:00.000Z",
  "order": { "id": "5551112223", "gid": "gid://shopify/Order/5551112223", "name": "#1001", "email": "b@x.ua", "phone": "+380501234567" },
  "payment": { "provider": "mono", "externalId": "2410…", "status": "success", "amount": 125000, "currency": "UAH",
               "prepay": false, "paidAt": "2026-10-31T11:59:58.000Z", "fiscalUrl": "https://cabinet.tax.gov.ua/cashregs/check?…" }
}

Суми — в мінімальних одиницях валюти (копійки, гроші, центи). Фільтр подій — ON_EVENT_WEBHOOK_EVENTS=paid,refunded.

Доставка і повтори

Чим це відрізняється від Workflow у GetCheckout v2: там події воронки чекауту (перегляд, покинутий кошик, купон, place_order, purchase) без підпису, з 2 повторами; у Містка — події життєвого циклу платежу після замовлення (оплата, відмова, повернення, reversed, чек ПРРО, нагадування) з HMAC-підписом і 5 повторами. Воронку до оплати Місток збирає у власній аналітиці.