Current File : /var/www/prestashop/modules/ps_checkout/src/Presenter/Order/OrderPresenter.php
<?php
/**
 * Copyright since 2007 PrestaShop SA and Contributors
 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License version 3.0
 * that is bundled with this package in the file LICENSE.md.
 * It is also available through the world-wide-web at this URL:
 * https://opensource.org/licenses/AFL-3.0
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com so we can send you a copy immediately.
 *
 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
 * @copyright Since 2007 PrestaShop SA and Contributors
 * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
 */

namespace PrestaShop\Module\PrestashopCheckout\Presenter\Order;

use PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceTranslationProvider;
use PrestaShop\Module\PrestashopCheckout\PayPal\Card3DSecure;
use PrestaShop\Module\PrestashopCheckout\Presenter\Date\DatePresenter;
use PrestaShop\Module\PrestashopCheckout\Provider\PaymentMethodLogoProvider;
use Ps_checkout;
use PsCheckoutCart;

class OrderPresenter
{
    /**
     * @var Ps_checkout
     */
    private $module;

    /**
     * @var array
     */
    private $orderPayPal;
    /**
     * @var FundingSourceTranslationProvider
     */
    private $fundingSourceTranslationProvider;

    /**
     * @param Ps_checkout $module
     * @param array $orderPayPal
     */
    public function __construct(Ps_checkout $module, array $orderPayPal)
    {
        $this->module = $module;
        $this->orderPayPal = $orderPayPal;
        /** @var FundingSourceTranslationProvider $fundingSourceTranslationProvider */
        $fundingSourceTranslationProvider = $this->module->getService(FundingSourceTranslationProvider::class);
        $this->fundingSourceTranslationProvider = $fundingSourceTranslationProvider;
    }

    /**
     * @return array
     */
    public function present()
    {
        if (empty($this->orderPayPal)) {
            return [];
        }

        $card3DSecure = new Card3DSecure();

        return array_merge(
            [
                'id' => $this->orderPayPal['id'],
                'intent' => $this->orderPayPal['intent'],
                'status' => $this->getOrderStatus(),
                'transactions' => $this->getTransactions(),
                'is3DSecureAvailable' => $card3DSecure->is3DSecureAvailable($this->orderPayPal),
                'isLiabilityShifted' => $card3DSecure->isLiabilityShifted($this->orderPayPal),
                'paymentSource' => $this->getPaymentSourceName($this->orderPayPal),
                'paymentSourceLogo' => $this->getPaymentSourceLogo($this->orderPayPal),
            ],
            $this->getOrderTotals()
        );
    }

    /**
     * @return array
     */
    private function getOrderStatus()
    {
        $translated = '';
        $class = '';

        if (PsCheckoutCart::STATUS_CREATED === $this->orderPayPal['status']) {
            $translated = $this->module->l('Created', 'orderpresenter');
            $class = 'info';
        }

        if (PsCheckoutCart::STATUS_SAVED === $this->orderPayPal['status']) {
            $translated = $this->module->l('Saved', 'orderpresenter');
            $class = 'info';
        }

        if (PsCheckoutCart::STATUS_APPROVED === $this->orderPayPal['status']) {
            $translated = $this->module->l('Approved', 'orderpresenter');
            $class = 'info';
        }

        if (PsCheckoutCart::STATUS_VOIDED === $this->orderPayPal['status']) {
            $translated = $this->module->l('Voided', 'orderpresenter');
            $class = 'warning';
        }

        if (PsCheckoutCart::STATUS_COMPLETED === $this->orderPayPal['status']) {
            $translated = $this->module->l('Completed', 'orderpresenter');
            $class = 'success';
        }

        return [
            'value' => $this->orderPayPal['status'],
            'translated' => $translated,
            'class' => $class,
        ];
    }

