Zum Inhalt

Programmierbeispiele im Zusammenhang mit Zahluns-Plugins

Zahlungsart soll nur mit "Zahlung nach Bestellabschluss" verwendbar sein

Zuerst muss sichergestellt werden, dass die Zahlungsart deaktiviert wird, wenn die falsche Einstellung aktiv ist. Dazu wird in der Payment-Klasse die Methode isValidIntern überschrieben.

/**
 * @inheritDoc
 */
public function isValidIntern(array $args_arr = []): bool
{
    return parent::isValidIntern($args_arr) && $this->duringCheckout === 0;
}

Um den Shop-Admin über den Fehlerzustand zu informieren, wird eine Backend-Notification angezeigt, wenn die falsche Einstellung aktiv ist. Dazu wird im Bootstrapper des Plugins ein Listener für backend.notification registriert, dort die Einstellung ausgewertet und ggfs. eine Meldung angezeigt.

Bootstrap.php

/**
 * @inheritDoc
 */
public function boot(Dispatcher $dispatcher): void
{
    parent::boot($dispatcher);

    if (Shop::isFrontend()) {
        ... do whatever is neccessary in frontend
    } else {
        $dispatcher->listen('backend.notification', [$this, 'checkPayments']);
    }
}

/**
 * @return void
 */
public function checkPayments(): void
{
    /** @var PaymentMethod $paymentMethod */
    foreach ($this->getPlugin()->getPaymentMethods()->getMethods() as $paymentMethod) {
        $method = Method::create($paymentMethod->getModuleID());
        if ($method instanceof SimplePayment && $method->duringCheckout !== 0) {
            $note = new NotificationEntry(
                NotificationEntry::TYPE_WARNING,
                $paymentMethod->getName(),
                'Die Zahlungsart kann nicht mit Zahlung vor Bestellabschluss verwendet werden',
                Shop::getAdminURL() . '/paymentmethods?kZahlungsart=' . $method->kZahlungsart
                . '&token=' . $_SESSION['jtl_token']
            );
            $note->setPluginId($this->getPlugin()->getPluginID());
            Notification::getInstance()->addNotify($note);
        }
    }
}

Hint

Durch Umkehren der Logik kann das Code-Beispiel auch für die Prüfung auf "Zahlung vor Bestellabschluss" verwendet werden.

Step-by-Step Beispiel für ein Zahlungsplugin

Dieses Beispiel soll die Implementation eines einfachen Zahlungs-Plugins demonstrieren. Das Plugin soll Informationen für eine Überweisung bereitstellen und sowohl vor als auch nach Bestellabschluss funktionieren. Das Plugin soll sowohl im Front- als auch im Backend mehrsprachig (Deutsch und Englisch) funktionieren. Durch einen "Weiter"- und "Abbrechen"-Button wird beim "Zahlungsvorgang" eine erfolgreiche Zahlung bzw. ein Zahlungsabbruch simuliert.

Was wird benötigt

- Grundstruktur für ein Zahlungsplugin
- Einstellungen für die Überweisungsinformationen
- Template für die Anzeige der Überweisungsinformation
- Implementation der Zahlungsart

Grundstruktur für ein Zahlungsplugin

Es wird die Grundstruktur für ein Zahlungs-Plugin erstellt.

[jtl_samplepayment]/
    ├── locale
    │   └── de-DE
    │   |    └── base.mo
    │   |    └── base.po
    |   └── en-GB
    │   |    └── base.mo
    │   |    └── base.po
    ├── paymentmethod
    │   └── template
    │   |    └── sample_payment.tpl
    │   └── SamplePayment.php
    ├── Bootstrap.php
    └── info.xml

In der info.xml werden alle Einstellungen und Parameter für das Plugin und die Zahlungsart definiert. Das umfasst:

  • Die Zahlungsart "Sample payment" mit den Einstellungen für Zahlungsempfänger, Bankname und IBAN.
  • Die Definition eines Templates für die Zahlungsart.
  • Die Frontend-Sprachvariablen.

info.xml

