Integrating the Easy Credit payment method for Heidelpay

Edit on GitHub

Setup

The following configuration should be implemented after Heidelpay has been installed and integrated.

Configuration

CONFIGURATION KEY TYPE DESCRIPTION
HeidelpayConstants::CONFIG_HEIDELPAY_EASYCREDIT_CRITERIA_REJECTED_DELIVERY_ADDRESS string Criteria to reject by delivery address (for example ‘Packstation’)
HeidelpayConstants::CONFIG_HEIDELPAY_EASYCREDIT_CRITERIA_GRAND_TOTAL_LESS_THAN int Criteria to reject if grand total less than (for example 200)
HeidelpayConstants::CONFIG_HEIDELPAY_EASYCREDIT_CRITERIA_GRAND_TOTAL_MORE_THAN int Criteria to reject if grand total greater than (for example 5000)
HeidelpayConstants::CONFIG_HEIDELPAY_TRANSACTION_CHANNEL_EASY_CREDIT string Transaction channel for Easy Credit payment method (provided by Heidelpay)
  1. Activate Heidelpay Easycredit payment method.

OMS Configuration

$config[OmsConstants::PROCESS_LOCATION] = [
	...
	APPLICATION_ROOT_DIR . '/vendor/spryker-eco/heidelpay/config/Zed/Oms',
];
$config[OmsConstants::ACTIVE_PROCESSES] = [
	...
	'HeidelpayEasyCredit01',
];
$config[SalesConstants::PAYMENT_METHOD_STATEMACHINE_MAPPING] = [
	...
	HeidelpayConfig::PAYMENT_METHOD_EASY_CREDIT => 'HeidelpayEasyCredit01',
];
  1. Add Easycredit checkout steps to StepFactory.

\Pyz\Yves\CheckoutPage\Process\StepFactory

<?php

namespace Pyz\Yves\CheckoutPage\Process;

use Pyz\Yves\CheckoutPage\CheckoutPageDependencyProvider;
use Pyz\Yves\CheckoutPage\Plugin\Provider\CheckoutPageControllerProvider;
use Spryker\Yves\StepEngine\Process\StepCollection;
use SprykerEco\Client\Heidelpay\HeidelpayClientInterface;
use SprykerEco\Yves\Heidelpay\CheckoutPage\Process\Steps\HeidelpayEasycreditInitializeStep;
use SprykerEco\Yves\Heidelpay\CheckoutPage\Process\Steps\HeidelpayEasycreditStep;
use SprykerShop\Yves\CheckoutPage\Process\StepFactory as BaseStepFactory;
use SprykerShop\Yves\HomePage\Plugin\Provider\HomePageControllerProvider;

/**
 * @method \SprykerShop\Yves\CheckoutPage\CheckoutPageConfig getConfig()
 */
class StepFactory extends BaseStepFactory
{
	/**
	 * @return \Spryker\Yves\StepEngine\Process\StepCollectionInterface
	 */
	public function createStepCollection()
	{
		$stepCollection = new StepCollection(
			$this->getUrlGenerator(),
			CheckoutPageControllerProvider::CHECKOUT_ERROR
		);

		$stepCollection
			->addStep($this->createEntryStep())
			->addStep($this->createCustomerStep())
			->addStep($this->createAddressStep())
			->addStep($this->createShipmentStep())
			->addStep($this->createHeidelpayEasyCreditInitializeStep())
			->addStep($this->createPaymentStep())
			->addStep($this->createHeidelpayEasyCreditStep())
			->addStep($this->createSummaryStep())
			->addStep($this->createPlaceOrderStep())
			->addStep($this->createSuccessStep());

		return $stepCollection;
	}

