Spryker Integration Implementation

Written By Devin O'Neill ()

Updated at April 15th, 2025

Introduction

Spryker – A Composable Commerce

Composable Commerce embraces an API-first, headless architecture without risking vendor lock-in. In Composable Commerce, front end and back ends are decoupled, similar to a headless approach. Composable Commerce goes beyond Headless with the added personalization for your tech stack.

Spryker – B2C: A Solution to Cloud Commerce

Spryker B2C is a cloud commerce solution which enables various types of features including Product Management, Order Management, Lists, Cart, Checkout and Shipping, and many more.

Setting Up Spryker-B2C Cloud Commerce into Development

There are various steps we need to follow as mentioned below:

Prerequisites for Installation

Install WSL if you are working on a Windows System, but we prefer Linux PC. It’s easy to use to maintain docker.

System Requirements:

Requirement Value or Version Additional Details
Windows 10 or 11 64 Bit Pro, Enterprise, or Education (1607 Anniversary Update, Build 14393 or later).
BIOS Virtualization Enabled  
CPU SLAT-capable feature Enabled  
Docker Latest  
Docker Compose Latest  
RAM Min 4GB or More  
Swap Min 2GB or More  

Installation Steps:

Using Linux PC:

Using Windows PC:

Installing Spryker Development Environment into the PC:

  • git clone https://github.com/spryker-shop/b2c-demo-shop.git -b [202212.0-p2] *** --single-branch/[b2c-demo-shop] **
  • cd b2c-demo-shop
  • git clone https://github.com/spryker/docker-sdk.git --single-branch docker è [Install docker/sdk] – It is used to run the internal commands.
  • Bootstrap the Spryker b2c development: docker/sdk bootstrap deploy.dev.yml – It is used to create development environment.
  • It will respond with Host setup environment.
  • Go into /etc/hosts file and paste all hosts environments into there and save the file.

  • Build and start the instance: docker/sdk up && docker/sdk console q:w:s -v -s -- to start the ques process along with build. It will take 20 minutes to build and start the Docker system with all the required services.
  • Open the browser and type spryker.local into the URL. It will populate different URLs in respective of country and platform. For example, yves: Store Front | zed: Backoffice etc.

** change the text as per your need. The text given as per demonstration purpose only.

*** check current version of Spryker b2c.

Setting Up Loyalty Functionality in Spryker

Annex Cloud loyalty functionality integrates with various features such as Loyalty Dashboard, Loyalty Cart Widget & Plugin, Checkout Widget to show the loyalty points a customer can earn when making the final order in Spryker and setup the loyalty configuration within the back-office.

Loyalty Configuration Setup:

Steps:

  • For loyalty setup we need the data transfer object and the data base schema as below in images
  • For Loyalty Data Base Schema (Propel Schema) under the src/Pyz/Zed/Loyalty/Persistence/Propel/Schema
  • To install schema into Spryker:B2C use : docker/sdk console propel:install
  • For Loyalty Data Transfer within the Spryker Store Front and Backoffice we need the below transfer Object under src/Pyz/Shared/Loyalty/Transfer
  • It helps to transfer loyalty data within the multiple modules and platform within Spryker B2C Commerce. To install the transfer object use: docker/sdk console transfer: generate.
  • Create a class as LoyaltyDependencyProvider.php in the Loyalty module. This injects all dependencies needed to run the loyalty module.

 


<?php
namespace Pyz\Zed\Loyalty;
use Spryker\Zed\Kernel\AbstractBundleDependencyProvider;
class LoyaltyDependencyProvider extends AbstractBundleDependencyProvider{}
  • Create a class LoyaltyConfig.php in Loyalty Module with the below codes. This exposes the configuration and dependency provider along with AC (Annex Cloud) loyalty API requests.
<?php

namespace Pyz\Zed\Loyalty;

use Firebase\JWT\JWT;
use Generated\Shared\Transfer\LoyaltyTransfer;
use Spryker\Zed\Kernel\AbstractBundleConfig;

class LoyaltyConfig extends AbstractBundleConfig
{
    public string $AC_SITE_ID;
    public string $AC_SECRET_KEY;
    public string $AC_SITE_SUB;
    public string $AC_SITE_URL;

    public function getJwtToken($payload): string
    {
        $payload = json_encode($payload);
        $payload = base64_encode($payload);
        $hmac = base64_encode(hash_hmac('sha256', $payload, $this->AC_SECRET_KEY, true));
        $token = array(
            "sub" => $this->AC_SITE_SUB,
            "exp" => time() + 36000,
            "site_id" => $this->AC_SITE_ID,
            "hmac" => $hmac
        );
        return JWT::encode($token, $this->AC_SECRET_KEY, 'HS256');
    }

    /**
@description make a curl request
@param string $token
@param string $endPoint
@param $method string
@param null $data array
@return bool|string
     */
    public function makeRequest(string $token, string $endPoint, string $method, $data = null): bool|string
    {
        $url = $this->AC_SITE_URL . $endPoint;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                "Content-Type:  application/json",
                "Authorization: Bearer $token",
                "X-AnnexCloud-Site:" . $this->AC_SITE_ID,
            )
        );
        if (null != $data) {
            $dataJson = json_encode($data);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $dataJson);
        }
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        return curl_exec($ch);
    }
}
  • Create the business layer in Loyalty Module to do the Database activities.
  • Create the business factory class to transact the DB data.

<?php

namespace Pyz\Zed\Loyalty\Business;

use Pyz\Zed\Loyalty\Business\Loyalty\Loyalty;
use Pyz\Zed\Loyalty\Business\Reader\LoyaltyReader;
use Pyz\Zed\Loyalty\Business\Writer\LoyaltyWriter;
use Spryker\Zed\Kernel\Business\AbstractBusinessFactory;

/**
@method \Pyz\Zed\Loyalty\Persistence\LoyaltyEntityManager getEntityManager()
@method \Pyz\Zed\Loyalty\Persistence\LoyaltyRepository getRepository()
@method \Pyz\Zed\Loyalty\Persistence\LoyaltyQueryContainer getQueryContainer()
 */
class LoyaltyBusinessFactory extends AbstractBusinessFactory
{
    public function createLoyaltyWriter():LoyaltyWriter{
        return new LoyaltyWriter(
            $this->getEntityManager(),
            $this->getQueryContainer()
        );
    }
    public function createLoyaltyReader():LoyaltyReader{
        return new LoyaltyReader(
            $this->getRepository()
        );
    }
}
  • Create the business layer in Loyalty Module to do the Database activities.
  • Create the business factory class to transact the DB data.

<?php

namespace Pyz\Zed\Loyalty\Business;

use Pyz\Zed\Loyalty\Business\Loyalty\Loyalty;
use Pyz\Zed\Loyalty\Business\Reader\LoyaltyReader;
use Pyz\Zed\Loyalty\Business\Writer\LoyaltyWriter;
use Spryker\Zed\Kernel\Business\AbstractBusinessFactory;

/**
@method \Pyz\Zed\Loyalty\Persistence\LoyaltyEntityManager getEntityManager()
@method \Pyz\Zed\Loyalty\Persistence\LoyaltyRepository getRepository()
@method \Pyz\Zed\Loyalty\Persistence\LoyaltyQueryContainer getQueryContainer()
 */
class LoyaltyBusinessFactory extends AbstractBusinessFactory
{
    public function createLoyaltyWriter():LoyaltyWriter{
        return new LoyaltyWriter(
            $this->getEntityManager(),
            $this->getQueryContainer()
        );
    }
    public function createLoyaltyReader():LoyaltyReader{
        return new LoyaltyReader(
            $this->getRepository()
        );
    }
}
  • Create the Loyalty Façade class to instantiate and expose the factory to gain access to the business layer.
<?php

namespace Pyz\Zed\Loyalty\Business;

use Generated\Shared\Transfer\LoyaltyCollectionTransfer;
use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyResponseTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;
use Propel\Runtime\Exception\PropelException;
use Spryker\Zed\Kernel\Business\AbstractFacade;

/**
@method \Pyz\Zed\Loyalty\Business\LoyaltyBusinessFactory getFactory()
 */
class LoyaltyFacade extends AbstractFacade implements LoyaltyFacadeInterface
{

    public function createLoyalty(LoyaltyTransfer $loyaltyTransfer): LoyaltyTransfer
    {
        return $this->getFactory()->createLoyaltyWriter()->create($loyaltyTransfer);
    }

    public function getLoyalty(LoyaltyCriteriaTransfer $loyaltyCriteria): LoyaltyResponseTransfer
    {
        return $this->getFactory()->createLoyaltyReader()->getLoyalty($loyaltyCriteria);
    }

    /**
@param LoyaltyTransfer $loyaltyTransfer
@return LoyaltyResponseTransfer
@throws PropelException
     */
    public function updateLoyalty(LoyaltyTransfer $loyaltyTransfer): LoyaltyResponseTransfer
    {
        return $this->getFactory()
            ->createLoyaltyWriter()
            ->update($loyaltyTransfer);
    }
    /**
@param LoyaltyTransfer $loyaltyTransfer
@return bool
@throws PropelException
     */
    public function deleteLoyalty(LoyaltyTransfer $loyaltyTransfer): bool
    {
        return $this->getFactory()->createLoyaltyWriter()->delete($loyaltyTransfer);
    }
}

<?php

namespace Pyz\Zed\Loyalty\Business;

use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyResponseTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;

interface LoyaltyFacadeInterface
{
    public function createLoyalty(LoyaltyTransfer $loyaltyTransfer):LoyaltyTransfer;
    public function getLoyalty(LoyaltyCriteriaTransfer $loyaltyCriteria):LoyaltyResponseTransfer;
    public function updateLoyalty(LoyaltyTransfer $loyaltyTransfer);

    public function deleteLoyalty(LoyaltyTransfer $loyaltyTransfer);
}
  • Create a Schema reader/writer layer to do the respective schema activities.

<?php

namespace Pyz\Zed\Loyalty\Business\Reader;

use Generated\Shared\Transfer\LoyaltyCollectionTransfer;
use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyResponseTransfer;
use Pyz\Zed\Loyalty\Persistence\LoyaltyRepositoryInterface;

class LoyaltyReader
{
    protected $loyaltyRepository;

    public function __construct(LoyaltyRepositoryInterface $loyaltyRepository)
    {
        $this->loyaltyRepository = $loyaltyRepository;
    }

    public function getLoyalty(LoyaltyCriteriaTransfer $loyaltyCriteria): LoyaltyResponseTransfer{
        $loyaltyTransfer = $this->loyaltyRepository
            ->getLoyalty($loyaltyCriteria);
        $loyaltyResponseTransfer = new LoyaltyResponseTransfer();
        $loyaltyResponseTransfer
            ->setIsSuccessful(false);
        if($loyaltyTransfer){
           $loyaltyResponseTransfer
               ->setLoyalty($loyaltyTransfer)
               ->setIsSuccessful(true);
        }
        return $loyaltyResponseTransfer;
    }
}

