Wiederkehrende Zahlungen und Kreditkartenverwaltung in Adobe Commerce mit PAYONE


Author Photo
Sunjay Patel
Senior Developer at comwrap

Subscription business with Adobe Commerce

PAYONE - ist einer der führenden Zahlungsanbieter in Deutschland und Österreich (https://www.payone.com/DE-de). Die PAYONE Online-Zahlungsintegration wird in vielen unserer Projekte eingesetzt und der Integrationsprozess ist in der Regel recht einfach.

Als Unterstützung für dieses Projekt gibt es eine offizielle Magento 2 Erweiterung: https://github.com/PAYONE-GmbH/magento-2.

In einigen Fällen ist es jedoch erforderlich, PAYONE für wiederkehrende Zahlungen zu verwenden. Da diese Funktion nicht standardmäßig von der offiziellen Erweiterung unterstützt wird, zeigen wir, wie Sie wiederkehrende Zahlungen in Ihr System integrieren können.


Payone CreditCard Recurring hinzufügen

Wiederkehrende Transaktionen per Kreditkarte mit der Zahlungsdiensterichtlinie 2 (PSD2), die vorschreibt, dass alle Kreditkartenzahlungen durch den Kunden mittels starker Kundenauthentifizierung (SCA) authentifiziert werden müssen.

"Genau dies ist bei Abo-Modellen und Micropayments (virtuelles Konto / Abrechnung) nicht der Fall, da diese in Abwesenheit des Kunden durchgeführt werden. Hierfür wird das Modell "cards on file" oder "credentials on file" (kurz CoF) angeboten, bei dem solche Zahlungen speziell gekennzeichnet und dann von der SCA ausgeschlossen werden. Ebenso muss die erste, initiale Zahlung mittels SCA authentifiziert werden, um die PSD2-Richtlinien zu erfüllen. Nachfolgende Zahlungstransaktionen können mit Bezug auf die erste Zahlungstransaktion ausgelöst werden. Die Referenz auf die erste Transaktion wird dann über die PAYONE Plattform abgewickelt."


Erste Transaktion, gefolgt von wiederkehrenden Zahlungen

Die PAYONE Integration unterstützt bereits Parameter für:

  • customer_is_present

  • recurrence

Diese Parameter werden für Kreditkartenzahlungen verwendet, um CoF-Zahlungen anzuzeigen.


Einige Informationen zum Ablauf der Anfragen

Der Kunde möchte seine Kreditkarte für zukünftige Zahlungen speichern. Die erste Transaktion wird mit 3-D Secure durchgeführt. Die folgenden Transaktionen erfolgen ohne 3-D Secure.

Step Use case Server-API request Parameters to set Comments
1a Get customer agreement for CoF - only get agreement, amount is sent with 1. preauthorization
  • amount=1
  • recurrence=recurring
  • customer_is_present=yes
  • in this case, the amount that will be auhtorized later is not known yet
  • Merchant must obtain consent that data will be stored and be used for subsequent payments
  • Customer has to agree to CoF
  • Initial payment will be handled with 3D-secure
1b OR get customer agreement for CoF - with amount being sent  preauthorization/authorization
  • amount=<amount>
  • recurrence=recurring
  • customer_is_present=yes
  • Merchant must obtain consent that data will be stored and be used for subsequent payments
  • Customer has to agree to CoF
  • Initial payment will be handled with 3D-secure
  • Amount has to be captured by request "capture" if preauthorization is used
2 Subsequent payments preauthorization/authorization
  • amount=<amount>
  • recurrence=recurring
  • customer_is_present=no
  • userid or pseudocardpan
  • Subsequent payments will be handled with CoF if customer agreed to the initial payment process
  • Amount has to be captured by request "capture" if preauthorization is used

Auf der PAYONE API-Schicht sieht die anfängliche Anfrage also wie folgt aus:

/** RECURRING PARAMS **/
recurrence=recurring
customer_is_present=yes
/** RECURRING PARAMS ENDS **/

Full request
mid=23456 (your mid)
portalid=12345123 (your portalid)
key=abcdefghijklmn123456789 (your key)
api_version=3.10
mode=test (set to „live“ for live-requests)
request=preauthorization

/** RECURRING PARAMS **/
recurrence=recurring
customer_is_present=yes
/** RECURRING PARAMS ENDS **/

encoding=UTF-8
aid=12345 (your aid)
clearingtype=cc
cardtype=M
cardexpiredate=2110
pseudocardpan=1312312312312321
cardholder=Testperson Approved
amount=3000 (or 1 for initial authentication, without knowing the recurring amount)
currency=EUR
lastname=Approved
firstname=Testperson
salutation=Herr
country=DE
language=de
gender=m
birthday=19600707
street=Hellersbergstraße 14
city=Musterstad
zip=12345
email=youremail@email.com
telephonenumber=01512345678

Vom PAYONE API Layer her sieht es recht einfach aus, schauen wir uns die Magento Integration an. Als Basismodul wird das Standard PAYONE Magento2 Modul verwendet.


PAYONE wiederkehrende Zahlung hinzufügen

In dem Custom Modul erstellen Sie ein Plugin:

 \Payone\Core\Model\Methods\Creditcard::getPaymentSpecificParameters

public function afterGetPaymentSpecificParameters(Creditcard $subject, array $result)
{
   $areaCode = $this->state->getAreaCode();

   /** @var CartInterface $quote */
   $quote = $this->session->getQuote();

   // First time payment on website by customer
   if ($this->quoteValidate->validateQuote($quote)) { // Our requirement depend on amasty subscription module so we added condition to check it is subscription product or not
       $result[self::RECURRENCE] = 'recurring';
       $result[self::CUSTOMER_IS_PRESENT] = 'yes';
   } elseif (in_array($areaCode, [Area::AREA_CRONTAB, Area::AREA_WEBAPI_REST])) { // Recurring payment for subscription product, order will be create by cron or rest api
       $result[self::RECURRENCE] = 'recurring';
       $result[self::CUSTOMER_IS_PRESENT] = 'no';
       $result[self::PSEUDOCARDPAN] = $pseudocardpan; // If not added in other place (take value from already saved credit card in table)
   }
   return $result;
}

Dann müssen wir dem Kunden die Möglichkeit geben, seine CC-Informationen hinzuzufügen und zu verwalten.


Erstellen Sie eine neue Controller-, Template- und Layout-Datei, um das Adressfeld und das Kundenfeld anzuzeigen Template-Datei für neue CC:

Dies ist ein Beispiel dafür, wie eine Vorlage für das Hinzufügen von CC aussehen kann. Verwenden Sie sie, um das Formular an der gewünschten Stelle hinzuzufügen.

view/frontend/templates/newcard.phtml

<?php
/** @var \Comwrap\RecurringPayone\Block\Newcard $block */
/** @var \Magento\Customer\ViewModel\Address $viewModel */
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
$viewModel = $block->getViewModel();
?>
<?php $_country_id = $block->getAttributeData()->getFrontendLabel('country_id'); ?>
<?php $_street = $block->getAttributeData()->getFrontendLabel('street'); ?>
<?php $_city = $block->getAttributeData()->getFrontendLabel('city'); ?>
<?php $_region = $block->getAttributeData()->getFrontendLabel('region'); ?>
<?php $_selectRegion = 'Please select a region, state or province.'; ?>
<?php $_displayAll = $block->getConfig('general/region/display_all'); ?>

<?php $_vatidValidationClass = $viewModel->addressGetAttributeValidationClass('vat_id'); ?>
<?php $_cityValidationClass = $viewModel->addressGetAttributeValidationClass('city'); ?>
<?php $_postcodeValidationClass_value = $viewModel->addressGetAttributeValidationClass('postcode'); ?>
<?php $_postcodeValidationClass = $_postcodeValidationClass_value; ?>
<?php $_streetValidationClass = $viewModel->addressGetAttributeValidationClass('street'); ?>
<?php $_streetValidationClassNotRequired = trim(str_replace('required-entry', '', $_streetValidationClass)); ?>
<?php $_regionValidationClass = $viewModel->addressGetAttributeValidationClass('region'); ?>
<form class="form-address-edit"
      action="<?= $escaper->escapeUrl($block->getSaveUrl()) ?>"
      method="post"
      id="payoneCcAddForm"
      name="payoneCcAddForm"
      enctype="multipart/form-data"
      data-hasrequired="<?= $escaper->escapeHtmlAttr(__('* Required Fields')) ?>">
    <fieldset class="fieldset">
        <legend class="legend"><span><?= $escaper->escapeHtml(__('Contact Information')) ?></span></legend><br>
        <?= $block->getBlockHtml('formkey') ?>
        <input type="hidden" name="success_url" value="<?= $escaper->escapeUrl($block->getSuccessUrl()) ?>">
        <input type="hidden" name="error_url" value="<?= $escaper->escapeUrl($block->getErrorUrl()) ?>">
        <input type="hidden" name="back_url" value="<?= $escaper->escapeUrl($block->getBackUrl()) ?>">
        <?= $block->getNameBlockHtml() ?>
        <div class="field company required">
            <label class="label" for="company">
                <span><?= $escaper->escapeHtmlAttr(__('Company')) ?></span>
            </label>
            <div class="control">
                <input type="text"
                       name="company"
                       value=""
                       title="<?= $escaper->escapeHtmlAttr(__('Company')) ?>"
                       class="input-text"
                       data-validate="{required:true}"
                       id="company">
            </div>
        </div>
        <div class="field telephonenumber required">
            <label class="label" for="telephone">
                <span><?= $escaper->escapeHtmlAttr(__('Phone Number')) ?></span>
            </label>
            <div class="control">
                <input type="text"
                       name="telephone"
                       value=""
                       title="<?= $escaper->escapeHtmlAttr(__('Phone Number')) ?>"
                       class="input-text"
                       data-validate="{required:true}"
                       id="telephone">
            </div>
        </div>
    </fieldset>
    <fieldset class="fieldset">
        <legend class="legend"><span><?= $escaper->escapeHtml(__('Address')) ?></span></legend><br>
        <div class="field street required">
            <label for="street_1" class="label"><span><?= /* @noEscape */ $_street ?></span></label>
            <div class="control">
                <div class="field primary">
                    <label for="street_1" class="label">
                        <span>
                            <?= $escaper->escapeHtml(__('Street Address: Line %1', 1)) ?>
                        </span>
                    </label>
                </div>
                <input type="text"
                       name="street[]"
                       value="<?= $escaper->escapeHtmlAttr($block->getStreetLine(1)) ?>"
                       title="<?= /* @noEscape */ $_street ?>"
                       id="street_1"
                       class="input-text <?= $escaper->escapeHtmlAttr($_streetValidationClass) ?>"/>
                <div class="nested">
                    <?php for ($_i = 1, $_n = $viewModel->addressGetStreetLines(); $_i < $_n; $_i++): ?>
                        <div class="field additional">
                            <label class="label" for="street_<?= /* @noEscape */ $_i + 1 ?>">
                                <span><?= $escaper->escapeHtml(__('Street Address: Line %1', $_i + 1)) ?></span>
                            </label>
                            <div class="control">
                                <input type="text" name="street[]"
                                       value="<?= $escaper->escapeHtmlAttr($block->getStreetLine($_i + 1)) ?>"
                                       title="<?= $escaper->escapeHtmlAttr(__('Street Address %1', $_i + 1)) ?>"
                                       id="street_<?= /* @noEscape */ $_i + 1 ?>"
                                       class="input-text
                                        <?= $escaper->escapeHtmlAttr($_streetValidationClassNotRequired) ?>">
                            </div>
                        </div>
                    <?php endfor; ?>
                </div>
            </div>
        </div>

        <div class="field zip required">
            <label class="label" for="zip">
                <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('postcode') ?></span>
            </label>
            <div class="control">
                <input type="text"
                       name="postcode"
                       value="<?= $escaper->escapeHtmlAttr($block->getAddress()->getPostcode()) ?>"
                       title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('postcode') ?>"
                       id="zip"
                       class="input-text validate-zip-international
                        <?= $escaper->escapeHtmlAttr($_postcodeValidationClass) ?>">
                <div role="alert" class="message warning">
                    <span></span>
                </div>
                <?= /* @noEscape */ $secureRenderer->renderStyleAsTag("display: none;", 'div.message.warning') ?>
            </div>
        </div>

        <div class="field city required">
            <label class="label" for="city">
                <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('city') ?></span>
            </label>
            <div class="control">
                <input type="text"
                       name="city"
                       value="<?= $escaper->escapeHtmlAttr($block->getAddress()->getCity()) ?>"
                       title="<?= $escaper->escapeHtmlAttr(__('City')) ?>"
                       class="input-text <?= $escaper->escapeHtmlAttr($_cityValidationClass) ?>"
                       id="city">
            </div>
        </div>

        <?php if ($viewModel->addressIsVatAttributeVisible()): ?>
            <div class="field taxvat">
                <label class="label" for="vat_id">
                    <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('vat_id') ?></span>
                </label>
                <div class="control">
                    <input type="text"
                           name="vat_id"
                           value="<?= $escaper->escapeHtmlAttr($block->getAddress()->getVatId()) ?>"
                           title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('vat_id') ?>"
                           class="input-text <?= $escaper->escapeHtmlAttr($_vatidValidationClass) ?>"
                           id="vat_id">
                </div>
            </div>
        <?php endif; ?>
        <div class="field country required">
            <label class="label" for="country">
                <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('country_id') ?></span>
            </label>
            <div class="control">
                <?= $block->getCountryHtmlSelect() ?>
            </div>
        </div>
    </fieldset>
    <fieldset class="fieldset">
        <input type="hidden" name="pseudocardpan" id="pseudocardpan">
        <input type="hidden" name="truncatedcardpan" id="truncatedcardpan">
        <input type="hidden" name="cardexpiredate" id="cardexpiredate">
        <legend class="legend"><span><?= $escaper->escapeHtml(__('Credit Card')) ?></span></legend><br>
        <div class="control">
            <div style="display: none" class="mage-error" generated="true" id="payone_creditcard-error">
                <?= $escaper->escapeHtmlAttr(__('Please enter complete credit card data.')) ?>
            </div>
        </div>
        <div class="field payone_creditcard_credit_card_type required">
            <label class="label" for="payone_creditcard_credit_card_type">
                <span><?= /* @noEscape */ __('Credit Card Type') ?></span>
            </label>
            <div class="control">
                <select id="payone_creditcard_credit_card_type" name="payment[cc_type]"
                        title="<?= /* @noEscape */ __('Credit Card Type') ?>"
                        class="validate-select"
                        data-validate="{required:true}">
                    <?php foreach ($block->getCardTypes() as $option): ?>
                        <option value="<?= $escaper->escapeHtmlAttr($option['id']) ?>">
                            <?= $block->escapeHtml($option['title']) ?>
                        </option>
                    <?php endforeach;?>
                </select>
            </div>
        </div>
        <div class="field firstname required">
            <label class="label" for="payone_creditcard_firstname">
                <span><?= $escaper->escapeHtmlAttr(__('Firstname')) ?></span>
            </label>
            <div class="control">
                <input type="text"
                       name="payment[cc_firstname]"
                       value=""
                       title="<?= $escaper->escapeHtmlAttr(__('Firstname')) ?>"
                       class="input-text"
                       data-validate="{required:true}"
                       id="payone_creditcard_firstname">
            </div>
        </div>
        <div class="field lastname required">
            <label class="label" for="payone_creditcard_lastname">
                <span><?= $escaper->escapeHtmlAttr(__('Lastname')) ?></span>
            </label>
            <div class="control">
                <input type="text"
                       name="payment[cc_lastname]"
                       value=""
                       title="<?= $escaper->escapeHtmlAttr(__('Lastname')) ?>"
                       class="input-text"
                       data-validate="{required:true}"
                       id="payone_creditcard_lastname">
            </div>
        </div>
        <div class="field payone_creditcard_cc_number required">
            <label class="label" for="payone_creditcard_cc_number">
                <span><?= $escaper->escapeHtmlAttr(__('Credit Card Number')) ?></span>
            </label>
            <div class="control">
                <span id="cardpan" class="inputIframe"></span>
            </div>
        </div>

        <div class="field expiration_date required">
            <label class="label" for="payone_creditcard_expiration">
                <span><?= $escaper->escapeHtmlAttr(__('Expiration Date')) ?></span>
            </label>
            <div class="control">
                <div class="fields group group-2">
                    <div class="field no-label month">
                        <div class="control">
                            <span id="cardexpiremonth"></span>
                        </div>
                    </div>
                    <div class="field no-label year">
                        <div class="control">
                            <span id="cardexpireyear"></span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="field payone_creditcard_cc_cid required">
            <label class="label" for="payone_creditcard_cc_cid">
                <span><?= $escaper->escapeHtmlAttr(__('Card Verification Number')) ?></span>
            </label>
            <div class="control">
                <span id="cardcvc2" class="inputIframe"></span>
            </div>
        </div>
    </fieldset>
    <div class="actions-toolbar">
        <div class="primary">
            <input type="hidden" name="hideit" id="hideit" value="" />
            <button id="savecc"
                    type="button"
                    class="action save primary"
                    title="<?= $escaper->escapeHtmlAttr(__('Save Card')) ?>"
            >
                <span><?= $escaper->escapeHtml(__('Save Card')) ?></span>
            </button>
        </div>
        <div class="secondary">
            <a class="action back" href="<?= $escaper->escapeUrl($block->getBackUrl()) ?>">
                <span><?= $escaper->escapeHtml(__('Go back')) ?></span>
            </a>
        </div>
    </div>
</form>
<?php $serializedPayoneConfig = /* @noEscape */ $block->getPayoneConfig();
$scriptString = <<<script
        window.payoneConfig = {$serializedPayoneConfig};
script;
?>
<?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false) ?>
<?php $scriptString = <<<script
    require(["jquery","mage/mage"],function($){
       var dataForm = $('#payoneCcAddForm');
       $('button#savecc').click( function() {
            if (dataForm.validation('isValid')) {
                if (iframes.isComplete()) {
                    iframes.creditCardCheck('checkCallback');
                    return true;
                } else {
                    $('#payone_creditcard-error').show();
                    return false;
                }
            }
        });
    });
