Implementation of Direct Debit in Zed

Edit on GitHub

This article provides the instructions on how to implement the Direct Debit payment method and integrate it into Checkout, State Machine, and OMS on the back-end side.

Persisting payment details

The payment details for the Direct Debit payment method should be persisted in the database.

To persist the payment details, do the following:

1. Create a new table to store payment details data

Add the spy_payment_direct_debit.schema.xml file with the following content to the Persistence/Propel/Schema/ folder in Zed:

<?xml version="1.0"?>
<database xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="zed"
	xsi:noNamespaceSchemaLocation="http://static.spryker.com/schema-01.xsd"
	namespace="Orm\Zed\PaymentMethods\Persistence" package="src.Orm.Zed.PaymentMethods.Persistence">

<table name="spy_payment_direct_debit">
	<column name="id_payment_directdebit" type="INTEGER" autoIncrement="true" primaryKey="true"/>
	<column name="fk_sales_order" required="true" type="INTEGER"/>

	<column name="bank_account_holder" type="VARCHAR" />
	<column name="bank_account_bic" type="VARCHAR" size="100"/>
	<column name="bank_account_iban" size="50"  type="VARCHAR"/>

	<foreign-key name="spy_payment_directdebit-fk_sales_order" foreignTable="spy_sales_order">
		<reference foreign="id_sales_order" local="fk_sales_order"/>
	</foreign-key>

	<behavior name="timestampable"/>
	<id-method-parameter value="spy_payment_directdebit_pk_seq"/>
    </table>

    </database>

2. Perform a database migration and generate the query object:

Run the following command:

vendor/bin/console propel:install

3. Save the Direct Debit payment details in the persistence layer:

To do this, perform the following steps:

  1. Create the PaymentMethodsPersistenceFactory class on the persistence layer:
<?php
namespace Pyz\Zed\PaymentMethods\Persistence;

use Orm\Zed\PaymentMethods\Persistence\SpyPaymentDirectDebitQuery;
use Spryker\Zed\Kernel\Persistence\AbstractPersistenceFactory;

/**
* @method \Pyz\Zed\PaymentMethods\Persistence\PaymentMethodsQueryContainer getQueryContainer()
*/
class PaymentMethodsPersistenceFactory extends AbstractPersistenceFactory
{
	/**
	* @return \Orm\Zed\PaymentMethods\Persistence\SpyPaymentDirectDebitQuery
	*/
	public function createPaymentDirectDebitQuery()
	{
		return SpyPaymentDirectDebitQuery::create();
	}

}
  1. Implement the PaymentMethodsQueryContainer:
<?php
namespace Pyz\Zed\PaymentMethods\Persistence;

use Spryker\Zed\Kernel\Persistence\AbstractQueryContainer;

/**
* @method  \Pyz\Zed\PaymentMethods\Persistence\PaymentMethodsPersistenceFactory getFactory()
*/
class PaymentMethodsQueryContainer extends AbstractQueryContainer
{
	/**
	* @param int $idSalesOrder
	*
	* @return \Orm\Zed\PaymentMethods\Persistence\SpyPaymentDirectDebitQuery
	*/
	public function queryPaymentBySalesOrderId($idSalesOrder)
	{
		return $this->getFactory()->createPaymentDirectDebitQuery()->filterByFkSalesOrder($idSalesOrder);
	}
}

Saving Direct Debit payment details

To add the logic for saving and viewing the Direct Debit payment details on the business layer and expose them using the PaymentMethodsFacade, do the following:

  1. In the Business/Reader/ folder, add the DirectDebitReader class. This will implement the logic for viewing the Direct Debit payment details.
Code sample
<?php

namespace Pyz\Zed\PaymentMethods\Business\Reader;


use Generated\Shared\Transfer\PaymentDirectDebitTransfer;
use Pyz\Zed\PaymentMethods\Persistence\PaymentMethodsQueryContainer;

class DirectDebitReader
{

	/**
	* @var \Pyz\Zed\PaymentMethods\Persistence\PaymentMethodsQueryContainer
	*/
	protected $queryContainer;