<?xml version='1.0' encoding="UTF-8"?>
<jtlshopplugin>
    <Name>JTL Sample Payment</Name>
    <Description>JTL Sample Payment - This is for developers only! This is not a real payment plugin!</Description>
    <Author>JTL-Software-GmbH</Author>
    <URL>https://www.jtl-software.de</URL>
    <PluginID>jtl_samplepayment</PluginID>
    <XMLVersion>100</XMLVersion>
    <ShopVersion>5.2.3</ShopVersion>
    <Version>0.0.10</Version>
    <CreateDate>2023-08-16</CreateDate>
    <Install>
        <PaymentMethod>
            <Method>
                <Name>Sample payment</Name>
                <Sort>1</Sort>
                <SendMail>1</SendMail>
                <Provider>JTL Sample Payment</Provider>
                <TSCode>OTHER</TSCode>
                <PreOrder>1</PreOrder>
                <Soap>0</Soap>
                <Curl>0</Curl>
                <Sockets>0</Sockets>
                <ClassFile>SamplePayment.php</ClassFile>
                <TemplateFile>template/sample_payment.tpl</TemplateFile>
                <ClassName>SamplePayment</ClassName>
                <MethodLanguage iso="GER">
                    <Name>Einfache Zahlung (Banküberweisung)</Name>
                    <ChargeName>Einfache Zahlung wie Banküberweisung</ChargeName>
                    <InfoText>Einfache Zahlung wie eine Banküberweisung durch den Kunden. Zahlungsinformationen werden per Template abgezeigt.</InfoText>
                </MethodLanguage>
                <MethodLanguage iso="ENG">
                    <Name>Sample payment (bank transfer)</Name>
                    <ChargeName>Sample payment like bank transfer</ChargeName>
                    <InfoText>Sample payment like bank transfer by customer. Payment info will be displayed via template.</InfoText>
                </MethodLanguage>
                <Setting type="text" initialValue="" sort="1" conf="Y">
                    <Name>Empfängername</Name>
                    <Description>Empfängername der Zahlungsinformationen die dem Kunden angezeigt werden</Description>
                    <ValueName>payment_information_name</ValueName>
                </Setting>
                <Setting type="text" initialValue="" sort="1" conf="Y">
                    <Name>Bankname</Name>
                    <Description>Bankname der Zahlungsinformationen die dem Kunden angezeigt werden</Description>
                    <ValueName>payment_information_bankname</ValueName>
                </Setting>
                <Setting type="text" initialValue="" sort="1" conf="Y">
                    <Name>IBAN</Name>
                    <Description>IBAN der Zahlungsinformationen die dem Kunden angezeigt werden</Description>
                    <ValueName>payment_information_iban</ValueName>
                </Setting>
            </Method>
        </PaymentMethod>
        <Locales>
            <Variable>
                <Name>head_info_about_your_payment</Name>
                <VariableLocalized iso="GER">Informationen zu Ihrer Zahlung</VariableLocalized>
                <VariableLocalized iso="ENG">Information about your payment</VariableLocalized>
            </Variable>
            <Variable>
                <Name>info_payee</Name>
                <VariableLocalized iso="GER">Zahlungsempfänger</VariableLocalized>
                <VariableLocalized iso="ENG">Payee</VariableLocalized>
            </Variable>
            <Variable>
                <Name>info_bank</Name>
                <VariableLocalized iso="GER">Bankname</VariableLocalized>
                <VariableLocalized iso="ENG">Bank name</VariableLocalized>
            </Variable>
            <Variable>
                <Name>info_iban</Name>
                <VariableLocalized iso="GER">IBAN</VariableLocalized>
                <VariableLocalized iso="ENG">IBAN</VariableLocalized>
            </Variable>
        </Locales>
    </Install>
</jtlshopplugin>

Übersetzungen der Inhalte aus der info.xml

Für die Übersetzungen der Einträge der info.xml werden in den .po-Dateien für Deutsch und Englisch entsprechende Einträge angelegt und diese dann in .mo-Dateien kompiliert.

locale/de-DE/base.po