script;
?>
<?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false); ?>
<script>
    var iframes = new Payone.ClientApi.HostedIFrames(
        window.payoneConfig.payment.payone.fieldConfig,
        window.payoneConfig.payment.payone.hostedRequest
    );
    iframes.setCardType("V");

    document.getElementById('payone_creditcard_credit_card_type').onchange = function () {
        iframes.setCardType(this.value);      // on change: set new type of credit card to process
    };

    function checkCallback(response) {
        if (response.status === "VALID") {
            document.getElementById("pseudocardpan").value = response.pseudocardpan;
            document.getElementById("truncatedcardpan").value = response.truncatedcardpan;
            document.getElementById("cardexpiredate").value = response.cardexpiredate;
            document.payoneCcAddForm.submit();
        }
    }

</script>
<script type="text/x-magento-init">
    {
        "#payoneCcAddForm": {
            "addressValidation": {
                "postCodes": <?= /* @noEscape */ $block->getPostCodeConfig()->getSerializedPostCodes() ?>
            }
        },
        "#country": {
            "regionUpdater": {
                "optionalRegionAllowed": <?= /* @noEscape */ $_displayAll ? 'true' : 'false' ?>,
                "regionListId": "#region_id",
                "regionInputId": "#region",
                "postcodeId": "#zip",
                "form": "#form-validate",
                "regionJson": <?= /* @noEscape */ $viewModel->dataGetRegionJson() ?>,
                "defaultRegion": "<?= (int) $block->getRegionId() ?>",
                "countriesWithOptionalZip": <?= /* @noEscape */ $viewModel->dataGetCountriesWithOptionalZip(true) ?>
            }
        }
    }
