Архітектура
Потік платежу
1. Покупець завершує штатний Shopify Checkout з ручним способом оплати → замовлення PENDING 2. Thank-you page: Checkout UI Extension показує кнопки → GET /pay/:orderId?c=<confirmationNumber>&p=<provider> 3. Місток читає замовлення з Admin API, застосовує правила передоплати, створює рахунок у шлюзі, редіректить на hosted-сторінку 4. Шлюз → POST /webhooks/<provider> (підпис) → Місток перечитує статус з API шлюзу → звіряє суму/валюту 5. success → orderCreateManualPayment(назва методу) → PAID / PARTIALLY_PAID → теги, нотатки, ПРРО, SMS, CRM 6. Покупець повертається → GET /return/:id → той самий sync (страховка на випадок втрати вебхука) → Order status page 7. Поллер кожні 10 хв добиває відкриті рахунки (expired шлюзи не надсилають)
Модель статусів
Кожен шлюз мапиться в єдину модель: created → processing → (hold →) success | failure | expired | reversed. Запис на замовлення один; при зміні суми або методу старий рахунок анулюється (superseded), новий створюється на наступному /pay.
Надійність
- Ідемпотентність: прапорець
paidMarked+ м’ютекс на замовлення — одна транзакція в Shopify, скільки б вебхуків не прийшло. - Недовіра до вебхука: тіло — лише сигнал; істина — у відповіді API шлюзу.
- Дедуплікація Shopify-вебхуків за
X-Shopify-Webhook-Id. - Failover:
PROVIDER_FALLBACK— при 5xx основного шлюзу рахунок створюється в запасному. - Поллер і /return — два незалежні шляхи отримати статус без вебхука.
Дані
SQLite (WAL) у DB_FILE: invoices, refunds, seen_webhooks, fiscal_receipts, events (аналітика), phone_rules, pay_links. Карткових даних немає ніде; з персональних — email і телефон покупця (для квитанцій, SMS, правил).
Кілька магазинів на одному сервері
Один інстанс Містка = один Shopify-магазин (свій токен, база, домен, бот). Для другого магазину на тому самому дроплеті запустіть деплой з іменем інстансу та портом: INSTANCE=shop2 DOMAIN=pay.shop2.ua PORT=3001 bash mistok/ops/deploy.sh. Скрипт створить /opt/shop2, /root/shop2.env (секрети цього магазину), сервіс shop2, бекапи shop2-* і Caddy-сайт /etc/caddy/sites/shop2.caddy на власному домені — інстанси не бачать даних один одного. Оновлення — той самий zip, той самий рядок із тим самим INSTANCE.
Компоненти
| Файл | Роль |
|---|---|
src/app.js | HTTP: /pay, /l, /return, /api/order, /webhooks/*, /admin |
src/core.js | бізнес-логіка: платежі, статуси, правила, повернення, фіскалізація, події |
src/providers/* | адаптери шлюзів за єдиним інтерфейсом (types.js) |
src/analytics.js | воронка, географія, точки відмови, дашборд |
src/fiscal/checkbox.js, src/notify/* | ПРРО, SMS, email, Telegram, CRM-події |
extension/* | Thank-you та Order-status блоки (Preact) |