	/**
	* @param \Pyz\Zed\PaymentMethods\Persistence\PaymentMethodsQueryContainer $queryContainer
	*/
	public function __construct(PaymentMethodsQueryContainer $queryContainer)
	{
		$this->queryContainer = $queryContainer;
	}

	/**
	* @param int $salesOrderId
	*
	* @return \Generated\Shared\Transfer\PaymentDirectDebitTransfer
	*/
	public function getPaymentForOrderId($salesOrderId)
	{
		$entity = $this->queryContainer->queryPaymentBySalesOrderId($salesOrderId)->findOne();
		$directDebitTransfer = new PaymentDirectDebitTransfer();
		$directDebitTransfer->fromArray($entity->toArray(), true);

		return $directDebitTransfer;
	}

	/**
	* @param int $salesOrderId
	*
	* @return bool
	*/
	public function hasPaymentForOrderId($salesOrderId)
	{
		$entity = $this->queryContainer->queryPaymentBySalesOrderId($salesOrderId)->findOne();

		return $entity !== null;
	}

}
  1. In the Business/Writer/ folder, add the DirectDebitWriter class. This implements the logic for saving the Direct Debit payment details.
Code sample:
<?php

namespace Pyz\Zed\PaymentMethods\Business\Writer;

use Generated\Shared\Transfer\CheckoutResponseTransfer;
use Generated\Shared\Transfer\QuoteTransfer;
use Orm\Zed\PaymentMethods\Persistence\SpyPaymentDirectDebit;
use Pyz\Shared\PaymentMethods\PaymentMethodsConstants;

class DirectDebitWriter
{
	/**
	* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
	* @param \Generated\Shared\Transfer\CheckoutResponseTransfer $checkoutResponseTransfer
	*
	* @return void
	*/
	public function saveOrderPayment(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer $checkoutResponseTransfer)
	{
		if ($quoteTransfer->requirePayment()->getPayment()->requirePaymentMethodsDirectDebit()->getPaymentMethod() == PaymentMethodsConstants::PAYMENT_METHOD_DIRECTDEBIT) {
			$this->saveDirectDebit($quoteTransfer, $checkoutResponseTransfer);
		}
	}

	/**
	* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
	* @param \Generated\Shared\Transfer\CheckoutResponseTransfer $checkoutResponseTransfer
	*
	* @throws \Propel\Runtime\Exception\PropelException
	*
	* @return void
	*/
	protected function saveDirectDebit(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer $checkoutResponseTransfer)
	{
		$entity = new SpyPaymentDirectDebit();

		$directDebitTransfer = $quoteTransfer->requirePayment()->getPayment()->requirePaymentMethodsDirectDebit()->getPaymentmethodsdirectdebit();

		$entity->fromArray($directDebitTransfer->toArray());
		$entity->setFkSalesOrder($checkoutResponseTransfer->getSaveOrder()->getIdSalesOrder());

		$entity->save();
	}
}
  1. Implement the PaymentMethodsBusinessFactory to get instances for these 2 classes:

Code sample:

<?php

namespace Pyz\Zed\PaymentMethods\Business;

use Pyz\Zed\PaymentMethods\Business\Reader\DirectDebitReader;
use Pyz\Zed\PaymentMethods\Business\Writer\DirectDebitWriter;
use Spryker\Zed\Kernel\Business\AbstractBusinessFactory;

class PaymentMethodsBusinessFactory extends AbstractBusinessFactory
{
	/**
	* @return \Pyz\Zed\PaymentMethods\Business\Writer\DirectDebitWriter
	*/
	public function createDirectDebitWriter()
	{
		return new DirectDebitWriter();
	}

	/**
	* @return \Pyz\Zed\PaymentMethods\Business\Reader\DirectDebitReader
	*/
	public function createDirectDebitReader()
	{
		return new DirectDebitReader($this->getQueryContainer());
	}

}
  1. Expose the save/retrieve Direct Debit payment details using the PaymentMethodsFacade:
Code sample:
<?php

namespace Pyz\Zed\PaymentMethods\Business;

use Generated\Shared\Transfer\CheckoutResponseTransfer;
use Generated\Shared\Transfer\QuoteTransfer;
use Spryker\Zed\Kernel\Business\AbstractFacade;

