HTTP API
Машиночитана специфікація: /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/shopify | HMAC-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=<копійки>¤cy=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|90 | KPI, воронка, точки відмови з сумами, динаміка, країни 🇺🇦🇵🇱🇩🇪 / області / міста, провайдери з трендом, пристрої, частки, теплова карта «день × година», «зависли сьогодні», аномалії |
📈 /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/qr | JSON |
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/redeliver | QR-каси, повтор доставки події |
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.
Доставка і повтори
- Заголовки:
X-Mistok-Event(назва події),X-Mistok-Delivery(унікальний id доставки — для дедуплікації на вашому боці),X-Mistok-Signature. - Успіх — відповідь 2xx протягом 8 с. Інакше повтор через 1, 5, 30, 120 і 720 хвилин з тим самим тілом і тим самим id.
- Журнал доставок за 30 днів —
/admin/events(стан, спроби, HTTP-код, помилка, кнопка «повторити»); JSON —/admin/api/events. - Перевірка підпису (Node):
crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex') === req.get('X-Mistok-Signature').
Чим це відрізняється від Workflow у GetCheckout v2: там події воронки чекауту (перегляд, покинутий кошик, купон, place_order, purchase) без підпису, з 2 повторами; у Містка — події життєвого циклу платежу після замовлення (оплата, відмова, повернення, reversed, чек ПРРО, нагадування) з HMAC-підписом і 5 повторами. Воронку до оплати Місток збирає у власній аналітиці.