msgid "JTL Sample Payment"
msgstr "JTL Beispiel Zahlung"

msgid "JTL Sample Payment - This is for developers only! This is not a real payment plugin!"
msgstr "JTL Beispiel Zahlung - Dies ist für Entwickler gedacht! Es handelt sich nicht um ein echtes Zahlungs-Plugin!"

msgid "Sample payment"
msgstr "Beispiel Zahlungsmodul"

msgid "Empfängername"
msgstr "Zahlungsempfänger"

msgid "Bankname"
msgstr "Name der Bank"

msgid "IBAN"
msgstr "IBAN"

locale/en-GB/base.po

msgid "JTL Sample Payment"
msgstr "JTL Sample Payment"

msgid "JTL Sample Payment - This is for developers only! This is not a real payment plugin!"
msgstr "JTL Sample Payment - This is for developers only! This is not a real payment plugin!"

msgid "Sample payment"
msgstr "Sample payment module"

msgid "Empfängername"
msgstr "Payee"

msgid "Bankname"
msgstr "Bank name"

msgid "IBAN"
msgstr "IBAN"

Note

Zum Kompilieren der .po-Files stehen verschiedene Tools zur Verfügung. Auf einem Linux-System kann dies z.B. einfach mit dem Befehl msgfmt gemacht werden.

Implementation der Zahlungsart

Für das Handling des Plugins wird der Bootstrapper des Plugins erstellt.

Bootstrap.php

<?php declare(strict_types=1);

namespace Plugin\jtl_samplepayment;

use JTL\Plugin\Bootstrapper;

/**
 * Class Bootstrap
 * @package Plugin\jtl_samplepayment
 */
class Bootstrap extends Bootstrapper
{

}

Es wird eine Payment-Klasse erstellt, welche direkt von JTL\Plugin\Payment\Method abgeleitet wird.

paymentmethod/SamplePayment.php

<?php declare(strict_types=1);

namespace Plugin\jtl_samplepayment\paymentmethod;

use JTL\Plugin\Payment\Method;

/**
 * Class SamplePayment
 * @package Plugin\jtl_samplepayment\paymentmethod
 */
class SamplePayment extends Method
{

}

Ab diesem Moment ist die Zahlungsart im Shop verfügbar und kann konfiguriert und Versandarten zugeordnet werden. Damit die Versandart im Checkout nicht ohne Konfiguration angezeigt wird, wird die Methode isValidIntern der Payment-Klasse überschrieben und eine Notification für den Shopbetreiber im Backend implementiert.

paymentmethod/SamplePayment.php

<?php declare(strict_types=1);

namespace Plugin\jtl_samplepayment\paymentmethod;

use JTL\Plugin\Data\PaymentMethod;
use JTL\Plugin\Helper as PluginHelper;
use JTL\Plugin\Payment\Method;
use JTL\Plugin\PluginInterface;

/**
 * Class SamplePayment
 * @package Plugin\jtl_samplepayment\paymentmethod
 */
class SamplePayment extends Method
{
    /** @var PluginInterface */
    private PluginInterface $plugin;

    /** @var PaymentMethod|null */
    private ?PaymentMethod $method;

    /**
     * @inheritDoc
     */
    public function init(int $nAgainCheckout = 0): self
    {
        parent::init($nAgainCheckout);

        $pluginID     = PluginHelper::getIDByModuleID($this->moduleID);
        $this->plugin = PluginHelper::getLoaderByPluginID($pluginID)->init($pluginID);
        $this->method = $this->plugin->getPaymentMethods()->getMethodByID($this->moduleID);

        return $this;
    }

    /**
     * @return PaymentMethod
     */
    public function getMethod(): PaymentMethod
    {
        return $this->method;
    }

    /**
     * @inheritDoc
     */
    public function getSetting(string $key): mixed
    {
        $setting = parent::getSetting($key);

        if ($setting === null) {
            $setting = $this->plugin->getConfig()->getValue($this->getMethod()->getModuleID() . '_' . $key);
        }

        return $setting;
    }

