hipay/hipay-wallet-cashout-mirakl-library

View on GitHub
src/Cashout/Initializer.php

Summary

Maintainability
C
1 day
Test Coverage
<?php

namespace HiPay\Wallet\Mirakl\Cashout;

use DateTime;
use Exception;
use HiPay\Wallet\Mirakl\Api\Factory;
use HiPay\Wallet\Mirakl\Cashout\Model\Operation\ManagerInterface as OperationManager;
use HiPay\Wallet\Mirakl\Cashout\Model\Operation\OperationInterface;
use HiPay\Wallet\Mirakl\Cashout\Model\Operation\Status;
use HiPay\Wallet\Mirakl\Cashout\Model\Transaction\ValidatorInterface;
use HiPay\Wallet\Mirakl\Common\AbstractApiProcessor;
use HiPay\Wallet\Mirakl\Exception\AlreadyCreatedOperationException;
use HiPay\Wallet\Mirakl\Exception\InvalidOperationException;
use HiPay\Wallet\Mirakl\Exception\ValidationFailedException;
use HiPay\Wallet\Mirakl\Service\Validation\ModelValidator;
use HiPay\Wallet\Mirakl\Vendor\Model\VendorManagerInterface as VendorManager;
use HiPay\Wallet\Mirakl\Notification\Model\LogOperationsManagerInterface as LogOperationsManager;
use HiPay\Wallet\Mirakl\Vendor\Model\VendorInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use HiPay\Wallet\Mirakl\Notification\FormatNotification;
use HiPay\Wallet\Mirakl\Api\Mirakl;

/**
 * Generate and save the operation to be executed by the processor.
 *
 * @author    HiPay <support.wallet@hipay.com>
 * @copyright 2017 HiPay
 */
class Initializer extends AbstractOperationProcessor
{

    /** @var VendorInterface */
    protected $technicalAccount;

    /** @var  ValidatorInterface */
    protected $transactionValidator;

    /**
     * @var FormatNotification class
     */
    protected $formatNotification;

    protected $operationsLogs;

    protected $adjustedOperations;

    protected $mkpInternalTechnicalId;

    /**
     * Initializer constructor.
     * @param EventDispatcherInterface $dispatcher
     * @param LoggerInterface $logger
     * @param Factory $factory
     * @param VendorInterface $operatorAccount
     * @param VendorInterface $technicalAccount
     * @param ValidatorInterface $transactionValidator
     * @param OperationManager $operationHandler
     * @param LogOperationsManager $logOperationsManager
     * @param VendorManager $vendorManager
     */
    public function __construct(
        EventDispatcherInterface $dispatcher,
        LoggerInterface $logger,
        Factory $factory,
        VendorInterface $operatorAccount,
        VendorInterface $technicalAccount,
        ValidatorInterface $transactionValidator,
        OperationManager $operationHandler,
        LogOperationsManager $logOperationsManager,
        VendorManager $vendorManager,
        $mkpInternalTechnicalId = null
    ) {
        parent::__construct(
            $dispatcher,
            $logger,
            $factory,
            $operationHandler,
            $vendorManager,
            $logOperationsManager,
            $operatorAccount
        );

        ModelValidator::validate($technicalAccount, 'Operator');
        $this->technicalAccount = $technicalAccount;

        $this->transactionValidator = $transactionValidator;

        $this->formatNotification = new FormatNotification();

        $this->operationsLogs = array();

        $this->adjustedOperations = array();

        $this->mkpInternalTechnicalId = $mkpInternalTechnicalId;
    }

    /**
     * Main processing function
     * Generate and save operations.
     *
     * @param DateTime $startDate
     * @param DateTime $endDate
     * @param DateTime $cycleDate
     *
     * @throws Exception
     *
     * @codeCoverageIgnore
     */
    public function process(DateTime $startDate, DateTime $endDate, DateTime $cycleDate)
    {
        $this->logger->info('Control Mirakl Settings', array('miraklId' => null, "action" => "Operations creation"));
        // control mirakl settings
        $boolControl = $this->getControlMiraklSettings($this->documentTypes);
        if ($boolControl === false) {
            // log critical
            $title = $this->criticalMessageMiraklSettings;
            $message = $this->formatNotification->formatMessage($title);
            $this->logger->critical($message, array('miraklId' => null, "action" => "Operations creation"));
        } else {
            $this->logger->info(
                'Control Mirakl Settings OK',
                array('miraklId' => null, "action" => "Operations creation")
            );
        }

        $this->logger->info('Cashout Initializer', array('miraklId' => null, "action" => "Operations creation"));

        //Fetch Invoices
        $this->logger->info(
            'Fetch invoices documents from Mirakl from ' .
            $startDate->format('Y-m-d H:i') .
            ' to ' .
            $endDate->format('Y-m-d H:i')
            ,
            array('miraklId' => null, "action" => "Operations creation")
        );

        // get invoices from Mirakl API
        $invoices = $this->getInvoices($startDate, $endDate);

        $this->logger->info(
            '[OK] Fetched ' . count($invoices) . ' invoices',
            array('miraklId' => null, "action" => "Operations creation")
        );

        $this->logger->info('Process invoices', array('miraklId' => null, "action" => "Operations creation"));

        $operations = $this->processInvoices($invoices, $cycleDate);

        $this->saveOperations($operations);

    }