<?php

namespace Pyz\Zed\Loyalty\Business\Writer;

use Exception;
use Generated\Shared\Transfer\LoyaltyResponseTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;
use Propel\Runtime\Exception\PropelException;
use Pyz\Zed\Loyalty\Persistence\LoyaltyEntityManagerInterface;
use Pyz\Zed\Loyalty\Persistence\LoyaltyQueryContainerInterface;

class LoyaltyWriter
{
    protected $loyaltyEntityManager;
    protected $queryContainer;
    public function __construct(
        LoyaltyEntityManagerInterface $loyaltyEntityManager,
        LoyaltyQueryContainerInterface $loyaltyQueryContainer
    ){
        $this->loyaltyEntityManager = $loyaltyEntityManager;
        $this->queryContainer = $loyaltyQueryContainer;
    }

    public function create(LoyaltyTransfer $loyaltyTransfer) : LoyaltyTransfer{
        return $this->loyaltyEntityManager->createLoyalty($loyaltyTransfer);
    }

    /**
@throws PropelException
@throws Exception
     */
    public function delete(LoyaltyTransfer $loyaltyTransfer): bool
    {
        $loyaltyEntity = $this->getLoyalty($loyaltyTransfer);
        $loyaltyEntity->delete();
        return true;
    }

    /**
@throws PropelException
@throws Exception
     */
    public function update(LoyaltyTransfer $loyaltyTransfer): LoyaltyResponseTransfer
    {
        $loyaltyResponseTransfer = $this->createLoyaltyResponseTransfer();
        $loyaltyResponseTransfer->setLoyalty($loyaltyTransfer);

        $loyaltyEntity = $this->getLoyalty($loyaltyTransfer);
        $loyaltyEntity->fromArray($loyaltyTransfer->modifiedToArray());

        if (!$loyaltyResponseTransfer->getIsSuccessful()) {
            return $loyaltyResponseTransfer;
        }
        if (!$loyaltyEntity->isModified()) {
            return $loyaltyResponseTransfer;
        }

        $loyaltyEntity->save();

        return $loyaltyResponseTransfer;
    }

    protected function createLoyaltyResponseTransfer($isSuccess = true)
    {
        $loyaltyResponseTransfer = new LoyaltyResponseTransfer();
        $loyaltyResponseTransfer->setIsSuccessful($isSuccess);
        return $loyaltyResponseTransfer;
    }

    /**

@throws Exception
     */
    protected function getLoyalty(LoyaltyTransfer $loyaltyTransfer){
        $loyaltyEntity = null;

        if ($loyaltyTransfer->getIdLoyalty()) {
            $loyaltyEntity = $this->queryContainer->queryLoyaltyById($loyaltyTransfer->getIdLoyalty())
                ->findOne();
        }

        if ($loyaltyEntity !== null) {
            return $loyaltyEntity;
        }

        throw new Exception(sprintf(
            'Customer not found by either ID `%s`, email `%s` or restore password key `%s`.',
            $loyaltyTransfer->getIdLoyalty()
        ));
    }

}
  • Create the Communication layer to communicate the loyalty module to the audience.
  • Create the Gateway under the Controller folder to gain access to the Client Layer.

<?php
namespace Pyz\Zed\Loyalty\Communication\Controller;
use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyResponseTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;
use Spryker\Zed\Kernel\Communication\Controller\AbstractGatewayController;
/**

@method \Pyz\Zed\Loyalty\Business\LoyaltyFacadeInterface getFacade()
 */
class GatewayController extends AbstractGatewayController
{
    /**
@param LoyaltyCriteriaTransfer $loyaltyCriteria
@return LoyaltyResponseTransfer
     */
    public function getLoyaltyAction(LoyaltyCriteriaTransfer $loyaltyCriteria) : LoyaltyResponseTransfer{
        return $this->getFacade()->getLoyalty($loyaltyCriteria);
    }

    /**
@param LoyaltyTransfer $loyaltyTransfer
@return LoyaltyTransfer
     */
    public function createLoyaltyAction(LoyaltyTransfer $loyaltyTransfer):LoyaltyTransfer{
        return $this->getFacade()->createLoyalty($loyaltyTransfer);
    }

    /**
@param LoyaltyTransfer $loyaltyTransfer
@return LoyaltyTransfer
     */
    public function updateAction(LoyaltyTransfer $loyaltyTransfer):LoyaltyTransfer{
        return $this->getFacade()->updateLoyalty($loyaltyTransfer);
    }

    /**
@param LoyaltyTransfer $loyaltyTransfer
@return void
     */
    public function deleteAction(LoyaltyTransfer $loyaltyTransfer):void{
        $result = $this->getFacade()->deleteLoyalty($loyaltyTransfer);
        $this->setSuccess($result);
    }
}
  • Create the controller action.

<?php

namespace Pyz\Zed\Loyalty\Communication\Controller;

use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;
use Spryker\Service\UtilText\Model\Url\Url;
use Spryker\Service\UtilText\Model\Url\UrlInvalidException;
use Spryker\Zed\Kernel\Communication\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;

/**
@method \Pyz\Zed\Loyalty\Communication\LoyaltyCommunicationFactory getFactory()
@method \Pyz\Zed\Loyalty\Persistence\LoyaltyQueryContainerInterface getQueryContainer()
@method \Pyz\Zed\Loyalty\Business\LoyaltyFacadeInterface getFacade()
 */
class AddController extends AbstractController
{
    /**
@var string
     */
    public const MESSAGE_LOYALTY_CREATE_SUCCESS = 'Loyalty was created successfully.';

    /**
@var string
     */
    public const MESSAGE_LOYALTY_CREATE_ERROR = 'Loyalty was not created.';
    public const MESSAGE_LOYALTY_EXIST_ERROR = 'Loyalty is exist. You can not create more than one';
    /**
@var string
     */
    public const REDIRECT_URL_DEFAULT = '/loyalty';

    /**

@var string
     */
    public const REDIRECT_URL_KEY = 'redirectUrl';

    /**

@param Request $request
@return array|RedirectResponse
     */
    public function indexAction(Request $request): RedirectResponse|array
    {
        $baseRedirectUrl = urldecode((string)$request->query->get(static::REDIRECT_URL_KEY, static::REDIRECT_URL_DEFAULT));
        $loyaltyCriteria = new LoyaltyCriteriaTransfer();
        $loyaltyResponseTransfer = $this->getFacade()->getLoyalty($loyaltyCriteria);
        if($loyaltyResponseTransfer->getLoyalty()) {
            $loyalty = $loyaltyResponseTransfer->getLoyalty();
            if(count($loyalty->toArray()) > 0){
                $this->addErrorMessage(static::MESSAGE_LOYALTY_EXIST_ERROR);
                return $this->redirectResponse($baseRedirectUrl);
            }
        }
        $dataProvider = $this->getFactory()->createLoyaltyFormDataProvider();
        $form = $this->getFactory()
            ->createLoyaltyForm(
                $dataProvider->getData(),
                array_merge(
                    [
                        'action' => (string) Url::generate('/loyalty/add', [static::REDIRECT_URL_KEY => $baseRedirectUrl]),
                    ],
                ),
            )
            ->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $loyaltyTransfer = new LoyaltyTransfer();
            $loyaltyTransfer->fromArray($form->getData(), true);

            $loyaltyResponseTransfer = $this->getFacade()->createLoyalty($loyaltyTransfer);

            if (!$loyaltyResponseTransfer->getIdLoyalty()) {
                $this->addErrorMessage(static::MESSAGE_LOYALTY_CREATE_ERROR);

                return $this->redirectResponse($baseRedirectUrl);
            }

            $this->addSuccessMessage(static::MESSAGE_LOYALTY_CREATE_SUCCESS);

            return $this->redirectResponse($this->getSuccessRedirectUrl($baseRedirectUrl, $loyaltyTransfer));
        }
        return $this->viewResponse(['form' => $form->createView()]);
    }

    /**

@throws UrlInvalidException
     */
    protected function getSuccessRedirectUrl(string $baseRedirectUrl, LoyaltyTransfer $loyalty): string
    {
        $redirectUrl = Url::parse($baseRedirectUrl);
        $redirectUrl->addQuery('idLoyalty', $loyalty->getIdLoyalty());
        return $redirectUrl->build();
    }
}

<?php

namespace Pyz\Zed\Loyalty\Communication\Controller;

use Generated\Shared\Transfer\LoyaltyTransfer;
use Propel\Runtime\Exception\PropelException;
use Spryker\Zed\Kernel\Communication\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;

/**
@method \Pyz\Zed\Loyalty\Communication\LoyaltyCommunicationFactory getFactory()
@method \Pyz\Zed\Loyalty\Business\LoyaltyFacade getFacade()
 */
class EditController extends AbstractController
{
    /**

@var string
     */
    public const MESSAGE_LOYALTY_UPDATE_ERROR = 'Loyalty was not updated.';

    /**

@var string
     */
    public const MESSAGE_LOYALTY_UPDATE_SUCCESS = 'Loyalty was updated successfully.';

    /**

@var string
     */
    protected const MESSAGE_ERROR_LOYALTY_NOT_EXIST = 'Loyalty with id `%s` does not exist';

    /**

@var string
     */
    protected const URL_LOYALTY_LIST_PAGE = '/loyalty';

