Eigentlich wollte ich nur Benutzergruppen verkaufen
-
Marcel Beckers -
15. August 2026 um 11:38 -
272 Mal gelesen -
0 Kommentare
Eigentlich wollte ich nur Benutzergruppen verkaufen.
Also: Produkt kaufen → Benutzergruppe bekommen. Fertig.
Klingt erst einmal ziemlich unspektakulär.
Bis mir irgendwann der Gedanke kam:
Warum eigentlich nur Benutzergruppen?
Wenn der Shop nach einem Kauf irgendetwas auslösen kann, dann könnte das genauso gut eine Trophäe sein. Oder zusätzlicher Speicherplatz. Oder ein weiterer Eintrag in einem Verzeichnis. Oder eine Funktion in irgendeinem Plugin, die für 30 Tage freigeschaltet wird.
Und spätestens an der Stelle wurde aus „Benutzergruppen verkaufen“ ein etwas größeres Thema.
Genau daraus ist YoureCom Shop: Monetarisierung entstanden.
Kauf mir eine Funktion
Das Prinzip dahinter ist eigentlich herrlich simpel.
Der Shop kann bereits ziemlich gut Geld einsammeln. Er kennt Produkte, Bestellungen, Kunden und Zahlungen.
Was ihm bisher fehlte, war die Antwort auf eine andere Frage:
Was bekommt der Kunde eigentlich innerhalb der Community für sein Geld?
Nicht als Download. Sondern wirklich als Funktion.
Ein Beispiel:
Du legst ein Produkt „Premium-Mitgliedschaft“ an und sagst: Wer das kauft, bekommt die Benutzergruppe „Premium“ für 30 Tage.
Kunde kauft. Gruppe wird vergeben. 30 Tage später fliegt sie automatisch wieder raus.
Oder: Wer dieses Produkt kauft, bekommt dauerhaft die Trophäe „Supporter“.
Kaufen. Trophäe da. Fertig.
Und zusammen mit YoureCom Shop: Abonnements wird es noch interessanter.
Dann hängt die Berechtigung nämlich direkt am Abonnement. Läuft das Abo, läuft auch die Funktion. Wird verlängert, bleibt sie bestehen. Endet das Abo, verschwindet auch die Berechtigung wieder.
Bis hierhin könnte ich jetzt sagen: Cool. Ich habe einen Benutzergruppen- und Trophäen-Verkäufer gebaut.
Aber genau das wollte ich eben nicht.
Warum sollte ich bestimmen, was ihr verkaufen könnt?
Das war für mich der eigentlich interessante Gedanke bei der Entwicklung.
Ich kann unmöglich wissen, welche Funktionen andere Entwickler irgendwann monetarisieren möchten. Und ich habe ehrlich gesagt auch keine Lust, für jeden neuen Anwendungsfall ein Update zu veröffentlichen.
Heute möchte jemand Benutzergruppen verkaufen. Morgen möchte jemand zusätzliche Einträge in seinem Branchenverzeichnis anbieten. Der Nächste baut ein Plugin und möchte für 4,99 € im Monat die Werbung abschalten.
Jemand anderes verkauft zusätzliche Charakterplätze in einem Spiel. Oder mehr Speicherplatz. Oder besondere Profilfunktionen. Oder einen hervorgehobenen Eintrag. Oder 50 zusätzliche Was-auch-immer-sein-Plugin-da-gerade-macht.
Warum sollte Monetarisierung davon überhaupt etwas wissen müssen?
Muss es nicht.
Sag dem Shop einfach, was er nach dem Kauf tun soll
Deshalb ist Monetarisierung erweiterbar.
Ein anderes Plugin kann seinen eigenen Anspruchstyp registrieren und im Grunde sagen: „Hallo Shop. Ich habe hier etwas, das man verkaufen kann.“
Monetarisierung kümmert sich anschließend um den ganzen langweiligen Kram drumherum:
- Wann muss die Funktion aktiviert werden?
- Wie lange gilt sie?
- Wann muss sie wieder weg?
- Hat die Aktivierung funktioniert?
- Ist sie tatsächlich noch aktiv?
- Muss nach einem Fehler ein neuer Versuch gestartet werden?
Das eigentliche Plugin muss dagegen nur erklären, wie seine Funktion aktiviert und wieder deaktiviert wird.
Dafür gibt es den Erweiterungspunkt:
de.yourecom.shop.monetization.entitlementHandler
Und ab hier wird es für Entwickler interessant.
Bauen wir uns einfach etwas Verkaufbares
Nehmen wir als Beispiel ein Plugin, mit dem ein bestimmter Thread hervorgehoben werden kann.
Völlig egal, ob das als reales Produkt sinnvoll wäre – das Prinzip lässt sich daran schön zeigen.
Wir möchten am Ende sagen können: „Dieser Kunde darf diesen Thread für 7 Tage hervorheben.“
Also bringen wir unserem Plugin bei, dass diese Funktion verkauft werden kann.
1. Monetarisierung bekannt machen
Zuerst braucht unser Paket Monetarisierung als Abhängigkeit:
<requiredpackages>
<requiredpackage minversion="6.2.0">de.yourecom.shop.monetization</requiredpackage>
</requiredpackages>
<instructions type="install">
<instruction type="file" application="shop" />
<instruction type="language" />
<instruction type="objectType" />
</instructions>
Noch nichts Spannendes.
2. Jetzt sagen wir: „Ich habe etwas zu verkaufen“
Dafür registrieren wir einen eigenen Handler:
<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/5.4/objectType.xsd">
<import>
<type>
<name>de.example.forumboost.threadBoost</name>
<definitionname>de.yourecom.shop.monetization.entitlementHandler</definitionname>
<classname>example\forumboost\system\entitlement\ThreadBoostEntitlementHandler</classname>
</type>
</import>
</data>
Damit weiß Monetarisierung: Es gibt einen neuen Typ von Berechtigung.
Was dieser Typ macht? Keine Ahnung. Und genau das ist der Punkt.
Das weiß nur unser Plugin.
Jetzt kommt der eigentlich interessante Teil
Unser Handler bekommt unter anderem drei wichtige Aufgaben: Freischalten. Entziehen. Kontrollieren.
Sehr vereinfacht könnte das so aussehen:
<?php
namespace example\forumboost\system\entitlement;
use shop\data\monetization\entitlement\Entitlement;
use shop\data\monetization\grant\Grant;
use shop\system\entitlement\AbstractEntitlementHandler;
use wbb\data\thread\Thread;
use wbb\data\thread\ThreadEditor;
use wcf\system\exception\UserInputException;
use wcf\system\form\builder\field\IntegerFormField;
use wcf\system\form\builder\IFormDocument;
final class ThreadBoostEntitlementHandler extends AbstractEntitlementHandler
{
public function validateConfig(array $config): void
{
if (!(new Thread((int)($config['threadID'] ?? 0)))->threadID) {
throw new UserInputException('threadID', 'invalid');
}
}
public function requiresDuration(): bool
{
return true;
}
public function describe(Entitlement $entitlement): string
{
$thread = new Thread((int)($entitlement->getConfig()['threadID'] ?? 0));
return $thread->threadID ? $thread->getTitle() : '';
}
public function grant(Grant $grant): void
{
(new ThreadEditor($this->getThread($grant)))->update(['isSticky' => 1]);
}
public function revoke(Grant $grant): void
{
(new ThreadEditor($this->getThread($grant)))->update(['isSticky' => 0]);
}
public function isActive(Grant $grant): bool
{
return (bool)$this->getThread($grant)->isSticky;
}
public function getConfigFormFields(): array
{
return [
IntegerFormField::create('configThreadBoostThreadID')
->label('forumboost.acp.entitlement.threadID')
->required(),
];
}
public function getConfigFromFormFields(IFormDocument $form): array
{
$field = $form->getNodeById('configThreadBoostThreadID');
\assert($field instanceof IntegerFormField);
return ['threadID' => (int)$field->getValue()];
}
public function populateConfigFormFields(IFormDocument $form, array $config): void
{
$field = $form->getNodeById('configThreadBoostThreadID');
\assert($field instanceof IntegerFormField);
$field->value((int)($config['threadID'] ?? 0));
}
private function getThread(Grant $grant): Thread
{
return new Thread((int)$grant->getEntitlement()->getConfig()['threadID']);
}
}
Alles anzeigen
grant() sagt: Mach an.
revoke() sagt: Mach wieder aus.
isActive() beantwortet die Frage: Ist wirklich an?
Den Rest übernimmt Monetarisierung.
Natürlich ist das Beispiel bewusst vereinfacht. Eine produktive Umsetzung muss unter anderem parallele aktive Käufe berücksichtigen, Änderungen sauber über die jeweiligen Domain-APIs durchführen und ein vom Käufer ausgewähltes Ziel entsprechend speichern.
Aber das Entscheidende ist das Prinzip dahinter.
Monetarisierung muss nicht wissen, was ein Thread ist. Sie muss nicht wissen, was „Sticky“ bedeutet. Und sie muss erst recht nicht für dieses Plugin angepasst werden.
Und wie kommt das Ganze ins ACP?
Auch das kann der Handler selbst mitbringen.
Über validateConfig(), describe(), getConfigFormFields(), getConfigFromFormFields() und populateConfigFormFields() kann er seine eigene Konfiguration bereitstellen.
Noch eine Sprachvariable:
<?xml version="1.0" encoding="UTF-8"?>
<language xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/5.4/language.xsd" languagecode="de">
<import>
<category name="monetization.acp.entitlementHandler">
<item name="monetization.acp.entitlementHandler.de.example.forumboost.threadBoost"><![CDATA[Thread hervorheben]]></item>
</category>
<category name="forumboost.acp.entitlement">
<item name="forumboost.acp.entitlement.threadID"><![CDATA[Thread-ID]]></item>
</category>
</import>
</language>
Alles anzeigen
Plugin installieren. Und der neue Anspruchstyp steht in Monetarisierung zur Verfügung.
Ohne eine einzige Änderung am Monetarisierungs-Plugin.
Das ist der Teil, den ich persönlich ziemlich cool finde.
Und plötzlich verkauft der Shop keine Produkte mehr
Zumindest nicht nur.
Denn wenn man das Prinzip einmal weiterdenkt, wird es ziemlich interessant.
Ein Entwickler baut eine Funktion. Er registriert einen Handler. Ein Betreiber macht daraus ein Produkt.
Und plötzlich kann diese Funktion einmalig für 9,99 €, für 30 Tage, für ein Jahr oder zusammen mit einem Abonnement verkauft werden.
Der Entwickler muss dafür keinen eigenen Checkout bauen. Keine Zahlungsarten integrieren. Keinen eigenen Ablauf für Abonnements entwickeln. Und auch nicht irgendwo einen Cronjob zusammenfrickeln, der nachts Benutzergruppen wieder entfernt.
Das alles existiert bereits.
Er muss nur noch das bauen, womit er eigentlich Geld verdienen möchte: seine Funktion.
Und genau deshalb heißt das Ding auch nicht „Benutzergruppen verkaufen“.
Sondern Monetarisierung.
Denn Benutzergruppen und Trophäen sind eigentlich nur zwei Beispiele.
Was am Ende wirklich damit verkauft wird, entscheidet nicht YoureCom.
Das entscheidet ihr.