    /**
     * Create the vendor operation
     * dispatch <b>after.operation.create</b>.
     *
     * @param $amount
     * @param $originAmount
     * @param DateTime $cycleDate
     * @param $paymentVoucher
     * @param $vendor
     * @param null $adjustedOperationsIds
     * @return OperationInterface
     */
    public function createOperation(
        $amount,
        $originAmount,
        DateTime $cycleDate,
        $paymentVoucher,
        $vendor,
        $merchantUniqueId = null,
        $adjustedOperationsIds = null
    ) {
        //Set hipay id
        $hipayId = $vendor->getHiPayId();

        //Call implementation function
        $operation = $this->operationManager->create($amount, $cycleDate, $paymentVoucher, $vendor->getMiraklId());

        $operation->setHiPayId($hipayId);

        //Sets mandatory values
        $operation->setMiraklId($vendor->getMiraklId());
        $operation->setStatus(new Status(Status::CREATED));
        $operation->setUpdatedAt(new DateTime());
        $operation->setAmount($amount);
        $operation->setOriginAmount($originAmount);
        $operation->setCycleDate($cycleDate);
        $operation->setPaymentVoucher((string)$paymentVoucher);
        $operation->setMerchantUniqueId($merchantUniqueId);

        // if adjustments were made, save adjusted operations
        if ($adjustedOperationsIds !== null) {
            $operation->setAdjustmentIds(json_encode($adjustedOperationsIds));
        }

        $this->isOperationValid($operation);

        $this->operationsLogs[] = $this->logOperationsManager->create(
            $vendor->getMiraklId(),
            $hipayId,
            (string)$paymentVoucher,
            $amount,
            $originAmount,
            $this->hipay->getBalance($vendor)
        );
        return $operation;
    }

    /**
     * Validate an operation
     *
     * @param OperationInterface $operation
     * @return bool
     * @throws AlreadyCreatedOperationException
     * @throws InvalidOperationException
     */
    public function isOperationValid(OperationInterface $operation)
    {
        if ($this->operationManager->findByMiraklIdAndPaymentVoucherNumber(
            $operation->getMiraklId(),
            $operation->getPaymentVoucher()
        )
        ) {
            throw new AlreadyCreatedOperationException($operation);
        }

        if (!$this->operationManager->isValid($operation)) {
            throw new InvalidOperationException($operation);
        }

        ModelValidator::validate($operation);

        return true;
    }

    /**
     * Save operations
     *
     * @param array $operations
     */
    public function saveOperations(array $operations)
    {

        $this->logger->info('Save operations', array('miraklId' => null, "action" => "Operations creation"));
        $this->operationManager->saveAll($operations);
        $this->logOperationsManager->saveAll($this->operationsLogs);
        $this->saveAdjustedOperations($this->adjustedOperations);
        $this->logger->info('[OK] Operations saved', array('miraklId' => null, "action" => "Operations creation"));
    }

    /**
     * Control if Mirakl Setting is ok with HiPay prerequisites
     *
     * @param $docTypes
     */
    public function getControlMiraklSettings($docTypes)
    {
        $this->mirakl->controlMiraklSettings($docTypes);
    }

    /**
     * Create operations from Mirakl invoices
     *
     * @param array $invoices
     * @param DateTime $cycleDate
     * @return type
     */
    private function processInvoices(array $invoices, DateTime $cycleDate)
    {

        $operations = array();

        foreach ($invoices as $invoice) {

            $this->logger->debug(
                "ShopId : " . $invoice['shop_id'],
                array('miraklId' => $invoice['shop_id'], "action" => "Operations creation")
            );

            $vendor = $this->vendorManager->findByMiraklId($invoice['shop_id']);

            try {
                $enabled = $this->vendorEnabled($vendor);

                if (!$enabled) {
                    throw new Exception("vendor is disabled");
                }

                $operationsFromInvoice = $this->createOperationsFromInvoice($invoice, $vendor, $cycleDate);
                if ($operationsFromInvoice) {
                    $operations = array_merge($operations, $operationsFromInvoice);
                } else {
                    //something went wrong
                    $title = "The operations for invoice n° " . $invoice['invoice_id'] . " are wrong";
                    $message = $this->formatNotification->formatMessage($title);
                    $this->logger->error($message, array('miraklId' => null, "action" => "Operations creation"));
                }

            } catch (Exception $e) {
                $this->logger->info(
                    "Operation wasn't created because vendor doesn't exit in database or vendor is disabled " .
                    " (verify HiPay process value in Mirakl BO)",
                    array('miraklId' => $invoice['shop_id'], "action" => "Operations creation")
                );
            }
        }

        return $operations;
    }