/**
* @method \Pyz\Zed\PaymentMethods\Business\PaymentMethodsBusinessFactory getFactory()
*/
class PaymentMethodsFacade extends AbstractFacade
{

	/**
	* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
	* @param \Generated\Shared\Transfer\CheckoutResponseTransfer $checkoutResponseTransfer
	*
	* @return void
	*/
	public function saveOrderPayment(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer $checkoutResponseTransfer)
	{
		$this->getFactory()->createDirectDebitWriter()->saveOrderPayment($quoteTransfer, $checkoutResponseTransfer);
	}

	/**
	* @param int $idSalesOrder
	*
	* @return \Generated\Shared\Transfer\PaymentDirectDebitTransfer
	*/
	public function getOrderDirectDebit($idSalesOrder)
	{
		return $this->getFactory()->createDirectDebitReader()->getPaymentForOrderId($idSalesOrder);
	}

	/**
	* @param int $idSalesOrder
	*
	* @return bool
	*/
	public function hasOrderDirectDebit($idSalesOrder)
	{
		return $this->getFactory()->createDirectDebitReader()->hasPaymentForOrderId($idSalesOrder);
	}

}

Listing Direct Debit payment details in Zed UI

In Zed, when looking over the details on a submitted order, check the payment details.

To view the payment details, do the following:

  1. Extend the order details page by adding the PaymentMethods/Presentation/Sales/list.twig template with the following content:

Code sample:

{% if (idPayment) %}
	<div class="row">
		<div class="col-sm-12">
			{% embed '@Gui/Partials/widget.twig' with { widget_title: 'Direct Debit' | trans } %}
				{% block widget_content %}
					<table class="footable table table-striped toggle-arrow-tiny">
						<tbody>
						<tr>
							<td><strong>{{ 'Account Holder' | trans }}</strong></td>
                            <td>{{ paymentDetails.bankAccountHolder }}</td>
                            </tr>
						    <tr>
                            <td><strong>{{ 'IBAN' | trans }}</strong></td>
							<td>{{ paymentDetails.bankAccountIban }}</td>
						</tr>
						<tr>
                            <td><strong>{{ 'BIC' | trans }}</strong></td>
							<td>{{ paymentDetails.bankAccountBic }}</td>
                        </tr>
                        </tbody>
                    </table>
				{% endblock %}
			{% endembed %}
        </div>
    </div>
{% endif %}
  1. Add the corresponding controller action for this view in PaymentMethods/Communication/Controller/SalesController.php:

Code sample:

<?php

namespace Pyz\Zed\PaymentMethods\Communication\Controller;

use Spryker\Zed\Kernel\Communication\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;

/**
* @method \Pyz\Zed\PaymentMethods\Persistence\PaymentMethodsQueryContainer getQueryContainer()
* @method \Pyz\Zed\PaymentMethods\Business\PaymentMethodsFacade getFacade()
*/
class SalesController extends AbstractController
{

	/**
	* @param \Symfony\Component\HttpFoundation\Request $request
	*
	* @return array
	*/
	public function listAction(Request $request)
	{
		$idSalesOrder = $request->query->get('id-sales-order');

		if ($this->getFacade()->hasOrderDirectDebit($idSalesOrder)) {
			$directDebitTransfer = $this->getFacade()->getOrderDirectDebit($idSalesOrder);

			return [
				'idPayment' => $directDebitTransfer->getIdPaymentDirectdebit(),
				'paymentDetails' => $directDebitTransfer,
			];
		}

		return [
			'idPayment' => null,
			'paymentDetails' => null,
		];
	}
}

Information is available here: /payment-methods/sales/list?id-sales-order=1

Integrating the Direct Debit Method into the checkout

To integrate the Direct Debit method into the checkout, implement these 3 plugins:

  • PaymentMethodsDirectDebitCheckoutPreConditionPlugin
  • PaymentMethodsDirectDebitCheckoutDoSaveOrderPlugin
  • PaymentMethodsDirectDebitCheckoutPostSavePlugin

To do this, take the following steps:

  1. In Zed, add the following 3 plugins to the Communication/Plugin/Checkout/ folder of the new module you’ve created (PaymentMethods).

