Abo-Modelle und wiederkehrende Zahlungen als Custom-Modul
Magento 2 bringt von Haus aus keine Subscription-Engine mit, Abo-Produkte müssen als eigenständiges Modul entwickelt werden. Dieser Artikel zeigt, wie Datenmodell, Service Contracts, Cronjob-Abrechnung und Vault-Tokenisierung für wiederkehrende Zahlungen technisch sauber in Magento 2.4.8 umgesetzt werden. Im Fokus stehen Service Contracts, declarative Schema und GraphQL-Mutationen für den Kunden-Self-Service.
Inhaltsverzeichnis
- 1. Warum Magento 2 keine native Subscription-Engine hat
- 2. Datenmodell für Abo-Produkte: eigenes Modul mit db_schema.xml
- 3. Subscription-Entity als Service Contract modellieren
- 4. Wiederkehrende Zahlungen: Recurring Profile vs. eigene Payment-Gateway-Integration
- 5. Cronjob-basierte Abrechnungszyklen implementieren
- 6. Kunden-Self-Service: Pause, Kündigung und Änderung über GraphQL
- 7. Rechnungsstellung und Steuerkonformität bei wiederkehrenden Zahlungen
- 8. Integration mit Payment-Providern: Vault und Tokenisierung
- 9. Dunning-Management bei fehlgeschlagenen Zahlungen
- 10. Zusammenfassung
- 11. FAQ
1. Warum Magento 2 keine native Subscription-Engine hat
Magento 2 bietet im Kern keinen Produkttyp für Abo-Produkte und keine Engine für wiederkehrende Abrechnung. Der aus Magento 1 übernommene Recurring-Profile-Mechanismus existiert zwar technisch noch im Sales-Modul, ist aber seit Jahren nicht weiterentwickelt worden und für moderne Payment-Provider praktisch ungeeignet. Wer Subscription-Produkte im Magento-2-Shop verkaufen will, seien es Software-Lizenzen, Verbrauchsmaterial im Abo oder Mitgliedschaften, muss die komplette Logik selbst entwickeln oder eine Marktplatz-Extension einsetzen.
Diese Lücke ist kein Zufall, sondern Ausdruck der Architektur: Magento 2 ist als Transaktions-Commerce-Plattform für Einzelbestellungen konzipiert, nicht als Abrechnungssystem für wiederkehrende Umsätze. Ein Custom-Modul für Abo-Produkte muss deshalb eigene Konzepte einführen, die es im Core nicht gibt: eine Subscription-Entity, einen Abrechnungszyklus, einen Zustand für aktive, pausierte und gekündigte Abos sowie eine Verbindung zwischen wiederkehrender Zahlung und regulärer Bestellabwicklung.
Für Agenturen bedeutet das eine bewusste Architekturentscheidung am Projektanfang. Service Contracts, deklaratives Schema und Dependency Injection über di.xml sind hier keine Kür, sondern Voraussetzung, damit das Subscription-Modul sauber in bestehende Checkout-, Zahlungs- und Fulfillment-Prozesse integriert werden kann, ohne den Magento-2-Core zu verändern. Dieser Artikel zeigt genau diesen Aufbau: von der Datenbanktabelle über den Service Contract bis zum Cronjob, der Abo-Produkte automatisch abrechnet.
2. Datenmodell für Abo-Produkte: eigenes Modul mit db_schema.xml
Der erste Baustein für Abo-Produkte ist ein eigenes Datenmodell, das über db_schema.xml deklariert wird statt über klassische InstallSchema-Skripte. Die zentrale Tabelle mironsoft_subscription speichert pro Abo eine subscription_id, die customer_id als Fremdschlüssel auf customer_entity, die original_order_id als Fremdschlüssel auf die ursprüngliche sales_order sowie den betroffenen product_sku und Intervallangaben wie interval_unit und interval_count.
Zusätzlich braucht die Tabelle ein next_billing_date für den nächsten Abrechnungstermin, einen status (active, paused, past_due, canceled) und eine Referenz auf den gespeicherten Vault-Payment-Token, damit die wiederkehrende Zahlung ohne erneute Zahlungsdateneingabe ausgeführt werden kann. Fremdschlüssel auf sales_order und customer_entity stellen referenzielle Integrität sicher und verhindern verwaiste Subscription-Datensätze, wenn ein Kunde oder eine Bestellung gelöscht wird.
Der Vorteil von db_schema.xml gegenüber InstallSchema liegt in der Idempotenz: Schemaänderungen werden deklarativ beschrieben und bei jedem setup:upgrade automatisch diff-basiert angewendet, inklusive Whitelist-Generierung. Das reduziert Migrationsfehler erheblich und macht die Struktur des Subscription-Moduls über Versionsstände hinweg nachvollziehbar.
<?xml version="1.0"?>
<!-- app/code/Mironsoft/Subscription/etc/db_schema.xml -->
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
<table name="mironsoft_subscription" resource="default" engine="innodb" comment="Subscription Entity Table">
<column xsi:type="int" name="subscription_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Subscription ID"/>
<column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="false" comment="Customer ID"/>
<column xsi:type="int" name="original_order_id" padding="10" unsigned="true" nullable="false" comment="Original Order ID"/>
<column xsi:type="varchar" name="product_sku" nullable="false" length="64" comment="Product SKU"/>
<column xsi:type="varchar" name="interval_unit" nullable="false" length="16" default="month" comment="Interval Unit"/>
<column xsi:type="smallint" name="interval_count" unsigned="true" nullable="false" default="1" comment="Interval Count"/>
<column xsi:type="date" name="next_billing_date" nullable="false" comment="Next Billing Date"/>
<column xsi:type="varchar" name="status" nullable="false" length="16" default="active" comment="Subscription Status"/>
<column xsi:type="int" name="payment_token_id" padding="10" unsigned="true" nullable="true" comment="Vault Payment Token ID"/>
<column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Created At"/>
<column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="subscription_id"/>
</constraint>
<constraint xsi:type="foreign" referenceId="MIRONSOFT_SUBSCRIPTION_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID"
table="mironsoft_subscription" column="customer_id"
referenceTable="customer_entity" referenceColumn="entity_id" onDelete="CASCADE"/>
<constraint xsi:type="foreign" referenceId="MIRONSOFT_SUBSCRIPTION_ORIGINAL_ORDER_ID_SALES_ORDER_ENTITY_ID"
table="mironsoft_subscription" column="original_order_id"
referenceTable="sales_order" referenceColumn="entity_id" onDelete="CASCADE"/>
<index referenceId="MIRONSOFT_SUBSCRIPTION_STATUS_NEXT_BILLING_DATE" indexType="btree">
<column name="status"/>
<column name="next_billing_date"/>
</index>
</table>
</schema>
3. Subscription-Entity als Service Contract modellieren
Die Subscription-Entity wird nicht als einfaches Model, sondern als Service Contract modelliert: ein SubscriptionInterface im Api/Data-Namespace definiert die Getter und Setter, ein SubscriptionRepositoryInterface im Api-Namespace definiert save, getById, getList und deleteById. Diese Trennung entkoppelt die öffentliche API des Subscription-Moduls von der konkreten ORM-Implementierung und ermöglicht, dass andere Module oder GraphQL-Resolver ausschließlich gegen die Interfaces programmieren.
Die konkrete Repository-Implementierung nutzt Constructor Property Promotion aus PHP 8.4, um ResourceModel, Factory und CollectionProcessor kompakt und typsicher zu injizieren. Für Abfragen mit SearchCriteriaInterface kommt ein CollectionProcessorInterface zum Einsatz, das Filter wie status oder customer_id nie mit einem rohen Integer, sondern immer in der Array-Form mit dem eq-Schlüssel anwendet.
Diese Service-Contract-Struktur zahlt sich vor allem bei Abo-Produkten aus, weil Subscription-Daten von mehreren Kanälen gelesen werden: dem Admin-Grid, dem Cronjob und der GraphQL-Schicht für den Kunden-Self-Service. Ein sauberer Contract verhindert, dass an drei verschiedenen Stellen dieselbe Query-Logik dupliziert wird.
<?php
declare(strict_types=1);
namespace Mironsoft\Subscription\Model;
use Mironsoft\Subscription\Api\Data\SubscriptionInterface;
use Mironsoft\Subscription\Api\SubscriptionRepositoryInterface;
use Mironsoft\Subscription\Model\ResourceModel\Subscription as SubscriptionResource;
use Mironsoft\Subscription\Model\ResourceModel\Subscription\CollectionFactory;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterfaceFactory;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
/**
* Repository implementation for Subscription entities.
*/
class SubscriptionRepository implements SubscriptionRepositoryInterface
{
/**
* @param SubscriptionResource $resource Subscription resource model
* @param SubscriptionFactory $subscriptionFactory Subscription model factory
* @param CollectionFactory $collectionFactory Subscription collection factory
* @param SearchResultsInterfaceFactory $searchResultsFactory Search results factory
*/
public function __construct(
private readonly SubscriptionResource $resource,
private readonly SubscriptionFactory $subscriptionFactory,
private readonly CollectionFactory $collectionFactory,
private readonly SearchResultsInterfaceFactory $searchResultsFactory,
) {
}
/**
* Persists a subscription entity.
*
* @param SubscriptionInterface $subscription Subscription entity to save
* @return SubscriptionInterface
* @throws CouldNotSaveException
*/
public function save(SubscriptionInterface $subscription): SubscriptionInterface
{
try {
$this->resource->save($subscription);
} catch (\Exception $exception) {
throw new CouldNotSaveException(__('Could not save subscription: %1', $exception->getMessage()));
}
return $subscription;
}
/**
* Loads a subscription by its ID.
*
* @param int $subscriptionId Subscription entity ID
* @return SubscriptionInterface
* @throws NoSuchEntityException
*/
public function getById(int $subscriptionId): SubscriptionInterface
{
$subscription = $this->subscriptionFactory->create();
$this->resource->load($subscription, $subscriptionId);
if (!$subscription->getId()) {
throw new NoSuchEntityException(__('Subscription with id "%1" does not exist.', $subscriptionId));
}
return $subscription;
}
/**
* Returns due subscriptions for a given billing date, filtered by active status.
*
* @param SearchCriteriaInterface $searchCriteria Search criteria with status and next_billing_date filters
* @return \Magento\Framework\Api\SearchResultsInterface
*/
public function getList(SearchCriteriaInterface $searchCriteria): \Magento\Framework\Api\SearchResultsInterface
{
$collection = $this->collectionFactory->create();
foreach ($searchCriteria->getFilterGroups() as $group) {
foreach ($group->getFilters() as $filter) {
$collection->addFieldToFilter($filter->getField(), [$filter->getConditionType() ?: 'eq' => $filter->getValue()]);
}
}
$searchResults = $this->searchResultsFactory->create();
$searchResults->setSearchCriteria($searchCriteria);
$searchResults->setItems($collection->getItems());
$searchResults->setTotalCount($collection->getSize());
return $searchResults;
}
}
4. Wiederkehrende Zahlungen: Recurring Profile vs. eigene Payment-Gateway-Integration
Magento 2 kennt aus dem Sales-Modul das Konzept des Recurring Profile, ursprünglich für Authorize.net-Zahlungen entwickelt. Es erlaubt, ein Zahlungsprofil beim Payment-Gateway anzulegen, das der Provider selbst periodisch abrechnet. Für neue Subscription-Produkte ist dieser Ansatz in der Praxis kaum noch relevant, weil moderne Payment-Provider wie Adyen, Braintree oder Stripe eigene, deutlich flexiblere Subscription-APIs anbieten oder keine serverseitigen Recurring Profiles im Sinne des alten Magento-Konzepts unterstützen.
Der praxistaugliche Ansatz für Abo-Produkte in Magento 2.4.8 ist deshalb die Wiederverwendung von Vault-Payment-Tokens: Bei der ersten Bestellung tokenisiert der Payment-Provider die Zahlungsmethode des Kunden, Magento speichert nur eine Referenz-ID über PaymentTokenRepositoryInterface. Jede weitere Abrechnung im Abo greift auf diesen Token zu, ohne dass Magento selbst Kartendaten verarbeitet oder der Kunde erneut Zahlungsdaten eingeben muss.
Dieser Unterschied zwischen provider-verwaltetem Recurring Profile und eigener Vault-Token-Wiederverwendung bestimmt maßgeblich, wie viel Kontrolle die Subscription-Logik über Abrechnungszeitpunkt, Retry-Verhalten und Fehlerbehandlung behält.
| Billing-Strategie | Kontrolle | PCI-Scope | Komplexität | Fehlerbehandlung |
|---|---|---|---|---|
| Vault-Token-Rebilling (eigener Cronjob) | Vollständig im Shop | SAQ A, kein Kartendaten-Handling | Mittel bis hoch, eigenes Modul | Frei über eigene Dunning-State-Machine steuerbar |
| Provider-verwaltete Subscription (z.B. Stripe Billing) | Beim Provider, Magento nur Sync | Minimal, Billing komplett extern | Niedrig, Provider-API und Webhooks | Provider-seitig vordefiniert, wenig Anpassbarkeit |
| Manuelles Recurring Profile (Magento-Legacy) | Gering, veraltete API | Abhängig vom Gateway, oft höherer SAQ-Scope | Hoch, kaum Dokumentation, kaum gepflegt | Kaum vorhanden, manuelles Monitoring nötig |
5. Cronjob-basierte Abrechnungszyklen implementieren
Der Abrechnungszyklus für Abo-Produkte läuft über einen eigenen Cronjob, der in crontab.xml registriert wird und in einer eigenen Cron-Group, nicht der Default-Group, um andere Magento-Jobs nicht zu blockieren, üblicherweise stündlich läuft. Der Job selektiert alle Subscription-Datensätze mit status active und next_billing_date kleiner oder gleich dem aktuellen Zeitpunkt.
Für jede fällige Subscription erzeugt der Cronjob programmatisch eine neue Bestellung. Statt eines manuellen Warenkorb-Checkouts wird eine Order direkt aus den gespeicherten Produkt- und Kundendaten aufgebaut und über OrderRepositoryInterface persistiert, anschließend wird über den hinterlegten Vault-Token die Zahlung beim Provider ausgelöst und bei Erfolg eine Invoice erzeugt.
Nach erfolgreicher Abrechnung aktualisiert der Job next_billing_date um das konfigurierte Intervall und protokolliert das Ergebnis. Schlägt die Zahlung fehl, greift die in Abschnitt 9 beschriebene Dunning-Logik statt eines stillen Fehlschlags.
<?xml version="1.0"?>
<!-- app/code/Mironsoft/Subscription/etc/crontab.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
<group id="mironsoft_subscription">
<job name="mironsoft_subscription_process_due" instance="Mironsoft\Subscription\Cron\ProcessDueSubscriptions" method="execute">
<schedule>0 * * * *</schedule>
</job>
</group>
</config>
<?php
declare(strict_types=1);
namespace Mironsoft\Subscription\Cron;
use Mironsoft\Subscription\Api\SubscriptionRepositoryInterface;
use Mironsoft\Subscription\Api\Data\SubscriptionInterface;
use Mironsoft\Subscription\Model\OrderBuilder;
use Magento\Framework\Api\SearchCriteriaBuilderFactory;
use Magento\Sales\Api\OrderRepositoryInterface;
use Psr\Log\LoggerInterface;
/**
* Cron job that processes due subscriptions and creates recurring orders.
*/
class ProcessDueSubscriptions
{
/**
* @param SubscriptionRepositoryInterface $subscriptionRepository Subscription repository
* @param SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory Search criteria builder factory
* @param OrderBuilder $orderBuilder Builds a recurring order from a subscription
* @param OrderRepositoryInterface $orderRepository Persists the newly created order
* @param LoggerInterface $logger Logs billing results and failures
*/
public function __construct(
private readonly SubscriptionRepositoryInterface $subscriptionRepository,
private readonly SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory,
private readonly OrderBuilder $orderBuilder,
private readonly OrderRepositoryInterface $orderRepository,
private readonly LoggerInterface $logger,
) {
}
/**
* Selects due subscriptions and creates one order per subscription.
*
* @return void
*/
public function execute(): void
{
$searchCriteriaBuilder = $this->searchCriteriaBuilderFactory->create();
$searchCriteria = $searchCriteriaBuilder
->addFilter(SubscriptionInterface::STATUS, 'active')
->addFilter(SubscriptionInterface::NEXT_BILLING_DATE, date('Y-m-d'), 'lteq')
->create();
$dueSubscriptions = $this->subscriptionRepository->getList($searchCriteria)->getItems();
foreach ($dueSubscriptions as $subscription) {
try {
$order = $this->orderBuilder->buildFromSubscription($subscription);
$this->orderRepository->save($order);
$subscription->setNextBillingDate($this->calculateNextBillingDate($subscription));
$this->subscriptionRepository->save($subscription);
} catch (\Throwable $exception) {
$this->logger->error(sprintf(
'Subscription #%d billing failed: %s',
$subscription->getSubscriptionId(),
$exception->getMessage()
));
}
}
}
/**
* Calculates the next billing date based on the subscription interval.
*
* @param SubscriptionInterface $subscription Subscription entity
* @return string
*/
private function calculateNextBillingDate(SubscriptionInterface $subscription): string
{
$modifier = sprintf('+%d %s', $subscription->getIntervalCount(), $subscription->getIntervalUnit());
return (new \DateTime($subscription->getNextBillingDate()))->modify($modifier)->format('Y-m-d');
}
}
6. Kunden-Self-Service: Pause, Kündigung und Änderung über GraphQL
Kunden erwarten bei Abo-Produkten die Möglichkeit, ihr Abonnement selbst zu pausieren, zu kündigen oder Menge beziehungsweise Intervall zu ändern, ohne den Support kontaktieren zu müssen. In einer Hyvä- oder Headless-Architektur ist GraphQL der naheliegende Weg: Ein eigenes schema.graphql erweitert das Magento-Schema um Mutationen wie subscriptionPause, subscriptionCancel und subscriptionUpdate.
Jede Mutation wird über eine Resolver-Klasse implementiert, die ResolverInterface implementiert und im Kontext den authentifizierten Kunden über den Customer-Context prüft, bevor sie auf das SubscriptionRepositoryInterface zugreift. So kann sichergestellt werden, dass ein Kunde ausschließlich seine eigenen Subscription-Datensätze verändern kann.
Die GraphQL-Mutation für das Pausieren setzt den status der Subscription auf paused und überspringt sie im nächsten Cronlauf, ohne next_billing_date zu verändern, sodass eine spätere Reaktivierung nahtlos an derselben Stelle im Abrechnungszyklus fortgesetzt wird.
# app/code/Mironsoft/Subscription/etc/schema.graphqls
type Mutation {
subscriptionPause(subscription_id: Int! @doc(description: "Subscription entity ID")): SubscriptionPauseOutput @resolver(class: "Mironsoft\\Subscription\\Model\\Resolver\\SubscriptionPause")
}
type SubscriptionPauseOutput {
subscription_id: Int! @doc(description: "Paused subscription ID")
status: String! @doc(description: "New subscription status")
next_billing_date: String @doc(description: "Unchanged next billing date")
}
# Example mutation call
mutation {
subscriptionPause(subscription_id: 42) {
subscription_id
status
next_billing_date
}
}
7. Rechnungsstellung und Steuerkonformität bei wiederkehrenden Zahlungen
Jede Abrechnung eines Abo-Produkts erzeugt eine eigenständige Bestellung und damit auch eine eigene Rechnung, die über den regulären InvoiceService von Magento erstellt wird. Wichtig ist, dass Steuersätze und gegebenenfalls Produktpreise bei jedem Abrechnungszyklus neu berechnet werden, weil sich zwischen zwei Abrechnungsterminen Umsatzsteuersätze, Kundenadresse oder Preislisten geändert haben können.
In Deutschland und der EU gelten für wiederkehrende Zahlungen zusätzliche Anforderungen: fortlaufende Rechnungsnummerierung nach GoBD, korrekte Umsatzsteuerausweisung je Abrechnungsperiode und eine nachvollziehbare Historie aller erzeugten Bestellungen und Rechnungen pro Subscription. Diese Nachvollziehbarkeit ist auch bei einem Widerrufsrecht oder einer nachträglichen Kündigung im laufenden Abrechnungszeitraum relevant.
Ein sauberes Subscription-Modul verknüpft deshalb jede erzeugte Order und Invoice über die subscription_id zurück zum ursprünglichen Abo, damit im Admin-Grid und im Kundenkonto die vollständige Abrechnungshistorie eines Abo-Produkts nachvollziehbar bleibt.
8. Integration mit Payment-Providern: Vault und Tokenisierung
Die Vault-Funktionalität von Magento 2 ist die Grundlage für PCI-konforme wiederkehrende Zahlungen bei Abo-Produkten. Statt Kartendaten im eigenen System zu speichern, verwaltet Vault ausschließlich ein PaymentTokenInterface mit einer providerseitigen Token-Referenz, die über PaymentTokenManagementInterface gelesen und gespeichert wird.
Payment-Methoden, die CanSaveInVault unterstützen, etwa Adyen, Braintree oder Stripe, tokenisieren die Zahlungsmethode einmalig beim ersten Checkout. Der Subscription-Cronjob nutzt anschließend ausschließlich die Token-ID zusammen mit dem Payment-Methodencode, um die Bestellung im Hintergrund zu autorisieren, ganz ohne erneute Interaktion des Kunden.
Der zentrale Vorteil dieser Architektur liegt im reduzierten PCI-DSS-Scope: Da Magento selbst niemals Klartext-Kartendaten verarbeitet oder speichert, bleibt der Shop im SAQ-A-Bereich, während die eigentliche Kartendatenverarbeitung vollständig beim zertifizierten Payment-Provider verbleibt.
9. Dunning-Management bei fehlgeschlagenen Zahlungen
Fehlgeschlagene Zahlungen sind bei Abo-Produkten der Normalfall, nicht die Ausnahme, etwa durch abgelaufene Karten oder unzureichende Kontodeckung. Ein belastbares Subscription-Modul braucht deshalb Dunning-Management: eine definierte Retry-Logik, die eine fehlgeschlagene Abrechnung nach einer konfigurierbaren Anzahl Tage automatisch erneut versucht, bevor das Abo in einen kritischen Zustand wechselt.
Dafür eignet sich eine State Machine mit den Zuständen active, past_due, suspended und canceled. Nach dem ersten Fehlschlag wechselt die Subscription zu past_due und der Kunde erhält automatisch eine Benachrichtigung per E-Mail-Template. Bleiben mehrere Retry-Versuche erfolglos, wechselt der Status zu suspended, und erst nach einer konfigurierten Kulanzfrist zu canceled.
Diese Zustandslogik gehört in den Cronjob selbst, nicht in verstreute Bedingungen im Checkout, damit das Verhalten bei fehlgeschlagenen Zahlungen für alle Subscription-Produkte im Shop konsistent bleibt und im Admin-Grid transparent nachvollzogen werden kann.
10. Zusammenfassung
Die wichtigsten Schritte für Abo-Produkte in Magento 2 lösen immer dasselbe Grundproblem: Magento 2 bringt keine native Subscription-Engine mit, deshalb muss ein eigenes Modul das Datenmodell, den Abrechnungszyklus und die Zahlungsintegration übernehmen. db_schema.xml definiert die Subscription-Tabelle deklarativ mit Fremdschlüsseln auf sales_order und customer_entity. Service Contracts mit SubscriptionInterface und SubscriptionRepositoryInterface kapseln den Zugriff für Admin-Grid, Cron und GraphQL einheitlich.
Der Cronjob ist das Herzstück jedes Subscription-Moduls: Er selektiert fällige Abo-Produkte, erzeugt Bestellungen über OrderRepositoryInterface und belastet den Kunden über einen gespeicherten Vault-Token, ohne dass Kartendaten im eigenen System landen. GraphQL-Mutationen geben Kunden Self-Service für Pause, Kündigung und Änderung, während eine Dunning-State-Machine fehlgeschlagene Zahlungen kontrolliert eskaliert, statt Abos stillschweigend zu verlieren.
Subscription-Produkte in Magento 2, das Wichtigste auf einen Blick
Datenmodell
db_schema.xml mit eigener Subscription-Tabelle, Fremdschlüsseln auf sales_order und customer_entity sowie Index auf status und next_billing_date.
Service Contracts
SubscriptionInterface und SubscriptionRepositoryInterface entkoppeln Admin-Grid, Cron und GraphQL vom ORM.
Abrechnung
Cronjob über crontab.xml erzeugt Bestellungen für fällige Abo-Produkte und belastet Kunden über Vault-Payment-Tokens.
Self-Service & Dunning
GraphQL-Mutationen für Pause und Kündigung, State Machine active/past_due/suspended/canceled bei fehlgeschlagenen Zahlungen.
11. FAQ: Abo-Produkte und Subscription-Produkte in Magento 2
1Hat Magento 2 eine native Subscription-Funktion?
2Recurring Profile vs. Vault-Token-Rebilling?
3Welche Tabelle braucht ein Subscription-Modul?
4Warum Service Contracts statt Model-Zugriff?
5Wie oft sollte der Cronjob laufen?
6Wie pausieren Kunden ihr Abo selbst?
7Neue Rechnung bei jeder Abo-Abrechnung?
8Ist Vault-Tokenisierung PCI-DSS-konform?
9Was passiert bei fehlgeschlagener Zahlung?
10Marktplatz-Extension statt Custom-Modul?
Mironsoft
Magento 2 Custom-Module, Subscription Commerce und Payment-Integrationen
Abo-Produkte in eurem Magento-2-Shop technisch sauber umsetzen?
Wir entwickeln Subscription-Module mit Service Contracts, Cronjob-Abrechnung und Vault-Tokenisierung, passgenau auf euren Payment-Provider und eure Steuer- und Rechnungsanforderungen zugeschnitten.
Architektur-Review
Datenmodell, Service Contracts und Cron-Strategie für Abo-Produkte prüfen
Subscription-Modul
Eigenentwicklung mit Vault-Integration, GraphQL-Self-Service und Dunning-Management
Payment-Integration
Anbindung von Adyen, Braintree oder Stripe mit PCI-konformer Tokenisierung