Integrate PayOne

Edit on GitHub

PayOne is not compatible with gift cards. We will update this document once the issue is resolved.

Objectives:

  • Place order with PayPal express checkout.
  • Be redirected to summary page of standard checkout.
  • Have a shipping method selector on summary page.

Frontend Update

To make JavaScript and CSS styles enabled in the Suite, update: <root>/tsconfig.json in section “include”:

./vendor/spryker-eco/**/*",

Update <root>/frontend/settings.js:

add entry to ‘paths’:

// eco folders
eco: {
 // all modules
 modules: './vendor/spryker-eco'
},

add one more directory to module.exports.find.componentEntryPoints.dirs:

path.join(context, paths.eco.modules)

Run npm run yves after applying these changes. Due to the pending update to Suite, you also have to apply Checkout template .../src/Pyz/Yves/CheckoutPage/Theme/default/views/payment/payment.twig update:

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

{% define data = {
 backUrl: _view.previousStepUrl,
 forms: {
 payment: _view.paymentForm
 },
 title: 'checkout.step.payment.title' | trans
} %}

{% block content %}
 {% embed molecule('form') with {
 class: 'box',
 data: {
 form: data.forms.payment,
 options: {
 attr: {
 id: 'payment-form'
 }
 },
 submit: {
 enable: true,
 text: 'checkout.step.summary' | trans
 },
 cancel: {
 enable: true,
 url: data.backUrl,
 text: 'general.back.button' | trans
 }
 }
 } only %}
 {% block fieldset %}
 {% for name, choices in data.form.paymentSelection.vars.choices %}
 {% set paymentProviderIndex = loop.index0 %}
 <h5>{{ ('checkout.payment.provider.' ~ name) | trans }}</h5>
 <ul>
 {% for key, choice in choices %}
 <li class="list__item spacing-y clear">
 {% embed molecule('form') with {
 data: {
 form: data.form[data.form.paymentSelection[key].vars.value],
 enableStart: false,
 enableEnd: false
 },
 embed: {
 index: loop.index ~ '-' ~ paymentProviderIndex,
 toggler: data.form.paymentSelection[key],
 }
 } only %}
 {% block fieldset %}
 {{ form_row(embed.toggler, {
 required: false,
 component: molecule('toggler-radio'),
 attributes: {
 'target-selector': '.js-payment-method-' ~ embed.index,
 'class-to-toggle': 'is-hidden'
 }
 }) }}
 <div class="col col--sm-12 is-hidden js-payment-method-{{embed.index}}">
 <div class="col col--sm-12 col--md-6">
 {% if data.form.vars.template_path is defined %}
 {% set paymentPath = data.form.vars.template_path | split('/') %}
 {% set nameTemplate = paymentPath[1] | replace('_', '-') %}
 {% include view(nameTemplate, paymentPath[0]) ignore missing with {
 form: data.form.parent
 } only %}
 {% else %}
 {{parent()}}
 {% endif %}
 </div>
 </div>
 {% endblock %}
 {% endembed %}
 </li>
 {% endfor %}
 </ul>
 {% endfor %}
 {% endblock %}
 {% endembed %}
{% endblock %}

Run console cache:empty afterwards to make this template used during rendering.

Backend Update

First of all we need to provide a URL to Payone module, which will be used to redirect user when the quote is filled with data obtained from PayPal. To achieve this, make the following steps:

  1. Add custom controller action to src/Pyz/Yves/Checkout/Controller/CheckoutController.php:
