← Alle Tutorials

TUTORIAL 4 / 8

Checkout erstellen. Zahlung sicher prüfen.

Rechnungen im Backend erstellen und verifizierte Zahlungsereignisse verarbeiten.

Dein Ziel

Ein serverseitiger Zahlungsablauf mit Schutz vor doppelter Verarbeitung.

1. Bereite IDs und Zugriff vor

Starte mit einem aktiven Store und getesteten Zahlungsmethoden. Erstelle unter Settings → API access einen Lese-/Schreibzugang nur für das nötige Projekt. Der Token bleibt im Backend, nie im Browser-Code oder öffentlichen Repository.

Kopiere Project API ID und Store API ID aus Basic → API IDs im Store. Das sind UUIDs, nicht die lesbare Projektkennung oder deine Bestellnummer. Nutze deine eigene API-Domain.

Erzeuge unter Store → IPN ein Signaturgeheimnis, bevor du ipn_url übergibst. Dein HTTPS-Empfänger muss vom Merchant-VPS erreichbar sein.

2. Erstelle eine Rechnung

Ersetze die Platzhalter und sende diese Anfrage aus deinem Backend. Beträge sind Dezimal-Strings, keine Gleitkomma-Berechnungen.

curl --fail-with-body --request POST \
  'https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices' \
  --header 'Authorization: Bearer YOUR_MERCHANT_API_TOKEN' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: order-1042-attempt-1' \
  --data '{
  "amount": "10.00",
  "currency": "EUR",
  "order_id": "order-1042",
  "description": "Example order",
  "ipn_url": "https://shop.example.com/payments/wholly-ipn",
  "metadata": {
    "cart_id": "cart-1042"
  }
}'

Speichere data.invoice_id bei der Bestellung und leite den Kunden zu links.checkout weiter. Jeder neue Zahlungsversuch bekommt einen eigenen Idempotency-Key. Nach einem Timeout wiederholst du mit demselben Zugang, Key und exakt gleichen Body-Bytes.

Ohne payment_methods gelten die akzeptierten Store-Methoden. Pro Rechnung kannst du sie mit Chain-Slugs und Tickern eingrenzen. Nicht akzeptierte Assets werden dadurch nie aktiviert. Alle Felder und Antwortbeispiele →

3. Wähle IPN, Webhooks oder beides

IPN begleitet den Rechnungsverlauf. Setze eine Standard-IPN-URL im Store oder überschreibe sie pro Rechnung mit ipn_url. Webhooks abonnieren bestimmte Ereignisse, etwa invoice.settled.

Das Signaturformat ist gleich, die Geheimnisse sind verschieden: IPN nutzt das Store-IPN-Geheimnis. Jeder Webhook-Endpunkt hat sein eigenes. Der API-Bearer-Token ist kein Signaturgeheimnis.

Wenn beides deine App erreicht, können sich Benachrichtigungen überschneiden. Schreibe eine Bestellung nicht doppelt gut.

4. Prüfe und speichere die Nachricht

  1. Lies den unveränderten Request-Body vor dem JSON-Parsen. Prüfe Wholly-Signature mit dem passenden Geheimnis samt Zeitstempel-/Replay-Prüfung. Die offiziellen SDKs bieten Verifier.
  2. Prüfe Projekt, Store, Rechnung und Ereignis im signierten Body. Unsignierte Zustellheader sind kein Authentifizierungsnachweis.
  3. Speichere das Ereignis dauerhaft mit eindeutiger event_id und antworte zeitnah mit HTTP 2xx. Verarbeite Bestellungen in einem Hintergrund-Worker.
  4. Rufe die aktuelle Rechnung von deiner fest konfigurierten API-Domain ab, nicht von einer beliebigen Domain aus einer Anfrage. Vergleiche gespeichertes Projekt, Store, Betrag, Währung und Bestellreferenz.

PHP · Python · JavaScript / TypeScript · Signaturformat und Empfängerbeispiele

5. Liefere nach Abschluss einmal aus

Bei einem ereignisbasierten Empfänger verarbeitest du event_type = invoice.settled. Prüfe danach den aktuellen status = settled und deine Ausnahmeregeln. Liefere einmal aus, abgesichert durch Datenbanktransaktion und eindeutige Bestellung.

Ereignis und Zustand sind verschieden

Eine schnell finalisierende Chain kann payment.received und invoice.settled beide mit status = settled senden. Anderswo steht bei payment.received noch processing. Beides ist korrekt.

Erkenne doppelte Ereignisse über event_id, nicht nur die Sequenz. Verschiedene Ereignistypen können dieselbe Sequenz haben. Zustandsbasierte SDK-Handler fassen stattdessen Rechnungsrevisionen zusammen und prüfen den Zustand unabhängig vom Ereignistyp. Kombiniere das nicht mit einem Ereignistyp-Filter. Beide Wege brauchen zusätzlich Schutz pro Bestellung.

Prüfe requires_review und manuelle Entscheidungen. amount_status = paid allein beweist keine Bestätigung. Die obersten Paid-Asset-Felder beschreiben den Abschluss; payment_info enthält detaillierte Eingänge und Kursdaten. Alle Zustände, Ereignisse und Ausnahmen →

6. Teste Wiederholungen und Ausfälle

Teste eine kleine Zahlung, doppelte Zustellung, eine abgelaufene Rechnung und einen zeitweise unerreichbaren Empfänger. Ein wiederholtes Ereignis darf nicht erneut gutschreiben. Ältere Ereignisse dürfen keinen neueren Zustand überschreiben.

Prüfe Store → IPN / Webhooks → History → Details oder die Zustellungen in Invoice Details. Resend nutzt das gespeicherte Ereignis und ist kein neuer Zahlungsabschluss.

Bei wenig Verarbeitungsguthaben pausieren IPN/Webhooks, Zahlungen laufen weiter. Gleiche offene Bestellungen über die API ab und verarbeite zurückgehaltene Nachrichten nach der Wiederherstellung. Liefere nie allein wegen Browser-Weiterleitung oder Kunden-Screenshot aus.