Skip to main content
Inhaltsverzeichnis

Inhaltsverzeichnis

Lesezeit: 20 Min.
#monobank#acquiring#vibecoding#ai_tools#payments
NEUEinsteiger20 Min.

Monobank-Acquiring ohne Entwickler per Vibe Coding integrieren

Schritt-für-Schritt-Anleitung zur Integration von Monobank-Online-Zahlungen auf einer Website: Compliance für Einzelunternehmer (FOP), Terminal-Einrichtung, ECDSA-Webhook-Verifizierung, zuverlässige UX und Sicherheitstests.

Veröffentlicht:
FÜR KI-AGENTChatGPTClaude

Vibe Coding hat die Entwicklung digitaler Produkte grundlegend verändert. Heute ist es nicht mehr nötig, einen Backend-Entwickler einzustellen oder sich selbst in kryptografische Protokolle einzuarbeiten, um auf einer Website Zahlungen über Apple Pay, Google Pay oder Bankkarten zu akzeptieren. Es reicht aus, die Geschäftslogik klar gegenüber einem AI-Agenten (Codex, Antigravity, Cursor oder Claude Code) zu formulieren und ihm den richtigen Kontext der technischen Dokumentation bereitzustellen.

Dieses Handbuch behandelt den gesamten Lebenszyklus der Anbindung von Monobank Internet-Acquiring an Ihr Projekt: vom Durchlaufen des Bank-Compliance-Prozesses und der Einrichtung des Unternehmer-Kontos bis hin zur Generierung eines sicheren Zahlungs-Gateways mit kryptografischer Verifizierung der ECDSA-Signaturen in Webhooks, der Behandlung von Race Conditions im Interface sowie dem Setzen obligatorischer automatisierter Tests.

Tipp

Video-Version des Handbuchs: Wenn Sie eine visuelle Darstellung bevorzugen, sehen Sie sich das detaillierte Praxis-Video auf YouTube → an, in dem der gesamte Prozess live am Bildschirm gezeigt wird – von der ersten Anfrage bis zur tatsächlichen Abbuchung.

Hinweis

Agent Skills-Paket: Für maximale Genauigkeit bei der Codegenerierung laden Sie das offizielle Wissenspaket für Ihren AI-Assistenten herunter:
Vollständiges Archiv monobank-acquiring.zip herunterladen (38 KB) →


1. Architektur des Hosted Checkout und Zahlungslebenszyklus

Monobank Acquiring funktioniert nach dem Schema Hosted Checkout (Zahlung auf einer geschützten Bankseite). Das bedeutet, dass Ihre Website keine komplexe und teure PCI DSS-Sicherheitszertifizierung benötigt, da der Benutzer die Kartendaten direkt auf der geschützten Domain der Bank eingibt.

Die offizielle Monobank-Dokumentation für AI-Tools ist unter monobank.ua/api-docs/acquiring/dev/ai-tools/docs--ai-prompts → verfügbar. Speichern Sie diesen Link als kanonische Erstquelle für die technischen Anforderungen der Bank.

1.1. Grundlegender Zahlungsfluss

terminal
[ Клієнт на сайті ] │ ├─ 1. Натискає «Оплатити» ▼ [ Ваш Сервер ] ──────────────► [ Monobank API: /invoice/create ] ▲ │ │ отримує pageUrl, invoiceId │ └──────────────────────────────┘ │ ├─ 2. Редирект клієнта на pageUrl (Hosted Checkout) ▼ [ Платіжна сторінка Monobank ] ───► Оплата (Apple Pay / Google Pay / Картка) │ ├─ 3. Асинхронний POST вебхук із підписом x-sign ▼ [ Ваш Вебхук-ендпоінт ] ─────────► Перевірка ECDSA SHA-256 → Статус "success" │ ├─ 4. Клієнт повертається на /payment-result ▼ [ Сторінка успіху ] ────────────► Відображення доступу / чека
  • 1. Erstellung der Rechnung (invoice/create): Der Kunde wählt auf Ihrer Website ein Produkt oder einen Tarif aus. Der Server-Handler sendet eine POST-Anfrage an die Monobank API mit Betrag, Verwendungszweck der Zahlung und Rückleitungsadresse. Die Bank gibt eine eindeutige invoiceId und den Link pageUrl zurück.
  • 2. Umleitung zur Zahlungsseite: Der Kunde wird auf die pageUrl weitergeleitet, wo er die Bestellung über Apple Pay, Google Pay, die Monobank-App oder durch Eingabe der Bankkartendaten bezahlt.
  • 3. Empfang des Webhooks: Nach Abschluss der Zahlung sendet Monobank automatisch eine POST-Anfrage an Ihre vorab konfigurierte webHookUrl mit dem Transaktionsstatus und einer kryptografischen digitalen Signatur.
  • 4. Statusfixierung (Terminal States): Ihr Server muss auf zwei finale Status reagieren: success (Zahlung erfolgreich, Zugriff gewähren oder Bestellung versenden) und failure (Fehler oder Ablehnung der Zahlung durch die Bank).
Wichtig

Der Status expired (Ablaufdatum des Links überschritten) löst keinen Webhook aus. Wenn der Kunde die Zahlungsseite schließt und nicht zahlt, muss der Bestellstatus über ein Backup-Polling der API überprüft werden.


2. Rechtliche Voraussetzungen und Compliance-Checkliste für Einzelunternehmer (FOP)

