Live Demo: Try this and other plugins live — no installation, no risk: demo.markus-michalski.net
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).
Flow in validate():
sepaUseExistingMandate=true an active mandate of the customer must exist.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():
MandateCreatedEvent.| 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.
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.
| 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" |
payment-method.html.twig (fields at checkout), the customer account sidebar, the order details and the order finish details.SepaPaymentFieldsPlugin formats IBAN and BIC, validates the IBAN in the browser and passes the fields on to the order form.mmd-sepa-mandate module with a PDF button; revoking on the detail page saves status and timestamp through the repository.bcmod() from the PHP bcmath extension.validate-iban route lives in the application cache (cache.app).