/**
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function paypalExpressCheckoutEntryPointAction()
{
$this->getFactory()
->createExpressCheckoutHandler()
->fulfillPostConditionsUntilSummaryStep();
return $this->redirectResponseInternal(CheckoutPageControllerProvider::CHECKOUT_SUMMARY);
}
  1. Register a new controller action in controller provider:
...
const CHECKOUT_PAYPAL_EXPRESS_CHECKOUT_ENTRY_POINT = 'checkout-paypal-express-checkout-entry-point';
...
protected function defineControllers(Application $app)
{
...
$this->createController('/{checkout}/paypal-express-checkout-entry-point', self::CHECKOUT_PAYPAL_EXPRESS_CHECKOUT_ENTRY_POINT, 'Checkout', 'Checkout', 'paypalExpressCheckoutEntryPoint')
->assert('checkout', $allowedLocalesPattern . 'checkout|checkout')
->value('checkout', 'checkout')
->method('GET');
...
}
  1. Create ExpressCheckoutHandler class src/Pyz/Yves/CheckoutPage/Handler/ExpressCheckoutHandler.php with corresponding interface:
namespace Pyz\Yves\CheckoutPage\Handler;
use Generated\Shared\Transfer\ExpenseTransfer;
use Spryker\Client\Cart\CartClientInterface;
use Spryker\Shared\Shipment\ShipmentConstants;
class ExpressCheckoutHandler implements ExpressCheckoutHandlerInterface
{
/**
* @var \Spryker\Client\Cart\CartClientInterface
*/
protected $cartClient;
/**
* @param \Spryker\Client\Cart\CartClientInterface $cartClient
*/
public function __construct(CartClientInterface $cartClient)
{
$this->cartClient = $cartClient;
}
/**
* @return void
*/
public function fulfillPostConditionsUntilSummaryStep()
{
$quoteTransfer = $this->cartClient->getQuote();
$quoteTransfer->addExpense(
(new ExpenseTransfer())->setType(ShipmentConstants::SHIPMENT_EXPENSE_TYPE)
);
}
}
  1. Add ExpressCheckoutHandler related method in src/Pyz/Yves/CheckoutPage/CheckoutPageFactory.php:
/**
* @return \Pyz\Yves\CheckoutPage\Handler\ExpressCheckoutHandler
*/
public function createExpressCheckoutHandler()
{
return new ExpressCheckoutHandler(
$this->getCartClient()
);
}
  1. Extend ShipmentForm class to override a property_path option (create src/Pyz/Yves/Shipment/Form/ShipmentSubForm.php):
namespace Pyz\Yves\Shipment\Form;
class ShipmentSubForm extends ShipmentForm
{
/**
* @const string
*/
const SHIPMENT_SELECTION_PROPERTY_PATH = self::SHIPMENT_SELECTION;
}
  1. Adjust summary form by adding a shipment subform in src/Pyz/Yves/CheckoutPage/Form/Steps/SummaryForm.php:
class SummaryForm extends AbstractType
{
/**
* Builds the form.
*
* This method is called for each type in the hierarchy starting from the
* top most type. Type extensions can further modify the form.
*
* @see FormTypeExtensionInterface::buildForm()
*
* @param \Symfony\Component\Form\FormBuilderInterface $builder The form builder
* @param array $options The options
*
* @return void
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'shipmentForm',
ShipmentSubForm::class,
array_merge(
$options,
[
'data_class' => ShipmentTransfer::class,
'property_path' => 'shipment',
]
)
);
}
/**
* @param \Symfony\Component\OptionsResolver\OptionsResolver $resolver
*
* @return void
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired('shipmentMethods');
$resolver->setDefaults([
'data_class' => QuoteTransfer::class,
]);
}
/**
* Returns the name of this type.
*
* @return string The name of this type
*/
public function getName()
{
return 'summaryForm';
}
}
  1. Create shipment subform template for summary page in src/Pyz/Yves/CheckoutPage/Theme/default/checkout/partials/shipment.twig:

<div class="row columns">

 {% set shipmentForm = summaryForm.shipmentForm %}

 <div class="callout">
  <ul class="no-bullet">

   {% for name, choices in shipmentForm.idShipmentMethod.vars.choices %}

   <h4>{{ name }}</h4>

   {% for key, choice in choices %}
   <li>
    <label>
     {{ form_widget(shipmentForm.idShipmentMethod[key], {'attr': {'class': '__toggler'}}) }}
     {{ shipmentForm.idShipmentMethod[key].vars.label | raw }}
    </label>
   </li>
   {% endfor %}
   {% endfor %}
  </ul>
 </div>
</div>
  1. Update summary twig template in src/Pyz/Yves/CheckoutPage/Theme/default/checkout/summary.twig. Move "form_start" expression to the top of a content section and "form_end" to the end:
...
{% block content %}
{{ form_start(summaryForm, {'attr': {'class': 'row'}}) }}
...
...
...
{{ form_end(summaryForm) }}
{% endblock %}
  1. Remove {% include '@checkout/checkout/partials/voucher-form.twig' %} include from summary template. Include checkout/partials/shipment.twig in your summary template:
      ...
{{ 'checkout.step.summary.shipping' | trans }}
      {% include '@checkout/checkout/partials/shipment.twig' %}
      ...
  1. Add shipment form data provider and remove voucher form from summary form collection(It is just an example, if you need voucher form, you need to adjust summary page on your own). In src/Pyz/Yves/CheckoutPage/Form/FormFactory.php:
/**
* @param \Generated\Shared\Transfer\QuoteTransfer
*
* @return \Spryker\Yves\StepEngine\Form\FormCollectionHandlerInterface
*/
public function createSummaryFormCollection()
{
return $this->createFormCollection($this->createSummaryFormTypes(), $this->getShipmentFormDataProviderPlugin());
}

....


/**
* @return \Symfony\Component\Form\FormTypeInterface[]
*/
protected function createSummaryFormTypes()
{
return [
$this->createSummaryForm(),
//$this->createVoucherFormType(),
];
}
  1. Handle shipment form data, when summary form is submitted. Inject two dependencies into src/Pyz/Yves/CheckoutPage/Process/Steps/SummaryStep.php:
/**
* @var \Spryker\Client\Calculation\CalculationClientInterface
*/
protected $calculationClient;
/**
* @var \Spryker\Yves\StepEngine\Dependency\Plugin\Handler\StepHandlerPluginCollection
*/
protected $shipmentPlugins;
/**
* @param \Spryker\Yves\ProductBundle\Grouper\ProductBundleGrouperInterface $productBundleGrouper
* @param \Spryker\Client\Cart\CartClientInterface $cartClient
* @param \Spryker\Client\Calculation\CalculationClientInterface $calculationClient
* @param \Spryker\Client\Calculation\CalculationClientInterface $shipmentPlugins
* @param string $stepRoute
* @param string $escapeRoute
*/
public function __construct(
ProductBundleGrouperInterface $productBundleGrouper,
CartClientInterface $cartClient,
CalculationClientInterface $calculationClient,
StepHandlerPluginCollection $shipmentPlugins,
$stepRoute,
$escapeRoute
) {
parent::__construct($stepRoute, $escapeRoute);
$this->productBundleGrouper = $productBundleGrouper;
$this->cartClient = $cartClient;
$this->calculationClient = $calculationClient;
$this->shipmentPlugins = $shipmentPlugins;
}
  1. Extend execute method:
/**
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \Spryker\Shared\Kernel\Transfer\AbstractTransfer|\Generated\Shared\Transfer\QuoteTransfer $quoteTransfer
*
* @return \Generated\Shared\Transfer\QuoteTransfer
*/
public function execute(Request $request, AbstractTransfer $quoteTransfer)
{
$shipmentHandler = $this->shipmentPlugins->get(CheckoutPageDependencyProvider::PLUGIN_SHIPMENT_STEP_HANDLER);
$shipmentHandler->addToDataClass($request, $quoteTransfer);
$this->calculationClient->recalculate($quoteTransfer);
$this->markCheckoutConfirmed($request, $quoteTransfer);
return $quoteTransfer;
}
  1. Add the Payone checkout plugins to Pyz\Zed\Checkout\CheckoutDependencyProvider:
<?php

namespace Pyz\Zed\Checkout;
//...
use SprykerEco\Zed\Payone\Communication\Plugin\Checkout\PayoneCheckoutDoSaveOrderPlugin;
use SprykerEco\Zed\Payone\Communication\Plugin\Checkout\PayoneCheckoutPostSavePlugin;
use SprykerEco\Zed\Payone\Communication\Plugin\Checkout\PayoneCheckoutPreConditionPlugin;
class CheckoutDependencyProvider extends SprykerCheckoutDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPreConditionPluginInterface[]
     */
    protected function getCheckoutPreConditions(Container $container)
    {
        return [
            //...
            new PayoneCheckoutPreConditionPlugin(),
            //...
        ];
    }

    /**
     * @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 PayoneCheckoutDoSaveOrderPlugin(),
            //...
        ];
    }

    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPostSaveInterface[]
     */
    protected function getCheckoutPostHooks(Container $container)
    {
        return [
            //...
            new PayoneCheckoutPostSavePlugin(),
            //...
        ];
    }
    //...
}

Now, go to checkout and try placing an order with PayPal express checkout.