    /**

@throws PropelException
     */
    public function indexAction(Request $request): array|RedirectResponse
    {
        $idLoyalty = $this->castId($request->query->get('id-loyalty'));

        $dataProvider = $this->getFactory()->createLoyaltyUpdateFormDataProvider();
        $formData = $dataProvider->getData($idLoyalty);

        if ($fo
  • Create the Communication Factory to expose the factory and Controller Actions to the Dependency injection to the Client layer as per the Spryker code conventions.

<?php

namespace Pyz\Zed\Loyalty\Communication;

use Orm\Zed\Loyalty\Persistence\PyzLoyaltyQuery;
use Pyz\Zed\Loyalty\Communication\Form\DataProvider\LoyaltyFormDataProvider;
use Pyz\Zed\Loyalty\Communication\Form\DataProvider\LoyaltyUpdateFormDataProvider;
use Pyz\Zed\Loyalty\Communication\Form\LoyaltyForm;
use Pyz\Zed\Loyalty\Communication\Form\LoyaltyUpdateForm;
use Pyz\Zed\Loyalty\Communication\Table\LoyaltyTable;
use Spryker\Zed\Kernel\Communication\AbstractCommunicationFactory;
use Symfony\Component\Form\FormInterface;

class LoyaltyCommunicationFactory extends AbstractCommunicationFactory
{
    /**
@param array $data
@param array $options
@return FormInterface
     */
    public function createLoyaltyForm(array $data = [], array $options = []): FormInterface
    {
        return $this->getFormFactory()->create(LoyaltyForm::class, $data, $options);
    }

    /**
@param array $data
@param array $options
@return FormInterface
     */
    public function createLoyaltyUpdateForm(array $data = [], array $options = []): FormInterface
    {
        return $this->getFormFactory()->create(LoyaltyUpdateForm::class, $data, $options);
    }

    /**
@return LoyaltyFormDataProvider
     */
    public function createLoyaltyFormDataProvider(): LoyaltyFormDataProvider
    {
        return new LoyaltyFormDataProvider(
            $this->getQueryContainer()
        );
    }

    /**

@return LoyaltyTable
     */
    public function createLoyaltyTable(): LoyaltyTable
    {
        return new LoyaltyTable(
            $this->createLoyaltyQuery()
        );
    }

    /**

@return PyzLoyaltyQuery
     */
    public function createLoyaltyQuery():PyzLoyaltyQuery{
        return PyzLoyaltyQuery::create();
    }

    /**

@return LoyaltyUpdateFormDataProvider
     */
    public function createLoyaltyUpdateFormDataProvider(): LoyaltyUpdateFormDataProvider
    {
        return new LoyaltyUpdateFormDataProvider(
            $this->getQueryContainer()
        );
    }

}
  • Create a persistence layer. It is the layer which only interacts with the Backend Loyalty Module Communication and Business Layers as per the Spryker coding conventions.
  • Create the Loyalty Entity Manager and Loyalty Entity Manager Interface to expose it to the Business and Communication layer.

<?php

namespace Pyz\Zed\Loyalty\Persistence;

use Generated\Shared\Transfer\LoyaltyTransfer;
use Orm\Zed\Loyalty\Persistence\PyzLoyalty;
use Propel\Runtime\Exception\PropelException;
use Spryker\Zed\Kernel\Persistence\AbstractEntityManager;

class LoyaltyEntityManager extends AbstractEntityManager implements LoyaltyEntityManagerInterface
{

    /**

@param LoyaltyTransfer $loyaltyTransfer
@return LoyaltyTransfer
@throws PropelException
     */
    public function createLoyalty(LoyaltyTransfer $loyaltyTransfer): LoyaltyTransfer
    {
        $loyaltyEntity = new PyzLoyalty();
        $loyaltyEntity->fromArray($loyaltyTransfer->modifiedToArray());
        $loyaltyEntity->save();
        return $loyaltyTransfer->fromArray($loyaltyEntity->toArray(), true);
    }
}

<?php

namespace Pyz\Zed\Loyalty\Persistence;

use Generated\Shared\Transfer\LoyaltyTransfer;

interface LoyaltyEntityManagerInterface
{
    /**

@param LoyaltyTransfer $loyaltyTransfer
@return LoyaltyTransfer
     */
    public function createLoyalty(LoyaltyTransfer $loyaltyTransfer):LoyaltyTransfer;
}
  • Create the persistence factory layer.

<?php

namespace Pyz\Zed\Loyalty\Persistence;

use Orm\Zed\Loyalty\Persistence\PyzLoyaltyQuery;
use Pyz\Zed\Loyalty\Persistence\Mapper\LoyaltyMapper;
use Spryker\Zed\Kernel\Persistence\AbstractPersistenceFactory;

class LoyaltyPersistenceFactory extends AbstractPersistenceFactory
{
    /**

@return PyzLoyaltyQuery
     */
    public function createLoyaltyQuery():PyzLoyaltyQuery{
        return PyzLoyaltyQuery::create();
    }

    /**

@return LoyaltyMapper
     */
    public function createLoyaltyMapper(): LoyaltyMapper
    {
        return new LoyaltyMapper();
    }

}
  • Create a query container along with a query container interface to get the transactions.
<?php

namespace Pyz\Zed\Loyalty\Persistence;

use Orm\Zed\Loyalty\Persistence\PyzLoyaltyQuery;
use Propel\Runtime\ActiveQuery\Criteria;
use Spryker\Zed\Kernel\Persistence\AbstractQueryContainer;
use Spryker\Zed\Propel\Business\Exception\AmbiguousComparisonException;

/**

@method \Pyz\Zed\Loyalty\Persistence\LoyaltyPersistenceFactory getFactory()
 */
class LoyaltyQueryContainer extends AbstractQueryContainer implements LoyaltyQueryContainerInterface
{

    /**

@param int $id
@return PyzLoyaltyQuery
@throws AmbiguousComparisonException
     */
    public function queryLoyaltyById(int $id): PyzLoyaltyQuery
    {
        $query = $this->queryLoyalty();
        $query->filterByIdLoyalty($id);
        return $query;
    }

    /**

@param $siteId
@return PyzLoyaltyQuery
@throws AmbiguousComparisonException
     */
    public function queryLoyaltyBuSiteId($siteId): PyzLoyaltyQuery
    {
        $query = $this->queryLoyalty();
        $query->filterBySiteid($siteId, Criteria::EQUAL);
        return $query;
    }
    /**

@api
     * @inheritdoc
     */
    public function queryLoyalty(): PyzLoyaltyQuery
    {
        return $this->getFactory()->createLoyaltyQuery();
    }
}

<?php

namespace Pyz\Zed\Loyalty\Persistence;

use Orm\Zed\Loyalty\Persistence\PyzLoyaltyQuery;
use Spryker\Zed\Kernel\Persistence\QueryContainer\QueryContainerInterface;

interface LoyaltyQueryContainerInterface extends QueryContainerInterface
{
    /**

@param int $id
@return PyzLoyaltyQuery;
@api
     */
    public function queryLoyaltyById(int $id): PyzLoyaltyQuery;

    /**

@param $siteId
@return mixed
     */
    public function queryLoyaltyBuSiteId($siteId);

    /**

@return PyzLoyaltyQuery
     */
    public function queryLoyalty(): PyzLoyaltyQuery;
}
  • Create the Loyalty Repository interface and repository according to Spryker coding conventions. This enables data transaction activities such as select, fetch, filter, and paginating the data results.
<?php

namespace Pyz\Zed\Loyalty\Persistence;

use ArrayObject;
use Propel\Runtime\ActiveQuery\Criteria;
use Spryker\Shared\Kernel\Transfer\Exception\RequiredTransferPropertyException;
use Spryker\Zed\Propel\PropelFilterCriteria;
use Generated\Shared\Transfer\FilterTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;
use Generated\Shared\Transfer\PaginationTransfer;
use Orm\Zed\Loyalty\Persistence\Base\PyzLoyaltyQuery;
use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Spryker\Zed\Kernel\Persistence\AbstractRepository;
use Generated\Shared\Transfer\LoyaltyCollectionTransfer;
use Spryker\Zed\Propel\Business\Exception\AmbiguousComparisonException;

/**

@method \Pyz\Zed\Loyalty\Persistence\LoyaltyPersistenceFactory getFactory()
 */
class LoyaltyRepository extends AbstractRepository implements LoyaltyRepositoryInterface
{

    /**

@param LoyaltyCriteriaTransfer $loyaltyCriteriaTransfer
@return LoyaltyTransfer|null
@throws AmbiguousComparisonException
     */
    public function getLoyalty(LoyaltyCriteriaTransfer $loyaltyCriteriaTransfer): ?LoyaltyTransfer
    {
        $loyaltyQuery = $this->getFactory()->createLoyaltyQuery();
        if($loyaltyCriteriaTransfer->getIdLoyalty() !== NULL){
            $loyaltyQuery = $loyaltyQuery->filterByIdLoyalty($loyaltyCriteriaTransfer->getIdLoyalty());
        }elseif($loyaltyCriteriaTransfer->getSiteId() !== NULL){
            $loyaltyQuery = $loyaltyQuery->filterBySiteid($loyaltyCriteriaTransfer->getSiteId());
        }
        $loyaltyEntity = $loyaltyQuery->findOne();
        if(!$loyaltyEntity){
            return null;
        }
        $loyaltyTransfer = new LoyaltyTransfer();
        return $loyaltyTransfer->fromArray($loyaltyEntity->toArray(), true);
    }

    /**

@param PyzLoyaltyQuery $loyaltyQuery
@param FilterTransfer|null $filterTransfer
@return PyzLoyaltyQuery
     */
    protected function applyFilterToQuery(PyzLoyaltyQuery $loyaltyQuery, ?FilterTransfer $filterTransfer): PyzLoyaltyQuery
    {
        $criteria = new Criteria();
        if ($filterTransfer !== null) {
            $criteria = (new PropelFilterCriteria($filterTransfer))
                ->toCriteria();
        }

        $loyaltyQuery->mergeWith($criteria);

        return $loyaltyQuery;
    }
    /**
@param PyzLoyaltyQuery $loyaltyQuery
@param PaginationTransfer|null $paginationTransfer
@return PyzLoyaltyQuery
@throws RequiredTransferPropertyException
     */
    protected function applyPagination(PyzLoyaltyQuery $loyaltyQuery, ?PaginationTransfer $paginationTransfer = null): PyzLoyaltyQuery
    {
        if (!$paginationTransfer) {
            return $loyaltyQuery;
        }

        $page = $paginationTransfer
            ->requirePage()
            ->getPage();

        $maxPerPage = $paginationTransfer
            ->requireMaxPerPage()
            ->getMaxPerPage();

        $paginationModel = $loyaltyQuery->paginate($page, $maxPerPage);

        $paginationTransfer->setNbResults($paginationModel->getNbResults());
        $paginationTransfer->setFirstIndex($paginationModel->getFirstIndex());
        $paginationTransfer->setLastIndex($paginationModel->getLastIndex());
        $paginationTransfer->setFirstPage($paginationModel->getFirstPage());
        $paginationTransfer->setLastPage($paginationModel->getLastPage());
        $paginationTransfer->setNextPage($paginationModel->getNextPage());
        $paginationTransfer->setPreviousPage($paginationModel->getPreviousPage());

        /** @var \Orm\Zed\Loyalty\Persistence\PyzLoyaltyQuery $paginatedLoyaltyQuery */
        $paginatedLoyaltyQuery = $paginationModel->getQuery();

        return $paginatedLoyaltyQuery;
    }
    /**

@param LoyaltyCollectionTransfer $loyaltyListTransfer
@param array $loyalties
@return void
     */
    public function hydrateLoyaltyListWithLoyalty(LoyaltyCollectionTransfer $loyaltyListTransfer, array $loyalties): void
    {
        $loyaltyCollection = new ArrayObject();

        foreach ($loyalties as $loyalty) {
            $loyaltyCollection->append(
                $this->getFactory()
                    ->createLoyaltyMapper()
                    ->mapLoyaltyEntityToLoyalty($loyalty),
            );
        }

        $loyaltyListTransfer->setLoyalties($loyaltyCollection);
    }

    /**

@param LoyaltyCriteriaTransfer $loyaltyCriteriaTransfer
@return LoyaltyTransfer|null
@throws AmbiguousComparisonException
     */
    public function filterLoyaltyByCriteria(LoyaltyCriteriaTransfer $loyaltyCriteriaTransfer): ?LoyaltyTransfer
    {
        $loyaltyQuery = $this->getFactory()->createLoyaltyQuery();

        if ($loyaltyCriteriaTransfer->getIdLoyalty()) {
            $loyaltyQuery->filterByIdLoyalty($loyaltyCriteriaTransfer->getIdLoyalty());
        }

        $loyaltyEntity = $loyaltyQuery->findOne();

        if ($loyaltyEntity === null) {
            return null;
        }

        return $this->getFactory()
            ->createLoyaltyMapper()
            ->mapLoyaltyEntityToLoyalty($loyaltyEntity->toArray());
    }
}

<?php

namespace Pyz\Zed\Loyalty\Persistence;

use Generated\Shared\Transfer\LoyaltyCollectionTransfer;
use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;

interface LoyaltyRepositoryInterface
{
    /**

@param LoyaltyCriteriaTransfer $loyaltyCriteriaTransfer
@return LoyaltyTransfer|null
     */
    public function getLoyalty(LoyaltyCriteriaTransfer $loyaltyCriteriaTransfer): ?LoyaltyTransfer;

    /**

@param LoyaltyCriteriaTransfer $loyaltyCriteriaTransfer
@return LoyaltyTransfer|null
     */
    public function filterLoyaltyByCriteria(LoyaltyCriteriaTransfer $loyaltyCriteriaTransfer): ?LoyaltyTransfer;
}
  • Create a data mapper and data mapper interface to expose the data mapper to other layers in persistence layer under the Mapper folder.
<?php

namespace Pyz\Zed\Loyalty\Persistence\Mapper;

use Generated\Shared\Transfer\LoyaltyTransfer;

interface LoyaltyMapperInterface
{
    /**

@param array $loyalty
@return LoyaltyTransfer
     */
    public function mapLoyaltyEntityToLoyalty(array $loyalty):LoyaltyTransfer;

}

<?php

namespace Pyz\Zed\Loyalty\Persistence\Mapper;

use Generated\Shared\Transfer\LoyaltyTransfer;

class LoyaltyMapper implements LoyaltyMapperInterface
{

    /**

@param array $loyalty
@return LoyaltyTransfer
     */
    public function mapLoyaltyEntityToLoyalty(array $loyalty): LoyaltyTransfer
    {
        return (new LoyaltyTransfer())
            ->fromArray(
                $loyalty,
                true,
            );
    }
}
  • Create the Presentation Layer to view the response. Every controller in the Communication Layer will go with the respective presentation layer to interact with its associated actions. So, each action has the same naming conventions, such as AddController has Presentation layer as Add/Index.twig and the same with other actions too.

Add/Index.twig

{% extends '@Gui/Layout/layout.twig' %}
{% set widget_title = 'Add Loyalty' %}

{% block head_title %}
    {{ widget_title | trans }}
{% endblock %}
{% block section_title %}
    {{ widget_title | trans }}
{% endblock %}

{% block action %}
    {{ backActionButton('/loyalty', 'Back to Loyalty' | trans) }}
{% endblock %}
{% block content %}
    {% embed '@Gui/Partials/widget.twig' %}
        {% block widget_content %}

            {{ form_start(form) }}
            {{ form_widget(form) }}

            <input type="submit" class="btn btn-primary safe-submit" value="{{ 'Save' | trans }}"/>
            {{ form_end(form) }}
        {% endblock %}
    {% endembed %}
{% endblock %}

Edit/index.twig

{% extends '@Gui/Layout/layout.twig' %}
{% set widget_title = 'Add Loyalty' %}

{% block head_title %}
    {{ widget_title | trans }}
{% endblock %}
{% block section_title %}
    {{ widget_title | trans }}
{% endblock %}

{% block action %}
    {{ backActionButton('/loyalty', 'Back to Loyalty' | trans) }}
{% endblock %}
{% block content %}
    {% embed '@Gui/Partials/widget.twig' %}
        {% block widget_content %}

            {{ form_start(form) }}
            {{ form_widget(form) }}

            <input type="submit" class="btn btn-primary safe-submit" value="{{ 'Save' | trans }}"/>
            {{ form_end(form) }}

        {% endblock %}

    {% endembed %}
{% endblock %}

Index/Index.twig

{% extends '@Gui/Layout/layout.twig' %}

{% set widget_title = 'Loyalty' %}

{% block head_title widget_title | trans %}

{% block section_title widget_title | trans %}

{% block action %}
    {{ createActionButton('/loyalty/add', 'Add Loyalty' | trans) }}
{% endblock %}

{% block content %}

    {% embed '@Gui/Partials/widget.twig' %}

        {% block widget_content %}

            {{ loyaltyTable | raw }}

        {% endblock %}

    {% endembed %}

{% endblock %}
  • Create a Form in the Communication layer to interact with the Business Layer to perform the Loyalty Configuration. For this create Data Provider under the Communication/Form layer.
<?php

namespace Pyz\Zed\Loyalty\Communication\Form\DataProvider;

class LoyaltyUpdateFormDataProvider extends  LoyaltyFormDataProvider
{
    /**

@param $idLoyalty null
@return array
     */
    public function getData($idLoyalty = null): array
    {
        if ($idLoyalty === null) {
            return parent::getData();
        }

        $loyaltyEntity = $this
            ->loyaltyQueryContainer
            ->queryLoyaltyById($idLoyalty)
            ->findOne();

        if ($loyaltyEntity === null) {
            return parent::getData();
        }

        return $loyaltyEntity->toArray();
    }
}

<?php

namespace Pyz\Zed\Loyalty\Communication\Form\DataProvider;

use Pyz\Zed\Loyalty\Persistence\LoyaltyQueryContainerInterface;

class LoyaltyFormDataProvider
{
    /**

@var LoyaltyQueryContainerInterface
     */
    protected $loyaltyQueryContainer;

    /**

@param LoyaltyQueryContainerInterface $loyaltyQueryContainer
     */
    public function __construct($loyaltyQueryContainer){
        $this->loyaltyQueryContainer = $loyaltyQueryContainer;
    }

    /**

@return array
     */
    public function getData(): array
    {
        return [];
    }
}
  • Create the Form class to make the form.
<?php

namespace Pyz\Zed\Loyalty\Communication\Form;

use Spryker\Zed\Kernel\Communication\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class LoyaltyForm extends AbstractType
{
    public const FIELD_SITE_ID = 'site_id';
    public const FIELD_SITE_SUB = 'site_sub';
    public const FIELD_SECRET_KEY = 'secret_key';

    public const FIELD_SITE_URL = 'site_url';

    public const LABEL_SITE_ID = 'Site ID';
    public const LABEL_SITE_SUB = 'Site Sub';
    public const LABEL_SECRET_KEY = 'Site Secret Key';

    public const LABEL_SITE_URL = 'Site URL';

    public function getBlockPrefix()
    {
        return 'loyalty';
    }

    /**

@param FormBuilderInterface $builder
@param array $options
@return LoyaltyForm
     */
    public function buildForm(FormBuilderInterface $builder, array $options): static
    {
        $builder->add(static::FIELD_SITE_ID, TextType::class, [
            'label' => static::LABEL_SITE_ID
        ]);

        $builder->add(static::FIELD_SECRET_KEY, TextType::class, [
            'label' => static::LABEL_SECRET_KEY
        ]);

        $builder->add(static::FIELD_SITE_SUB, TextType::class, [
            'label' => static::LABEL_SITE_SUB
        ]);

        $builder->add(static::FIELD_SITE_URL, TextType::class, [
            'label' => static::LABEL_SITE_URL
        ]);

        return $this;
    }

}
  • Create the Form Update class which extends the Main Form and Instantiate with previous data.
<?php
namespace Pyz\Zed\Loyalty\Communication\Form;
class LoyaltyUpdateForm extends LoyaltyForm
{
}
  • Call under the CommunicationFactory into the Communication Layer as below:
  • Create a table which provides the view for the record along with pagination.
<?php

namespace Pyz\Zed\Loyalty\Communication\Table;

use Orm\Zed\Loyalty\Persistence\Base\PyzLoyalty;
use Orm\Zed\Loyalty\Persistence\Map\PyzLoyaltyTableMap;
use Orm\Zed\Loyalty\Persistence\PyzLoyaltyQuery;
use Propel\Runtime\Collection\ObjectCollection;
use Spryker\Zed\Gui\Communication\Table\AbstractTable;
use Spryker\Zed\Gui\Communication\Table\TableConfiguration;

class LoyaltyTable extends AbstractTable
{
    public const ACTIONS = 'actions';
    public const COL_ID_LOYALTY = 'COL_ID_LOYALTY';
    public const COL_SITE_ID = 'COL_SITE_ID';
    public const COL_SITE_SUB = 'COL_SITE_SUB';
    public const COL_SECRET_KEY = 'COL_SECRET_KEY';

    public const COL_SITE_URL = 'COL_SITE_URL';
    /**

@var PyzLoyaltyQuery
     */
    protected $pyzLoyaltyQuery;

    /**

@param PyzLoyaltyQuery $pyzLoyaltyQuery
     */
    public function __construct($pyzLoyaltyQuery)
    {
        $this->pyzLoyaltyQuery = $pyzLoyaltyQuery;
    }
    /**
@param TableConfiguration $config
@return TableConfiguration
     */
    protected function configure(TableConfiguration $config): TableConfiguration
    {
        $config->setHeader([
            PyzLoyaltyTableMap::COL_ID_LOYALTY => 'Loyalty ID',
            PyzLoyaltyTableMap::COL_SITE_ID => 'Site ID',
            PyzLoyaltyTableMap::COL_SITE_SUB => 'Site Sub',
            PyzLoyaltyTableMap::COL_SECRET_KEY => 'Secret Key',
            PyzLoyaltyTableMap::COL_SITE_URL => 'Site URL',
            static::ACTIONS => static::ACTIONS,
        ]);
        $config->addRawColumn(static::ACTIONS);
        $config->setSortable([
            static::COL_ID_LOYALTY,
            static::COL_SITE_ID,
            static::COL_SITE_SUB,
            static::COL_SECRET_KEY,
            static::COL_SITE_URL,
        ]);
        $config->setUrl('table');
        $config->setSearchable([
            PyzLoyaltyTableMap::COL_ID_LOYALTY,
            PyzLoyaltyTableMap::COL_SITE_ID,
            PyzLoyaltyTableMap::COL_SITE_SUB,
            PyzLoyaltyTableMap::COL_SECRET_KEY,
            PyzLoyaltyTableMap::COL_SITE_URL,
        ]);
        return $config;
    }

    /**

@param TableConfiguration $config
@return array
     */
    protected function prepareData(TableConfiguration $config): array
    {
        $loyaltyCollection = $this->runQuery($this->pyzLoyaltyQuery, $config);
        $results = array();
        foreach ($loyaltyCollection as $resultItem) {
            $results[] = [
                PyzLoyaltyTableMap::COL_ID_LOYALTY => $resultItem[PyzLoyaltyTableMap::COL_ID_LOYALTY],
                PyzLoyaltyTableMap::COL_SITE_ID => $resultItem[PyzLoyaltyTableMap::COL_SITE_ID],
                PyzLoyaltyTableMap::COL_SITE_SUB => $resultItem[PyzLoyaltyTableMap::COL_SITE_SUB],
                PyzLoyaltyTableMap::COL_SECRET_KEY => $resultItem[PyzLoyaltyTableMap::COL_SECRET_KEY],
                PyzLoyaltyTableMap::COL_SITE_URL => $resultItem[PyzLoyaltyTableMap::COL_SITE_URL],
                static::ACTIONS => $this->buildLinks($resultItem)
            ];
        }
        return $results;
    }
    /**

@param array|null $loyalty
@return string
     */
    protected function buildLinks(array $loyalty = null): string
    {
        if ($loyalty === null) {
            return '';
        }

        $buttons = [];
        $buttons[] = $this->generateEditButton('/loyalty/edit?id-loyalty=' . $loyalty[PyzLoyaltyTableMap::COL_ID_LOYALTY], 'Edit');

        return implode(' ', $buttons);
    }
}
  • After creating and setting up all the Backend Layer Loyalty code, the Backend layer loyalty Admin Setup Configuration will look like this.
  • Now we can create only one data but we can update many.
  • We only add one configuration so it will generate the error or warning upon trying to add another configuration data once you created one already.

Creating the Client Layer

The Client Layer exchanges backend data with StoreFront.

  • Create Loyalty Folder in src\Pyz\Client.
  • Create LoyaltyClientInterface class to expose to the Yves layer.
<?php

namespace Pyz\Client\Loyalty;

use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyResponseTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;

interface LoyaltyClientInterface
{
    public function getLoyalty(LoyaltyCriteriaTransfer $loyaltyCriteria):LoyaltyResponseTransfer;
    public function createLoyalty(LoyaltyTransfer $loyaltyTransfer):LoyaltyTransfer;
}
  • Create the Loyalty Client.
<?php

namespace Pyz\Client\Loyalty;

use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyResponseTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;
use Spryker\Client\Kernel\AbstractClient;
use Spryker\Client\Kernel\Exception\Container\ContainerKeyNotFoundException;

/**
@method \Pyz\Client\Loyalty\LoyaltyFactory getFactory()
 */
class LoyaltyClient extends AbstractClient implements LoyaltyClientInterface
{
    /**
@throws ContainerKeyNotFoundException
     */
    public function getLoyalty(LoyaltyCriteriaTransfer $loyaltyCriteria): LoyaltyResponseTransfer
    {
        return $this->getFactory()->createLoyaltyStub()->getLoyalty($loyaltyCriteria);
    }
    /**
@throws ContainerKeyNotFoundException
     */
    public function createLoyalty(LoyaltyTransfer $loyaltyTransfer): LoyaltyTransfer
    {
        return $this->getFactory()->createLoyaltyStub()->createLoyalty($loyaltyTransfer);
    }
}
  • Create the Loyalty Client Dependency Provider to inject the dependencies from the Zed Layer to the Yves Layer.
<?php

namespace Pyz\Client\Loyalty;

use _PHPStan_582a9cb8b\Composer\CaBundle\CaBundle;
use Spryker\Client\Kernel\Container;
use Spryker\Service\Container\Exception\FrozenServiceException;

class LoyaltyDependencyProvider extends \Spryker\Client\Kernel\AbstractDependencyProvider
{
    public const CLIENT_ZED_REQUEST = 'CLIENT_ZED_REQUEST';
    /**
@param Container $container
@return Container
@throws FrozenServiceException
     */
    public function provideServiceLayerDependencies(Container $container): Container
    {
        /** @var Container $container */
        $container = $this->addZedRequestClient($container);
        return $container;
    }
    /**
@throws FrozenServiceException
     */
    protected function addZedRequestClient(Container $container): Container
    {
        $container->set(static::CLIENT_ZED_REQUEST, function (Container $container) {
            return $container->getLocator()->zedRequest()->client();
        });
        return $container;
    }
}
  • Create the Loyalty Client Factory class which exposes the client layer to Zed and Yves layers.
<?php

namespace Pyz\Client\Loyalty;

use Pyz\Client\Loyalty\Stub\LoyaltyStub;
use Spryker\Client\Kernel\AbstractFactory;
use Spryker\Client\Kernel\Exception\Container\ContainerKeyNotFoundException;
use Spryker\Client\ZedRequest\ZedRequestClientInterface;

class LoyaltyFactory extends AbstractFactory
{
    /**
@throws ContainerKeyNotFoundException
     */
    public function createLoyaltyStub():LoyaltyStub{
        return new LoyaltyStub(
            $this->getZedRequestClient()
        );
    }
    /**
@throws ContainerKeyNotFoundException
     */
    public function getZedRequestClient():ZedRequestClientInterface{
        return $this->getProvidedDependency(LoyaltyDependencyProvider::CLIENT_ZED_REQUEST);
    }
}
  • Create the Client/Stub Layer to instantiate the actual call to Zed.
<?php

namespace Pyz\Client\Loyalty\Stub;

use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyResponseTransfer;
use Generated\Shared\Transfer\LoyaltyTransfer;
use Spryker\Client\ZedRequest\ZedRequestClientInterface;

class LoyaltyStub
{
    /**
@var \Spryker\Client\ZedRequest\ZedRequestClientInterface;
     */
    protected $zedRequestClient;
    /**
@param ZedRequestClientInterface $zedRequestClient
     */
    public function __construct(ZedRequestClientInterface $zedRequestClient){
        $this->zedRequestClient = $zedRequestClient;
    }
    public function getLoyalty(LoyaltyCriteriaTransfer $loyaltyCriteria):LoyaltyResponseTransfer{
        /** @var LoyaltyResponseTransfer $loyaltyResponseTransfer */
        $loyaltyResponseTransfer = $this->zedRequestClient->call('/loyalty/gateway/get-loyalty', $loyaltyCriteria);
        return $loyaltyResponseTransfer;
    }
    public function createLoyalty(LoyaltyTransfer $loyaltyTransfer):LoyaltyTransfer{
        /**
@var LoyaltyTransfer $loyaltyTransfer
         */
        $loyaltyTransfer = $this->zedRequestClient->call('/loyalty/gateway/create-loyalty', $loyaltyTransfer);
        return $loyaltyTransfer;
    }
}
  • Create the loyalty module in the Yves layer to expose the loyalty module for public view.
  • Create the Yves Loyalty Dependency provider to inject the dependencies in the Yves layer

<?php

namespace Pyz\Yves\Loyalty;

use Spryker\Service\Container\Exception\FrozenServiceException;
use Spryker\Yves\Kernel\AbstractBundleDependencyProvider;
use Spryker\Yves\Kernel\Container;

class LoyaltyDependencyProvider extends AbstractBundleDependencyProvider
{
    public const CLIENT_CUSTOMER = 'CLIENT_CUSTOMER';
    public const CLIENT_LOYALTY = 'CLIENT_LOYALTY';

    public const CLIENT_SALES_RETURN = 'CLIENT_SALES_RETURN';

    /**

@throws FrozenServiceException
     */
    public function provideDependencies(Container $container): Container
    {
          $container = $this->addCustomerLoyaltyClient($container);
          $container = $this->addLoyaltyClient($container);
          $container = $this->addSalesReturnClient($container);
          return $container;
    }

    /**

@throws FrozenServiceException
     */
    protected function addCustomerLoyaltyClient(Container $container): Container
    {
        $container->set(static::CLIENT_CUSTOMER, function (Container $container) {
            return $container->getLocator()->customer()->client();
        });
        return $container;
    }

    /**

@throws FrozenServiceException
     */
    protected function addLoyaltyClient(Container $container): Container
    {
        $container->set(static::CLIENT_LOYALTY, function (Container $container) {
            return $container->getLocator()->loyalty()->client();
        });
        return $container;
    }

    /**

@throws FrozenServiceException
     */
    protected function addSalesReturnClient(Container $container):Container{
        $container->set(static::CLIENT_SALES_RETURN, function(Container $container){
            return $container->getLocator()->salesReturn()->client();
        });
        return $container;
    }
}
  • Create the Yves loyalty configuration class which calls the AC (Annex Cloud) APIs.
<?php

namespace Pyz\Yves\Loyalty;

use Firebase\JWT\JWT;
use Generated\Shared\Transfer\LoyaltyTransfer;
use Spryker\Yves\Kernel\AbstractBundleConfig;

class LoyaltyConfig extends AbstractBundleConfig
{
    /**

@var string
     */
    public $AC_SITE_ID = '59560380';
    /**

@var string
     */
    public $AC_SITE_SUB = 'Product Demo';
    /**

@var string
     */
    public $AC_SITE_KEY = 'TCvfOY2KN5JsRv2F8FSZ';
    /**

@var string
     */
    public $AC_SITE_URL = 'https://s15.socialannex.net/api/3.0/';

    public function setLoyalty(LoyaltyTransfer $loyalty){
        $this->AC_SITE_ID = $loyalty->getSiteId();
        $this->AC_SITE_SUB = $loyalty->getSiteSub();
        $this->AC_SITE_KEY = $loyalty->getSecretKey();
        $this->AC_SITE_URL = $loyalty->getSiteUrl();
    }
    public function getJwtToken($payload): string
    {
        $payload = base64_encode(json_encode($payload));
        $hmac = base64_encode(hash_hmac('sha256', $payload, $this->AC_SITE_KEY, true));
        $token = array(
            "sub" => $this->AC_SITE_SUB,
            "exp" => time() + 36000,
            "site_id" => $this->AC_SITE_ID,
            "hmac" => $hmac
        );
        return JWT::encode($token, $this->AC_SITE_KEY, 'HS256');
    }

    /**

@description make a curl request
@param $url string
@param $method  string
@param $data array
@param $jwt string
@return string|bool
     */
    public function makeRequest(string $url, string $method, string $jwt, array $data = []): string|bool
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                "Content-Type:  application/json",
                "Authorization: Bearer $jwt",
                "X-AnnexCloud-Site: $this->AC_SITE_ID"
            )
        );

        if (null != $data || is_array($data)) {
            $dataJson = json_encode($data);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $dataJson);
        }

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        return curl_exec($ch);
    }
}
  • Create the Yves loyalty factory.