	/**
	 * @return \SprykerEco\Yves\Heidelpay\CheckoutPage\Process\Steps\HeidelpayEasycreditInitializeStep
	 */
	public function createHeidelpayEasyCreditInitializeStep(): HeidelpayEasycreditInitializeStep
	{
		return new HeidelpayEasycreditInitializeStep(
			CheckoutPageControllerProvider::CHECKOUT_EASYCREDIT_INITIALIZE,
			HomePageControllerProvider::ROUTE_HOME,
			$this->getHeidelpayClient()
		);
	}

	/**
	 * @return \SprykerEco\Yves\Heidelpay\CheckoutPage\Process\Steps\HeidelpayEasycreditStep
	 */
	public function createHeidelpayEasyCreditStep(): HeidelpayEasycreditStep
	{
		return new HeidelpayEasycreditStep(
			CheckoutPageControllerProvider::CHECKOUT_EASYCREDIT,
			HomePageControllerProvider::ROUTE_HOME
		);
	}

	/**
	 * @return \SprykerEco\Client\Heidelpay\HeidelpayClientInterface
	 */
	public function getHeidelpayClient(): HeidelpayClientInterface
	{
		return $this->getProvidedDependency(CheckoutPageDependencyProvider::CLIENT_HEIDELPAY);
	}
}
  1. Extend CheckoutPageFactory to change step factory creation:

\Pyz\Yves\CheckoutPage\CheckoutPageFactory

<?php

namespace Pyz\Yves\CheckoutPage;

use Pyz\Yves\CheckoutPage\Process\StepFactory;
use SprykerShop\Yves\CheckoutPage\CheckoutPageFactory as SprykerShopCheckoutPageFactory;

class CheckoutPageFactory extends SprykerShopCheckoutPageFactory
{
	/**
	 * @return \Pyz\Yves\CheckoutPage\Process\StepFactory
	 */
	public function createStepFactory()
	{
		return new StepFactory();
	}
}
  1. Extend CheckoutController to add Easycredit step action:

\Pyz\Yves\CheckoutPage\Controller\CheckoutController

<?php

namespace Pyz\Yves\CheckoutPage\Controller;

use SprykerShop\Yves\CheckoutPage\Controller\CheckoutController as BaseCheckoutController;
use Symfony\Component\HttpFoundation\Request;

/**
 * @method \SprykerShop\Yves\CheckoutPage\CheckoutPageFactory getFactory()
 */
class CheckoutController extends BaseCheckoutController
{
	/**
	 * @param \Symfony\Component\HttpFoundation\Request $request
	 *
	 * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
	 */
	public function easyCreditAction(Request $request)
	{
		return $this->createStepProcess()->process($request);
	}
}
  1. Extend CheckoutPageRouteProviderPlugin to add Easycredit actions:

\Pyz\Yves\CheckoutPage\Plugin\Router\CheckoutPageControllerProvider

<?php

namespace Pyz\Yves\CheckoutPage\Plugin\Provider;

use Spryker\Yves\Router\Route\RouteCollection;
use SprykerShop\Yves\CheckoutPage\Plugin\Router\CheckoutPageRouteProviderPlugin as SprykerShopCheckoutPageRouteProviderPlugin;

class CheckoutPageRouteProviderPlugin extends SprykerShopCheckoutPageRouteProviderPlugin
{
    public const ROUTE_NAME_CHECKOUT_EASYCREDIT = 'checkout-easycredit';
    public const ROUTE_NAME_CHECKOUT_EASYCREDIT_INITIALIZE = 'checkout-easycredit-initialize';

    /**
     * Specification:
     * - Adds Routes to the RouteCollection.
     *
     * @api
     *
     * @param \Spryker\Yves\Router\Route\RouteCollection $routeCollection
     *
     * @return \Spryker\Yves\Router\Route\RouteCollection
     */
    public function addRoutes(RouteCollection $routeCollection): RouteCollection
    {
	$route = $this->buildRoute('/checkout/easycredit', 'CheckoutPage', 'Checkout', 'easyCreditAction');
        $routeCollection->add(static::ROUTE_NAME_CHECKOUT_EASYCREDIT, $route);

	$route = $this->buildRoute('/checkout/easycredit-initialize', 'CheckoutPage', 'Checkout', 'easyCreditAction');
        $routeCollection->add(static::ROUTE_NAME_CHECKOUT_EASYCREDIT_INITIALIZE, $route);

        return $routeCollection;
    }
}
  1. Update CheckoutPageDependencyProvider with Easycredit related modifications:

