Backend Engineering, Integration
E-Commerce-SaaS
2022-2024
Drei Zahlungsanbieter. Drei Architekturen. Ein zuverlässiger Checkout.
Eine SaaS-Plattform für digitale Download-Codes musste Kreditkarten, PayPal und europäische Banküberweisungen akzeptieren. Jeder Anbieter hatte ein grundlegend anderes Integrationsmuster: Braintree verarbeitete Karten synchron in einem einzigen API-Aufruf, PayPal erforderte einen Redirect-and-Return-Flow mit einem separaten Ausführungsschritt, und Sofort operierte vollständig über asynchrone XML-Webhooks. Die ursprüngliche Implementierung behandelte jeden als eigenständigen Codepfad, mit duplizierter Finalisierungslogik und ohne Schutz gegen Preismanipulation zwischen Genehmigung und Ausführung.
Drei Gateways, drei Fehlerszenarien
Jeder Zahlungsanbieter setzte einen anderen Vertrag voraus. Braintree lieferte sofort ein Ergebnisobjekt - Erfolg oder Misserfolg in einem Round-Trip. PayPal erforderte, dass der Benutzer die Seite verließ, auf paypal.com genehmigte und dann zurückkehrte, woraufhin das System einen zweiten API-Aufruf zur Ausführung machen musste. Sofort war am komplexesten: Das System sendete XML, um eine Transaktion zu erstellen, leitete den Benutzer zur Website seiner Bank weiter und wartete dann auf Webhook-Benachrichtigungen, die Minuten bis Stunden später eintrafen. Eine einzelne Bestellung konnte in einem Schwebezustand enden, wenn der PayPal-Redirect ablief, wenn Sofort's Webhook nie ankam, oder wenn der Benutzer sein Code-Paket zwischen Initiierung und Abschluss der Zahlung änderte. Die Finalisierungslogik - Code-Generierung, Rechnungsversand, Empfehlungsprovisionen - war über alle drei Pfade kopiert, jeweils mit leicht unterschiedlicher Fehlerbehandlung.
PayPal leitet dich weiter. Sofort sendet Stunden später einen Webhook. Beide müssen dieselbe Bestellung finalisieren. Der Code war dreifach dupliziert.
Einheitliche Finalisierung mit Gateway-spezifischen Adaptern
Ich habe die Zahlungsschicht um eine klare Trennung herum umstrukturiert: Gateway-spezifische Logik behandelt die Mechanik jedes Anbieters, aber jede erfolgreiche Belastung mündet in einen gemeinsamen Finalisierungspfad. Kreditkartenzahlungen rufen Braintrees API auf und finalisieren inline. PayPal-Zahlungen re-validieren den Preis bei der Rückkehr, bevor sie ausgeführt werden, und fangen jede Manipulation während des Redirect-Fensters ab. Sofort-Zahlungen sperren das Code-Paket bei Initiierung als nicht-editierbar und verlassen sich auf Webhook-Benachrichtigungen zur Finalisierung. Die Empfehlungsprovisionsberechnung, Code-Generierung und Rechnungsversand laufen identisch, unabhängig davon, welches Gateway die Belastung abgeschlossen hat.
Der Kreditkarten-Flow - synchron, ein Round-Trip:
@standard_ajax
def create_braintree_payment(request, code_package):
nonce = request.POST.get('payment_method_nonce')
amount = str(code_package.calculated_price)
result = braintree.Transaction.sale({
'amount': amount,
'payment_method_nonce': nonce,
'merchant_account_id': get_merchant_account(
code_package.currency
),
'options': {'submit_for_settlement': True},
})
if result.is_success:
code_package.paid = True
code_package.payment_method = 'braintree'
code_package.transaction_id = result.transaction.id
code_package.save()
generate_codes(code_package)
send_invoice_email(code_package)
create_referral_commission(code_package)
return AJAX_RESULT_SUCCESS, 'Payment processed.'
return AJAX_RESULT_FAILED, result.messageSo funktioniert jeder Zahlungsflow
Wähle ein Gateway, um seinen Flow animiert zu sehen. Aktiviere 'Preisänderung während Genehmigung', um zu sehen, wie PayPals Revalidierung eine Abweichung erkennt.
PayPal-Return-Handler mit Preis-Revalidierung:
def execute_paypal_payment(request):
payment_id = request.GET.get('paymentId')
payer_id = request.GET.get('PayerID')
package_id = request.GET.get('package_id')
code_package = CodePackage.objects.get(pk=package_id)
payment = paypalrestsdk.Payment.find(payment_id)
# Re-validate: price may have changed during redirect
current_price = code_package.calculated_price
stored_price = Decimal(
payment.transactions[0].amount.total
)
if current_price != stored_price:
send_admin_alert(
f'Price mismatch: {current_price} vs {stored_price}'
)
payment.void()
return redirect_with_error(
'payment-cancelled'
)
if payment.execute({'payer_id': payer_id}):
code_package.paid = True
code_package.payment_method = 'paypal'
code_package.transaction_id = payment_id
code_package.save()
generate_codes(code_package)
send_invoice_email(code_package)
create_referral_commission(code_package)
return redirect('payment-success')
return redirect_with_error('payment-failed')Edge Cases in der Produktion
Sofort-Timeout-Recovery
Wenn eine Sofort-Zahlung initiiert wird, wird das Code-Paket nicht-editierbar. Wenn der Benutzer die Bankseite verlässt oder der Webhook nie eintrifft, steuert ein valid_time-Timestamp, wann die Editierbarkeit wiederhergestellt wird. Bei jedem folgenden Zugriff prüft das System datetime.now() > payment.valid_time und aktiviert die Bearbeitung nach Ablauf des Timeouts. Kein Cron-Job nötig.
Dezimal-Präzision für Provisionen
Empfehlungsprovisionen werden als Decimal(float(amount) * REFERRAL_SHARE) berechnet und dann auf Cents gerundet via int(referral_share * 100) / 100.0. Dies vermeidet Gleitkomma-Drift, der Ein-Cent-Abweichungen zwischen Provisionsdatensatz und tatsächlicher Auszahlung erzeugen könnte. Dieselbe Formel läuft identisch über alle drei Gateway-Pfade.
Idempotente Code-Generierung
Sofort sendet sowohl eine 'pending'- als auch eine 'received'-Benachrichtigung für eine einzelne erfolgreiche Zahlung. Die generate_codes()-Funktion ist idempotent - zweimaliger Aufruf erzeugt dasselbe Ergebnis. Der 'received'-Webhook führt die Generierung sicher erneut aus und markiert das Paket als bezahlt, auch wenn 'pending' beides bereits erledigt hat. Keine doppelten Codes, keine doppelten Belastungen.
Sofort-Webhook-Handler mit Zustandsmaschinen-Übergängen:
@csrf_exempt
def sofort_notification(request):
xml = xmltodict.parse(request.body)
txn_id = xml['status_notification']['transaction']
status = xml['status_notification']['status']
payment = SofortPayment.objects.get(
transaction_id=txn_id
)
code_package = payment.code_package
if status == 'pending':
code_package.paid = True
code_package.payment_method = 'sofort'
code_package.save()
generate_codes(code_package)
send_invoice_email(code_package)
create_referral_commission(code_package)
elif status == 'received':
# Idempotent: safe to re-run
generate_codes(code_package)
code_package.paid = True
code_package.save()
elif status == 'loss':
code_package.editable = False
code_package.lock_reason = 'Payment failed'
code_package.save()
elif status == 'refunded':
code_package.editable = False
code_package.lock_reason = 'Payment refunded'
code_package.save()
return HttpResponse(status=200)Zuverlässiger Multi-Gateway-Checkout im Betrieb
Die umstrukturierte Zahlungsschicht verarbeitete drei Gateway-Architekturen über eine einheitliche Pipeline. PayPal-Preismanipulationsversuche wurden vor der Ausführung erkannt und geloggt. Sofort's asynchrone Webhooks finalisierten Bestellungen innerhalb von Sekunden nach Bankbestätigung. Der duplizierte Finalisierungscode wurde konsolidiert, wodurch die Drift zwischen den Gateway-Pfaden eliminiert wurde, die intermittierende Fehler bei der Provisionsberechnung verursacht hatte. Der Checkout lief kontinuierlich über alle drei Schienen ohne eine einzige verlorene Transaktion.
WICHTIGE KENNZAHLEN
"Wir sind von wöchentlichem Debugging von Zahlungs-Edgecases zu einem System übergegangen, das einfach funktioniert. Die PayPal-Preisvalidierung allein hat zwei Exploit-Versuche im ersten Monat gestoppt."
CTO
E-Commerce-SaaS · Digitale Produkte