So bestätigen Sie die Zahlung einer Bestellung automatisch per Webhook
Verknüpfen Sie Rechnung und Bestellung, prüfen Sie Webhook-Signaturen und schützen Sie die Freigabe vor Wiederholungen, Unterzahlung und Rücknahmen.
Aktualisiert:
Damit Ihre Website eine Bestellung selbst als bezahlt markiert, erstellen Sie dafür eine Rechnung in Cryptanio und richten einen Webhook ein: eine Benachrichtigung, die der Dienst bei einer Änderung des Zahlungszustands an Ihren Server sendet. Der Server prüft die Echtheit der Nachricht, findet die Bestellung und gleicht den Rechnungsstatus ab. Danach können Sie dem Käufer den bezahlten Zugang freischalten oder die Bestellung zur Ausführung weitergeben.
Als Beispiel betrachten wir einen Shop, der eine Rechnung in US-Dollar als Rechnungseinheit erstellt. Der Käufer wählt die verfügbare Kryptowährung und das Netzwerk auf der Zahlungsseite aus. Die Mittel gehen an die Adressen des Shops. Bestellungen und Warenausgabe verbleiben in der Shop-Anwendung. Cryptanio verknüpft die Rechnung mit der Überweisung und meldet deren Zustand. Für diese Integration benötigen Sie einen Entwickler oder die Möglichkeit, die Serverseite der Website zu ändern.
Bereiten Sie das Projekt und die Zahlungsadressen vor
Wählen Sie im Cryptanio-Konto ein Projekt aus und fügen Sie Empfangsadressen hinzu, die Ihnen gehören. Verbinden Sie sie mit dem Dienst Payments: Eine gewöhnliche Adressüberwachung reicht zum Ausstellen von Rechnungen nicht aus. Prüfen Sie, ob die Adressen aktiv sind und zum gewählten Netzwerk gehören. Für echte Zahlungen benötigen Sie Adressen im Live-Modus.
Planen Sie mehrere Adressen ein, wenn Kunden gleichzeitig Bestellungen bezahlen können. Eine Adresse kann durch eine offene Rechnung belegt oder nach deren Schließung vorübergehend nicht verfügbar sein. Die Integration sollte eine verständliche Fehlermeldung anzeigen, wenn keine freie Adresse vorhanden ist, und einen späteren erneuten Versuch ermöglichen.
Erstellen Sie einen API-Schlüssel und speichern Sie ihn auf dem Shop-Server. Sie benötigen ihn für Anfragen an https://api.cryptanio.com/v1. Der Schlüssel darf weder im JavaScript der Seite noch in einer mobilen Anwendung oder einem öffentlich zugänglichen Repository stehen.
Richten Sie die Empfangsadresse für Benachrichtigungen ein
Öffnen Sie die „Mitteilungen“ des gewählten Projekts. Ist „Einstellungen für Kontobenachrichtigungen verwenden“ aktiviert, konfigurieren Sie Webhook über „Kontoeinstellungen“, oder schalten Sie die Vererbung aus und legen Sie einen eigenen Projektendpunkt fest.
Tragen Sie in der Webhook-Karte die HTTPS-Adresse Ihres Handlers unter „Endpunkt URL“ ein und klicken Sie auf „Speichern“. Der Handler muss POST-Anfragen direkt annehmen; eine Weiterleitung an eine andere URL wird nicht verwendet. Rufen Sie das „Signaturgeheimnis“ in derselben Karte ab und bewahren Sie es auf dem Server getrennt vom API-Schlüssel auf.
Prüfen Sie, dass Webhook nicht pausiert ist. Verarbeiten Sie payment.confirmed und payment.rolled_back in Ihrem Handler und verwenden Sie bei Bedarf payment.pending für einen Zwischenstatus. Prüfen Sie vor der Erfüllung der Bestellung den Live-Modus und die weiteren Bedingungen unten. Ein gesonderter Webhook-Kanal muss für die Rechnungsadresse nicht aktiviert werden.
Erstellen Sie eine Rechnung für eine bestimmte Bestellung
Speichern Sie zuerst die Bestellung in der Datenbank des Shops. Anschließend erstellt der Server über POST /v1/invoices eine Rechnung. Übergeben Sie in reference Ihre eigene Bestellkennung. In diesem Beispiel beträgt der Bestellpreis 12.50 USD: invoice_currency und invoice_amount legen den Preis fest, während der Käufer das verfügbare Netzwerk und den Vermögenswert auf der Zahlungsseite auswählt. Deshalb werden chain, asset, amount, address und tolerance in dieser Anfrage nicht übergeben. Die Angabe von USD bedeutet weder die Annahme von Fiatgeld noch einen Umtausch der eingegangenen Kryptowährung in Dollar.
#!/usr/bin/env bash
set -euo pipefail
: "${CRYPTANIO_API_KEY:?Set CRYPTANIO_API_KEY}"
: "${CRYPTANIO_OPERATION_ID:?Set CRYPTANIO_OPERATION_ID}"
: "${CRYPTANIO_PROJECT_ID:?Set CRYPTANIO_PROJECT_ID}"
json_string() {
local text="$1" char code index output='"'
for ((index=0; index<${#text}; index++)); do
char="${text:index:1}"
case "$char" in
'"'|'\') output+="\\$char" ;;
*) printf -v code '%d' "'$char"
if ((code < 32)); then printf -v char '\\u%04x' "$code"; fi
output+="$char" ;;
esac
done
printf '%s"' "$output"
}
response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
transfer_status=0
http_status="$(curl \
--silent --show-error --max-time 30 --request POST \
--header "Authorization: Bearer $CRYPTANIO_API_KEY" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: $CRYPTANIO_OPERATION_ID" \
--data-binary '{"project":'"$(json_string "$CRYPTANIO_PROJECT_ID")"',"invoice_currency":"USD","invoice_amount":"12.50","reference":"order-42"}' \
"https://api.cryptanio.com/v1/invoices" \
--output "$response_file" --write-out '%{http_code}')" || transfer_status=$?
if [[ "$http_status" == 2?? ]]; then
cat "$response_file"
exit "$transfer_status"
fi
printf 'HTTP %s\n' "$http_status" >&2
cat "$response_file" >&2
exit 1
Setzen Sie vor dem Start auf dem Server CRYPTANIO_API_KEY, CRYPTANIO_PROJECT_ID und CRYPTANIO_OPERATION_ID. Verwenden Sie einen Live-API-Schlüssel mit der Berechtigung zum Erstellen von Rechnungen. CRYPTANIO_PROJECT_ID muss auf das vorbereitete Payments-Projekt verweisen. CRYPTANIO_OPERATION_ID ist die gespeicherte Kennung des Erstellungsvorgangs für diese Bestellung: 16 bis 80 lateinische ASCII-Buchstaben, Ziffern, Bindestriche oder Unterstriche. Das Beispiel übermittelt sie im Header Idempotency-Key, nicht als JSON-Feld. Erstellen Sie für eine neue Bestellung einen anderen Wert. Verwenden Sie bei einer Wiederholung derselben Anfrage den bisherigen Wert weiter.
Bei erfolgreicher Erstellung gibt die API HTTP 201 zurück. Speichern Sie die id der Rechnung und die erhaltene checkout_url bei der Bestellung und leiten Sie den Käufer zu diesem Link weiter. Solange keine Zahlungsart gewählt wurde, sind Netzwerk, Adresse und Kryptobetrag noch nicht festgelegt. Auf der Zahlungsseite wählt der Käufer eine verfügbare Zahlungsart und erhält die aktuellen Zahlungsdaten sowie die Zahlungsfrist. Wenn Sie die Zahlungsdaten in Ihrer eigenen Oberfläche anzeigen, verwenden Sie die Daten des jeweiligen Zahlungsversuchs, den genauen Betrag aus amount und die Frist expires_at. Runden Sie den Betrag nicht und ersetzen Sie ihn nicht durch den Wert aus asked. Links aus späteren GET-Anfragen und aus der Rechnungsliste sind für die Ansicht bestimmt. Speichern Sie den interaktiven Link aus der Erstellungsantwort separat und geben Sie ihn nur an den betreffenden Käufer weiter.
Wenn die Verbindung nach dem Absenden der Anfrage abbricht, senden Sie denselben Anfragekörper mit demselben Idempotency-Key erneut. Eine erfolgreiche Wiederholung gibt HTTP 201 und dieselbe id zurück. Werden Parameter mit einem bereits verwendeten Schlüssel geändert, führt dies zu HTTP 409. reference verknüpft die Rechnung mit der Bestellung, ersetzt aber nicht den Schlüssel für die einmalige Erstellung. Ändern Sie den Schlüssel nicht allein wegen einer Zeitüberschreitung.
Prüfen Sie die Echtheit des eingehenden Webhooks
Cryptanio übermittelt X-Timestamp, X-Signature, X-Event-Id, X-Event-Type und X-Attempt. Die Signatur wird wie folgt berechnet:
hex(HMAC-SHA256(secret, timestamp + "." + raw_body))
raw_body bezeichnet die ursprünglichen Bytes der Anfrage. Lesen Sie diese vor dem Parsen des JSON ein: Eine erneute Serialisierung kann Leerzeichen oder die Reihenfolge der Felder verändern und die Prüfung ungültig machen. Begrenzen Sie die zulässige Größe des Anfragekörpers und weisen Sie Anfragen ohne gültige Signatur zurück.
Im folgenden Beispiel ist ein Zeitfenster von fünf Minuten gewählt. Das ist eine Einstellung Ihres Handlers. Damit sie korrekt funktioniert, muss die Uhr des Servers synchronisiert sein.
import hashlib
import hmac
import re
import time
def verify_signature(secret, timestamp, raw_body, signature):
if not secret or not timestamp or not re.fullmatch(r"[0-9]{1,12}", timestamp):
return False
if not signature or not re.fullmatch(r"[0-9a-f]{64}", signature):
return False
if abs(time.time() - int(timestamp)) > 300:
return False
signed = timestamp.encode("ascii") + b"." + raw_body
expected = hmac.new(secret.encode("utf-8"), signed,
hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Die Funktion prüft die Signatur und den Zeitpunkt der Anfrage. Parsen Sie nach erfolgreicher Prüfung das JSON und prüfen Sie schema_version="v1", die erwartete project_id, mode="live" und sandbox=false.
Verwenden Sie für Entscheidungen event_id und event_type aus dem signierten Nachrichtentext. Sie können die gleichnamigen Header damit abgleichen; diese Header selbst sind nicht durch die Signatur geschützt. Starten Sie die Zahlungslogik nur für das erwartete Objekt payment mit der Richtung in.
Unterscheiden Sie die Bestätigung einer Überweisung von der Bezahlung einer Bestellung
Die wichtigsten Ereignisse für den Shop sind:
payment.pending— die Überweisung wurde erkannt und wartet noch auf Bestätigung;payment.confirmed— die Überweisung hat die erforderlichen Bestätigungen erreicht;payment.rolled_back— eine zuvor erkannte Überweisung wurde aufgrund einer Änderung der Blockchain zurückgenommen.
Das Ereignis payment.confirmed bedeutet noch nicht, dass eine bestimmte Bestellung vollständig bezahlt ist. Prüfen Sie das Objekt invoice und gleichen Sie invoice.id sowie invoice.reference mit der gespeicherten Bestellung ab. Eine Nachricht ohne zugeordnete Rechnung darf nicht allein anhand eines ähnlichen Betrags automatisch einem Kauf zugeordnet werden.
Fragen Sie vor der Warenausgabe den aktuellen Zustand über GET /v1/invoices/{id} ab. Gleichen Sie id, project_id, reference, den Bestellpreis in invoice_currency und invoice_amount sowie sandbox=false ab. Prüfen Sie Netzwerk, Vermögenswert, Adresse und den gewählten Zahlungsversuch. Berücksichtigen Sie bei mehreren Versuchen settled_attempt_id und settled_attempt. Einer erfolgreichen Zahlung entspricht der Rechnungsstatus paid; pending, seen und partial erlauben noch keine Auslieferung. Die Rückkehr des Käufers zur Website oder das Öffnen des Zahlungslinks bestätigt keine Zahlung.
Für Unterzahlungen gibt es underpaid, für eine Teilzahlung mit ausstehendem Restbetrag partial und für verspätete Eingänge late. Bei einer Überzahlung kann die Projektrichtlinie den Rechnungsstatus auf paid belassen oder overpaid mit needs_decision=true setzen. Die Zustände underpaid, overpaid und late erfordern eine gesonderte Entscheidung, bevor die Bestellung ausgeliefert wird. Die Felder amount_raw und paid werden als Zeichenfolgen übermittelt. Parsen Sie sie zum Vergleich als exakte Ganzzahlen in den kleinsten Einheiten des Vermögenswerts; decimals legt die Skalierung fest. Vergleichen Sie die Zeichenfolgen nicht lexikografisch und verwenden Sie keine binären Gleitkommazahlen. Legen Sie die Regeln für Abweichungen vorher fest; eine automatische Rückzahlung ergibt sich daraus nicht.
Verarbeiten Sie Wiederholungen und Rücknahmen
Bei der Zustellung sind wiederholte Nachrichten möglich. Speichern Sie das geprüfte Ereignis in einem dauerhaften Speicher und verwenden Sie event_id als eindeutigen Schlüssel. Ein bereits angenommenes Ereignis muss bei erneuter Zustellung eine erfolgreiche Antwort erhalten, ohne eine weitere Warenausgabe auszulösen.
Die Transaktionskennung allein reicht nicht aus, um alle nachfolgenden Nachrichten auszusortieren: Eine Überweisung durchläuft unterschiedliche Zustände. Speichern Sie für die Kombination aus chain, tx_id, index, address und der Identität des Vermögenswerts die zuletzt angewendete payment.revision. Wenden Sie einen Übergang nur an, wenn die revision größer als die bereits verarbeitete ist.
Speichern Sie die Bestelländerung und das Anlegen des Auslieferungsauftrags konsistent und geschützt gegen parallele Verarbeitung. Der Auslieferungsauftrag muss einen eindeutigen Bestellschlüssel besitzen. Prüfen Sie unmittelbar vor seiner Ausführung, ob die Bestellung weiterhin ausgeliefert werden darf. Wenn eine Rücknahme eintrifft, aktualisieren Sie den Zustand und stoppen Sie eine noch nicht ausgeführte Auslieferung. Für bereits freigeschaltete Zugänge sollten Sie eine erneute Prüfung oder Sperrung vorsehen.
Prüfen Sie den gesamten Ablauf
Prüfen Sie vor einer echten Zahlung das Einlesen des Bodys, die Signatur und den Schutz vor Duplikaten mit vorbereiteten Daten. Ein technisches Ereignis webhook.test bestätigt keinen Kauf und ersetzt keine Prüfung des vollständigen Ablaufs.
Prüfen Sie anschließend mit einer eigenen Kontrollbestellung den vollständigen Ablauf: Rechnungserstellung, Überweisung, Bestätigung und Aktualisierung der Bestellung. Prüfen Sie außerdem anhand vorbereiteter Daten ein wiederholtes Ereignis, eine Unterzahlung und eine Rücknahme. Geben Sie HTTP 2xx erst zurück, nachdem die angenommene Nachricht zuverlässig gespeichert wurde. Wenn sie vorübergehend nicht gespeichert werden kann, verwenden Sie einen Serverfehler, damit die Zustellung wiederholt werden kann.
Gleichen Sie im Konto das Ereignis und das Zustellprotokoll mit dem Eintrag in Ihrer Anwendung ab. Die Prüfung ist abgeschlossen, wenn eine bestätigte Bestellung genau einmal ausgeführt wird und ein vorläufiges, fremdes oder wiederholtes Ereignis keine fehlerhafte Auslieferung auslöst.
Referenz: Rechnungen in API v1, Prüfung der Webhook-Signatur.