    /**
     * @inheritDoc
     */
    public function isValidIntern(array $args_arr = []): bool
    {
        if ($this->method === null) {
            return false;
        }

        $name     = $this->getSetting('payment_information_name') ?? '';
        $bankName = $this->getSetting('payment_information_bankname') ?? '';
        $IBAN     = $this->getSetting('payment_information_iban') ?? '';

        return parent::isValidIntern($args_arr) && $name !== '' && $bankName !== '' && $IBAN !== '';
    }
}

Bootstrap.php

<?php declare(strict_types=1);

namespace Plugin\jtl_samplepayment;

use JTL\Backend\Notification;
use JTL\Backend\NotificationEntry;
use JTL\Events\Dispatcher;
use JTL\Plugin\Bootstrapper;
use JTL\Plugin\Payment\Method;
use JTL\Shop;
use Plugin\jtl_samplepayment\paymentmethod\SamplePayment;

/**
 * Class Bootstrap
 * @package Plugin\jtl_samplepayment
 */
class Bootstrap extends Bootstrapper
{
    /**
     * @inheritDoc
     */
    public function boot(Dispatcher $dispatcher): void
    {
        parent::boot($dispatcher);

        if (!Shop::isFrontend()) {
            $dispatcher->listen('backend.notification', [$this, 'checkPayments']);
        }
    }

    /**
     * @return void
     */
    public function checkPayments(): void
    {
        foreach ($this->getPlugin()->getPaymentMethods()->getMethods() as $paymentMethod) {
            $method = Method::create($paymentMethod->getModuleID());
            if ($method instanceof SamplePayment && !$method->isValidIntern()) {
                $note = new NotificationEntry(
                    NotificationEntry::TYPE_WARNING,
                    $paymentMethod->getName(),
                    __('Die Zahlungsart ist nicht konfiguriert'),
                    Shop::getAdminURL() . '/paymentmethods?kZahlungsart=' . $method->getMethod()->getMethodID()
                    . '&token=' . $_SESSION['jtl_token']
                );
                $note->setPluginId($this->getPlugin()->getPluginID());
                Notification::getInstance()->addNotify($note);
            }
        }
    }
}

Die Übersetzungen für die Fehlermeldungen müssen dann noch in den Lokalisierungen ergänzt werden.

locale/de-DE/base.po

...
msgid "Die Zahlungsart ist nicht konfiguriert"
msgstr "Die Zahlungsart ist nicht konfiguriert"

locale/en-GB/base.po

...
msgid "Die Zahlungsart ist nicht konfiguriert"
msgstr "The payment method is not configured"

Zunächst soll die "Zahlung vor Bestellabschluss" implementiert werden. Dazu werden nach dem Klick auf "Zahlungspflichtig bestellen" die Informationen zur Zahlung angezeigt. Eine erfolgreiche Zahlung wird durch einen Klick auf den Button "Weiter" simuliert und eine fehlgeschlagene Zahlung durch den Button "Abbrechen". Dafür wird zunächst das Template angelegt.

paymentmethod/template/sample_payment.tpl

<hr>
<p>{$samplePaymentDescription}</p>
<dl>
    <dt>{$samplePayment_LabelPayee}</dt>
    <dd>{$samplePayment_ConfigPayee}</dd>
    <dt>{$samplePayment_LabelBankName}</dt>
    <dd>{$samplePayment_ConfigBankName}</dd>
    <dt>{$samplePayment_LabelIBAN}</dt>
    <dd>{$samplePayment_ConfigIBAN}</dd>
</dl>

<a class="btn  btn-primary" href="{$samplePaymentSuccessURL}">{lang key='continueOrder' section='account data'}</a>

<a class="btn  btn-secondary" href="{$samplePaymentCancelURL}">{lang key='filterCancel' section='global'}</a>

Die Bereitstellung der Daten passiert in preparePaymentProcess der Payment-Klasse. Die Methode wird deshalb überschrieben.

paymentmethod/SamplePayment.php

/**
 * @inheritDoc
 */
