Implement new payment methods on the project level
Edit on GitHubThis document shows how to implement Unzer payment types, which are not currently available in the Unzer Eco module. This document uses Unzer PayPal as an example.
Prerequisites
Before implementing the Unzer payment method, make sure to check and fulfill the following preconditions:
Implementation
- Add PayPal to the OMS process list and payment methods
state-machine
mappings:
config/Shared/config_default.php
$config[OmsConstants::ACTIVE_PROCESSES] = [
...
'UnzerPayPal01',
];
...
$config[SalesConstants::PAYMENT_METHOD_STATEMACHINE_MAPPING] = [
...
UnzerConfig::PAYMENT_METHOD_KEY_PAYPAL => 'UnzerPayPal01',
];
- Add your OMS schema for PayPal payment or use the following example:
config/Zed/oms/UnzerPayPal01.xml
<?xml version="1.0" encoding="utf-8"?>
<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="UnzerPayPal01" main="true">
<states>
<state name="new" reserved="true"/>
<state name="authorize pending" reserved="true"/>
<state name="authorize succeeded" reserved="true"/>
<state name="authorize failed"/>
<state name="authorize canceled"/>
<state name="charge pending" reserved="true"/>
<state name="payment completed" reserved="true"/>
<state name="charge failed"/>
<state name="refunded"/>
<state name="payment chargeback"/>
<state name="shipped"/>
<state name="closed"/>
</states>
<events>
<event name="charge" manual="true" command="Unzer/Charge"/>
<event name="refund" manual="true" command="Unzer/Refund"/>
<event name="ship" manual="true"/>
<event name="close" manual="false" timeout="14 days"/>
</events>
<transitions>
<transition happy="true">
<source>new</source>
<target>authorize pending</target>
</transition>
<transition condition="Unzer/IsAuthorizeSucceeded" happy="true">
<source>authorize pending</source>
<target>authorize succeeded</target>
</transition>
<transition condition="Unzer/IsAuthorizeFailed" happy="true">
<source>authorize pending</source>
<target>authorize failed</target>
</transition>
<transition condition="Unzer/IsAuthorizeCanceled" happy="true">
<source>authorize pending</source>
<target>authorize canceled</target>
</transition>
<transition happy="true">
<source>authorize succeeded</source>
<target>charge pending</target>
<event>charge</event>
</transition>
<transition condition="Unzer/IsPaymentCompleted" happy="true">
<source>charge pending</source>
<target>payment completed</target>
</transition>
<transition condition="Unzer/IsChargeFailed" happy="true">
<source>charge pending</source>
<target>charge failed</target>
</transition>
<transition condition="Unzer/IsPaymentChargeback" happy="true">
<source>payment completed</source>
<target>payment chargeback</target>
</transition>
<transition happy="true">
<source>payment completed</source>
<target>refunded</target>
<event>refund</event>
</transition>
<transition happy="true">
<source>payment completed</source>
<target>shipped</target>
<event>ship</event>
</transition>
<transition happy="true">
<source>shipped</source>
<target>closed</target>
<event>close</event>
</transition>
</transitions>
</process>
</statemachine>
- Extend
PaymentTransfer
with new property:
src/Pyz/Shared/Payment/Transfer/payment.transfer.xml
<?xml version="1.0"?>
<transfers xmlns="spryker:transfer-01" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="spryker:transfer-01 http://static.spryker.com/transfer-01.xsd">
<transfer name="Payment">
<property name="unzerPayPal" type="UnzerPayment"/>
</transfer>
</transfers>
- Edit payment template:
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: {
...
'Unzer/views/pay-pal': ['pay-pal', 'unzer'],
},
} %}
- Introduce
PayPalFormDataProvider
:
src/Pyz/Yves/Unzer/Form/DataProvider/PayPalFormDataProvider.php
<?php
/**
* This file is part of the Spryker Suite.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/
namespace Pyz\Yves\Unzer\Form\DataProvider;
use Generated\Shared\Transfer\QuoteTransfer;
use Generated\Shared\Transfer\UnzerPaymentTransfer;
use Spryker\Shared\Kernel\Transfer\AbstractTransfer;
use SprykerEco\Yves\Unzer\Dependency\Client\UnzerToQuoteClientInterface;
use SprykerEco\Yves\Unzer\Form\DataProvider\AbstractFormDataProvider;
class PayPalFormDataProvider extends AbstractFormDataProvider
{
/**
* @var \SprykerEco\Yves\Unzer\Dependency\Client\UnzerToQuoteClientInterface
*/
protected $quoteClient;
/**
* @param \SprykerEco\Yves\Unzer\Dependency\Client\UnzerToQuoteClientInterface $quoteClient
*/
public function __construct(UnzerToQuoteClientInterface $quoteClient)
{
$this->quoteClient = $quoteClient;
}
/**
* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
*
* @return \Generated\Shared\Transfer\QuoteTransfer
*/
public function getData(AbstractTransfer $quoteTransfer): QuoteTransfer
{
$quoteTransfer = $this->updateQuoteWithPaymentData($quoteTransfer);
$quoteTransfer->getPaymentOrFail()->setUnzerPayPal(new UnzerPaymentTransfer());
$this->quoteClient->setQuote($quoteTransfer);
return $quoteTransfer;
}
}
- Introduce
PayPalSubform
:
src/Pyz/Yves/Unzer/Form/PayPalSubform.php
<?php
/**
* This file is part of the Spryker Suite.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/
namespace Pyz\Yves\Unzer\Form;
use Generated\Shared\Transfer\PaymentTransfer;
use Generated\Shared\Transfer\UnzerPaymentTransfer;
use SprykerEco\Yves\Unzer\Form\AbstractUnzerSubForm;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @method \SprykerEco\Yves\Unzer\UnzerConfig getConfig()
*/
class PayPalSubform extends AbstractUnzerSubForm
{
/**
* @var string
*/
protected const TEMPLATE_VIEW_PATH = 'views/pay-pal/pay-pal';
/**
* @return string
*/
public function getPropertyPath(): string
{
return PaymentTransfer::UNZER_PAY_PAL;
}
/**
* @return string
*/
public function getName(): string
{
return PaymentTransfer::UNZER_PAY_PAL;
}
/**
* @param \Symfony\Component\OptionsResolver\OptionsResolver $resolver
*
* @return void
*/
public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'data_class' => UnzerPaymentTransfer::class,
])->setRequired(static::OPTIONS_FIELD_NAME);
}
}
- Introduce
UnzerPayPalSubFormPlugin
:
src/Pyz/Yves/Unzer/Plugin/StepEngine/UnzerPayPalSubFormPlugin.php
<?php
/**
* This file is part of the Spryker Suite.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/
namespace Pyz\Yves\Unzer\Plugin\StepEngine;
use Spryker\Yves\Kernel\AbstractPlugin;
use Spryker\Yves\StepEngine\Dependency\Form\StepEngineFormDataProviderInterface;
use Spryker\Yves\StepEngine\Dependency\Form\SubFormInterface;
use Spryker\Yves\StepEngine\Dependency\Plugin\Form\SubFormPluginInterface;
/**
* @method \Pyz\Yves\Unzer\UnzerFactory getFactory()
* @method \SprykerEco\Yves\Unzer\UnzerConfig getConfig()
*/
class UnzerPayPalSubFormPlugin extends AbstractPlugin implements SubFormPluginInterface
{
/**
* {@inheritDoc}
* - Creates `PayPal` subform.
*
* @api
*
* @return \Spryker\Yves\StepEngine\Dependency\Form\SubFormInterface
*/
public function createSubForm(): SubFormInterface
{
return $this->getFactory()->createPayPalSubForm();
}
/**
* {@inheritDoc}
* - Creates `PayPal` subform data provider.
*
* @api
*
* @return \Spryker\Yves\StepEngine\Dependency\Form\StepEngineFormDataProviderInterface
*/
public function createSubFormDataProvider(): StepEngineFormDataProviderInterface
{
return $this->getFactory()->createPayPalFormDataProvider();
}
}
- Add
CheckoutPage
plugins to the plugin stack:
src/Pyz/Yves/CheckoutPage/CheckoutPageDependencyProvider.php
...
use Pyz\Yves\Unzer\Plugin\StepEngine\UnzerPayPalSubFormPlugin;
...
/**
* @param \Spryker\Yves\Kernel\Container $container
*
* @return \Spryker\Yves\Kernel\Container
*/
protected function extendPaymentMethodHandler(Container $container): Container
{
$container->extend(static::PAYMENT_METHOD_HANDLER, function (StepHandlerPluginCollection $paymentMethodHandler) {
...
$paymentMethodHandler->add(new UnzerStepHandlerPlugin(), UnzerConfig::PAYMENT_METHOD_KEY_PAYPAL);
return $paymentMethodHandler;
});
}
...
/**
* @param \Spryker\Yves\Kernel\Container $container
*
* @return \Spryker\Yves\Kernel\Container
*/
protected function extendSubFormPluginCollection(Container $container): Container
{
$container->extend(static::PAYMENT_SUB_FORMS, function (SubFormPluginCollection $paymentSubFormPluginCollection) {
...
$paymentSubFormPluginCollection->add(new UnzerPayPalSubFormPlugin());
});
}
- To introduce new methods in the
Yves
layer, overrideUnzerFactory
:
src/Pyz/Yves/Unzer/UnzerFactory.php
<?php
/**
* This file is part of the Spryker Suite.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/
namespace Pyz\Yves\Unzer;
use Pyz\Yves\Unzer\Form\DataProvider\PayPalFormDataProvider;
use Pyz\Yves\Unzer\Form\PayPalSubform;
use Spryker\Yves\StepEngine\Dependency\Form\StepEngineFormDataProviderInterface;
use Spryker\Yves\StepEngine\Dependency\Form\SubFormInterface;
use SprykerEco\Yves\Unzer\UnzerFactory as EcoUnzerFactory;
class UnzerFactory extends EcoUnzerFactory
{
/**
* @return \Spryker\Yves\StepEngine\Dependency\Form\SubFormInterface
*/
public function createPayPalSubForm(): SubFormInterface
{
return new PayPalSubform();
}
/**
* @return \Spryker\Yves\StepEngine\Dependency\Form\StepEngineFormDataProviderInterface
*/
public function createPayPalFormDataProvider(): StepEngineFormDataProviderInterface
{
return new PayPalFormDataProvider(
$this->getQuoteClient(),
);
}
}
- Introduce
PayPalPaymentProcessor
:
src/Pyz/Zed/Unzer/Business/Payment/Processor/PayPalPaymentProcessor.php
<?php
/**
* This file is part of the Spryker Suite.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/
namespace Pyz\Zed\Unzer\Business\Payment\Processor;
use Generated\Shared\Transfer\OrderTransfer;
use Generated\Shared\Transfer\QuoteTransfer;
use Generated\Shared\Transfer\RefundTransfer;
use Generated\Shared\Transfer\SaveOrderTransfer;
use Generated\Shared\Transfer\UnzerPaymentResourceTransfer;
use Generated\Shared\Transfer\UnzerPaymentTransfer;
use SprykerEco\Zed\Unzer\Business\ApiAdapter\UnzerAuthorizeAdapterInterface;
use SprykerEco\Zed\Unzer\Business\ApiAdapter\UnzerPaymentAdapterInterface;
use SprykerEco\Zed\Unzer\Business\ApiAdapter\UnzerPaymentResourceAdapterInterface;
use SprykerEco\Zed\Unzer\Business\Checkout\Mapper\UnzerCheckoutMapperInterface;
use SprykerEco\Zed\Unzer\Business\Payment\Processor\Charge\UnzerChargeProcessorInterface;
use SprykerEco\Zed\Unzer\Business\Payment\Processor\PreparePayment\UnzerPreparePaymentProcessorInterface;
use SprykerEco\Zed\Unzer\Business\Payment\Processor\Refund\UnzerRefundProcessorInterface;
use SprykerEco\Zed\Unzer\Business\Payment\Processor\UnzerChargeablePaymentProcessorInterface;
class PayPalPaymentProcessor implements UnzerChargeablePaymentProcessorInterface
{
/**
* @var \SprykerEco\Zed\Unzer\Business\ApiAdapter\UnzerAuthorizeAdapterInterface
*/
protected UnzerAuthorizeAdapterInterface $unzerAuthorizeAdapter;
/**
* @var \SprykerEco\Zed\Unzer\Business\ApiAdapter\UnzerPaymentAdapterInterface
*/
protected UnzerPaymentAdapterInterface $unzerPaymentAdapter;
/**
* @var \SprykerEco\Zed\Unzer\Business\ApiAdapter\UnzerPaymentResourceAdapterInterface
*/
protected UnzerPaymentResourceAdapterInterface $unzerPaymentResourceAdapter;
/**
* @var \SprykerEco\Zed\Unzer\Business\Payment\Processor\Charge\UnzerChargeProcessorInterface
*/
protected UnzerChargeProcessorInterface $unzerChargeProcessor;
/**
* @var \SprykerEco\Zed\Unzer\Business\Payment\Processor\Refund\UnzerRefundProcessorInterface
*/
protected UnzerRefundProcessorInterface $unzerRefundProcessor;
/**
* @var \SprykerEco\Zed\Unzer\Business\Payment\Processor\PreparePayment\UnzerPreparePaymentProcessorInterface
*/
protected UnzerPreparePaymentProcessorInterface $unzerPreparePaymentProcessor;
/**
* @var \SprykerEco\Zed\Unzer\Business\Checkout\Mapper\UnzerCheckoutMapperInterface
*/
protected UnzerCheckoutMapperInterface $unzerCheckoutMapper;
/**
* @param \SprykerEco\Zed\Unzer\Business\ApiAdapter\UnzerAuthorizeAdapterInterface $unzerAuthorizeAdapter
* @param \SprykerEco\Zed\Unzer\Business\ApiAdapter\UnzerPaymentAdapterInterface $unzerPaymentAdapter
* @param \SprykerEco\Zed\Unzer\Business\ApiAdapter\UnzerPaymentResourceAdapterInterface $unzerPaymentResourceAdapter
* @param \SprykerEco\Zed\Unzer\Business\Payment\Processor\Charge\UnzerChargeProcessorInterface $unzerChargeProcessor
* @param \SprykerEco\Zed\Unzer\Business\Payment\Processor\Refund\UnzerRefundProcessorInterface $unzerRefundProcessor
* @param \SprykerEco\Zed\Unzer\Business\Payment\Processor\PreparePayment\UnzerPreparePaymentProcessorInterface $unzerPreparePaymentProcessor
* @param \SprykerEco\Zed\Unzer\Business\Checkout\Mapper\UnzerCheckoutMapperInterface $unzerCheckoutMapper
*/
public function __construct(
UnzerAuthorizeAdapterInterface $unzerAuthorizeAdapter,
UnzerPaymentAdapterInterface $unzerPaymentAdapter,
UnzerPaymentResourceAdapterInterface $unzerPaymentResourceAdapter,
UnzerChargeProcessorInterface $unzerChargeProcessor,
UnzerRefundProcessorInterface $unzerRefundProcessor,
UnzerPreparePaymentProcessorInterface $unzerPreparePaymentProcessor,
UnzerCheckoutMapperInterface $unzerCheckoutMapper
) {
$this->unzerAuthorizeAdapter = $unzerAuthorizeAdapter;
$this->unzerPaymentAdapter = $unzerPaymentAdapter;
$this->unzerPaymentResourceAdapter = $unzerPaymentResourceAdapter;
$this->unzerChargeProcessor = $unzerChargeProcessor;
$this->unzerRefundProcessor = $unzerRefundProcessor;
$this->unzerPreparePaymentProcessor = $unzerPreparePaymentProcessor;
$this->unzerCheckoutMapper = $unzerCheckoutMapper;
}
/**
* @param \Generated\Shared\Transfer\OrderTransfer $orderTransfer
* @param array<int> $salesOrderItemIds
*
* @return void
*/
public function processCharge(OrderTransfer $orderTransfer, array $salesOrderItemIds): void
{
$this->unzerChargeProcessor->charge($orderTransfer, $salesOrderItemIds);
}
/**
* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
* @param \Generated\Shared\Transfer\SaveOrderTransfer $saveOrderTransfer
*
* @return \Generated\Shared\Transfer\UnzerPaymentTransfer
*/
public function processOrderPayment(QuoteTransfer $quoteTransfer, SaveOrderTransfer $saveOrderTransfer): UnzerPaymentTransfer
{
$unzerPaymentTransfer = $this->unzerPreparePaymentProcessor->prepareUnzerPaymentTransfer($quoteTransfer, $saveOrderTransfer);
$unzerPaymentTransfer->setPaymentResource($this->createUnzerPaymentResource($quoteTransfer));
$unzerPaymentTransfer = $this->unzerAuthorizeAdapter->authorizePayment($unzerPaymentTransfer);
return $this->unzerPaymentAdapter->getPaymentInfo($unzerPaymentTransfer);
}
/**
* @param \Generated\Shared\Transfer\RefundTransfer $refundTransfer
* @param \Generated\Shared\Transfer\OrderTransfer $orderTransfer
* @param array<int> $salesOrderItemIds
*
* @return void
*/
public function processRefund(RefundTransfer $refundTransfer, OrderTransfer $orderTransfer, array $salesOrderItemIds): void
{
$this->unzerRefundProcessor->refund($refundTransfer, $orderTransfer, $salesOrderItemIds);
}
/**
* @param \Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
*
* @return \Generated\Shared\Transfer\UnzerPaymentResourceTransfer
*/
protected function createUnzerPaymentResource(QuoteTransfer $quoteTransfer): UnzerPaymentResourceTransfer
{
$unzerPaymentResourceTransfer = $this->unzerCheckoutMapper
->mapQuoteTransferToUnzerPaymentResourceTransfer(
$quoteTransfer,
new UnzerPaymentResourceTransfer(),
);
return $this->unzerPaymentResourceAdapter->createPaymentResource(
$unzerPaymentResourceTransfer,
$quoteTransfer->getPaymentOrFail()->getUnzerPaymentOrFail()->getUnzerKeypairOrFail(),
);
}
}
- To introduce new methods on the
Zed
layer, overrideUnzerBusinessFactory
:
src/Pyz/Zed/Unzer/Business/UnzerBusinessFactory.php
<?php
/**
* This file is part of the Spryker Suite.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/
namespace Pyz\Zed\Unzer\Business;
use Pyz\Zed\Unzer\Business\Payment\Processor\PayPalPaymentProcessor;
use SprykerEco\Shared\Unzer\UnzerConfig;
use SprykerEco\Zed\Unzer\Business\Payment\Processor\UnzerPaymentProcessorInterface;
use SprykerEco\Zed\Unzer\Business\UnzerBusinessFactory as EcoUnzerBusinessFactory;
/**
* @method \Pyz\Zed\Unzer\UnzerConfig getConfig()
* @method \SprykerEco\Zed\Unzer\Persistence\UnzerRepositoryInterface getRepository()
* @method \SprykerEco\Zed\Unzer\Persistence\UnzerEntityManagerInterface getEntityManager()
*/
class UnzerBusinessFactory extends EcoUnzerBusinessFactory
{
/**
* @return array<string, \Closure>
*/
public function getUnzerPaymentProcessors(): array
{
return array_merge(parent::getUnzerPaymentProcessors(), [
UnzerConfig::PAYMENT_METHOD_KEY_PAYPAL => function () {
return $this->createPayPalPaymentProcessor();
},
]);
}
/**
* @return \SprykerEco\Zed\Unzer\Business\Payment\Processor\UnzerPaymentProcessorInterface
*/
public function createPayPalPaymentProcessor(): UnzerPaymentProcessorInterface
{
return new PayPalPaymentProcessor(
$this->createUnzerAuthorizeAdapter(),
$this->createUnzerPaymentAdapter(),
$this->createUnzerPaymentResourceAdapter(),
$this->createUnzerCreditCardChargeProcessor(),
$this->createUnzerRefundProcessor(),
$this->createUnzerPreparePaymentProcessor(),
$this->createUnzerCheckoutMapper(),
);
}
}
- To add PayPal to authorizable payment methods, override
UnzerConfig
:
src/Pyz/Zed/Unzer/UnzerConfig.php
<?php
/**
* This file is part of the Spryker Suite.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/
namespace Pyz\Zed\Unzer;
use SprykerEco\Shared\Unzer\UnzerConfig as UnzerSharedConfig;
use SprykerEco\Zed\Unzer\UnzerConfig as EcoUnzerConfig;
class UnzerConfig extends EcoUnzerConfig
{
/**
* @var array<int, string>
*/
protected const AUTHORIZE_PAYMENT_METHODS = [
UnzerSharedConfig::PAYMENT_METHOD_KEY_MARKETPLACE_CREDIT_CARD,
UnzerSharedConfig::PAYMENT_METHOD_KEY_CREDIT_CARD,
UnzerSharedConfig::PAYMENT_METHOD_KEY_PAYPAL,
];
}
Implemented payment method on the Storefront
The following is an example of how the implemented payment method Unzer looks on the Storefront during the checkout.
Order checkout from Guest shopping cart
Selecting the Unzer PayPal payment method
Loggin in PayPal
Paying with PayPal
Successful order placement
Triggering shipment states in the Back Office
The following are examples of the shipment state changes when the order paid with the implemented payment method is processed in the Back Office.
Triggering the charge state
Triggering the refund state
The shipment is refunded
Thank you!
For submitting the form