<?php

namespace Pyz\Yves\Loyalty;

use Pyz\Client\Loyalty\LoyaltyClientInterface;
use Pyz\Yves\Loyalty\Handler\LoyaltyReturnFormHandler;
use Pyz\Yves\Loyalty\Handler\LoyaltyReturnFormHandlerInterface;
use Spryker\Client\Customer\CustomerClientInterface;
use Spryker\Client\SalesReturn\SalesReturnClient;
use Spryker\Client\SalesReturn\SalesReturnClientInterface;
use Spryker\Yves\Kernel\AbstractFactory;
use Spryker\Yves\Kernel\Exception\Container\ContainerKeyNotFoundException;

/**

@method \Pyz\Yves\Loyalty\LoyaltyConfig getConfig()
 */
class LoyaltyFactory extends AbstractFactory
{
    /**

@throws ContainerKeyNotFoundException
     */
    public function getCustomerLoyaltyClient():CustomerClientInterface{
        return $this->getProvidedDependency(LoyaltyDependencyProvider::CLIENT_CUSTOMER);
    }

    /**

@throws ContainerKeyNotFoundException
     */
    public function getLoyaltyClient():LoyaltyClientInterface{
        return $this->getProvidedDependency(LoyaltyDependencyProvider::CLIENT_LOYALTY);
    }