Selbst wenn der Zahlungscode fehlerfrei geschrieben ist, aktiviert der Finanzüberwachungs- und Sicherheitsdienst von Monobank die Live-Zahlungsannahme nicht, wenn die Website nicht den Anforderungen der ukrainischen Gesetzgebung und den Regeln der Zahlungssysteme Visa/Mastercard entspricht.

2.1. Bankvoraussetzungen

  • Offenes FOP- oder juristisches Personenkonto bei Monobank: Acquiring wird ausschließlich an Geschäftskonten angebunden. Die Annahme kommerzieller Zahlungen auf persönliche schwarze oder weiße Karten von Privatpersonen ist gesetzlich verboten.
  • Passende KVED-Codes: In den Registrierungsdaten des FOP müssen Codes für Online-Handel oder Dienstleistungen angegeben sein (z. B. 47.91 – Einzelhandel über das Internet, 62.01/62.02 – Computerprogrammierung und Beratung, 85.59 – Sonstige Bildungsformen).

2.2. Obligatorische Checkliste der Seiten vor Einreichung zur Moderation

Bevor Sie den Antrag auf Aktivierung des Internet-Terminals stellen, stellen Sie sicher, dass folgende Abschnitte auf Ihrer Website vorhanden sind (üblicherweise im Footer platziert):

  • Öffentliches Angebot (Public Offer Agreement): Beschreibung des Vertragsgegenstands, Zeitpunkt des Vertragsschlusses, Rechte und Pflichten der Parteien.
  • Datenschutzrichtlinie (Privacy Policy): Klare Formulierung darüber, welche Kundendaten gesammelt werden und wie sie gemäß dem Gesetz „Über den Schutz personenbezogener Daten“ geschützt werden.
  • Rückgabe- und Lieferbedingungen: Verfahren für die Rückgabe von Waren oder Geld innerhalb von 14 Tagen gemäß dem Gesetz „Über den Schutz der Verbraucherrechte“, sowie für digitale Produkte/Abonnements – Regeln für den Widerruf der Dienstleistung.
  • Vollständige Unternehmensangaben im Website-Footer: Name des FOP oder der GmbH, Steuer-ID / EDRPOU, juristische Adresse, Kontakttelefonnummer und funktionierende Support-E-Mail-Adresse.
  • Transparente Preise und Beschreibungen: Jede Zahlungsschaltfläche muss einen festen Betrag in Hrywnja (UAH) und eine verständliche Beschreibung dessen enthalten, wofür der Kunde genau bezahlt.
Warnung

Wenn auf der Website kein öffentliches Angebot, keine FOP-Angaben vorhanden sind oder statt Preisen Platzhalter wie „nach Vereinbarung“ stehen, lehnt der Sicherheitsdienst von Monobank die Registrierung des Terminals im Prüfprozess ab.


3. Erstellung des Web-Terminals im Monobank Business-Konto

Für die Interaktion mit der Zahlungs-API benötigen Sie einen persönlichen Zugriffsschlüssel – X-Token. Er wird kostenlos im persönlichen Unternehmerkonto generiert.

3.1. Schritt-für-Schritt-Algorithmus zur Kasseneröffnung

  1. Autorisierung: Gehen Sie zu web.monobank.ua → und melden Sie sich per QR-Code über die Monobank-App auf Ihrem Smartphone an.
  2. Wechsel zur Kasse: Wählen Sie im linken Navigationsmenü den Abschnitt „Kasse“.
  3. Tool hinzufügen: Klicken Sie auf die Schaltfläche „+ Tool hinzufügen“ und wählen Sie die Option „Zahlung auf der Website (Eigenentwicklung)“.
  4. Terminalregistrierung: Geben Sie den Projektnamen ein (z. B. Meine Website oder Hauptterminal) und bestätigen Sie den Vorgang mit der Schaltfläche „Verbinden“.
  5. Generierung des API-Tokens: Öffnen Sie das erstellte Terminal, wechseln Sie zum Reiter „Integrationen / API Keys“, klicken Sie auf „Token erstellen“ und kopieren Sie den generierten Schlüssel.

📍 Navigation im Konto: web.monobank.ua → Kasse → + Tool hinzufügen → Zahlung auf der Website (Eigenentwicklung)

Erstellung des Zahlungstools im Monobank Kasse-Konto ЗбільшитиErstellung des Zahlungstools im Monobank Kasse-KontoErstellung des Zahlungstools im Monobank Kasse-Konto

📍 Empfang des API-Schlüssels: Kasse → Ihr Terminal → Integration → X-Token erstellen

Modalfenster zur Erstellung und zum Kopieren des X-Token-Zugriffsschlüssels ЗбільшитиModalfenster zur Erstellung und zum Kopieren des X-Token-ZugriffsschlüsselsModalfenster zur Erstellung und zum Kopieren des X-Token-Zugriffsschlüssels

3.2. Eiserne Regeln für die Token-Sicherheit

  • Kein Hardcoding im Client-Code: X-Token gewährt direkten Zugriff auf die Verwaltung Ihrer Finanzen und Rückerstattungen. Es ist strengstens verboten, ihn in offenem JavaScript/HTML zu speichern oder in öffentlichen GitHub-Repositorien zu veröffentlichen.
  • Verwendung von Umgebungsvariablen: Speichern Sie das Token ausschließlich in der Datei .env auf dem Server unter dem Namen MONOBANK_TOKEN oder im Secrets-Bereich Ihrer Hosting-Plattform (Vercel, Render, Railway, Replit, Lovable).
  • Test-Token für die Entwicklung: Für erste Experimente stellt die Bank einen separaten Test-Token auf der Seite api.monobank.ua → bereit, der die Simulation von Transaktionen ohne echte Abbuchungen ermöglicht.