\Pyz\Yves\CheckoutPage\CheckoutPageDependencyProvider

<?php

namespace Pyz\Yves\CheckoutPage;

...
use SprykerEco\Yves\Heidelpay\Plugin\Subform\HeidelpayEasyCreditSubFormPlugin;
...

class CheckoutPageDependencyProvider extends SprykerShopCheckoutPageDependencyProvider
{
	public const CLIENT_HEIDELPAY = 'CLIENT_HEIDELPAY';

	/**
	 * @param \Spryker\Yves\Kernel\Container $container
	 *
	 * @return \Spryker\Yves\Kernel\Container
	 */
	public function provideDependencies(Container $container)
	{
		$container = parent::provideDependencies($container);
		$container = $this->addHeidelpayClient($container);

		return $container;
	}

	/**
	 * @param \Spryker\Yves\Kernel\Container $container
	 *
	 * @return \Spryker\Yves\Kernel\Container
	 */
	protected function addSubFormPluginCollection(Container $container): Container
	{
		$container[self::PAYMENT_SUB_FORMS] = function () {
			$subFormPluginCollection = new SubFormPluginCollection();
			...
			$subFormPluginCollection->add(new HeidelpayEasyCreditSubFormPlugin());

			return $subFormPluginCollection;
		};

		return $container;
	}

	/**
	 * @param \Spryker\Yves\Kernel\Container $container
	 *
	 * @return \Spryker\Yves\Kernel\Container
	 */
	protected function addPaymentMethodHandlerPluginCollection(Container $container): Container
	{
		$container[self::PAYMENT_METHOD_HANDLER] = function () {
			$stepHandlerPluginCollection = new StepHandlerPluginCollection();
			...
			$stepHandlerPluginCollection->add(new HeidelpayHandlerPlugin(), PaymentTransfer::HEIDELPAY_EASY_CREDIT);

			return $stepHandlerPluginCollection;
		};

		return $container;
	}

	/**
	 * @param \Spryker\Yves\Kernel\Container $container
	 *
	 * @return \Spryker\Yves\Kernel\Container
	 */
	protected function addHeidelpayClient(Container $container): Container
	{
		$container[static::CLIENT_HEIDELPAY] = function () use ($container) {
			return $container->getLocator()->heidelpay()->client();
		};

		return $container;
	}
}
  1. Update payment.twig template with Easycredit payment method:

src/Pyz/Yves/CheckoutPage/Theme/default/views/payment/payment.twig

{% extends template('page-layout-checkout', 'CheckoutPage') %}

{% define data = {
	backUrl: _view.previousStepUrl,
	forms: {
		payment: _view.paymentForm
	},

	title: 'checkout.step.payment.title' | trans,
	customForms: {
		...
		'heidelpay/easy-credit': ['easy-credit', 'heidelpay'],
	}
} %}

{% block content %}
	...
{% endblock %}
  1. Update summary.twig to template to display Easycredit related fees:
src/Pyz/Yves/CheckoutPage/Theme/default/views/payment/payment.twig
{% extends template('page-layout-checkout', 'CheckoutPage') %}

