Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Zahlungsart-Konfiguration und Verfügbarkeitsprüfung: canUseCheckout und isAvailable

Zahlungsart-Konfiguration und Verfügbarkeitsprüfung: canUseCheckout und isAvailable

~7 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026

Kapitel 62 hat can_use_checkout und can_use_internal bereits als statische Config-Werte gesetzt, Kapitel 63 hat begründet, in welchem einen Fall die Zahlungsart überhaupt sichtbar werden soll. Dieses Kapitel verbindet beides: die Capability-Flags im Detail, und die individuelle isAvailable()-Logik, die diese eine Bedingung tatsächlich prüft.

canUseCheckout und canUseInternal im Detail

MethodInterface kennt eine ganze Reihe von can*-Fähigkeiten - canCapture, canRefund, canVoid und mehr, für ein rein rabattbasiertes Verfahren ohne echtes Gateway größtenteils irrelevant. Zwei davon sind hier bewusst gesetzt: can_use_checkout = 1, weil die Methode ausschließlich im Storefront-Checkout Sinn ergibt, und can_use_internal = 0, weil Admin-Bestellungsanlage (Sales > Orders > Create New Order) keinen vergleichbaren, eingeloggten Kundenkontext kennt wie der Storefront-Checkout - CustomerSession existiert dort schlicht nicht in derselben Form. Der Adapter aus Kapitel 62 liest beide Werte automatisch über den default-Handler des ValueHandlerPools aus payment/mironsoft_loyalty_points/can_use_checkout bzw. .../can_use_internal - keine einzige Zeile PHP nötig, reine Konfiguration.

isAvailable() und der individuelle AvailabilityHandler

isAvailable() ist etwas anderes als can_use_checkout: Es fragt nicht "darf diese Methode grundsätzlich im Checkout auftauchen", sondern "soll sie es JETZT, für DIESEN Warenkorb" - dynamisch, pro Request. Der default-Handler kann das nicht beantworten, er liest nur statische Config-Werte. Genau dafür registriert ValueHandlerPool einen zweiten, eigenen Handler unter dem Feldnamen availability:

app/code/Mironsoft/Loyalty/Model/Payment/AvailabilityHandler.php
<?php

declare(strict_types=1);

namespace Mironsoft\Loyalty\Model\Payment;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Payment\Gateway\Config\ValueHandlerInterface;
use Magento\Quote\Api\Data\CartInterface;
use Magento\Store\Model\ScopeInterface;
use Mironsoft\Loyalty\Model\Config\LoyaltyConfig;

/**
 * Decides whether "Mit Treuepunkten bezahlt" may be offered for the current
 * quote. Unlike the static can_use_checkout/can_use_internal flags from
 * chapter 62's config.xml, availability depends on live, per-quote data - the
 * default ConfigValueHandler cannot answer this, hence a dedicated handler.
 */
class AvailabilityHandler implements ValueHandlerInterface
{
    /**
     * Config path for the method's own active flag.
     */
    private const string XML_PATH_ACTIVE = 'payment/mironsoft_loyalty_points/active';

    /**
     * @param ScopeConfigInterface $scopeConfig Store-scoped configuration reader
     * @param LoyaltyConfig $loyaltyConfig Loyalty module configuration reader
     */
    public function __construct(
        private readonly ScopeConfigInterface $scopeConfig,
        private readonly LoyaltyConfig $loyaltyConfig,
    ) {
    }

    /**
     * Returns true only if the method is enabled, a customer is logged in, and
     * the points already applied to the quote (chapter 63) already cover the
     * quote's full grand total - the one edge case chapter 63 identified where
     * the points payment method itself, rather than a discount, is used.
     *
     * @param array $subject Handler subject, contains the "quote" key for availability checks
     * @param int|null $storeId Store ID for scoped config reads
     * @return bool
     */
    public function handle(array $subject, $storeId = null): bool
    {
        $quote = $subject['quote'] ?? null;

        if (!$quote instanceof CartInterface || !$quote->getCustomerId()) {
            return false;
        }

        $isActive = (bool) $this->scopeConfig->getValue(
            self::XML_PATH_ACTIVE,
            ScopeInterface::SCOPE_STORE,
            $storeId
        );

        if (!$isActive) {
            return false;
        }

        $pointsToRedeem = (int) $quote->getData('loyalty_points_to_redeem');

        if ($pointsToRedeem <= 0) {
            return false;
        }

        $coveredAmount = $pointsToRedeem / $this->loyaltyConfig->getPointsPerEuro();

        return $coveredAmount >= (float) $quote->getData('grand_total');
    }
}

etc/di.xml aus Kapitel 62 bekommt dafür genau eine Ergänzung im bereits bestehenden ValueHandlerPool-virtualType:

app/code/Mironsoft/Loyalty/etc/di.xml (Ausschnitt, ergänzt Kapitel 62)
<virtualType name="Mironsoft\Loyalty\Model\Payment\ValueHandlerPool"
             type="Magento\Payment\Gateway\Config\ValueHandlerPool">
    <arguments>
        <argument name="handlers" xsi:type="array">
            <item name="default" xsi:type="string">Magento\Payment\Gateway\Config\ConfigValueHandler</item>
            <item name="availability" xsi:type="string">Mironsoft\Loyalty\Model\Payment\AvailabilityHandler</item>
        </argument>
    </arguments>
</virtualType>
<!-- rest of etc/di.xml unchanged, see chapter 62 -->

Achtung: AvailabilityHandler::handle() darf niemals eine Exception werfen. Der ValueHandlerPool fängt hier nichts ab - eine unbehandelte Exception beim Rendern der Zahlungsart-Liste würde den GESAMTEN Payment-Step abstürzen lassen, auch für Kunden, die die Punkte-Zahlungsart gar nicht nutzen wollen. Kapitel 70 nimmt diesen Fehler noch einmal konkret auf.

Die Adminkonfiguration

Damit active, title und sort_order überhaupt im Admin unter Stores > Configuration > Sales > Payment Methods editierbar sind, braucht die bestehende payment-Sektion eine neue Gruppe:

app/code/Mironsoft/Loyalty/etc/adminhtml/system.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="payment">
            <group id="mironsoft_loyalty_points" translate="label" type="text"
                   sortOrder="500" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Mironsoft Loyalty - Punkte einlösen</label>
                <field id="active" translate="label" type="select" sortOrder="10"
                       showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Aktiviert</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="title" translate="label" type="text" sortOrder="20"
                       showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Titel im Checkout</label>
                </field>
                <field id="sort_order" translate="label" type="text" sortOrder="30"
                       showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Sortierreihenfolge</label>
                </field>
            </group>
        </section>
    </system>
</config>

Tipp: Nach jeder Änderung an system.xml oder config.xml reicht bin/cache-clean config - dieselbe Faustregel wie bei widget.xml (Kapitel 55) oder crontab.xml (Kapitel 32). Ein volles setup:upgrade ist hier nicht nötig, solange keine Datenbanktabelle betroffen ist; für die neue quote-Spalte und den Data Patch aus Kapitel 63 dagegen schon.

Konfiguriert, verfügbarkeitsgeprüft - was jetzt noch fehlt, ist die Sichtbarkeit im Checkout selbst. Kapitel 65 registriert dafür die Frontend-Komponente.