public function preparePaymentProcess(Bestellung $order): void
{
    parent::preparePaymentProcess($order);

    $localization = $this->plugin->getLocalization();
    $paymentHash  = $this->generateHash($order);
    if ($this->duringCheckout) {
        Shop::Smarty()
            ->assign('samplePaymentSuccessURL', $this->getNotificationURL($paymentHash) . '&payed')
            ->assign('samplePaymentCancelURL', $this->getNotificationURL($paymentHash))
            ->assign('samplePaymentDescription', $localization->getTranslation('head_info_about_your_payment'))
            ->assign('samplePayment_LabelPayee', $localization->getTranslation('info_payee'))
            ->assign('samplePayment_ConfigPayee', $this->getSetting('payment_information_name'))
            ->assign('samplePayment_LabelBankName', $localization->getTranslation('info_bank'))
            ->assign('samplePayment_ConfigBankName', $this->getSetting('payment_information_bankname'))
            ->assign('samplePayment_LabelIBAN', $localization->getTranslation('info_iban'))
            ->assign('samplePayment_ConfigIBAN', $this->getSetting('payment_information_iban'));
    }
}

Im Falle von "Weiter" wird der URL-Parameter payed übergeben. Dieser wird im Zahlungsprozess ausgewertet und entsprechend die Zahlung abgebrochen oder ausgeführt. Dazu muss die Methode finalizeOrder überschrieben werden.

paymentmethod/SamplePayment.php

use JTL\Checkout\Bestellung;

...

/**
 * @inheritDoc
 */
public function finalizeOrder(Bestellung $order, string $hash, array $args): bool
{
    parent::finalizeOrder($order, $hash, $args);

    $payed = isset($args['payed']);
    $this->doLog('SamplePayment::finalizeOrder ' . ($payed ? 'payed' : 'not payed'));

    return $payed;
}

Das Verhalten nach der Zahlung wird über die Methoden redirectOnPaymentSuccess und redirectOnCancel gesteuert. Es sollen jeweils die Redirects ausgeführt werden. Die beiden Methoden müssen deshalb true zurückliefern.

paymentmethod/SamplePayment.php

/**
 * @inheritDoc
 */
public function redirectOnPaymentSuccess(): bool
{
    return true;
}

/**
 * @inheritDoc
 */
public function redirectOnCancel(): bool
{
    return true;
}

Um den Status der Bestellung im Falle von "payed" auf "Bezahlt" zu setzen und einen Zahlungseingang zu speichern, wird die "Notification des Zahlungsproviders" - im Beispiel der Klick auf "Weiter" mit der Notification-URL - ausgewertet. Dazu wird die Methode handleNotification überschrieben.

paymentmethod/SamplePayment.php

/**
 * @inheritDoc
 */
public function handleNotification(Bestellung $order, string $hash, array $args): void
{
    parent::handleNotification($order, $hash, $args);

    if (isset($args['payed'])) {
        $this->addIncomingPayment($order, (object)[
            'fBetrag'           => $order->fGesamtsumme,
            'fZahlungsgebuehr'  => 0,
        ]);
        $this->setOrderStatusToPaid($order);
    }
}

An diesem Punkt ist die "Zahlung vor Bestellabschluss" für unser Beispiel komplett implementiert. Nach dem Klick auf "Zahlungspflichtig bestellen" wird die Zahlung "gestartet". Diese wird "ausgeführt" oder "abgebrochen" und je nachdem eine Bestellung im Shop angelegt oder wieder zurück zur Zahlungsart-Auswahl geleitet.

Hint

Es ist sinnvoll an dieser Stelle eine Meldung auszugeben, um den Nutzer über den Abbruch der Zahlung zu informieren. Dies könnte in der Methode finalizeOrder durch Erstellen eines Alerts erfolgen.

Für die Unterstützung einer "Zahlung nach Bestellabschluss" ändert sich in der Beispiel-Zahlung nicht viel. Die Methode preparePaymentProcess wird jedoch vom Shop-Core zu einem anderen Zeitpunkt aufgerufen. Die Bestellung ist dann bereits in der DB gespeichert. In diesem Fall besteht keine Möglichkeit mehr, die Bestellung nach gescheiterter Zahlung abzubrechen. Als Cancel-URL wird deshalb die Return-URL verwendet. In diesem Fall bleibt die Bestellung dann unbezahlt und es gibt keinen Zahlungseingang. Die Methode preparePaymentProcess wird deshalb angepasst.