{% define data = {
	backUrl: _view.previousStepUrl,
	transfer: _view.quoteTransfer,
	cartItems: _view.cartItems,
	shippingAddress: _view.quoteTransfer.shippingAddress,
	billingAddress: _view.quoteTransfer.billingAddress,
	shipmentMethod: _view.quoteTransfer.shipment.method.name,
	paymentMethod: _view.quoteTransfer.payment.paymentMethod,
	heidelpayEasyCredit: _view.quoteTransfer.payment.heidelpayEasyCredit | default(null),

	forms: {
		summary: _view.summaryForm
	},

	overview: {
		shipmentMethod: _view.quoteTransfer.shipment.method.name,
		expenses: _view.quoteTransfer.expenses,
		voucherDiscounts: _view.quoteTransfer.voucherDiscounts,
		cartRuleDiscounts: _view.quoteTransfer.cartRuleDiscounts,

		prices: {
			subTotal: _view.quoteTransfer.totals.subtotal,
			storeCurrency: _view.quoteTransfer.shipment.method.storeCurrencyPrice,
			grandTotal: _view.quoteTransfer.totals.grandtotal,
			tax: _view.quoteTransfer.totals.taxtotal.amount,
			discountTotal: _view.quoteTransfer.totals.discounttotal | default
		}
	},

	title: 'checkout.step.summary.title' | trans
} %}