    public function createReturnFormHandler():LoyaltyReturnFormHandlerInterface{
        return new LoyaltyReturnFormHandler();
    }

    public function getSalesReturnClient():SalesReturnClientInterface{
        return $this->getProvidedDependency(LoyaltyDependencyProvider::CLIENT_SALES_RETURN);
    }
}
  • Create the controller to perform the actions.
<?php

namespace Pyz\Yves\Loyalty\Controller;

use Firebase\JWT\JWT;
use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Spryker\Yves\Kernel\Exception\Container\ContainerKeyNotFoundException;
use Spryker\Yves\Kernel\Controller\AbstractController;
use Spryker\Yves\Kernel\View\View;
use Symfony\Component\HttpFoundation\Request;

/**

@method \Pyz\Yves\Loyalty\LoyaltyFactory getFactory()
@method \Pyz\Yves\Loyalty\LoyaltyConfig getConfig()
 */
class IndexController extends AbstractController
{
    /**

@throws ContainerKeyNotFoundException
     */
    public function indexAction(Request $request): View
    {
        $data = ['token' => '', 'email' => '', 'siteId' => ''];
        $loyaltyCriteria = new LoyaltyCriteriaTransfer();
        $loyaltyResponse = $this->getFactory()->getLoyaltyClient()->getLoyalty($loyaltyCriteria);
        if ($loyaltyResponse->getLoyalty()->getIdLoyalty()) {
            $this->getFactory()->getConfig()->setLoyalty($loyaltyResponse->getLoyalty());
            $siteId = $loyaltyResponse->getLoyalty()->getSiteId();
            $session = $request->getSession();
            $customerData = $session->get('customer data');
            if (isset($customerData['email'])) {
                $userEmail = $customerData['email'];
                $userPayload = $userEmail;
                $token = $this->getFactory()->getConfig()->getJwtToken($userPayload);
                $data = ['token' => $token, 'email' => $userEmail, 'siteId' => $siteId];
            }
        }
        return $this->view($data, [], '@loyalty/views/index/index.twig');
    }
}
  • Create views to view the actions and responses under src/Pyz/Yves/Loyalty/Theme/default/views/index.
{% extends template('page-layout-main') %}
{% define data = {
    token: _view.token ?? '',
    email: _view.email ?? '',
    siteId: _view.siteId ?? ''
} %}
{% block content %}
    <div id='socialannex_dashboard'></div>
{% endblock %}
{% block footerScripts %}
    <script src="https://code.jquery.com/jquery-3.7.0.slim.min.js" integrity="sha256-tG5mcZUtJsZvyKAxYLVXrmjKBVLd6VpVccqz/r4ypFE=" crossorigin="anonymous"></script>
    <script type="text/javascript">
       let sa_emailid = "{{ data.email }}";
       let siteid = {{ data.siteId }};
       let saPage = 5;
       let token = "{{data.token}}";
       let sa_uni = [];
       sa_uni.push(['sa_pg', saPage]);
       (function () {
          function saAsyncLoad() {
             let sa = document.createElement('script');
             sa.type = 'text/javascript';
             sa.async = true;
             sa.src = '//cdn.socialannex.com/partner/' + siteid + '/universal.js';
             let sax = document.getElementsByTagName('script')[0];
             sax.parentNode?.insertBefore(sa,sax);
          }

          if (window.attachEvent) {
             window.attachEvent('onload', saAsyncLoad);
          } else {
             window.addEventListener('load', saAsyncLoad, false);
          }

       })();
    </script>
{% endblock %}
  • Create the Yves route which generates the URL to interact with the public. For this we need to create Yves/Loyalty/Plugin/Provider/LoyaltyRouteProviderPlugin.