    /**
     * @return array
     */
    private function getTransactions()
    {
        if (empty($this->orderPayPal['purchase_units'])) {
            return [];
        }

        $transactions = [];

        foreach ($this->orderPayPal['purchase_units'] as $purchase) {
            if (empty($purchase['payments'])) {
                continue;
            }

            $totalRefunded = 0;

            if (!empty($purchase['payments']['refunds'])) {
                foreach ($purchase['payments']['refunds'] as $refund) {
                    $totalRefunded += $refund['amount']['value'];
                    $transactions[] = [
                        'type' => $this->getTransactionType('refund'),
                        'id' => $refund['id'],
                        'status' => $this->getTransactionStatus($refund['status']),
                        'amount' => $refund['amount']['value'],
                        'currency' => $refund['amount']['currency_code'],
                        'date' => (new DatePresenter($refund['create_time'], 'Y-m-d H:i:s'))->present(),
                        'isRefundable' => false,
                        'maxAmountRefundable' => 0,
                        'gross_amount' => isset($refund['seller_payable_breakdown']['gross_amount']['value']) ? $refund['seller_payable_breakdown']['gross_amount']['value'] : '',
                        'paypal_fee' => isset($refund['seller_payable_breakdown']['paypal_fee']['value']) ? $refund['seller_payable_breakdown']['paypal_fee']['value'] : '',
                        'net_amount' => isset($refund['seller_payable_breakdown']['net_amount']['value']) ? $refund['seller_payable_breakdown']['net_amount']['value'] : '',
                    ];
                }
            }

            if (!empty($purchase['payments']['captures'])) {
                foreach ($purchase['payments']['captures'] as $payment) {
                    $maxAmountRefundable = $payment['amount']['value'] - $totalRefunded;
                    $transactions[] = [
                        'type' => $this->getTransactionType('capture'),
                        'id' => $payment['id'],
                        'status' => $this->getTransactionStatus($payment['status']),
                        'amount' => $payment['amount']['value'],
                        'currency' => $payment['amount']['currency_code'],
                        'date' => (new DatePresenter($payment['create_time'], 'Y-m-d H:i:s'))->present(),
                        'isRefundable' => in_array($payment['status'], ['COMPLETED', 'PARTIALLY_REFUNDED']),
                        'maxAmountRefundable' => $maxAmountRefundable > 0 ? $maxAmountRefundable : 0,
                        'gross_amount' => isset($payment['seller_receivable_breakdown']['gross_amount']['value']) ? $payment['seller_receivable_breakdown']['gross_amount']['value'] : '',
                        'paypal_fee' => isset($payment['seller_receivable_breakdown']['paypal_fee']['value']) ? $payment['seller_receivable_breakdown']['paypal_fee']['value'] : '',
                        'net_amount' => isset($payment['seller_receivable_breakdown']['net_amount']['value']) ? $payment['seller_receivable_breakdown']['net_amount']['value'] : '',
                        'seller_protection' => $this->getSellerProtection($payment),
                    ];
                }
            }
        }

        if (!empty($transactions)) {
            uasort($transactions, function (array $transactionA, array $transactionB) {
                return strtotime($transactionB['date']) - strtotime($transactionA['date']);
            });
        }

        return $transactions;
    }

    /**
     * @param string $status
     *
     * @return array
     */
    private function getTransactionStatus($status)
    {
        $translated = '';
        $class = '';

        if ('COMPLETED' === $status) {
            $translated = $this->module->l('Completed', 'orderpresenter');
            $class = 'success';
        }

        if ('PENDING' === $status) {
            $translated = $this->module->l('Pending', 'orderpresenter');
            $class = 'warning';
        }

        if ('DECLINED' === $status) {
            $translated = $this->module->l('Declined', 'orderpresenter');
            $class = 'danger';
        }

        if ('PARTIALLY_REFUNDED' === $status) {
            $translated = $this->module->l('Partially refunded', 'orderpresenter');
            $class = 'info';
        }

        if ('REFUNDED' === $status) {
            $translated = $this->module->l('Refunded', 'orderpresenter');
            $class = 'info';
        }

        return [
            'value' => $status,
            'translated' => $translated,
            'class' => $class,
        ];
    }

    /**
     * @param string $type
     *
     * @return array
     */
    private function getTransactionType($type)
    {
        $translated = '';
        $class = '';

        if ('capture' === $type) {
            $translated = $this->module->l('Payment', 'orderpresenter');
            $class = 'payment';
        }

        if ('refund' === $type) {
            $translated = $this->module->l('Refund', 'orderpresenter');
            $class = 'refund';
        }

        return [
            'value' => $type,
            'translated' => $translated,
            'class' => $class,
        ];
    }

    private function getTotal()
    {
        if (empty($this->orderPayPal['purchase_units'])) {
            return '0';
        }

        $total = 0.0;
        $currency = '';

        foreach ($this->orderPayPal['purchase_units'] as $purchase) {
            if (empty($purchase['payments'])) {
                continue;
            }

            $total += (float) $purchase['amount']['value'];
            $currency = $purchase['amount']['currency_code'];
        }

        return number_format($total, 2) . " $currency";
    }

    private function getBalance()
    {
        if (empty($this->orderPayPal['purchase_units'])) {
            return '0';
        }

        $balance = 0.0;
        $totalRefunded = 0.0;
        $currency = '';

        foreach ($this->orderPayPal['purchase_units'] as $purchase) {
            if (empty($purchase['payments'])) {
                continue;
            }

            $currency = $purchase['amount']['currency_code'];

            if (!empty($purchase['payments']['refunds'])) {
                foreach ($purchase['payments']['refunds'] as $refund) {
                    $totalRefunded += $refund['amount']['value'];
                }
            }

            if (!empty($purchase['payments']['captures'])) {
                foreach ($purchase['payments']['captures'] as $payment) {
                    $balance += $payment['amount']['value'];
                    if (isset($payment['seller_receivable_breakdown']['paypal_fee']['value'])) {
                        $balance -= $payment['seller_receivable_breakdown']['paypal_fee']['value'];
                    }
                }
            }
        }

        return number_format($balance - $totalRefunded, 2) . " $currency";
    }