{% block content %}
	<div class="grid">
		<div class="col col--sm-12 col--lg-4">
			<div class="box">
				<span class="float-right">{{ 'checkout.step.summary.with_method' | trans }} <strong>{{data.paymentMethod}}</strong></span>
				<h6>{{ 'checkout.step.summary.payment' | trans }}</h6>
				<br>
				{% if data.heidelpayEasyCredit is not null %}
					<span class="float-right">{{ data.heidelpayEasyCredit.amortisationText }}</span>
					<div class="grid">
						<a href="{{ data.heidelpayEasyCredit.preContractionInformationUrl }} "class="float-left">
							{{ 'heidelpay.payment.easy_credit.pre_contraction_info_link_text' | trans }}
						</a>
					</div>
					<br>
					<div>
						<ul>
							<li><strong>{{ 'heidelpay.payment.easy_credit.order_total' | trans }} </strong>{{ data.heidelpayEasyCredit.totalOrderAmount | money }}</li>
							<li><strong>{{ 'heidelpay.payment.easy_credit.interest' | trans }} </strong>{{ data.heidelpayEasyCredit.accruingInterest | money }}</li>
							<li><strong>{{ 'heidelpay.payment.easy_credit.total_inc_interest' | trans }} </strong>{{ data.heidelpayEasyCredit.totalAmount | money }}</li>
						</ul>
					</div>
				{% endif %}
				<hr/>

				{% include molecule('display-address') with {
					class: 'text-small',
					data: {
						address: data.billingAddress
					}
				} only %}
			</div>

			<div class="box">
				<span class="float-right">{{ 'checkout.step.summary.with_method' | trans }} <strong>{{ data.shipmentMethod }}</strong></span>
				<h6>{{ 'checkout.step.summary.shipping' | trans }}</h6>
				<hr/>

				{% include molecule('display-address') with {
					class: 'text-small',
					data: {
						address: data.shippingAddress
					}
				} only %}
			</div>
		</div>

		<div class="col col--sm-12 col--lg-8">
			<div class="box">
				{% for item in data.cartItems %}
					{% set item = item.bundleProduct is defined ? item.bundleProduct : item %}
					{% embed molecule('summary-item', 'CheckoutPage') with {
						data: {
							name: item.name,
							quantity: item.quantity,
							price: item.sumPrice | money,
							options: item.productOptions | default({}),
							bundleItems: item.bundleItems | default([]),
							quantitySalesUnit: item.quantitySalesUnit,
							amountSalesUnit: item.amountSalesUnit,
							amount: item.amount
						},
						embed: {
							isLast: not loop.last,
							item: item
						}
					} only %}
						{% block body %}
							{{parent()}}
							{% if widgetExists('CartNoteQuoteItemNoteWidgetPlugin') %}
								{% if embed.item.cartNote is not empty %}
									{{ widget('CartNoteQuoteItemNoteWidgetPlugin', embed.item) }} {# @deprecated Use molecule('note-list', 'CartNoteWidget') instead. #}
								{% endif %}
							{% elseif embed.item.cartNote is not empty %}
								{% include molecule('note-list', 'CartNoteWidget') ignore missing with {
									data: {
										label: 'cart_note.checkout_page.item_note',
										note: embed.item.cartNote
									}
								} only %}
							{% endif %}
							{% if embed.isLast %}<hr/>{% endif %}
						{% endblock %}
					{% endembed %}
				{% endfor %}
			</div>

			{% if data.transfer.cartNote is not empty %}
				{% if widgetExists('CartNoteQuoteNoteWidgetPlugin') %}
					<div class="box">
						{{ widget('CartNoteQuoteNoteWidgetPlugin', data.transfer) }}  {#@deprecated Use molecule('note-list', 'CartNoteWidget') instead.#}
					</div>
				{% else %}
					<div class="box">
						{% include molecule('note-list', 'CartNoteWidget') ignore missing with {
							data: {
								label: 'cart_note.checkout_page.quote_note',
								note: data.transfer.cartNote
							}
						} only %}
					</div>
				{% endif %}
			{% endif %}

			<div class="box">
				{% widget 'CheckoutVoucherFormWidget' args [data.transfer] only %}
				{% elsewidget 'CheckoutVoucherFormWidgetPlugin' args [data.transfer] only %} {# @deprecated Use CheckoutVoucherFormWidget instead. #}
				{% endwidget %}
			</div>

			{% embed molecule('form') with {
				class: 'box',
				data: {
					form: data.forms.summary,
					submit: {
						enable: can('SeeOrderPlaceSubmitPermissionPlugin'),
						text: 'checkout.step.place.order' | trans
					},
					cancel: {
						enable: true,
						url: data.backUrl,
						text: 'general.back.button' | trans
					}
				},
				embed: {
					overview: data.overview
				}
			} only %}
				{% block body %}
					{% include molecule('summary-overview', 'CheckoutPage') with {
						data: embed.overview
					} only %}

					<hr />
					{{parent()}}
				{% endblock %}
			{% endembed %}
		</div>
	</div>
{% endblock %}

Checkout Payment Step Display

Displays payment method name with a radio button. No extra input fields are required.

Payment Step Submitting

No further actions are needed, the quote being filled with payment method selection as default. After selecting Easy Credit as a payment method “HP.IN” request will be sent. In the response, Heidelpay returns an URL string which defines where the customer has to be redirected. If everything was ok, the user would be redirected to the Easy Credit Externally.

Summary Review and Order Submitting

Once the customer is redirected back to us, the response from Easy Credit is sent to the Heidelpay, and Heidelpay makes a synchronous POST request to the shop’s CONFIG_HEIDELPAY_PAYMENT_RESPONSE_URL URL (Yves), with the result of payment (see EasyCreditController::paymentAction()). It is called “external response transaction,” the result will be persisted in spy_payment_heidelpay_transaction_log as usual. The most important data here - is the payment reference ID which can be used for further transactions like finalize/reserve/etc.

After that, the customer can see the order summary page, where they can review all related data.

There the user will see:

  • Rate plan (CRITERION.EASYCREDIT_AMORTISATIONTEXT)
  • Interest fees (CRITERION_EASYCREDIT_ACCRUINGINTEREST)
  • Total sum including the interest fees (CRITERION.EASYCREDIT_TOTALAMOUNT)

If the customer has not yet completed the HP.IN they must do that again.

On the “save order” event - save Heidelpay payment per order and items, as usual.

When the state machine is initialized, an event “send authorize on registration request” will trigger the authorize on registration request. In case of success, the state will be changed.

Finalize - later on, when the item is shipped to the customer, it is time to call “finalize” command of the state machine. This will send HP.FI request to the Payment API. This is done in FinalizePlugin of the OMS command.