PaymentMethodsDirectDebitCheckoutPreConditionPlugin

<?php

namespace Pyz\Zed\PaymentMethods\Communication\Plugin\Checkout;

use Generated\Shared\Transfer\CheckoutResponseTransfer;
use Generated\Shared\Transfer\QuoteTransfer;
use Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPreConditionPluginInterface;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;


class PaymentMethodsDirectDebitCheckoutPreConditionPlugin extends AbstractPlugin implements CheckoutPreConditionPluginInterface

{
  /**
   * @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
   * @param \Generated\Shared\Transfer\CheckoutResponseTransfer $checkoutResponseTransfer
   *
   * @return bool
   */
  public function checkCondition(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer $checkoutResponseTransfer)
  {
      checkoutResponseTransfer->setIsSuccess(true);

      return true;
  }
}

PaymentMethodsDirectDebitCheckoutDoSaveOrderPlugin

<?php

namespace Pyz\Zed\PaymentMethods\Communication\Plugin\Checkout;

use Generated\Shared\Transfer\CheckoutResponseTransfer;
use Generated\Shared\Transfer\QuoteTransfer;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
use Spryker\Zed\Payment\Dependency\Plugin\Checkout\CheckoutSaveOrderPluginInterface;

    /**
    * @method \Pyz\Zed\PaymentMethods\Business\PaymentMethodsFacade getFacade()
     */
    class DirectDebitSaveOrderPlugin extends AbstractPlugin implements                      CheckoutSaveOrderPluginInterface
    {

    /**
     * @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
     * @param \Generated\Shared\Transfer\CheckoutResponseTransfer $checkoutResponseTransfer
     *
     * @return void
     */
    public function execute(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer $checkoutResponseTransfer)
    {
      $this->getFacade()->saveOrderPayment($quoteTransfer, $checkoutResponseTransfer);
    }
				}

DirectDebitPostCheckPlugin

<?php
namespace Pyz\Zed\PaymentMethods\Communication\Plugin\Checkout;

use Generated\Shared\Transfer\CheckoutResponseTransfer;
use Generated\Shared\Transfer\QuoteTransfer;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
use Spryker\Zed\Payment\Dependency\Plugin\Checkout\CheckoutPostCheckPluginInterface;

class DirectDebitPostCheckPlugin extends AbstractPlugin implements CheckoutPostCheckPluginInterface
{
	/**
	* @api
	*
	* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
	* @param \Generated\Shared\Transfer\CheckoutResponseTransfer $checkoutResponseTransfer
	*
	* @return \Generated\Shared\Transfer\CheckoutResponseTransfer
	*/
	public function execute(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer $checkoutResponseTransfer)
	{
		return $checkoutResponseTransfer;
	}
}
  1. In the Checkout module, add these 3 plugins to the related methods (plugin stacks) in CheckoutDependencyProvider.
Code sample
<?php
namespace Pyz\Zed\Checkout;

use Pyz\Zed\PaymentMethods\Communication\Plugin\Checkout\PaymentMethodsDirectDebitCheckoutPreConditionPlugin;
use Pyz\Zed\PaymentMethods\Communication\Plugin\Checkout\PaymentMethodsDirectDebitCheckoutDoSaveOrderPlugin;
use Pyz\Zed\PaymentMethods\Communication\Plugin\Checkout\PaymentMethodsDirectDebitCheckoutPostSavePlugin;
use Spryker\Zed\Checkout\CheckoutDependencyProvider as SprykerCheckoutDependencyProvider;
use Spryker\Zed\Kernel\Container;


class CheckoutDependencyProvider extends SprykerCheckoutDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPreConditionPluginInterface[]
     */
    protected function getCheckoutPreConditions(Container $container)
    {
        return [
            ...
            new PaymentMethodsDirectDebitCheckoutPreConditionPlugin(),
        ];
    }
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Spryker\Zed\Checkout\Dependency\Plugin\CheckoutSaveOrderInterface[]|\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutDoSaveOrderInterface[]
     */
    protected function getCheckoutOrderSavers(Container $container)
    {
        return [
            ...
            new PaymentMethodsDirectDebitCheckoutDoSaveOrderPlugin(),
        ];
    }
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPostSaveInterface[]
     */
    protected function getCheckoutPostHooks(Container $container)
    {
        return [
            ...
            new PaymentMethodsDirectDebitCheckoutPostSavePlugin(),
        ];
    }
}