paymentmethod/SamplePayment.php

/**
 * @inheritDoc
 */
public function preparePaymentProcess(Bestellung $order): void
{
    parent::preparePaymentProcess($order);

    $localization = $this->plugin->getLocalization();
    $paymentHash  = $this->generateHash($order);
    $smarty       = Shop::Smarty();

    Shop::Smarty()
        ->assign('samplePaymentSuccessURL', $this->getNotificationURL($paymentHash) . '&payed')
        ->assign('samplePaymentDescription', $localization->getTranslation('head_info_about_your_payment'))
        ->assign('samplePayment_LabelPayee', $localization->getTranslation('info_payee'))
        ->assign('samplePayment_ConfigPayee', $this->getSetting('payment_information_name'))
        ->assign('samplePayment_LabelBankName', $localization->getTranslation('info_bank'))
        ->assign('samplePayment_ConfigBankName', $this->getSetting('payment_information_bankname'))
        ->assign('samplePayment_LabelIBAN', $localization->getTranslation('info_iban'))
        ->assign('samplePayment_ConfigIBAN', $this->getSetting('payment_information_iban'));

    if ($this->duringCheckout) {
        $smarty->assign('samplePaymentCancelURL', $this->getNotificationURL($paymentHash));
    } else {
        $smarty->assign('samplePaymentCancelURL', $this->getReturnURL($order));
    }
}

Hint

Im Falle eines "Zahlungsabbruches" wird trotzdem der Bestellabschuss angezeigt. Hier ist es deshalb umso wichtiger, dem Kunden eine Meldung über die nicht erfolgte Zahlung anzuzeigen und darauf hinzuweisen, dass die Bestellung zwar eingegangen aber unbezahlt ist und deshalb nicht bearbeitet werden kann. Dies kann z.B. durch Erstellen eines Alerts erfolgen.

Im Falle einer "Zahlung nach Bestellabschluss" kann es jetzt vorkommen, dass Bestellungen als unbezahlt angelegt werden. In der Zahlungsmethode kann deshalb die nachträgliche Bezahlung angeboten werden. Die Möglichkeit wird über die Methode canPayAgain aktiviert. Die Methode muss dazu true zurückliefern.

paymentmethod/SamplePayment.php

/**
 * @inheritDoc
 */
public function canPayAgain(): bool
{
    return true;
}

Ob eine Zahlung eine "nachträgliche" Zahlung ist, wird der Init-Methode als Parameter übergeben. Um diesen Status innerhalb der Instanz der Payment-Klasse zur Verfügung zu haben, wird eine neue Property implementiert.

paymentmethod/SamplePayment.php

/**
 * Class SamplePayment
 * @package Plugin\jtl_samplepayment\paymentmethod
 */
class SamplePayment extends Method
{
    ...

    /** @var bool */
    private bool $payAgain;

    /**
     * @inheritDoc
     */
    public function init(int $nAgainCheckout = 0): self
    {
        parent::init($nAgainCheckout);

        $pluginID       = PluginHelper::getIDByModuleID($this->moduleID);
        $this->plugin   = PluginHelper::getLoaderByPluginID($pluginID)->init($pluginID);
        $this->method   = $this->plugin->getPaymentMethods()->getMethodByID($this->moduleID);
        $this->payAgain = $nAgainCheckout > 0;

        return $this;
    }

    ...
}

Da die Zuordnung einer Bestellung zu einer Unique-ID im Bestellabschluss aus Sicherheitsgründen gelöscht wird, muss diese Verbindung im Falle einer nachträglichen Bezahlung neu erstellt werden. Dazu wird die Methode preparePaymentProcess angepasst.

paymentmethod/SamplePayment.php

/**
 * @inheritDoc
 */