4. Offizielle Monobank AI-Prompts für Vibe-Coder

Das Monobank-Team hat einen Satz offizieller System-Prompts für AI-Agenten entwickelt. Ihr Hauptvorteil liegt in der exakten Übereinstimmung mit den aktuellen Endpunkten der Bank.

Seite der offiziellen Monobank-Dokumentation für AI-Tools ЗбільшитиSeite der offiziellen Monobank-Dokumentation für AI-ToolsSeite der offiziellen Monobank-Dokumentation für AI-Tools
Warnung

Die wichtigste Anfängerfalle – Betrag in Kopeken: Die Monobank API akzeptiert alle Beträge ausschließlich in minimalen Währungseinheiten (Kopeken). $100\text{ UAH} = 10,000\text{ Kopeken}$. Wenn Sie amount: 100 übergeben, zahlt der Kunde nur 1 Hrywnja.

4.1. Basis-Prompt zur Erstellung der Zahlung

Kopieren Sie diesen Prompt und senden Sie ihn im Chat Ihres AI-Assistenten:

markdown
Я хочу додати на свій сайт можливість приймати платежі через Monobank. ЗАДАЧА: Напиши повний готовий код для створення платежу та перенаправлення користувача на оплату. ЩО МАЄ СТАТИСЯ: 1. На моєму сайті кнопка "Перейти до оплати". 2. Користувач клацає → створюється платіж в Monobank. 3. Користувач переходить на сторінку оплати Monobank. 4. Після оплати він повертається на мій сайт (https://mysite.com/payment-result). 5. Я перевіряю статус платежу та показую результат. ТЕСТОВІ ДАНІ: - Сума: 100 грн (10000 копійок) - Опис платежу: "Оплата замовлення" - Куди повертати користувача: https://mysite.com/payment-result МІЙ ТОКЕН MONOBANK: Змінна оточення MONOBANK_TOKEN з файлу .env ДОКУМЕНТАЦІЯ: - Створення платежу: https://monobank.ua/api-docs/acquiring/methods/ia/post--api--merchant--invoice--create - Перевірка статусу: https://monobank.ua/api-docs/acquiring/methods/ia/get--api--merchant--invoice--status Напиши весь код для цього flow.

4.2. Prompt zur Einrichtung des Webhook-Handlers

Ohne Webhook erfährt der Server nicht von der erfolgreichen Zahlung, wenn der Kunde den Browser sofort nach der Abbuchung schließt:

markdown
Мені потрібно, щоб мій сервер автоматично дізнавався, коли користувач оплатив замовлення. ЗАДАЧА: Налаштуй автоматичне отримання інформації про платежі від Monobank (webhook). ЩО МАЄ СТАТИСЯ: 1. Користувач сплачує на сторінці Monobank. 2. Monobank автоматично надсилає POST-запит на мій сервер. 3. Мій сервер отримує дані: invoiceId, статус (success/failure), суму. 4. Сервер перевіряє криптографічний підпис (ECDSA SHA-256 через заголовок x-sign). 5. Оновлює статус замовлення в базі даних та логує результат. ВАЖЛИВО: - Обов'язково валідуй заголовок x-sign за допомогою публічного ключа банку. - Обробляй сире тіло запиту (raw body Buffer / raw string), а не розпарсений JSON. - Обробляй статуси: success, failure, processing, hold, expired. - Забезпеч ідемпотентність: якщо один і той самий вебхук прийде двічі, не дублюй видачу товару. Напиши весь код для надійного webhook-обробника.

5. Skills-Paket monobank-acquiring: Optimierung des Agenten

Wenn man sich auf einen kurzen Prompt beschränkt, schreibt der AI-Assistent einen Basiscode (ca. 6,8 von 10 Punkten): Der Button funktioniert zwar, aber der Code enthält keine Überprüfung der kryptografischen Signaturen, keinen Schutz gegen Preismanipulation und keine Behandlung seltener Netzwerkfehler.

Um Produktionsniveau (9,8–10 Punkte) zu erreichen, wird im Projektstammverzeichnis ein spezialisiertes Skills-Paket monobank-acquiring hinzugefügt.

Audit der Zahlungsintegration: Vergleich der Sicherheit vor und nach Nutzung des Skills ЗбільшитиAudit der Zahlungsintegration: Vergleich der Sicherheit vor und nach Nutzung des SkillsAudit der Zahlungsintegration: Vergleich der Sicherheit vor und nach Nutzung des Skills
bash
# Розпакування пакета скілів у проєкт curl -L -o monobank-acquiring.zip https://gotburnout.io/downloads/monobank-acquiring.zip unzip monobank-acquiring.zip -d monobank-acquiring/

5.1. Anatomie und Struktur des Skills-Pakets

Skill-DateiInhalt und zuständige Aufgaben
SKILL.mdZentrales Manifest: Basis-Flow, X-Token-Autorisierung, Datentypen und Fehlerbehandlung für 400, 403, 429, 500.
quickstart.mdSchnellstart: Schritt-für-Schritt-Tutorial zur Erstellung einer Rechnung und zum Fallback-Polling mit fertigen curl-Befehlen.
invoice.mdRechnungslebenszyklus: Endpunkte zur Erstellung der Rechnung, Statusprüfung, Stornierung und Ungültigerklärung von Links.
webhook.mdKryptografischer Schutz: Mathematisch exakte Verifizierung der ECDSA SHA-256-Signatur aus dem Header x-sign.
payment.mdDirekte Zahlungen: Abbuchung mit gespeichertem Kartentoken, synchrone Transaktionen und 3DS-Prüfung.
wallet.mdKartentokenisierung (Wallet): Sichere Speicherung des Zahlungsmittels des Kunden im Bankspeicher für One-Click-Zahlungen.
fiscal.mdQuittungen und pRRO: Beschreibung der Warenkorbstruktur basketOrder, Berechnung von Steuern, Rabatten und Export der Quittungen als PDF.
statement.mdKontoauszüge und Analysen: Abruf des Registers erfolgreicher Operationen für den gewählten Zeitraum inklusive Berechnung der Bankgebühren.
merchant.mdHändlerdaten: Abruf des öffentlichen Schlüssels der Bank, Verwaltung von Sub-Merchants und Kassierern.
examples/Fertige Server: Arbeitsbeispiele für Server in 6 populären Sprachen (Node.js, Python, Go, PHP, C#, Java).

6. Praktische Umsetzung: Dynamischer Zahlungsfluss

Jedes Projekt hat seine eigene Struktur: Manche verkaufen digitale Beratungen mit festen Tarifen, andere haben einen Online-Shop mit dynamischem Warenkorb, wieder andere einen einfachen Landing Page mit einer einzigen Spenden- oder Abonnement-Schaltfläche.

Der häufigste Anfängerfehler besteht darin, den Betrag (z. B. 1000 UAH) hart in den Client-Code des Buttons oder in den Body der POST-Anfrage vom Browser einzubetten. Dieser Ansatz erzeugt eine kritische Sicherheitslücke.

6.1. Sicherheitsprinzip: Dynamischer Preis statt Hardcoding

  • Vertrauen Sie niemals dem Betrag aus dem Browser: Wenn das Client-JavaScript { price: 1000 } an den Server sendet, kann ein Angreifer über den Code-Inspektor (DevTools) oder Postman diesen Wert auf { price: 1 } ändern und den Zugriff für 1 Hrywnja erwerben.

  • Server als einzige Wahrheitsquelle (Single Source of Truth): Das Frontend übergibt an das Backend nur die Produkt-ID (productId), den gewählten Tarif (planId: "pro") oder ein Array von Warenkorb-IDs (items: [{ id: "book_1", qty: 2 }]).

  • Automatische Konvertierung in Kopeken: Der Server ruft den aktuellen Preis aus der Konfigurationsdatei oder Datenbank ab und multipliziert ihn selbstständig mit 100:

    $$\text{amount} = \text{Math.round}(\text{realPrice} \times 100)$$ Wenn Sie den Preis in der Datenbank ändern oder einen Rabatt ankündigen, wird die Monobank-Rechnung mit dem neuen korrekten Betrag generiert, ohne dass Sie in das Zahlungs-Gateway eingreifen müssen.

6.2. Universeller AI-Prompt zur Anpassung an jedes Projekt

Kopieren Sie diesen Prompt und senden Sie ihn im Chat Ihres AI-Agenten (Codex, Antigravity, Cursor oder Claude Code). Der Agent scannt selbst die Dateien Ihrer Website, findet vorhandene Buttons und Preise und erstellt eine robuste Integration:

markdown
У корінь нашого проєкту додано офіційний пакет знань Monobank Acquiring у папку /monobank-acquiring. ЗАДАЧА: 1. Проаналізуй архітектуру нашого проєкту: знайди, де у нас описані товари, послуги, тарифи чи кнопки оформлення замовлення/оплати. 2. Створи або інтегруй захищений серверний ендпоінт створення платежу Monobank: - Клієнт надсилає ТІЛЬКИ ідентифікатор товару/тарифу (наприклад, tariffId або productId), але НЕ суму. - Сервер знаходить реальну актуальну ціну з нашого конфігу чи бази даних і переводить її у копійки (ціна * 100). - Токен береться безпечно з process.env.MONOBANK_TOKEN. - Робиться POST-запит до https://api.monobank.ua/api/merchant/invoice/create. - Клієнту повертається посилання pageUrl для переходу на чекаут банку. 3. Онови наші існуючі кнопки оплати на фронтенді: - Додай статус завантаження (індикатор Loader та блокування кнопки від повторного кліку). - При успішній відповіді здійснюй плавний редирект користувача на отримане посилання Monobank. - Додай обробку помилок з інтуїтивними сповіщеннями для користувача. 4. Створи сторінку результату оплати (/payment-result), яка перевіряє статус замовлення та інформує користувача про успіх. 5. Напиши юніт-тести для перевірки динамічного розрахунку сум і створення інвойсу. Усі технічні вимоги, структури запитів та коди валюти бери з файлів у папці /monobank-acquiring (особливо SKILL.md та invoice.md).
Arbeit des AI-Agenten in der IDE mit Analyse der Struktur und Erstellung des dynamischen Endpunkts ЗбільшитиArbeit des AI-Agenten in der IDE mit Analyse der Struktur und Erstellung des dynamischen EndpunktsArbeit des AI-Agenten in der IDE mit Analyse der Struktur und Erstellung des dynamischen Endpunkts

6.3. Architekturmuster des Backends zur Rechnungsstellung

Hier sind geprüfte Server-Implementierungen für die beiden beliebtesten Web-Stacks:

typescript
// app/api/checkout/create-invoice/route.ts import { NextResponse } from "next/server"; const PRODUCTS_CATALOG: Record<string, { title: string; priceUah: number }> = { plan_starter: { title: "Тариф Starter", priceUah: 490 }, plan_pro: { title: "Тариф Pro", priceUah: 990 }, plan_vip: { title: "Тариф VIP", priceUah: 2490 }, }; export async function POST(req: Request) { try { const { productId } = await req.json(); // 1. Валідація: ціна формується виключно на бекенді const product = PRODUCTS_CATALOG[productId]; if (!product) { return NextResponse.json({ error: "Обраний товар або тариф не знайдено" }, { status: 400 }); } const amountInKopecks = Math.round(product.priceUah * 100); const orderReference = `order_${productId}_${Date.now()}`; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://mysite.com"; // 2. Запит до Monobank API const response = await fetch("https://api.monobank.ua/api/merchant/invoice/create", { method: "POST", headers: { "X-Token": process.env.MONOBANK_TOKEN!, "Content-Type": "application/json", }, body: JSON.stringify({ amount: amountInKopecks, ccy: 980, // Гривня (ISO 4217) merchantPaymInfo: { reference: orderReference, destination: `Оплата: ${product.title}`, comment: `Замовлення ${orderReference}`, }, redirectUrl: `${siteUrl}/payment-result?ref=${orderReference}`, webHookUrl: `${siteUrl}/api/payment/webhook`, validity: 3600, // Рахунок діє 1 годину }), }); const data = await response.json(); if (!response.ok) { return NextResponse.json({ error: data.errText || "Помилка створення рахунку в банку" }, { status: response.status }); } return NextResponse.json({ checkoutUrl: data.pageUrl, invoiceId: data.invoiceId }); } catch (error) { return NextResponse.json({ error: "Внутрішня помилка ініціалізації платежу" }, { status: 500 }); } }

7. Sichere Webhook-Verarbeitung und kryptografische ECDSA-Signatur

Der verantwortungsvollste Teil jeder finanziellen Integration ist die Verifizierung der Zahlungsbenachrichtigungen. Ein Angreifer könnte eine gefälschte HTTP-Anfrage an Ihre Adresse /api/payment/webhook mit einer falschen Erfolgsmeldung senden.

Um dies zu verhindern, signiert Monobank jeden Webhook mit dem asymmetrischen Algorithmus ECDSA (Kurve secp256r1 / SHA-256) und übergibt die Signatur im HTTP-Header x-sign.

7.1. Warum JSON.stringify die Signaturverifizierung bricht

Warnung

Kritische rawBody-Falle: Für die Überprüfung der ECDSA-Signatur wird der strikt originale Byte-Stream benötigt, den der Monobank-Server gesendet hat. Wenn Sie versuchen, JSON zu parsen und erneut JSON.stringify(req.body) aufzurufen, ändern sich die Reihenfolge der Schlüssel, Leerzeichen oder Zeilenumbrüche. Dies führt zu einem anderen SHA-256-Hash, und die Signaturprüfung garantiert einen Fehler!

7.2. Implementierung der Webhook-Verifizierung

Unten finden Sie die korrekte Implementierung zum Abrufen des rohen Bodies und zur Verifizierung für Next.js App Router und Express:

typescript
// app/api/payment/webhook/route.ts import { NextResponse } from "next/server"; import crypto from "crypto"; let cachedPubKey: string | null = null; async function getMonobankPubKey(token: string): Promise<string> { if (cachedPubKey) return cachedPubKey; const res = await fetch("https://api.monobank.ua/api/merchant/pubkey", { headers: { "X-Token": token }, next: { revalidate: 86400 }, // Кешуємо публічний ключ на 24 години }); const data = await res.json(); cachedPubKey = `-----BEGIN PUBLIC KEY-----\n${data.key}\n-----END PUBLIC KEY-----`; return cachedPubKey; } export async function POST(req: Request) { const signature = req.headers.get("x-sign"); if (!signature) { return new NextResponse("Missing x-sign header", { status: 400 }); } // 1. Отримуємо сире незмінене тіло запиту у вигляді тексту const rawBody = await req.text(); try { const pubKey = await getMonobankPubKey(process.env.MONOBANK_TOKEN!); // 2. Верифікуємо підпис ECDSA SHA-256 const verifier = crypto.createVerify("SHA256"); verifier.update(rawBody); const isValid = verifier.verify(pubKey, Buffer.from(signature, "base64")); if (!isValid) { console.error("Вебхук відхилено: недійсний підпис x-sign"); return new NextResponse("Invalid signature", { status: 400 }); } // 3. Парсимо дані тільки після успішної перевірки криптографії const payload = JSON.parse(rawBody); const { invoiceId, status, amount, reference } = payload; if (status === "success") { // Тут активуємо замовлення в базі даних з перевіркою на дублікати (ідемпотентність) console.log(`Замовлення ${reference} (${invoiceId}) оплачено на суму ${amount / 100} грн`); } return new NextResponse("OK", { status: 200 }); } catch (error) { console.error("Помилка обробки вебхука:", error); return new NextResponse("Internal verification error", { status: 500 }); } }

8. Lokales Testen von Webhooks (Localhost & Cloudflare Tunnels)

Ein verbreitetes Problem bei Vibe-Codern: Beim Starten des Servers auf http://localhost:3000 kann die Bank den Webhook nicht zustellen, da der lokale Rechner keine öffentliche IP-Adresse im Internet hat.

Monobank sendet Webhooks ausschließlich an öffentliche Adressen mit gültigem HTTPS-Protokoll. Um den vollständigen Zyklus auf Ihrem Computer zu testen, richten Sie einen sicheren Tunnel ein.

8.1. Schneller Tunnel-Start (ohne Registrierung und kostenlos)

Nutzen Sie Cloudflare Tunnel (cloudflared) oder untun:

bash
# Миттєвий HTTPS-тунель на 3000 порт без встановлення утиліт npx untun@latest tunnel --port 3000

Nach dem Start erhalten Sie eine temporäre öffentliche HTTPS-Adresse, z. B.: https://your-tunnel-name.trycloudflare.com

8.2. Einstellung des Webhooks für lokale Tests

Geben Sie im Code zur Rechnungsstellung während der Entwicklung die erhaltene Adresse an:

typescript
webHookUrl: "https://your-tunnel-name.trycloudflare.com/api/payment/webhook"

Nun sendet die Bank bei einer Testzahlung den echten Webhook direkt an Ihren lokalen Server im Terminal, und Sie können sicherstellen, dass die Signatur x-sign die Prüfung erfolgreich passiert.


9. UX der Rückleitungsseite und Lösung der Race Condition

Wenn der Kunde die Rechnung über die Monobank-App oder Apple Pay bezahlt, leitet die Bank ihn sofort auf die redirectUrl (/payment-result?ref=...) weiter.

Der Netzwerk-Webhook vom Bankserver zu Ihrem Server kann jedoch aufgrund von Netzwerk-Routings um 1–2 Sekunden verzögert sein. Wenn die Ergebnis-Seite den Status sofort in der Datenbank prüft, riskiert sie die Anzeige: „Bestellung nicht bezahlt“, was beim Kunden Panik auslöst (Geld wurde von der Karte abgebucht, aber die Website meldet fehlende Zahlung).

9.1. Ingenieursmuster zur Lösung der Race Condition

  1. Initialer Ladestatus: Die Seite öffnet sich mit neutralem Status: „Zahlung bei der Bank bestätigen...“ und einem animierten Indikator.
  2. Kurzes Polling (Short Polling): Das Frontend führt bis zu 5 schnelle Anfragen an die eigene API alle 1,5 Sekunden durch (/api/orders/check-status?ref=...), während darauf gewartet wird, dass der Webhook den Bestellstatus in der Datenbank auf success ändert.
  3. Fallback: Wenn der Webhook nach 8 Sekunden nicht eingetroffen ist, sieht der Kunde die Meldung: „Zahlung zur Bearbeitung angenommen. Sobald die Bank die Transaktion bestätigt, wird der Zugriff automatisch freigeschaltet.“

9.2. Fertige React-Komponente der Ergebnis-Seite

tsx
// app/payment-result/page.tsx "use client"; import { useEffect, useState } from "react"; import { useSearchParams, useRouter } from "next/navigation"; export default function PaymentResultPage() { const searchParams = useSearchParams(); const router = useRouter(); const ref = searchParams.get("ref"); const [status, setStatus] = useState<"checking" | "success" | "pending" | "failed">("checking"); useEffect(() => { if (!ref) { setStatus("failed"); return; } let attempts = 0; const maxAttempts = 5; const interval = setInterval(async () => { attempts++; try { const res = await fetch(`/api/orders/status?ref=${encodeURIComponent(ref)}`); const data = await res.json(); if (data.status === "success") { clearInterval(interval); setStatus("success"); } else if (attempts >= maxAttempts) { clearInterval(interval); // Якщо вебхук затримався, переводимо в статус очікування, а не помилки setStatus(data.status === "processing" ? "pending" : "pending"); } } catch (err) { if (attempts >= maxAttempts) { clearInterval(interval); setStatus("pending"); } } }, 1500); return () => clearInterval(interval); }, [ref]); return ( <div className="max-w-md mx-auto my-16 p-8 rounded-2xl bg-neutral-900 border border-neutral-800 text-center text-white"> {status === "checking" && ( <div> <div className="w-12 h-12 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" /> <h2 className="text-xl font-semibold mb-2">Підтверджуємо оплату...</h2> <p className="text-sm text-neutral-400">Отримуємо статус транзакції від банку. Зачекайте кілька секунд.</p> </div> )} {status === "success" && ( <div> <div className="w-12 h-12 bg-emerald-500/20 text-emerald-400 rounded-full flex items-center justify-center mx-auto mb-4 text-2xl font-bold">✓</div> <h2 className="text-xl font-semibold mb-2">Оплата успішна!</h2> <p className="text-sm text-neutral-400 mb-6">Ваше замовлення #{ref} успішно підтверджено.</p> <button onClick={() => router.push("/dashboard")} className="px-6 py-2.5 rounded-xl bg-amber-500 hover:bg-amber-400 text-black font-semibold transition"> Перейти до кабінету </button> </div> )} {status === "pending" && ( <div> <div className="w-12 h-12 bg-amber-500/20 text-amber-400 rounded-full flex items-center justify-center mx-auto mb-4 text-2xl font-bold">⏳</div> <h2 className="text-xl font-semibold mb-2">Платіж в обробці</h2> <p className="text-sm text-neutral-400 mb-6">Кошти зарезервовано. Підтвердження надійде протягом 1–2 хвилин.</p> <button onClick={() => router.push("/")} className="px-6 py-2.5 rounded-xl bg-neutral-800 hover:bg-neutral-700 text-white font-medium transition"> На головну </button> </div> )} </div> ); }

10. Erweiterte Funktionen: Integriertes pRRO, Hold und Wallet

Monobank Acquiring bietet ein umfassendes Spektrum an Tools für komplexere Geschäftsmodelle:

Finale Monobank Hosted Checkout-Zahlungsseite mit Apple Pay und Karten ЗбільшитиFinale Monobank Hosted Checkout-Zahlungsseite mit Apple Pay und KartenFinale Monobank Hosted Checkout-Zahlungsseite mit Apple Pay und Karten

10.1. Software-RRO: Kostenloser integrierter Checkbox in der Monobank Kasse

Für die meisten ukrainischen FOPs der Gruppen 2 und 3 ist die Fiscalisierung von Online-Verkäufen eine gesetzliche Anforderung.

Der Hauptvorteil von Monobank für Unternehmer ist die kostenlose integrierte Integration mit dem pRRO-Dienst Checkbox:

  • Aktivierung mit einem Klick: Im Konto web.monobank.ua gehen Sie zu den Einstellungen des erstellten Terminals und aktivieren den Schalter „Fiscalisierung über Checkbox“. Monobank erstellt die Kasse kostenlos selbst und signiert die Quittungen mit Ihrer KEP.
  • Automatische Fiscalisierung: Wenn Sie einen Standard-Warenkatalog mit einheitlichem Steuersatz haben, müssen Sie den Code zur Zahlungsstellung nicht einmal ändern – die Quittung wird automatisch basierend auf dem Feld destination im Verwendungszweck gebildet.
  • Erweiterte Fiscalisierung über API: Wenn Sie Waren mit unterschiedlichen MwSt-Sätzen oder akzisepflichtige Positionen mit UKTZED-Codes verkaufen, übergeben Sie das Array basketOrder innerhalb des Objekts merchantPaymInfo:
json
"merchantPaymInfo": { "reference": "order_1001", "destination": "Оплата онлайн-курсу", "customerEmails": ["client@example.com"], "basketOrder": [ { "name": "Доступ до курсу з вайбкодингу", "qty": 1, "sum": 99000, "code": "SKU-COURSE-01", "unit": "шт.", "total": 99000 } ] }

10.2. Zweistufige Zahlung (Hold)

Wenn Sie physische Waren verkaufen, die möglicherweise nicht auf Lager sind, nutzen Sie den Hold-Modus:

  • Blockierung: Übergeben Sie paymentType: "hold" bei der Erstellung der Rechnung. Das Geld wird auf der Karte des Käufers für bis zu 9 Tage eingefroren.
  • Abbuchung (Finalize): Nach Prüfung der Verfügbarkeit rufen Sie /api/merchant/invoice/finalize auf. Sie können den vollen Betrag oder einen geringeren abbuchen (z. B. wenn eine Position fehlt).
  • Stornierung: Wenn die Ware nicht verfügbar ist, rufen Sie /api/merchant/invoice/cancel auf – die Zahlung wird storniert, ohne dass für den Käufer Bankgebühren anfallen.

10.3. Kartenspeicherung und Abonnements (Wallet)

Wenn Sie einen SaaS-Dienst mit regelmäßiger monatlicher Abonnementgebühr starten, übergeben Sie saveCardData: true bei der Erstellung der ersten Rechnung. Nach erfolgreicher Zahlung wird im Webhook die walletId zurückgegeben. Nutzen Sie dieses Token für zukünftige automatische Abbuchungen, ohne dass der Kunde die Kartendaten erneut eingeben muss.


11. Sicherheitsmatrix und automatische Tests (Vitest / Jest)

Zahlungen sind der Bereich maximaler finanzieller Verantwortung. Ein Fehler in einem normalen Button führt zu Ärgernis, aber ein Fehler im Zahlungs-Gateway führt zu direkten materiellen Verlusten oder zur Sperrung der Kasse durch die Bank.

Führen Sie vor dem Wechsel in den Live-Betrieb die Sicherheits-Testmatrix durch:

SicherheitstestWas genau geprüft wirdErwartetes Systemverhalten
1. Schutz vor Preisänderung (Price Tampering)Der Kunde sendet productId: "vip", versucht aber, im Request amount: 100 (1 UAH) zu manipulierenDer Server ignoriert das Feld amount vom Client, nimmt den realen Preis aus der Konfiguration (2490 UAH = 249 000 Kopeken). Wenn productId gefälscht ist – gibt HTTP 400 zurück.
2. Blockierung ohne SignaturAuf den Endpunkt /api/payment/webhook kommt eine POST-Anfrage ohne Header x-signDie Anfrage wird sofort mit Antwort HTTP 400 Bad Request blockiert. Keine Bestellverarbeitung findet statt.
3. Blockierung gefälschter SignaturenEin Angreifer hat eine Fake-Signatur in x-sign mit Status status: "success" gesendetDie Prüfung crypto.verify(SHA256, ...) gibt false zurück. Der Server antwortet mit HTTP 400/401, Zugriff oder Ware werden nicht gewährt.
4. Idempotenz des Webhooks (Duplicate Delivery)Monobank hat denselben success-Webhook zweimal oder dreimal hintereinander wegen Netzwerk-Lag gesendetZugriff oder Ware werden nur einmal aktiviert. Der wiederholte Webhook löst keine doppelte Ausgabe aus und gibt der Bank HTTP 200 OK zurück.
5. Resistenz gegen Race ConditionsDer Webhook der Bank kam ein, bevor die ursprüngliche HTTP-Anfrage zur Rechnungsstellung in der DB abgeschlossen warDer Webhook-Handler ist resistent gegen fehlende Datensätze in der DB (nutzt UPSERT oder erstellt die Bestellung on-the-fly).
6. Einhaltung der Rate Limits (Rate Limit Buffer)Bei Ausfall des Webhooks startet das Status-Polling /api/merchant/invoice/statusAnfragen werden mit einer Pause von mindestens 15 Sekunden ausgeführt, was das Terminal vor der Blockierung HTTP 429 Too Many Requests schützt.

11.1. Fertiger Satz automatischer Tests für Ihr Projekt

Bitten Sie Ihren AI-Agenten, den folgenden Testsatz zum Projekt hinzuzufügen (monobank-acquiring.test.ts):

typescript
import { describe, it, expect } from "vitest"; import crypto from "crypto"; // Генерація тестової пари ECDSA secp256r1 для симуляції підписів Monobank const { publicKey, privateKey } = crypto.generateKeyPairSync("ec", { namedCurve: "prime256v1", publicKeyEncoding: { type: "spki", format: "pem" }, privateKeyEncoding: { type: "pkcs8", format: "pem" }, }); describe("Monobank Acquiring Security Tests", () => { it("Тест 1: Не дозволяє клієнту маніпулювати ціною", () => { const CATALOG: Record<string, { priceUah: number }> = { plan_pro: { priceUah: 990 } }; const clientPayload = { productId: "plan_pro", amount: 100 }; // Спроба підсунути 1 грн const safeAmount = Math.round(CATALOG[clientPayload.productId].priceUah * 100); expect(safeAmount).toBe(99000); // 990.00 грн, а не 1.00 грн }); it("Тест 2: Відхиляє вебхук без обов'язкового заголовка x-sign", () => { const headers: Record<string, string> = {}; const hasSignature = Boolean(headers["x-sign"]); expect(hasSignature).toBe(false); }); it("Тест 3: Успішно верифікує валідний цифровий підпис банку", () => { const rawPayload = JSON.stringify({ invoiceId: "inv_123", status: "success", amount: 99000 }); const signer = crypto.createSign("SHA256"); signer.update(rawPayload); const validSignatureBase64 = signer.sign(privateKey, "base64"); const verifier = crypto.createVerify("SHA256"); verifier.update(rawPayload); const isValid = verifier.verify(publicKey, Buffer.from(validSignatureBase64, "base64")); expect(isValid).toBe(true); }); it("Тест 4: Блокує підроблений підпис або модифіковане тіло запиту", () => { const originalPayload = JSON.stringify({ invoiceId: "inv_123", status: "success", amount: 99000 }); const tamperedPayload = JSON.stringify({ invoiceId: "inv_123", status: "success", amount: 1000 }); const signer = crypto.createSign("SHA256"); signer.update(originalPayload); const signature = signer.sign(privateKey, "base64"); const verifier = crypto.createVerify("SHA256"); verifier.update(tamperedPayload); const isValid = verifier.verify(publicKey, Buffer.from(signature, "base64")); expect(isValid).toBe(false); }); it("Тест 5: Ідемпотентність — повторний вебхук не видає товар двічі", () => { const processedOrders = new Set<string>(); function handleOrder(invoiceId: string): { processed: boolean } { if (processedOrders.has(invoiceId)) { return { processed: false }; // Дубль відхилено } processedOrders.add(invoiceId); return { processed: true }; } expect(handleOrder("inv_001").processed).toBe(true); // Перший вебхук expect(handleOrder("inv_001").processed).toBe(false); // Повторний прихід дубліката expect(processedOrders.size).toBe(1); }); });

12. Finale Ingenieur-Checkliste vor dem Launch

Prüfen Sie Ihr Zahlungsmodul anhand dieser 10 Punkte vor dem Umschalten auf Live-Zahlungen:

  • Website-Compliance: Im Footer sind Links zum Öffentlichen Angebot, zur Datenschutzrichtlinie, zu den Rückgabebedingungen und den vollständigen FOP-Angaben mit Steuer-ID hinzugefügt.
  • Betrag in Kopeken: Alle Werte amount sind durch Math.round mit 100 multipliziert ($1\text{ UAH} = 100\text{ Kopeken}$).
  • Schlüsselsicherheit: Das Token ist in .env unter dem Namen MONOBANK_TOKEN ausgelagert und in .gitignore aufgenommen.
  • Preise vom Backend (SSOT): Der Client übergibt nur die Produkt-ID, der Betrag wird ausschließlich aus der DB oder Konfiguration geholt.
  • RawBody-Verarbeitung: Der Webhook verifiziert den originalen Text- oder Binärbuffer (req.text() oder req.rawBody), um doppeltes Parsen via JSON.stringify zu vermeiden.
  • Kryptografischer Schutz: Der Webhook verifiziert die Signatur x-sign über den öffentlichen Schlüssel der Bank mit dem Algorithmus SHA256 ECDSA.
  • Öffentliche URL für Webhook: Der Server ist extern über ein gültiges SSL-Zertifikat HTTPS erreichbar (für lokale Tests eignet sich Cloudflare Tunnel oder ngrok).
  • Idempotenz: Der erneute Eingang desselben Webhooks löst keine doppelte Ausgabe von Ware oder Abonnement aus.
  • UX der Ergebnis-Seite: Auf /payment-result ist ein Wartelader und kurzes Status-Polling eingerichtet, um den Nutzer vor Race Conditions zu schützen.
  • Echte Zahlung von 1–5 UAH: Eine erfolgreiche Testtransaktion mit echter Karte wurde durchgeführt, die Abbuchung und die Anzeige im Bankkonto wurden überprüft.

Nützliche Ressourcen und Download-Dateien

Dieser Leitfaden ist komplett kostenlos. Wenn er Ihnen einen Abend erspart hat, können Sie das Wachstum des Projekts unterstützen.
Autor unterstützen