Implement Direct Debit in Zed
Edit on GitHubThis document provides shows how to implement the Direct Debit payment method and integrate it into Checkout, State Machine, and OMS on the backend side.
Persist payment details
The payment details for the Direct Debit payment method must 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:
- 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();
}
}
- Implement
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);
}
}
Save 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 PaymentMethodsFacade
, do the following:
- In the
Business/Reader/
folder, add theDirectDebitReader
class. This implements 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;
}
}
- In the
Business/Writer/
folder, add theDirectDebitWriter
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\SaveOrderTransfer $saveOrderTransfer
*
* @return void
*/
public function saveOrderPayment(QuoteTransfer $quoteTransfer, SaveOrderTransfer $saveOrderTransfer)
{
if ($quoteTransfer->requirePayment()->getPayment()->requirePaymentMethodsDirectDebit()->getPaymentMethod() == PaymentMethodsConstants::PAYMENT_METHOD_NAME_DIRECT_DEBIT) {
$this->saveDirectDebit($quoteTransfer, $saveOrderTransfer);
}
}
/**
* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
* @param \Generated\Shared\Transfer\SaveOrderTransfer $saveOrderTransfer
*
* @return void
*/
protected function saveDirectDebit(QuoteTransfer $quoteTransfer, SaveOrderTransfer $saveOrderTransfer)
{
$entity = new SpyPaymentDirectDebit();
$directDebitTransfer = $quoteTransfer->requirePayment()->getPayment()->requirePaymentMethodsDirectDebit()->getPaymentmethodsdirectdebit();
$entity->fromArray($directDebitTransfer->toArray());
$entity->setFkSalesOrder($saveOrderTransfer->getIdSalesOrder());
$entity->save();
}
}
- To get instances for these two classes, implement
PaymentMethodsBusinessFactory
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());
}
}
- Expose the
save/retrieve
Direct Debit payment details usingPaymentMethodsFacade
:
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\SaveOrderTransfer $saveOrderTransfer
*
* @return void
*/
public function saveOrderPayment(QuoteTransfer $quoteTransfer, SaveOrderTransfer $saveOrderTransfer)
{
$this->getFactory()->createDirectDebitWriter()->saveOrderPayment($quoteTransfer, $saveOrderTransfer);
}
/**
* @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);
}
}
Listen to 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:
- 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 %}
- In
PaymentMethods/Communication/Controller/SalesController.php
, add the corresponding controller action for this view:
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
Integrate the Direct Debit method into the checkout
To integrate the Direct Debit method into the checkout, implement these plugins:
PaymentMethodsDirectDebitCheckoutPreConditionPlugin
PaymentMethodsDirectDebitCheckoutDoSaveOrderPlugin
PaymentMethodsDirectDebitCheckoutPostSavePlugin
To do this, take the following steps:
- 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\QuoteTransfer;
use Generated\Shared\Transfer\SaveOrderTransfer;
use Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutDoSaveOrderInterface;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
/**
* @method \Pyz\Zed\PaymentMethods\Business\PaymentMethodsFacade getFacade()
*/
class PaymentMethodsDirectDebitCheckoutDoSaveOrderPlugin extends AbstractPlugin implements CheckoutDoSaveOrderInterface
{
/**
* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
* @param \Generated\Shared\Transfer\SaveOrderTransfer $saveOrderTransfer
*
* @return void
*/
public function saveOrder(QuoteTransfer $quoteTransfer, SaveOrderTransfer $saveOrderTransfer)
{
$this->getFacade()->saveOrderPayment($quoteTransfer, $saveOrderTransfer);
}
}
PaymentMethodsDirectDebitCheckoutPostSavePlugin
<?php
use Generated\Shared\Transfer\CheckoutResponseTransfer;
use Generated\Shared\Transfer\QuoteTransfer;
use Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPostSaveInterface;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
/**
* @method \Pyz\Zed\PaymentMethods\Business\PaymentMethodsFacadeInterface getFacade()
* @method \Pyz\Zed\PaymentMethods\PaymentMethodsConfig getConfig()
*/
class PaymentMethodsDirectDebitCheckoutPostSavePlugin extends AbstractPlugin implements CheckoutPostSaveInterface
{
/**
* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
* @param \Generated\Shared\Transfer\CheckoutResponseTransfer $checkoutResponseTransfer
*
* @return \Generated\Shared\Transfer\CheckoutResponseTransfer
*/
public function executeHook(QuoteTransfer $quoteTransfer, CheckoutResponseTransfer $checkoutResponseTransfer)
{
return $checkoutResponseTransfer;
}
}
- In the
Checkout
module, add the previous three plugins to the related methods (plugin stacks) inCheckoutDependencyProvider
.
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(),
];
}
}
Configure 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,
];
Integrate 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 creating and integrating the Direct Debit payment method into the backend, identify the new Direct Debit payment type in the shared layer.
Thank you!
For submitting the form