
Payrexx ist ein Schweizer Payment-Aggregator: Ein einziger Vertrag und eine einzige API decken mehrere PSP (Payment Service Provider, z. B. Kartenacquirer, Twint, Postfinance) ab. Dieser Artikel beschreibt den generischen Payrexx-Zahlungsablauf, dessen konkrete Umsetzung im Payment-Modul von JAXForms und die dabei relevanten Eigenheiten. Für die vollstaendige Konfigurationsreferenz (Schluessel, Defaults, Statusmodell-Tabelle) siehe die separate Feature-Doku zum Payrexx-Provider.
Zahlungsprozess Payrexx generell
Payrexx kennt zwei zentrale Objekte: das Gateway (der Bezahlvorgang als Ganzes, inkl. Checkout-Seite) und die Transaction (die konkrete Belastung, existiert erst ab Zahlungsabschluss). Ein Gateway kann theoretisch mehrere Transactions durchlaufen (z. B. bei einem Zahlungsabbruch mit erneutem Versuch).
Reservation vs. Pre-Authorization
Payrexx bietet zwei Formen der Vorautorisierung, die sich fachlich deutlich unterscheiden:
| Modus | Bedeutung | Besonderheit |
|---|
reservation | 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 |
Anmerkung: Nicht jeder PSP unterstuetzt Reservationen. Der Modus ist deshalb pro Mandant konfigurierbar.
Abbildung 1: Generischer Payrexx-Zahlungsablauf

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
Das Payment-Modul jaxforms-payment kapselt den Ablauf in PayrexxService (HTTP-Kommunikation) und SaveFormPayrexxAction (Workflow-Anbindung). Zwei Integrationsmodi stehen zur Wahl, gesteuert ueber die Workflow-Property integrationMode.
FORWARD (Redirect)
Der Nutzer wird auf die Payrexx-Checkout-Seite umgeleitet, analog zum bestehenden Datatrans-FORWARD-Muster. Die Rueckkehr laeuft ueber einen signierten serverAccessToken direkt im Formular-Workflow.
Abbildung 2: JAXForms FORWARD-Ablauf

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
Payrexx laedt den Checkout in einem eigenen Modal-Fenster (modal.min.js), das den Gateway-Link in einem iFrame anzeigt. Die Rueckkehr laeuft deshalb nicht direkt im Formular-Workflow, sondern ueber den oeffentlichen REST-Callback /pay/callback/payrexx/{mandant} - das gleiche Muster wie beim bestehenden Saferpay-IFRAME-Modus.
Abbildung 3: JAXForms MODAL-Ablauf

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. |
Offene Punkte
- Payrexx-Testinstanz und Test-PSP beschaffen, um den Ablauf end-to-end zu verifizieren
- Mit Payrexx-Support klaeren, ob der PSP der Ziel-Instanz Reservationen unterstuetzt
- Folge-Ticket: identische Tenant-Aufloesungsschwaeche im Saferpay-Zweig des REST-Callback-Handlers beheben
- Folge-Ticket: Replay-Verhalten im Callback-Handler (Status-Downgrade auf ERROR bei wiederholtem Aufruf) - betrifft alle drei Provider