Die Credentials (Payrexx Backoffice JAXForms) für das Login sind in Bitwarden hinterlegt.
Das API-Secret für die Anbindung findet sich im Backoffice unter Einstellungen > API. Es gehört nicht in Code oder Konfiguration im Repository, sondern ins Secret-Management.
In Zusammenarbeit mit Payrexx wurde ein Test-Vertrag ohne Ablaufdatum erstellt.
JAXForms legt pro Zahlung ein Gateway an (Betrag, Währung, Zweck, Rückkehr-URLs). Die eigentliche Transaktion entsteht erst, wenn die Kund:in auf der Payrexx-Seite bezahlt. Danach liest JAXForms den Status über die API nach und belastet die Reservation (Capture).
Grundsätzlich gibt es 2 Arten der Zahlungsseitenintegration, die JAXForms nutzt:
Die Weiterleitung (FORWARD), bei der die Kund:in über den Gateway-Link auf die Zahlungsseite von Payrexx geleitet wird und danach auf das Formular zurückkehrt.
Das Modal-Fenster (MODAL), bei dem die Zahlungsseite mithilfe der Payrexx JavaScript-Integration (modal.min.js) in einem Fenster über dem Formular geöffnet wird.
Betrag wird 7 Tage blockiert, danach Capture (Belastung) oder Aufhebung
Keine Kartenspeicherung noetig
preAuthorization
Karte wird tokenisiert fuer spaeter vom Haendler ausgeloeste Belastungen (Charge)
Datenschutz- und PCI-relevant, da Kartendaten bei Payrexx gespeichert bleiben
sequenceDiagram
autonumber
actor Zahler as Zahler
participant Merchant as Merchant-System
participant Payrexx as Payrexx API
participant PSP as PSP / Kartennetz
Merchant->>Payrexx: POST Gateway/ (amount, currency, reservation/preAuthorization, Redirect-URLs)
Payrexx-->>Merchant: Gateway { id, hash, link, status=waiting }
Merchant->>Zahler: Weiterleitung auf Gateway-Link (Payrexx-Checkout-Seite)
Zahler->>Payrexx: Zahlungsmittel waehlen, Daten eingeben
Payrexx->>PSP: Autorisierung anfragen
PSP-->>Payrexx: Autorisiert (reserved/authorized) oder abgelehnt (declined)
Payrexx-->>Zahler: Weiterleitung auf successRedirectUrl / failedRedirectUrl
Merchant->>Payrexx: GET Gateway/{id}/ (Status pruefen)
Payrexx-->>Merchant: Gateway-Status + Transaction-Referenz
Merchant->>Payrexx: GET Transaction/{id}/ (Details lesen)
Payrexx-->>Merchant: Transaction { status, payment.brand, invoice.totalAmount }
alt Reservation oder Pre-Authorization
Merchant->>Payrexx: POST Transaction/{id}/capture oder /charge
Payrexx->>PSP: Belastung ausloesen
PSP-->>Payrexx: Bestaetigt
Payrexx-->>Merchant: Transaction { status=confirmed }
else Zahlung fehlgeschlagen oder abgebrochen
Merchant->>Payrexx: DELETE Gateway/{id}/ oder DELETE Transaction/{id}/
Payrexx-->>Merchant: Gateway/Transaction { status=cancelled }
end
Zahlungsprozess Payrexx mit JAXForms
FORWARD (Redirect)
sequenceDiagram
autonumber
actor Nutzer as Formular-Nutzer
participant Browser as Browser
participant Action as SaveFormPayrexxAction
participant Service as PayrexxService
participant Payrexx as Payrexx API
participant DB as JAX_PAYMENT
Nutzer->>Browser: Zahlungs-Button klicken
Browser->>Action: commitWorkflow (kein serverAccessToken)
Action->>Service: initializePayment(user, formDescriptor, paymentItem)
Service->>Payrexx: POST Gateway/ (Redirect-URLs inkl. serverAccessToken)
Payrexx-->>Service: Gateway { id, link, status=waiting }
Service->>DB: persist INITIALIZED (TRANSACTION_ID = Gateway-ID)
Action->>Service: initializePaymentPage(gatewayId, FORWARD)
Service->>Payrexx: GET Gateway/{id}/ (Link lesen)
Payrexx-->>Service: Gateway { link }
Action->>Browser: redirect(gateway.link)
Browser->>Payrexx: Checkout-Seite oeffnen
Nutzer->>Payrexx: Zahlung durchfuehren
Payrexx-->>Browser: Redirect auf clientCallbackUrl + serverAccessToken
Browser->>Action: commitWorkflow (mit serverAccessToken)
Action->>Service: verifyTransactionAuthorized(dom, gatewayId)
loop Polling, 1s Intervall, 30s Timeout
Service->>Payrexx: GET Gateway/{id}/
Payrexx-->>Service: Status (waiting/reserved/authorized/confirmed)
end
Service->>Payrexx: GET Transaction/{id}/
Payrexx-->>Service: Transaction-Details
Service->>DB: persist Status, SUB_TRANSACTION_ID, PAYMENT_METHOD
alt captureAfterAuthorizing = true
Action->>Service: captureTransaction(user, gatewayId)
Service->>Payrexx: POST Transaction/{id}/capture oder /charge
Payrexx-->>Service: Transaction { status=confirmed }
Service->>DB: persist CONFIRMED
end
Action->>Browser: Workflow committet, Formular abgeschlossen
MODAL
sequenceDiagram
autonumber
actor Nutzer as Formular-Nutzer
participant Browser as Browser (JS)
participant Action as SaveFormPayrexxAction
participant Service as PayrexxService
participant Payrexx as Payrexx API
participant Callback as RESTPaymentCallbackHandler
Nutzer->>Browser: Zahlungs-Button klicken
Browser->>Action: commitWorkflow (kein serverAccessToken)
Action->>Service: initializePayment(...) + initializePaymentPage(gatewayId, MODAL)
Service->>Payrexx: POST Gateway/ + GET Gateway/{id}/
Payrexx-->>Service: Gateway { link }
Service-->>Action: PaymentConnection { url=link, scriptUrl=modal.min.js }
Action->>Browser: initPayrexxModal(jsUrl, link, gatewayId, domId, actionId)
Browser->>Browser: modal.min.js laden, window.jQuery Fallback pruefen
Browser->>Payrexx: Payrexx-Modal oeffnen (iFrame mit Gateway-Link)
Nutzer->>Payrexx: Zahlung im Modal durchfuehren
Payrexx->>Callback: Redirect im iFrame auf /pay/callback/payrexx/{mandant}?serverAccessToken=...
Callback->>Service: verifyTransactionAuthorized(dom, gatewayId)
Service->>Payrexx: GET Gateway/{id}/ + GET Transaction/{id}/ (Polling)
Payrexx-->>Service: Status + Transaction-Details
Callback-->>Browser: payrexxSuccess.html / payrexxFailed.html (im iFrame)
Browser->>Browser: postMessage ans Elternfenster (paymentOutcomeReady)
Browser->>Browser: shown/hidden-Callback: context=disableButton/enableButton
Browser->>Action: sendActionRequestHRefTargetWithDomID (SaveFormPayrexxAction)
alt captureAfterAuthorizing = true
Action->>Service: captureTransaction(user, gatewayId)
Service->>Payrexx: POST Transaction/{id}/capture oder /charge
end
Action->>Browser: Modal schliesst, Formular abgeschlossen
Eigenheiten
Payrexx-API
Eigenheit
Bedeutung fuer die Umsetzung
Envelope-Format
Jede Antwort ist {"status":"success"|"error","data":[...]} - mit HTTP-Status 200 auch im Fehlerfall. Ein reiner HTTP-Status-Check reicht nicht; der Envelope muss zusaetzlich ausgewertet werden.
Betrag in Rappen
amount ist ein Integer in Rappen/Cents, nicht Franken - Umrechnung via Math.round(betrag * 100).
Rate-Limit
600 Requests / 5 Minuten pro Instanz. Beim Status-Polling relevant (siehe unten).
Gateway vs. Transaction
Die Transaction-ID ist erst ab Zahlungsabschluss bekannt; bis dahin existiert nur die Gateway-ID.
Charge-Endpunkt undokumentiert
Der Endpunkt fuer PRE_AUTHORIZATION-Belastungen (POST Transaction/{id}/charge) ist auf der offiziellen Referenzseite nicht aufgefuehrt - verifiziert ueber die Action-Mapping-Logik des offiziellen PHP-SDK (payrexx/payrexx-php).
Modal-Widget
modal.min.js braucht ein globales window.jQuery - unabhaengig vom Framework-eigenen $jax.
JAXForms-Implementierung
Eigenheit
Begruendung
TRANSACTION_ID = Payrexx-Gateway-ID
Stabil ab Initialisierung, deshalb Schluessel fuer alle Lookups. Die Payrexx-Transaction-ID landet separat in der neuen Spalte SUB_TRANSACTION_ID (die bestehende REQUEST_ID-Spalte wird von keinem Provider tatsaechlich beschrieben und blieb deshalb unangetastet).
API-Key als ClientRequestFilter
Nicht als withHeader(...) gesetzt, da der generische HTTP-Client bei aktivem Debug-Logging alle per withHeader gesetzten Header inkl. Wert protokolliert. Analog zu BasicAuthenticator bei Datatrans/Saferpay.
MODAL folgt dem Saferpay-IFRAME-Muster, nicht Datatrans-LIGHTBOX
Payrexx laedt die Redirect-URLs innerhalb des Modal-iFrames; die Rueckkehr braucht deshalb einen oeffentlichen REST-Callback statt eines direkten Lightbox-Callbacks.
Tenant-Aufloesung im REST-Callback
Die Payrexx-API-Konfiguration (Secret/Instanz) wird ueber den durch den signierten serverAccessToken authentifizierten dom.getUser() aufgeloest, nicht ueber den frei waehlbaren URL-Pfadparameter mandantName - Fund aus dem Security-Review, im bestehenden Saferpay-Zweig nicht behoben (siehe Offene Punkte).
Polling statt Webhook
Ticket 1 verifiziert synchron per Polling (1s-Intervall, 30s-Timeout) auf GET Gateway/{id}/. Ein Webhook-Endpunkt fuer asynchrone Statusaenderungen ist nicht Teil von Ticket 1.