Markus Michalski FAQ

SEPA Direct Debit: Technical Details

Technical structure of SEPA Direct Debit for Shopware 6.7: flow in the payment handler, services, the mmd_sepa_mandate table, migrations, templates and the admin and storefront extensions.

Deutsche Version

Back to overview

SEPA Direct Debit: Technical Details

Live Demo: Try this and other plugins live — no installation, no risk: demo.markus-michalski.net

Flow in the payment handler

Mmd\SepaPayment\Payment\SepaPaymentHandler extends AbstractPaymentHandler and is registered as shopware.payment.method. Payment is synchronous, there is no redirect. The handler supports neither refunds nor recurring payments (supports() always returns false).

sequenceDiagram
    participant K as Customer
    participant H as SepaPaymentHandler
    participant S as SepaMandateService
    participant D as Database
    participant E as EventDispatcher
    K->>H: validate - submit checkout
    H->>H: Check existing mandate or check IBAN, account holder, consent
    H->>H: Encrypt IBAN and pass it on in the struct
    Note over K,H: Order is created
    K->>H: pay
    H->>S: Revoke old active mandate
    H->>S: createMandate
    S->>D: Store mandate
    H->>E: MandateCreatedEvent

Flow in validate():

  1. Without a customer ID the payment is aborted.
  2. With sepaUseExistingMandate=true an active mandate of the customer must exist.
  3. Otherwise sepaIban, sepaAccountHolder and sepaMandateAgreement=true must be present and the IBAN valid. The IBAN is then handed to pay() encrypted in a struct.

Flow in pay():

  1. With an existing mandate nothing more happens.
  2. Otherwise the handler loads order and customer, revokes an existing active mandate and creates the new one. If creating reports that the customer already has an active mandate (parallel request), the handler revokes that one and retries once.
  3. It then dispatches the MandateCreatedEvent.

Components

Class Purpose
MmdSepaPayment Plugin class; creates the payment method, (de)activates it and removes table and configuration on uninstall without keeping data
Service\SepaMandateService Create, change, revoke and read mandates
Service\EncryptionService AES-256-GCM encryption of the IBAN, key via HKDF-SHA256 from APP_SECRET
Service\IbanValidator Normalization, format, length and modulo-97 check, masking
Service\MandateReferenceGenerator Generates {prefix}{YYYYMMDD}-{8 characters}
Service\MandatePdfGenerator Renders the Twig template documents/sepa-mandate.html.twig to an A4 PDF with dompdf
Service\PaymentMethodInstaller Creates the payment method payment_sepa_direct_debit
Subscriber\CheckoutSubscriber Provides the active mandate with masked IBAN as the page extension mmdSepaMandate on the checkout confirm and finish pages
Storefront\Controller\SepaMandateController Customer account page, create, revoke, IBAN check, PDF downloads
Controller\Api\SepaMandateController Admin routes for PDF, revocation and masked IBAN
Event\MandateCreatedEvent Event for other plugins

The services are exchangeable through the interfaces in Service\Contract, for example SepaMandateServiceInterface, IbanValidatorInterface and EncryptionServiceInterface.

Database

Table mmd_sepa_mandate (entity mmd_sepa_mandate):

Column Type Note
id BINARY(16) Primary key
customer_id BINARY(16) Foreign key to customer.id, ON DELETE CASCADE
mandate_reference VARCHAR(255) Unique
iban LONGTEXT Base64 of initialization vector, auth tag and encrypted data
bic VARCHAR(11) Optional, unencrypted
account_holder VARCHAR(255)
status VARCHAR(50) active, revoked, expired; default active
signed_at DATETIME(3)
revoked_at DATETIME(3) Optional
created_at, updated_at DATETIME(3)

Indexes: unique on mandate_reference, plain on status, on customer_id and on (customer_id, status). The second migration removes the original uniqueness on customer_id. Several records per customer are therefore possible; that there is at most one active mandate is ensured by application logic, not by the database.

Where things live in the system

What Where
Payment method payment_method, technical name payment_sepa_direct_debit, afterOrderEnabled off, position 10
Configuration system_config, keys MmdSepaPayment.config.*
Storefront texts Snippet files mmd-sepa-payment.de-DE.json and mmd-sepa-payment.en-GB.json, namespace mmdSepaPayment
Admin module mmd-sepa-mandate, menu entry under "Orders"

Storefront and admin

Notes