Configuring dependency injectors for Yves and Zed

Add injectors for Zed and Yves and ActiveProcess and Statemachine mapping to the config\Shared\config_default.php file:

// ---------- Dependency injector
$config[KernelConstants::DEPENDENCY_INJECTOR_YVES] = [
	'CheckoutPage' => [
		PaymentMethodsConstants::PROVIDER,
	],
];
$config[KernelConstants::DEPENDENCY_INJECTOR_ZED] = [
	'Oms' => [
		PaymentMethodsConstants::PROVIDER,
	],
];
// ---------- State machine (OMS)
$config[OmsConstants::ACTIVE_PROCESSES] = [
	PaymentMethodsConstants::PAYMENT_METHOD_DIRECTDEBIT,
];
$config[SalesConstants::PAYMENT_METHOD_STATEMACHINE_MAPPING] = [
	PaymentMethodsConstants::PAYMENT_METHOD_DIRECTDEBIT => PaymentMethodsConstants::PAYMENT_METHOD_DIRECTDEBIT,
];

Integrating the Direct Debit Payment Method into a state machine

After the preceding procedures have been completed, set up a state machine, which is dedicated for processing orders that use Direct Debit as a payment type. For this purpose, add the paymentMethodsDirectDebit.xml file with the following content to the config/Zed/oms/ folder:

Code sample
<?xml version="1.0"?>
<statemachine
    xmlns="spryker:oms-01"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="spryker:oms-01 http://static.spryker.com/oms-01.xsd">

    <process name="DirectDebit" main="true">
    <states>
        <state name="new" reserved="true"/>
        <state name="payment issued" />
        <state name="payment received" />
        <state name="order shipped" />
        <state name="ready for return" />
        <state name="completed" />
        <state name="cancelled" />
        <state name="refunded" />
        <state name="returned" />
        <state name="payment clarification needed" />
    </states>

    <transitions>
        <transition>
            <source>new</source>
        <target>payment issued</target>
            <event>capture direct debit</event>
        </transition>

        <transition>
            <source>payment issued</source>
            <target>payment received</target>
        <event>payment received</event>
        </transition>

        <transition>
            <source>payment issued</source>
            <target>payment clarification needed</target>
    <event>clarify payment</event>
        </transition>

        <transition>
            <source>payment clarification needed</source>
            <target>cancelled</target>
            <event>cancel</event>
        </transition>

        <transition>
            <source>payment received</source>
            <target>order shipped</target>
            <event>ship order</event>
        </transition>

        <transition>
            <source>order shipped</source>
            <target>ready for return</target>
            <event>ready for return</event>
        </transition>

        <transition>
            <source>ready for return</source>
            <target>completed</target>
            <event>item not returned</event>
        </transition>

        <transition>
            <source>new</source>
            <target>cancelled</target>
            <event>cancel</event>
        </transition>

        <transition>
            <source>payment received</source>
            <target>refunded</target>
            <event>refund</event>
        </transition>

        <transition>
            <source>refunded</source>
            <target>cancelled</target>
            <event>cancel</event>
        </transition>

        <transition>
            <source>returned</source>
            <target>refunded</target>
            <event>refund</event>
        </transition>

        <transition>
            <source>ready for return</source>
            <target>returned</target>
            <event>return received</event>
        </transition>
    </transitions>

    <events>
        <event name="capture Direct Debit" manual="true" />
        <event name="payment received" manual="true" />
        <event name="ship order" manual="true" />
        <event name="ready for return"  onEnter="true" />
        <event name="item not returned" timeout="30days" />
        <event name="cancel" manual="true" />
        <event name="refund" manual="true" />
        <event name="return received" manual="true" />
        <event name="clarify payment" manual="true" />
    </events>
   </process>
</statemachine>

What’s next?

After the Direct Debit payment method has been created and integrated in the back-end, identify the new Direct Debit payment type in the shared layer.