</script>

Sobald alle erforderlichen Felder ausgefüllt sind und der Benutzer auf die Schaltfläche "Karte speichern" klickt, müssen wir die Kartendaten an Payone senden und die Informationen in Magento speichern.

In unserem Beispiel sieht die API-Kommunikationsklasse wie folgt aus (siehe sendRequest() als Einstiegspunkt und saveCard() als Methode zur Verarbeitung der Antwort.

<?php
declare(strict_types=1);

namespace Comwrap\RecurringPayone\Model\Api;

use Comwrap\RecurringPayone\Plugin\Payone\Core\Model\Methods\CreditcardAfterGetPaymentSpecificParameters;
use Magento\Customer\Model\Session;
use Magento\Framework\Exception\LocalizedException;
use Payone\Core\Helper\Api;
use Payone\Core\Model\PayoneConfig;
use Payone\Core\Model\ResourceModel\SavedPaymentData;

/**
 * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
 */
class CreditCard
{
    const PAYONE_STATUS = ['APPROVED', 'REDIRECT'];

    /**
     * URL of PAYONE Server API
     *
     * @var string
     */
    protected $sApiUrl = 'https://api.pay1.de/post-gateway/';

    /**
     * @var PayoneRequest
     */
    private $payoneRequest;

    /**
     * @var Api
     */
    private $apiHelper;

    /**
     * @var Session
     */
    private $customerSession;

    /**
     * @var SavedPaymentData
     */
    private $savedPaymentData;

    /**
     * @param PayoneRequest $payoneRequest
     * @param Api $apiHelper
     * @param Session $customerSession
     * @param SavedPaymentData $savedPaymentData
     */
    public function __construct(
        PayoneRequest $payoneRequest,
        Api $apiHelper,
        Session $customerSession,
        SavedPaymentData $savedPaymentData
    ) {
        $this->payoneRequest = $payoneRequest;
        $this->apiHelper = $apiHelper;
        $this->customerSession = $customerSession;
        $this->savedPaymentData = $savedPaymentData;
    }

    /**
     * Send request to payone api for add new credit card
     *
     * @param array $requestParams
     * @return array
     */
    public function sendRequest(array $requestParams): array
    {
        $apiRequestParams = $this->payoneRequest->getApiRequestParams($requestParams);
        $requestUrl = $this->apiHelper->getRequestUrl($apiRequestParams, $this->sApiUrl);
        $response = $this->apiHelper->sendApiRequest($requestUrl); // send request to PAYONE

        if (isset($response['status']) && in_array($response['status'], self::PAYONE_STATUS)) {
            $savedCardParams = $this->getSaveCardParams($requestParams);
            $this->saveCard($savedCardParams);
        }
        return $response;
    }

    /**
     * Save new card details in payone table 'payone_saved_payment_data'
     *
     * @param array $paymentData
     */
    private function saveCard(array $paymentData): void
    {
        $customerId = $this->customerSession->getId();
        $this->savedPaymentData->addSavedPaymentData(
            $customerId,
            PayoneConfig::METHOD_CREDITCARD,
            $paymentData
        );
        $savedPaymentData = $this->savedPaymentData->getSavedPaymentData($customerId);
        $lastSavedPaymentData = end($savedPaymentData);
        if (isset($lastSavedPaymentData['id'])) {
            $this->savedPaymentData->setDefault($lastSavedPaymentData['id'], $customerId);
        }
    }

    /**
     * Prepare require parameter to save credit card in magento
     *
     * @param array $requestParams
     * @return array
     */
    private function getSaveCardParams(array $requestParams): array
    {
        return [
            'cardpan' => $requestParams['pseudocardpan'],
            'masked' => $requestParams['truncatedcardpan'],
            'firstname' => $requestParams['payment']['cc_firstname'],
            'lastname' => $requestParams['payment']['cc_lastname'],
            'cardtype' => $requestParams['payment']['cc_type'],
            'cardexpiredate' => $requestParams['cardexpiredate']
        ];
    }

    /**
     * Get active credit card
     *
     * @param int $customerId
     * @return array
     * @throws LocalizedException
     */
    public function getActiveCreditCard(int $customerId): array
    {
        if (!$customerId) {
            throw new LocalizedException(
                __('Credit Card not available. Please add new credit card from customer account')
            );
        }
        $savedPaymentsData = $this->savedPaymentData->getSavedPaymentData($customerId);

        if (count($savedPaymentsData) > 1) {
            $savedPaymentsData = array_filter($savedPaymentsData, function ($savedPaymentsData) {
                return $savedPaymentsData['is_default'] == 1;
            });
        }

        if (!empty($savedPaymentsData)) {
            reset($savedPaymentsData);
            $savedPaymentsData = $savedPaymentsData[0]['payment_data'];
            $savedPaymentsData[CreditcardAfterGetPaymentSpecificParameters::PSEUDOCARDPAN] =
                $savedPaymentsData['cardpan'];
            $savedPaymentsData['truncatedcardpan'] = $savedPaymentsData['masked'];
            unset($savedPaymentsData['cardpan']);
            unset($savedPaymentsData['masked']);

            return $savedPaymentsData;
        }
        return [];
    }
}


Kreditkartenmanagement im Kundenkonto

Wir können die Standard-Kreditkarte im Kundenkonto über das Kreditkartenmanagement hinzufügen, löschen und einstellen.

Credit Card Management in Adobe Commerce Customer Account

Fazit

Im vorgestellten Beispiel haben wir einen Weg gezeigt, wie wiederkehrende Zahlungen für PAYONE integriert werden können. Sicherlich kann die Integration komplexer sein als hier gezeigt, da alles von Ihren speziellen Anwendungsfällen abhängt.

Für weitere Informationen können Sie immer einen Blick in die offizielle Dokumentation werfen:

Dashboard - PAYONE docs


Photo by Martin Adams on Unsplash

Weiterführende Artikel

  • 2021-12-15
    VAT Management in Adobe Commerce
    Author Photo
    Oleg Blinnikov
    Senior Developer at comwrap
    Best Practise zum MwSt Handling und zur Validierung der VAT-IDs in B2B Projekten mit Adobe Commerce (Magento)
  • 2021-11-01
    Integration von Adobe Commerce MSI mit externen ERP-Systemen
    Author Photo
    Oleg Blinnikov
    Senior Developer at comwrap
    Eine Erfolgsgeschichte über eine komplexe Integration von Magento MSI mit einem externen ERP-System.
  • 2021-10-15
    Hands-on Adobe Firefly
    Author Photo
    Alex Lyzun
    Lead Developer Adobe Commerce
    Das Projekt Firefly wurde von Adobe Mitte 2020 vorgestellt. Nachdem ich einige Tage mit Firefly experimentiert habe, wurde mir klar, dass es wirklich einen eigenen Artikel verdient hat, und so sind wir hier.
  • 2021-10-15
    Queue Systems integration review
    Author Photo
    Alex Lyzun
    Lead Developer Adobe Commerce
    Bestehende Message Queue Lösungen für Adobe Commerce unter die Lupe genommen