public function preparePaymentProcess(Bestellung $order): void
{
    parent::preparePaymentProcess($order);

    $smarty       = Shop::Smarty();
    $localization = $this->plugin->getLocalization();

    if ($this->payAgain) {
        $paymentHash = $this->getOrderHash($order);
        if ($paymentHash === null) {
            $this->getDB()->insert('tbestellid', (object)[
                'kBestellung' => $order->kBestellung,
                'cId'         => \uniqid('', true)
            ]);
            $paymentHash = $this->generateHash($order);
        }
    } else {
        $paymentHash = $this->generateHash($order);
    }

    Shop::Smarty()
        ->assign('samplePaymentSuccessURL', $this->getNotificationURL($paymentHash) . '&payed')
        ->assign('samplePaymentDescription', $localization->getTranslation('head_info_about_your_payment'))
        ->assign('samplePayment_LabelPayee', $localization->getTranslation('info_payee'))
        ->assign('samplePayment_ConfigPayee', $this->getSetting('payment_information_name'))
        ->assign('samplePayment_LabelBankName', $localization->getTranslation('info_bank'))
        ->assign('samplePayment_ConfigBankName', $this->getSetting('payment_information_bankname'))
        ->assign('samplePayment_LabelIBAN', $localization->getTranslation('info_iban'))
        ->assign('samplePayment_ConfigIBAN', $this->getSetting('payment_information_iban'));

    if ($this->duringCheckout) {
        $smarty->assign('samplePaymentCancelURL', $this->getNotificationURL($paymentHash));
    } else {
        $smarty->assign('samplePaymentCancelURL', $this->getReturnURL($order));
    }
}

Bestellung können jetzt auch nachträglich mit der Beispiel-Zahlungsart "bezahlt" werden.

Nachbemerkungen und Download des Beispiels

Reale Zahlungsprozesse sind leider nicht so einfach wie dieses Beispiel und es sind ggfs. weitere Faktoren zu berücksichtigen:

  • Reloads der "Zahlungsseite" führen ggfs. zum erneuten Starten des Zahlungsvorganges
  • Zahlungssummen entsprechen nicht immer der Bestellsumme (Verwendung von Gutscheinen)
  • Wenn die "Rückleitung" vom Zahlungsdienstleister (Notification-URL) nicht korrekt funktioniert (oder vom Kunden abgebrochen wird) wird im Falle von "Zahlung vor Bestellabschluss" keine Bestellung angelegt und es kommt zu Zahlungen ohne Bestellung
  • und, und, und ...

Hier sind also programmatisch weitere Vorkehrungen zu treffen, die im Rahmen dieses Beispiels jedoch nicht berücksichtigt werden.

Der Beispiel-Code ist zudem bewusst einfach gehalten und es wurden nur die für die gewünschte Funktionalität absolut notwendigen Dateien angelegt. Ziel ist es, den prinzipiellen Ablauf einer Zahlung aufzuzeigen und den Unterschied im Ablauf bei "Zahlung vor / nach Bestellabschluss" zu verdeutlichen. Auf eine Trennung im Code zwischen Zahlungsfunktionalität und Frontendausgabe (Verwendung von Smarty in preparePaymentProcess) wurde explizit ebenso verzichtet, wie auf das Schreiben eigener Methoden (für z.B. die Erstellung des Hash bei payAgain) oder die Verwendung eines Interfaces für die Gültigkeitsprüfung im Bootstraper.

Auch hier sind also weitere programmatische Anpassungen notwendig, um ein gutes Zahlungs-Plugin zu entwickeln.

Das Step-by-Step Beispiel ist dazu gedacht - anhand der einzelnen Code-Stellen - das Plugin Stück für Stück "zusammenzubauen". Es ist für die endgültige - hier beschriebene - Funktionalität nicht erforderlich, selbst Code hinzuzufügen. Alle Code-Stücke können durch Copy & Paste zusammengesetzt werden. Wer trotzdem lieber den Code "am Stück" testen möchte, der kann sich das komplette Beispiel hier: Beispiel-Sourcecode herunterladen.