    /**
     * Create operations from Mirakl invoice
     *
     * @param array $invoice
     * @param VendorInterface $vendor
     * @param DateTime $cycleDate
     * @return array
     */
    private function createOperationsFromInvoice(array $invoice, VendorInterface $vendor, DateTime $cycleDate)
    {

        $operations = array();

        try {
            // Calculate adjusted amount (for past negative operations)
            $adjustedInfos = $this->getAdjustedAmount($invoice['summary']['amount_transferred'], $vendor);

            $this->logger->debug(
                "Vendor origin amount " . $invoice['summary']['amount_transferred'],
                array('miraklId' => $invoice['shop_id'], "action" => "Operations creation")
            );

            $this->logger->debug(
                "Vendor adjusted amount " .
                $adjustedInfos['adjustedAmount'] .
                " (" .
                count($adjustedInfos['adujstedOperationsIds']) .
                " operations adjusted)",
                array('miraklId' => $invoice['shop_id'], "action" => "Operations creation")
            );

            if ($invoice['summary']['amount_transferred'] > 0) {

                // Vendor operation
                $operations[] = $this->createOperation(
                    $adjustedInfos['adjustedAmount'],
                    $invoice['summary']['amount_transferred'],
                    $cycleDate,
                    $invoice['invoice_id'],
                    $vendor,
                    $this->generateMerchantUniqueId("SALES", $invoice['invoice_id'], $vendor->getHiPayId()),
                    $adjustedInfos['adujstedOperationsIds']
                );

            } else {
                $this->logger->warning(
                    "Amount to transfer to vendor for Invoice n° " . $invoice['invoice_id'] . " is negative, will not be treated",
                    array('miraklId' => $invoice['shop_id'], "action" => "Operations creation")
                );
            }

            if ($invoice['summary']['amount_transferred_to_operator'] > 0) {
                // operator operation
                $operations[] = $this->createOperation(
                    $invoice['summary']['amount_transferred_to_operator'],
                    null,
                    $cycleDate,
                    $invoice['invoice_id'],
                    $this->operator,
                    $this->generateMerchantUniqueId("COMMISSION", $invoice['invoice_id'], $vendor->getHiPayId())
                );
            } else {
                $this->logger->warning(
                    "Amount to operator to vendor for Invoice n° " . $invoice['invoice_id'] . " is negative, will not be treated",
                    array('miraklId' => $invoice['shop_id'], "action" => "Operations creation")
                );
            }

            // save in memory for future saving in database
            $this->adjustedOperations = array_merge(
                $this->adjustedOperations,
                $adjustedInfos["adujstedOperations"]
            );

            return $operations;
        } catch (Exception $e) {
            $this->handleException($e);
            return false;
        }
    }

    /**
     * Set adjusted operations to the right status
     *
     * @param array $adjustedOperations
     */
    private function saveAdjustedOperations(array $adjustedOperations)
    {

        foreach ($adjustedOperations as $op) {
            $op->setStatus(new Status(Status::ADJUSTED_OPERATIONS));
            $this->operationManager->save($op);
            $this->logOperation(
                $op->getMiraklId(),
                $op->getPaymentVoucher(),
                Status::ADJUSTED_OPERATIONS,
                ""
            );
        }
    }

    /**
     * Calculate adjusted amount for this invoice
     *
     * @param type $originAmount
     * @param type $vendor
     * @return type
     */
    private function getAdjustedAmount($originAmount, $vendor)
    {

        $adjustedOperations = array();
        $adjustedOperationsIds = array();
        // retrieve not adjusted negative operations for vendor ID
        $negativeOperations = $this->operationManager->findNegativeOperations($vendor->getHipayId());

        foreach ($negativeOperations as $nop) {
            if (!in_array($nop, $this->adjustedOperations) && $originAmount + $nop->getAmount() > 0) {
                $originAmount += $nop->getAmount();
                $adjustedOperations[] = $nop;
                $adjustedOperationsIds[] = $nop->getId();
            }
        }

        return array(
            'adjustedAmount' => $originAmount,
            'adujstedOperations' => $adjustedOperations,
            'adujstedOperationsIds' => $adjustedOperationsIds
        );
    }

    /**
     * Get invoices from Mirakl API
     *
     * @param DateTime $startDate
     * @param DateTime $endDate
     * @return type
     */
    private function getInvoices(DateTime $startDate, DateTime $endDate)
    {
        $offset = Mirakl::MIRAKL_API_MAX_PAGINATE;

        $invoicesData = $this->mirakl->getInvoices(
            $startDate,
            $endDate
        );

        $invoices = array_merge(array(), $invoicesData['invoices']);

        $total = $invoicesData['total_count'];

        while ($total % $offset !== $total) {

            $invoicesData = $this->mirakl->getInvoices(
                $startDate,
                $endDate,
                Mirakl::MIRAKL_API_MAX_PAGINATE,
                $offset
            );

            $invoices = array_merge($invoices, $invoicesData['invoices']);

            $offset += Mirakl::MIRAKL_API_MAX_PAGINATE;
        }

        return $invoices;
    }

    private function generateMerchantUniqueId($paymentVoucherType, $invoiceId, $hipayId)
    {
        if(is_null($this->mkpInternalTechnicalId)){
            return null;
        }

        return $paymentVoucherType . "_" . $invoiceId . "_" . $this->mkpInternalTechnicalId . "_" . $hipayId;
    }
}