<?php

namespace Pyz\Yves\Loyalty\Plugin\Provider;

use Spryker\Yves\Router\Plugin\RouteProvider\AbstractRouteProviderPlugin;
use Spryker\Yves\Router\Route\RouteCollection;

class LoyaltyRouteProviderPlugin extends AbstractRouteProviderPlugin
{
    protected const ROUTE_LOYALTY_INDEX = 'loyalty/index';
    /**

@inheritDoc
     */
    public function addRoutes(RouteCollection $routeCollection): RouteCollection
    {
        // TODO: Implement addRoutes() method.
        $route = $this->buildRoute('loyalty/index', 'Loyalty','Index', 'indexAction');
        $routeCollection->add(static::ROUTE_LOYALTY_INDEX, $route);
        return $routeCollection;
    }
}
  • Add the LoyaltyRouteProviderPlugin instance to the Yves parent route provider to get it to call from the Store Front.
protected function getRouteProvider(): array
{
    return [
        ----
            new LoyaltyRouteProviderPlugin(),
        ----
    ]
}

Loyalty Platform Registration within Spryker

To show the loyalty dashboard within the Spryker, follow these below steps:

Steps:

  • Go to the Customer module for example src\Pyz\Zed\Customer
  • Open the CustomerDependencyProvider file in the editor window
  • Locate the getPostCustomerRegistrationPlugins() function.
  • Add the plugin PostCustomerRegistrationPlugin into the return array as below:

The plugin code as below:

<?php
namespace Pyz\Zed\Customer\Communication\Plugin;
use Firebase\JWT\JWT;
use Generated\Shared\Transfer\CustomerCriteriaTransfer;
use Generated\Shared\Transfer\CustomerTransfer;
use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Pyz\Zed\Customer\CustomerConfig;
use Spryker\Zed\CustomerExtension\Dependency\Plugin\PostCustomerRegistrationPluginInterface;
use Spryker\Zed\Kernel\Communication\AbstractCommunicationFactory;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
use Spryker\Zed\Kernel\Exception\Container\ContainerKeyNotFoundException;

/**

@method \Pyz\Zed\Customer\Business\CustomerFacade getFacade()
@method \Pyz\Zed\Customer\Communication\CustomerCommunicationFactory getFactory()
 */
class PostCustomerRegistrationPlugin extends AbstractPlugin implements PostCustomerRegistrationPluginInterface
{
    public function execute(CustomerTransfer $customerTransfer): void
    {
        $customerCriteriaTransfer = new CustomerCriteriaTransfer();
        $customerCriteriaTransfer->setCustomerReference($customerTransfer->getCustomerReference());
        $this->enrollLoyaltyCustomerInAC($customerTransfer);
        $this->getFacade()->executeLoyaltyCall($customerCriteriaTransfer);
    }

    /**

@throws ContainerKeyNotFoundException
     */
    protected function enrollLoyaltyCustomerInAC(CustomerTransfer $customerTransfer): void
    {
        // Get global config constant variables
        $config = new CustomerConfig();
        $loyaltyCriteria = new LoyaltyCriteriaTransfer();
        $loyaltyResponse = $this->getFactory()->getLoyaltyFacade()->getLoyalty($loyaltyCriteria);
        $config->AC_SITE_KEY = $loyaltyResponse->getLoyalty()->getSecretKey();
        $config->AC_SITE_ID = $loyaltyResponse->getLoyalty()->getSiteId();
        $config->AC_SITE_SUB = $loyaltyResponse->getLoyalty()->getSiteSub();
        $config->AC_SITE_URL = $loyaltyResponse->getLoyalty()->getSiteUrl();
        // loyalty code is starting
        $optInStatus = 'YES';
        $payload = [
            'id' => $customerTransfer->getEmail(),
            'firstName' => $customerTransfer->getFirstName(),
            'lastName' => $customerTransfer->getLastName(),
            'email' => $customerTransfer->getEmail(),
            "optInStatus" => $optInStatus,
            "status" => 'ACTIVE',
            "source" => 'Register'
        ];
        $isSuccess = false;
        $token = $config->getJwtToken($payload);
        $request = $config->makeRequest($token, 'users', 'POST', $payload);
if (!empty($request) && array_key_exists('siteId', json_decode($request, true))) {
        $isSuccess = true;
}
        // if loyalty user assignment is successful and opt out is true
        if ($isSuccess) {
            // Assigning point on successful user creation
            $pointPayload = [
                "id" => $customerTransfer->getEmail(),
                "actionId" => "101",
                "credit" => "50",
                "activity" => "CREDIT"
            ];

$pointToken = $config->getJwtToken($pointPayload);
$pointRequest = $config->makeRequest($pointToken, 'points', 'POST', $pointPayload);
        }
    }

}

Upon new customer registration this plugin will register the customer to the loyalty platform.

Loyalty Dashboard Implementation:

The loyalty dashboard module displays the Annex Cloud Loyalty Dashboard with all the activities for the current user registered with the Annex Cloud Loyalty Platform.

Steps:

  • Under the loyalty module template file, implement these codes:
{% extends template('page-layout-main') %}
{% define data = {
    token: _view.token ?? '',
    email: _view.email ?? '',
    siteId: _view.siteId ?? ''
} %}
{% block content %}
    <div id='socialannex_dashboard'></div>
{% endblock %}
{% block footerScripts %}
    <script src="https://code.jquery.com/jquery-3.7.0.slim.min.js" integrity="sha256-tG5mcZUtJsZvyKAxYLVXrmjKBVLd6VpVccqz/r4ypFE=" crossorigin="anonymous"></script>
    <script type="text/javascript">
       let sa_emailid = "{{ data.email }}";
       let siteid = {{ data.siteId }};
       let saPage = 5;
       let token = "{{data.token}}";
       let sa_uni = [];
       sa_uni.push(['sa_pg', saPage]);
       (function () {
          function saAsyncLoad() {
             let sa = document.createElement('script');
             sa.type = 'text/javascript';
             sa.async = true;
             sa.src = '//cdn.socialannex.com/partner/' + siteid + '/universal.js';
             let sax = document.getElementsByTagName('script')[0];
             sax.parentNode?.insertBefore(sa,sax);
          }
          if (window.attachEvent) {
             window.attachEvent('onload', saAsyncLoad);
          } else {
             window.addEventListener('load', saAsyncLoad, false);
          }
       })();
    </script>
{% endblock %}
  • Create the action which passes the view data needed to parse the loyalty dashboard call from AnnexCloud.
<?php

namespace Pyz\Yves\Loyalty\Controller;

use Firebase\JWT\JWT;
use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Spryker\Yves\Kernel\Exception\Container\ContainerKeyNotFoundException;
use Spryker\Yves\Kernel\Controller\AbstractController;
use Spryker\Yves\Kernel\View\View;
use Symfony\Component\HttpFoundation\Request;

