Gerege Wallet¶
Частично · Слой 4 — Отраслевой продукт ·
Репозиторий: wallet-gerege-mn · wallet.gerege.mn · api.wallet.gerege.mn
Цифровой кошелёк гражданина — продукт, где вход выполняется по eID, видно остаток и можно делать переводы по IBAN. В качестве финансового ядра работает Apache Fineract.
Мобильные приложения (iOS SwiftUI, Android Compose) — основная поверхность; веб — вспомогательная консоль.
Архитектура денег — самое важное правило¶
Fineract — единственный источник истины о деньгах. Остатки, транзакции и книга проводок находятся там. PostgreSQL хранит ТОЛЬКО сопоставление «гражданин ↔ ID в Fineract», записи идемпотентности и пользовательские настройки.
| Слой | За что отвечает |
|---|---|
| Бэкенд Wallet (Go) | Аутентификация, права, потоки, аудит |
| Apache Fineract 1.15 | Счета, транзакции, остатки, двойная запись, книга проводок |
| PostgreSQL | Только сопоставление и настройки — остатков здесь НЕТ НИКОГДА |
Из этого следуют три правила:
- Не кэшировать остаток.
/accounts/balanceчитает напрямую из Fineract — условия для расхождения двух систем просто не возникают. - Не делать деньги float. Текстовая форма из JSON преобразуется сразу в
int64в минорных единицах (для ₮ деньги = ₮×100). Путь к ошибкам округления закрыт. - Каждая транзакция идемпотентна. Подробности ниже.
Почему готовая core banking система?
Финансовая книга проводок — предметная область, которую трудно сделать правильно и дорого сделать неправильно: двойная запись, сведение баланса, закрытие периодов, аудиторский след. Fineract решает это за годы работы в production. Мы добавляем сверху только слой идентичности и слой услуг.
Гражданин ↔ счёт ↔ IBAN¶
Каждому гражданину полагается ровно один клиент в Fineract, один
сберегательный счёт и один IBAN. Ключом связи служит civil_id гражданина:
Монгольский IBAN — 20 символов:
MN | kk | bbbb | aaaaaaaaaaaa
2 | 2 | 4 | 12
│ │ │ └─ номер счёта (Fineract savings ID, с ведущими нулями)
│ │ └──────── код банка/учреждения (4 разряда)
│ └────────────── контрольные разряды mod-97
└─────────────────── код страны
12 разрядов номера счёта выводятся из ID в Fineract, поэтому дополнительная последовательность не нужна, а обратное сопоставление — чистая арифметика.
Ключ — civil_id, а НЕ внутренний user_id
Ранее externalId для Fineract выводился из user_id в PostgreSQL (новый
UUID при каждом создании строки) — это исправлено. При той схеме
пересоздание БД навсегда разрывало связь «гражданин ↔ счёт»: при повторном
входе создавался новый счёт и новый IBAN, а прежний остаток оставался
осиротевшим.
civil_id — пожизненный идентификатор (есть у каждого пользователя eID),
поэтому выведенный из него ключ не зависит от базы. ПЕРЕД открытием счёта
существующие клиент и счёт в Fineract разыскиваются по этому ключу и, если
найдены, используются повторно — так что даже при утрате сопоставления
гражданин получает обратно тот же IBAN.
Авторизация перевода¶
Перевод авторизуется JWT-сессией. Приложение обращается к
/transfer/iban с access token, полученным при входе.
Защита от дублирования: каждый запрос несёт заголовок Idempotency-Key. Второй
запрос с тем же ключом не создаёт новую транзакцию, а возвращает результат
ПЕРВОГО — поэтому при обрыве сети и повторной отправке деньги не уйдут дважды.
Гарантию в конечном счёте держит ограничение
UNIQUE (user_id, idempotency_key) в базе.
Привязка подписи удалена
Раньше каждый перевод повторно хешировался в канонической форме GWT и
сверялся с подписью eID PIN2 гражданина (WYSIWYS — «подписываешь то, что
видишь»). К этому прилагались побайтово идентичные реализации в трёх портах
(Go/Kotlin/Swift) и проверка golden fixtures в CI.
Всё это удалено продуктовым решением. Последствие: сторона, владеющая действительным access token, может двигать деньги — тогда как раньше даже украденный токен не позволял выполнить перевод без PIN2.
Вход по eID сохраняется — удалена только подпись ПЕРЕВОДА.
Поверхность API¶
Аутентификация приходит из базового слоя Gerege Platform; endpoint'ы кошелька находятся в этом репозитории.
| Метод | Путь | Что делает |
|---|---|---|
POST |
/api/v1/auth/initiate |
Отправляет eID push по номеру реестра |
GET |
/api/v1/auth/status/{sid} |
Статус + токен + IBAN (здесь кошелёк и открывается) |
GET |
/api/v1/accounts/balance |
Остаток (напрямую из Fineract) |
GET |
/api/v1/accounts/transactions |
Выписка по счёту |
GET |
/api/v1/accounts/lookup |
Проверка IBAN получателя |
POST |
/api/v1/transfer/iban |
Перевод (требует Idempotency-Key) |
GET/DELETE |
/api/v1/beneficiaries |
Сохранённые получатели |
POST/DELETE |
/api/v1/devices/register |
Регистрация push-токена |
POST |
/api/v1/pay/code/initiate |
Одноразовый платёжный QR-токен |
Две формы ответа¶
- Плоский JSON (без конверта) — для мобильных приложений. Кошелёк и мобильные auth-endpoint'ы используют эту форму.
- Конверт
{status, message, data}— для веб-BFF.
Добавляя новый endpoint для приложения, придерживайтесь плоской формы. Веб-BFF оборачивает плоский ответ в свой клиентский конверт и передаёт дальше.
Словарь статусов приложений¶
Терминальными приложения считают только CONFIRMED / REFUSED / TIMEOUT.
Внутренние eID-наименования бэкенда (COMPLETE/EXPIRED/…) отображаются наружу
в одном единственном месте.
Контракт задают приложения
Приложения для iOS и Android были созданы РАНЬШЕ бэкенда и ожидают приведённых выше форм. Если приложение и бэкенд расходятся, исправляют бэкенд.
Мобильные приложения¶
| iOS | Android | |
|---|---|---|
| Технология | SwiftUI | Kotlin + Compose |
| Вход | ✅ | ✅ |
| Остаток / выписка | ✅ | ✅ |
| Перевод | ✅ | ⏳ экран пока не сделан |
| Оплата по QR (EMVCo) | ✅ | ⏳ |
| Регистрация push | ✅ | ✅ |
Приложения не обращаются к домену eID напрямую — весь трафик идёт через
api.wallet.gerege.mn. Для входа гражданин вводит PIN в ответ на push,
пришедший в приложение eID.
Безопасность¶
- Row-Level Security. API подключается к БД ролью, которая НЕ является
superuser (в production это проверяет boot guard), поэтому политики RLS
действительно применяются. У каждой таблицы «на пользователя» своя политика.
Доверенные серверные записи — открытие счёта, создание записи о переводе —
выполняются отдельно под ролью
service; гражданину права записи в эти таблицы не выдаются. - TLS для БД. У PostgreSQL настроен TLS с приватным CA, поэтому
sslmode=verify-fullимеет реальный смысл. - Где живут секреты. Все секреты — в
/etc/gerege-wallet/*.env. Файл.envприложения ПЕРЕСОЗДАЁТСЯ при каждом деплое, так что правка вручную будет стёрта следующим деплоем. - Rate limit.
/auth/*~5 запросов/мин (cap тела 4 KiB), long-poll/auth/status— на отдельном, более свободном лимите, endpoint'ы, двигающие деньги, ~30/мин. - Идемпотентность. Каждый перевод требует
Idempotency-Key— при обрыве сети и повторной отправке деньги не уйдут дважды. Поскольку привязки подписи больше нет, это ГЛАВНЫЙ механизм защиты от дублирования. - Изоляция Fineract. Слушает только на
127.0.0.1:8090— пути извне нет.
Развёртывание¶
НЕ Docker — нативный systemd:
gerege-wallet.slice
├── gerege-wallet-fineract.service # Fineract 1.15 (JAR, 127.0.0.1:8090)
├── gerege-wallet-api.service # Go API (127.0.0.1:8080)
└── gerege-wallet-web.service # Next.js BFF (127.0.0.1:3000)
PostgreSQL, Redis и nginx — сервисы хоста. nginx публикует
wallet.gerege.mn и api.wallet.gerege.mn по TLS от Let's Encrypt.
CD: после слияния в main Deploy workflow запускается, как только CI
становится зелёным. Вся сборка выполняется на раннере, и на сервер попадает
только результат — там не нужен toolchain Go/Node, а окно простоя короткое.
Деплой считается успешным лишь после того, как проверено не только /health, но
и что защищённый путь отвечает корректно.
Текущее состояние¶
| Возможность | Состояние |
|---|---|
| Вход по eID (с RP-учётными данными) | Работает |
| Кошелёк открывается автоматически + выдаётся IBAN | Работает |
| Остаток / выписка | Работает |
| Приветственный бонус новым кошелькам | Работает |
| Перевод по IBAN (авторизация по JWT) | Реализовано, тестов мало |
| Перевод / QR на Android | В планах |
| App Store / TestFlight | Готовится |
Код банка в IBAN временный
Текущий код банка/учреждения — временное значение до получения реального кода от Банка Монголии. Выданный гражданину IBAN меняться не должен, поэтому после появления настоящих пользователей это значение менять не будут.
Подробная документация¶
Документы ARCHITECTURE, DEVELOPMENT, API_CONTRACT и SECURITY находятся в
каталоге backend/docs/ репозитория wallet-gerege-mn (парами EN/MN).
Инструкции по мобильным сборкам — в ios/README.md, по развёртыванию — в
docs/DEPLOYMENT.md.
Связанные платформы: eID Mongolia · G-Sign · Gerege Platform · Gerege Verify