    /**
     * returns order total, balance and fees.
     * Added into one function because they all require same foreach
     *
     * @return array
     */
    private function getOrderTotals()
    {
        $total = 0.0;
        $balance = 0.0;
        $totalRefunded = 0.0;
        $fees = 0.0;

        $currency = '';

        foreach ($this->orderPayPal['purchase_units'] as $purchase) {
            if (empty($purchase['payments'])) {
                continue;
            }

            $currency = $purchase['amount']['currency_code'];

            if (!empty($purchase['payments']['refunds'])) {
                foreach ($purchase['payments']['refunds'] as $refund) {
                    $totalRefunded += $refund['amount']['value'];
                }
            }

            if (!empty($purchase['payments']['captures'])) {
                foreach ($purchase['payments']['captures'] as $payment) {
                    $total += $payment['amount']['value'];
                    if (isset($payment['seller_receivable_breakdown']['paypal_fee']['value'])) {
                        $fees -= $payment['seller_receivable_breakdown']['paypal_fee']['value'];
                    }
                }
            }
        }

        return [
            'total' => number_format($total, 2) . " $currency",
            'fees' => number_format($fees, 2) . " $currency",
            'balance' => number_format($total - $totalRefunded + $fees, 2) . " $currency",
        ];
    }

    /**
     * @param array $orderPayPal
     *
     * @return string
     */
    private function getPaymentSourceName(array $orderPayPal)
    {
        if (isset($orderPayPal['payment_source'])) {
            return $this->fundingSourceTranslationProvider->getPaymentMethodName(key($orderPayPal['payment_source']));
        }

        return '';
    }

    /**
     * @param array $orderPayPal
     *
     * @return string
     */
    private function getPaymentSourceLogo(array $orderPayPal)
    {
        if (isset($orderPayPal['payment_source'])) {
            return (new PaymentMethodLogoProvider($this->module))->getLogoByPaymentSource($orderPayPal['payment_source']);
        }

        return '';
    }

    /**
     * @param array $payment
     *
     * @return array
     */
    private function getSellerProtection(array $payment)
    {
        if (empty($payment['seller_protection'])) {
            return [];
        }

        $help = [];
        $status = isset($payment['seller_protection']['status']) ? $payment['seller_protection']['status'] : '';
        $dispute_categories = isset($payment['seller_protection']['dispute_categories']) ? $this->getDisputeCategoriesValues($payment['seller_protection']['dispute_categories']) : [];

        switch ($status) {
            case 'ELIGIBLE':
                $help[] = $this->module->l('Your PayPal balance remains intact if the customer claims that they did not receive an item or the account holder claims that they did not authorize the payment.', 'orderpresenter');
                if (!empty($dispute_categories)) {
                    $help[] = $this->module->l('Dispute categories covered:', 'orderpresenter');
                    $help[] = implode(', ', $dispute_categories);
                }
                $help[] = $this->module->l('For more information, please go to the official PayPal website.', 'orderpresenter');

                return [
                    'value' => $status,
                    'translated' => $this->module->l('Eligible', 'orderpresenter'),
                    'help' => implode(' ', $help),
                    'class' => 'success',
                ];
            case 'PARTIALLY_ELIGIBLE':
                $help[] = $this->module->l('Your PayPal balance remains intact if the customer claims that they did not receive an item.', 'orderpresenter');
                if (!empty($dispute_categories)) {
                    $help[] = $this->module->l('Dispute categories covered:', 'orderpresenter');
                    $help[] = implode(', ', $dispute_categories);
                }
                $help[] = $this->module->l('For more information, please go to the official PayPal website.', 'orderpresenter');

                return [
                    'value' => $status,
                    'translated' => $this->module->l('Partially eligible', 'orderpresenter'),
                    'help' => implode(' ', $help),
                    'class' => 'info',
                ];
            case 'NOT_ELIGIBLE':
                $help[] = $this->module->l('Your PayPal balance is not protected, the transaction is not eligible to the seller protection program.', 'orderpresenter');
                if (!empty($dispute_categories)) {
                    $help[] = $this->module->l('Dispute categories covered:', 'orderpresenter');
                    $help[] = implode(', ', $dispute_categories);
                }
                $help[] = $this->module->l('For more information, please go to the official PayPal website.', 'orderpresenter');

                return [
                    'value' => $status,
                    'translated' => $this->module->l('Not eligible', 'orderpresenter'),
                    'help' => implode(' ', $help),
                    'class' => 'warning',
                ];
            default:
                return [
                    'value' => $status,
                    'translated' => $status,
                    'help' => $status,
                    'class' => 'info',
                ];
        }
    }

    /**
     * @param array $dispute_categories
     *
     * @return array
     */
    private function getDisputeCategoriesValues(array $dispute_categories)
    {
        $disputeCategories = [];

        foreach ($dispute_categories as $dispute_category) {
            switch ($dispute_category) {
                case 'ITEM_NOT_RECEIVED':
                    $disputeCategories['ITEM_NOT_RECEIVED'] = $this->module->l('The payer paid for an item that they did not receive.', 'orderpresenter');
                    break;
                case 'UNAUTHORIZED_TRANSACTION':
                    $disputeCategories['UNAUTHORIZED_TRANSACTION'] = $this->module->l('The payer did not authorize the payment.', 'orderpresenter');
                    break;
            }
        }

        return $disputeCategories;
    }
}