/**

@method \Pyz\Yves\Loyalty\LoyaltyFactory getFactory()

@method \Pyz\Yves\Loyalty\LoyaltyConfig getConfig()
 */
class IndexController extends AbstractController
{
    /**

@throws ContainerKeyNotFoundException
     */
    public function indexAction(Request $request): View
    {
        $data = ['token' => '', 'email' => '', 'siteId' => ''];
        $loyaltyCriteria = new LoyaltyCriteriaTransfer();
        $loyaltyResponse = $this->getFactory()->getLoyaltyClient()->getLoyalty($loyaltyCriteria);
        if ($loyaltyResponse->getLoyalty()->getIdLoyalty()) {
            $this->getFactory()->getConfig()->setLoyalty($loyaltyResponse->getLoyalty());
            $siteId = $loyaltyResponse->getLoyalty()->getSiteId();
            $session = $request->getSession();
            $customerData = $session->get('customer data');
            if (isset($customerData['email'])) {
                $userEmail = $customerData['email'];
                $userPayload = $userEmail;
                $token = $this->getFactory()->getConfig()->getJwtToken($userPayload);
                $data = ['token' => $token, 'email' => $userEmail, 'siteId' => $siteId];
            }
        }
        return $this->view($data, [], '@loyalty/views/index/index.twig');
    }
}
  • Create a plugin router to expose the functionality to callable. Follow the below steps:
    • Go to src/Pyz/Yves/Loyalty.
    • Create Plugin/Provider folder.
<?php

namespace Pyz\Yves\Loyalty\Plugin\Provider;

use Spryker\Yves\Router\Plugin\RouteProvider\AbstractRouteProviderPlugin;
use Spryker\Yves\Router\Route\RouteCollection;

class LoyaltyRouteProviderPlugin extends AbstractRouteProviderPlugin
{
    protected const ROUTE_LOYALTY_INDEX = 'loyalty/index';
    /**

@inheritDoc
     */
    public function addRoutes(RouteCollection $routeCollection): RouteCollection
    {
        // TODO: Implement addRoutes() method.
        $route = $this->buildRoute('loyalty/index', 'Loyalty','Index', 'indexAction');
        $routeCollection->add(static::ROUTE_LOYALTY_INDEX, $route);
        return $routeCollection;
    }
}
  • Add it to the store-front router so that it will expose to the public
    • Locate src\Pyz\Yves\Router\RouterDependencyProvider.
    • Locate getRouteProvider and apply the protected function getRouteProvider(): array
{     
return [
new LoyaltyRouteProviderPlugin(),
]
}
  • Clear the cache : docker/sdk console cache:empty-all and sudo docker/sdk console cache:class-resolver:build command to clear the cache and resolve the plugin.

Loyalty Point Widget:

The Loyalty Point widget is used to show the points which anyone can earn on the product. It represents individual product points which can be incurred after purchasing the product within the Spryker platform.

Steps:

  • Implement the widget into the pages where we need to show loyalty points.
  • The LoyaltyPointWidget needs the below arguments:
    • Configuration | config.name: It is a unique name of the age.
    • Product Object:Must contain the Product ID, Product Price, and Product Name
    • Current User Session Object.
  • Register the widget to expose it to the public.
    • Go to src\Pyz\Yves\ShopApplication\ShopApplicationDependencyProvider
    • Use Pyz\Yves\LoyaltyPointWidget\Widget\LoyaltyPointWidget;

#Add the loyaltyPointWidget to getGlobalWidgets array
protected function getGlobalWidgets(): array
       {
return [
…..
LoyaltyPointWidget::class,
…..
]                   
}

Loyalty Reward Widget

The Loyalty Reward widget is used to perform the operation by choosing the reward type from the drop-down menu, which further applies as a discount within the Spryker cart and checkout process.

Steps:

The LoyaltyRewardWidget is prepared to get the All Available Current User Rewards list and populate within the select input.

It needs the arguments like cart object, cart Items object, quote validation, quote editable option status and the current logged in user object.

  • Register the widget to expose it to the public.
    • Go to src\Pyz\Yves\ShopApplication\ShopApplicationDependencyProvider
    • Use Pyz\Yves\LoyaltyRewardWidget\Widget\LoyaltyRewardWidget;
# Add the loyaltyRewardWidget to getGlobalWidgets array
protected function getGlobalWidgets(): array
{
return [
…..
LoyaltyRewardWidget::class,
…..
]                   
}

Loyalty Widget:

The Loyalty Widget is used to show overall loyalty points in context with the cart items and cart items price. A customer can earn upon a successful order within the Spryker: B2C checkout process.

The LoyaltyWidget takes the User Object and the CartItems Object to process the loyalty point details and respond with calculated loyalty points which can be incurred after a successful order.

Steps:

  • Register the widget to expose it to the public.
    • Go to src\Pyz\Yves\ShopApplication\ShopApplicationDependencyProvider
    • Use Pyz\Yves\LoyaltyWidget\Widget\LoyaltyWidget;
# Add the loyaltyWidget to getGlobalWidgets array
protected function getGlobalWidgets(): array
{
return [
…..
LoyaltyWidget::class,
…..
]                   
}

Return Reward Widget

The Return Reward Widget is an Annex Cloud loyalty reward return plugin which is implemented for Full Loyalty Points return to the current user. It works with OMS (Order Management Service) and thus it must inject with Pyz\Zed\Oms\OmsDependencyProvider: class as below:

The code injected into Order Management Service from backend (Zed) is shown in the code window below. OrderStateMachine triggers events such as Sales/Return. When Sales/Return events occur the sales/return state fires and thus our injected widget plugin SalesReturnCommandPlugin will fire too.

protected function extendCommandPlugins(Container $container): Container
{
    $container->extend(self::COMMAND_PLUGINS, function (CommandCollectionInterface $commandCollection) {
      $commandCollection->add(new SalesReturnCommandPlugin(), 'Return/StartReturn');
    }
}

Apply the Loyalty return code in our custom OMS widget plugin under Backend (Zed) section Pyz\Zed\Loyalty\Communication\Plugin\Oms\Command.


<?php

namespace Pyz\Zed\Loyalty\Communication\Plugin\Oms\Command;

use Generated\Shared\Transfer\CustomerCriteriaTransfer;
use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Orm\Zed\Sales\Persistence\SpySalesOrder;
use Spryker\Zed\Kernel\Communication\AbstractPlugin;
use Spryker\Zed\Kernel\Exception\Container\ContainerKeyNotFoundException;
use Spryker\Zed\Oms\Business\Util\ReadOnlyArrayObject;
use Spryker\Zed\Oms\Dependency\Plugin\Command\CommandByOrderInterface;
/**

@method \Pyz\Zed\Loyalty\Business\LoyaltyFacadeInterface getFacade()
@method \Pyz\Zed\Loyalty\LoyaltyConfig getConfig()
@method \Pyz\Zed\Loyalty\Communication\LoyaltyCommunicationFactory getFactory()

 */
class SalesReturnCommandPlugin extends AbstractPlugin implements CommandByOrderInterface
{

    /**

@param array $orderItems
@param SpySalesOrder $orderEntity
@param ReadOnlyArrayObject $data
@return array
@throws ContainerKeyNotFoundException

     */
    public function run(array $orderItems, SpySalesOrder $orderEntity, ReadOnlyArrayObject $data):array
    {
        $loyaltyCriteria = new LoyaltyCriteriaTransfer();
        $loyaltyResponse = $this->getFacade()->getLoyalty($loyaltyCriteria);
        $loyalty = $loyaltyResponse->getLoyalty();
        $orderReference = $orderEntity->getOrderReference();
        $orders = $this->getFacade()->getSalesOrder($orderEntity->getIdSalesOrder());
        $this->getConfig()->AC_SITE_ID = $loyalty->getSiteId();
        $this->getConfig()->AC_SECRET_KEY = $loyalty->getSecretKey();
        $this->getConfig()->AC_SITE_SUB = $loyalty->getSiteSub();
        $this->getConfig()->AC_SITE_URL = $loyalty->getSiteUrl();
        $payload = [
            'orderId' => $orderReference,
            'status' => 'return',
        ];

        $token = $this->getConfig()->getJwtToken($payload);
        $response = $this->getConfig()->makeRequest($token, 'orders/'. $orderReference, 'PATCH', $payload);
                return [];
    }
}

After Implementation, this code will hit the AC (Annex Cloud) sales return API with appropriate Order ID and will execute the order return and thus reward (Loyalty Points) will be returned with it.

Prerequisites: The Loyalty configuration must be setup properly before this plugin to implement in the OMS state machine. Otherwise it will produce errors.

RAF (Refer A Friend) -- Annex Cloud Loyalty Integration with Spryker B2C Commerce

The RAF module is a referral service provided by Annex Cloud.

RAF – Spryker Widget

The Spryker- RafWidget is used to inject the RAF AC (Annex Cloud) service into Spryker B2C Commerce. It helps the user use the AC (Annex Cloud) RAF service in the Spryker B2C environment.

Follow the below steps to include Spryker—RAF Widget into the Spryker B2C commerce:

Prerequisites: The Loyalty Config Module must be properly set up with appropriate data otherwise it will produce errors.

The RAF widget can be used as below:

It needs the User Transfer object to get current user details to produce the referral button on the product details page like as below:

The “REFER A FRIEND” button generates a new window.

Now we are able to share the link to friends as a referral service.

How to implement the RAF service to Spryker B2C commerce:

The Annex Cloud RAF service includes the RAF dashboard and RAF unlock page modules from Annex Cloud.

The below steps can create the RAF module/service int the Spryker environment:

Steps:

  1. Under the Loyalty Module Pyz\Yves\Loyalty\Controller, create the controller class as
<?php

namespace Pyz\Yves\Loyalty\Controller;

use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Pyz\Yves\Loyalty\LoyaltyFactory;
use Spryker\Yves\Kernel\Controller\AbstractController;
use Spryker\Yves\Kernel\Exception\Container\ContainerKeyNotFoundException;
use Spryker\Yves\Kernel\View\View;
use Symfony\Component\HttpFoundation\Request;

/**

@method LoyaltyFactory getFactory()
 */
class RafController extends AbstractController
{
    /**

@throws ContainerKeyNotFoundException

     */
    public function indexAction(Request $request):View{
        $data = $this->extracted($request);
        return $this->view($data, [], '@loyalty/views/raf/index.twig');
    }

    /**

@throws ContainerKeyNotFoundException

     */
    public function unlockAction(Request $request):View{
        $data = $this->extracted($request);
        return $this->view($data, [], '@loyalty/views/raf-unlock/index.twig');
    }

    /**

@param Request $request
@return array
@throws ContainerKeyNotFoundException

     */
    public function extracted(Request $request):array
    {
        $loyaltyCriteria = new LoyaltyCriteriaTransfer();
        $data = ['siteId' => "", 'token' => "", 'email' => "", 'firstName' => "", 'lastName' => ""];
        $loyaltyResponse = $this->getFactory()->getLoyaltyClient()->getLoyalty($loyaltyCriteria);
        if (null !== $loyaltyResponse->getLoyalty()) {
            $this->getFactory()->getConfig()->setLoyalty($loyaltyResponse->getLoyalty());
            $siteId = $loyaltyResponse->getLoyalty()->getSiteId();
            $session = $request->getSession();
            $customerData = $session->get('customer data');
            if (isset($customerData['email'])) {
                $userEmail = $customerData['email'];
                $userPayload = $userEmail;
                $token = $this->getFactory()->getConfig()->getJwtToken($userPayload);
                $data = [

'siteId' => $siteId,
'token' => $token,
'email' => $userEmail,
'firstName' => $customerData['firstName'],
'lastName' => $customerData['lastName']
];
 }else{
      $data = [

'siteId' => $siteId,
'token' => "",
'email' => "",
'firstName' => "",
'lastName' => ""
];
}
}
        return $data;
    }
}
  1. Create the views as below:
    1. Create view for RAF dashboard as: Yves/Loyalty/Theme/default/views/raf/index.twig
  2. Set up the script code and pass the data from controller action to views like below. This script can be found from the Annex Cloud ADR)from the RAF module.
{% extends template('page-layout-main') %}
{% define data = {
    token: _view.token ?? '',
    email: _view.email ?? '',
    siteId: _view.siteId ?? '',
    firstName: _view.firstName ?? '',
    lastName: _view.lastName ?? '',
} %}
{% block content %}
    <div id="sa_refer_friend"></div>
{% endblock %}
{% block footerScripts %}
    <script
        src="https://code.jquery.com/jquery-3.7.0.slim.min.js"
        integrity="sha256-tG5mcZUtJsZvyKAxYLVXrmjKBVLd6VpVccqz/r4ypFE="
        crossorigin="anonymous">
    </script>
    <script type="text/javascript">
        let sa_emailID = "{{ data.email }}";
        let sa_lastName = "{{ data.lastName }}";
        let sa_firstName = "{{ data.firstName }}";
        let siteid = "{{ data.siteId }}";
        let token = "{{data.token}}";
        var sa_uni = sa_uni || [];
        sa_uni.push(['sa_pg', '11']);
        (function () { function sa_async_load() {
            var sa = document.createElement('script');
            sa.type = 'text/javascript'; sa.async = true;
            sa.src = '//cdn.socialannex.com/partner/'+ parseInt(siteid) +'/universal.js';
            var sax = document.getElementsByTagName('script')[0];
            sax.parentNode.insertBefore(sa, sax);
        } if (window.attachEvent) {
            window.attachEvent('onload', sa_async_load);
        } else {
            window.addEventListener('load', sa_async_load, false);
        }
        })();
    </script>
{% endblock %}
  1. Setup the route to instantiate the RAF Dashboard in Spryker B2C commerce.
    1. Locate to: Yves/Loyalty/Plugin/Provider/LoyaltyRouteProviderPlugin.php
    2. Insert the below code:
protected const ROUTE_LOYALTY_RAF = 'loyalty/raf';

public function addRoutes(RouteCollection $routeCollection): RouteCollection
{
    $route = $this->buildRoute('loyalty/raf', 'Loyalty', 'raf', 'indexAction');
    $routeCollection->add(static::ROUTE_LOYALTY_RAF, $route);
    return $routeCollection;
}
  1. .Run the command docker/sdk console cache:empty-all docker/sdk console cache:class-resolver:build,

Note: This clears all the cache and bind the code changes.

  1. Add the required translations which will be used under the RAF Dashboard under the “data/import/common/common/glossary.csv”:

raf.dashboard,Raf Dashboard,en_US
              raf.dashboard,Raf Dashboard,de_DE
  1. Implement Front-End Navigation for the user to locate the RAF Dashboard.
    1. Locate to: “Yves/ShopUi/Theme/default/components/molecules/user-block/user-block.twig”.
    2. Add the RAF Dashboard route.
  2. After successful implementation of the above code the system produces the below window:

  3. Create the second module for RAF Unlock Page functionality into Spryker B2C commerce. The unlock action from RafController will help to produce the RAF Unlock coupon code page into Spryker B2C commerce.
  4. Create the twig file Yves/Loyalty/Theme/default/views/raf-unlock/index.twig.
  5. Paste the below codes:

{% extends template('page-layout-main') %}
{% define data = {
    token: _view.token ?? '',
    email: _view.email ?? '',
    siteId: _view.siteId ?? '',
    firstName: _view.firstName ?? '',
    lastName: _view.lastName ?? '',
} %}
{% block content %}
    <div id="socialannex1"></div>
{% endblock %}
{% block footerScripts %}
    <script src="https://code.jquery.com/jquery-3.7.0.slim.min.js"
            integrity="sha256-tG5mcZUtJsZvyKAxYLVXrmjKBVLd6VpVccqz/r4ypFE="
            crossorigin="anonymous"
    >
    </script>
    <script type="text/javascript">
        let sa_emailID = "{{ data.email }}";
        let sa_lastName = "{{ data.lastName }}";
        let sa_firstName = "{{ data.firstName }}";
        let siteid = "{{ data.siteId }}";
        let token = "{{data.token}}";
        var sa_uni = sa_uni || [];
        sa_uni.push(['sa_pg', '12']);
        (function () {
            function sa_async_load() {
                var sa = document.createElement('script');
                sa.type = 'text/javascript';
                sa.async = true;
                sa.src = '//cdn.socialannex.com/partner/' + parseInt(siteid) + '/universal.js';
                var sax = document.getElementsByTagName('script')[0];
                sax.parentNode.insertBefore(sa, sax);
            }
            if (window.attachEvent) {
                window.attachEvent('onload', sa_async_load);
            } else {
                window.addEventListener('load', sa_async_load, false);
            }
        })();
    </script>
{% endblock %}
  1. Add the route for RAF unlock: Yves/Loyalty/Plugin/Provider/LoyaltyRouteProviderPlugin.php
protected const ROUTE_LOYALTY_RAF_UNLOCK = 'loyalty/raf/unlock';
public function addRoutes(RouteCollection $routeCollection): RouteCollection
{    
    $route = $this->buildRoute('loyalty/raf/unlock', 'Loyalty', 'raf', 'unlockAction');
    $routeCollection->add(static::ROUTE_LOYALTY_RAF_UNLOCK, $route);
    return $routeCollection;
}
  1. After successful implementation, using the generated referral code will generate this page.

It will produce the Coupon code which can be further used under the Spryker:cart or checkout to get Discounts.

Sales Tracking Widget

The Annex Cloud sales tracking widget is used to track orders to confirm how the order has been produced under Spryker B2C commerce.

To implement the Sales Tracking Widget into Spryker B2C commerce, follow the steps below.

To implement Sales Tracking Widget within the code

  1. Create the widget class file Yves/Loyalty/Widget/LoyaltySalesTracking/SalesTrackingWidget.php
<?php

namespace Pyz\Yves\Loyalty\Widget\LoyaltySalesTracking;

use Generated\Shared\Transfer\LoyaltyCriteriaTransfer;
use Generated\Shared\Transfer\QuoteTransfer;
use Pyz\Yves\Loyalty\LoyaltyFactory;
use Spryker\Yves\Kernel\Widget\AbstractWidget;

/**

@method LoyaltyFactory getFactory()

 */
class SalesTrackingWidget extends AbstractWidget
{    public function __construct(QuoteTransfer $quoteTransfer = null)
    {
        $options = [];
        $loyaltyCriteria = new LoyaltyCriteriaTransfer();
        $loyaltyResponse = $this->getFactory()->getLoyaltyClient()->getLoyalty($loyaltyCriteria);
        if (null !== $loyaltyResponse->getLoyalty()) {
            $orderId = $quoteTransfer->getOrderReference();
            $orderGrandTotal = $quoteTransfer->getTotals()->getGrandTotal();
            $orderDiscount = 0;
            $customerName = $quoteTransfer->getCustomer()->getFirstName() . " " . $quoteTransfer->getCustomer()->getLastName();
            $customerEmail = $quoteTransfer->getCustomer()->getEmail();
            $couponCode = '';
            $excludedProduct = [];
            $i = 0;
            foreach ($quoteTransfer->getItems() as $item) {
                foreach ($item->getCalculatedDiscounts() as $discount) {
                    if (null !== $discount->getVoucherCode()) {
                        $couponCode = $discount->getVoucherCode();
                    }
                    $orderDiscount += $discount->getSumAmount();
                }
                $imageUrl = '';
                foreach ($item->getImages() as $image) {
                    $imageUrl = $image->getExternalUrlLarge();
                }
                $excludedProduct[$i] = [
                    'id' => $item->getIdProductAbstract(),
                    'product_name' => $item->getName(),
                    'sku' => $item->getSku(),
                    'price' => $item->getUnitPrice(),
                    'qty' => $item->getQuantity(),
                    'product_url' => rtrim($this->getUrl(), '/') . $item->getUrl(),
                    'product_image_url' => $imageUrl,
                ];
                $i++;
            }

            if ($customerEmail !== '') {
                $options = [
                    "site_id" => $loyaltyResponse->getLoyalty()->getSiteId(),
                    "order_id" => $orderId,
                    "sale_amount" => $orderGrandTotal,
                    "email_id" => $customerEmail,
                    "name" => $customerName,
                    "coupon" => !empty($couponCode) ? $couponCode : '',
                    "exclude_products" => $excludedProduct,
                    "order_discount" => $orderDiscount
                ];
            }
        }
        $this->addParameter('loyalty', base64_encode(json_encode($options)));
    }

    /**

@return string

     */
    public static function getName(): string
    {
        return 'SalesTrackingWidget';
    }

    /**

@return string

     */
    public static function getTemplate(): string
    {
        return '@Loyalty/views/sales-tracking/sales-tracking.twig';
    }

    /**

@return string

     */
    private function getUrl():string
    {
        // output: /myproject/index.php
        $currentPath = $_SERVER['PHP_SELF'];

        // output: Array ( [dirname] => /myproject [basename] => index.php [extension] => php [filename] => index )
        $pathInfo = pathinfo($currentPath);

        // output: localhost
        $hostName = $_SERVER['HTTP_HOST'];
        $protocol = strtolower(substr($_SERVER["SERVER_PROTOCOL"],0,5))=='https'?'https':'http';   

return $protocol.'://'.$hostName.$pathInfo['dirname'];
    }
}
  1. Create View File for Sales Tracking widget:
{% extends template('widget') %}
{% define data = {
    loyalty: _widget.loyalty ?? []
} %}

{% block body %}
    {% block footerScripts %}
        <script src="https://c1.socialannex.com/sale-track/?parameter={{ data.loyalty }}"></script>
    {% endblock %}
{% endblock body %}