Current File : /var/www/vinorea/modules/ps_checkout/controllers/front/ExpressCheckout.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
 */

use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController;
use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException;
use PrestaShop\Module\PrestashopCheckout\PaypalCountryCodeMatrice;
use PrestaShop\Module\PrestashopCheckout\Repository\CountryRepository;
use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository;
use PrestaShop\Module\PrestashopCheckout\Updater\CustomerUpdater;

/**
 * This controller receive ajax call when customer click on an express checkout button
 * We retrieve data from PayPal in payload and save it in PrestaShop to prefill order page
 * Then customer must be redirected to order page to choose shipping method
 */
class ps_checkoutExpressCheckoutModuleFrontController extends AbstractFrontController
{
    /**
     * @var Ps_checkout
     */
    public $module;

    /**
     * @var array
     */
    private $payload;

    /**
     * {@inheritdoc}
     */
    public function postProcess()
    {
        try {
            // We receive data in a payload not in GET/POST
            $bodyContent = file_get_contents('php://input');

            if (empty($bodyContent)) {
                $this->exitWithResponse([
                    'httpCode' => 400,
                    'body' => 'Payload invalid',
                ]);
            }

            $this->payload = json_decode($bodyContent, true);

            if (empty($this->payload)) {
                $this->exitWithResponse([
                    'httpCode' => 400,
                    'body' => 'Payload invalid',
                ]);
            }

            if (empty($this->payload['orderID']) || false === Validate::isGenericName($this->payload['orderID'])) {
                $this->exitWithResponse([
                    'httpCode' => 400,
                    'body' => 'Payload invalid',
                ]);
            }

            /** @var PsCheckoutCartRepository $psCheckoutCartRepository */
            $psCheckoutCartRepository = $this->module->getService(PsCheckoutCartRepository::class);

            /** @var PsCheckoutCart|false $psCheckoutCart */
            $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($this->payload['orderID']);

            if (false !== $psCheckoutCart) {
                $psCheckoutCart->paypal_funding = $this->payload['fundingSource'];
                $psCheckoutCart->isExpressCheckout = true;
                $psCheckoutCart->isHostedFields = false;
                $psCheckoutCartRepository->save($psCheckoutCart);
            }

            if (false === $this->context->customer->isLogged()) {
                // @todo Extract factory in a Service.
                $this->createAndLoginCustomer(
                    $this->payload['order']['payer']['email_address'],
                    $this->payload['order']['payer']['name']['given_name'],
                    $this->payload['order']['payer']['name']['surname']
                );
            }

            $this->context->cookie->__set('paypalEmail', $this->payload['order']['payer']['email_address']);
            $this->context->cookie->write();

            $this->resetContextCartAddresses();
            // Always 0 index because we are not using the paypal marketplace system
            // This index is only used in a marketplace context
            // @todo Extract factory in a Service.
            $this->createAddress(
                $this->payload['order']['payer']['name']['given_name'],
                $this->payload['order']['payer']['name']['surname'],
                $this->payload['order']['shipping']['address']['address_line_1'],
                false === empty($this->payload['order']['shipping']['address']['address_line_2']) ? $this->payload['order']['shipping']['address']['address_line_2'] : '',
                $this->payload['order']['shipping']['address']['postal_code'],
                false === empty($this->payload['order']['shipping']['address']['admin_area_1']) ? $this->payload['order']['shipping']['address']['admin_area_1'] : '',
                $this->payload['order']['shipping']['address']['admin_area_2'],
                $this->payload['order']['shipping']['address']['country_code'],
                false === empty($this->payload['order']['payer']['phone']) ? $this->payload['order']['payer']['phone']['phone_number']['national_number'] : '',
                $this->payload['orderID']
            );
        } catch (Exception $exception) {
            $this->module->getLogger()->error(
                sprintf(
                    'ExpressCheckoutController - Exception %s : %s',
                    $exception->getCode(),
                    $exception->getMessage()
                ),
                [
                    'paypal_order' => isset($this->payload['orderID']) ? $this->payload['orderID'] : null,
                ]
            );

            $this->exitWithResponse([
                'status' => false,
                'httpCode' => 500,
                'body' => $this->payload,
                'exceptionCode' => $exception->getCode(),
                'exceptionMessage' => $exception->getMessage(),
            ]);
        }

        $this->exitWithResponse([
            'status' => true,
            'httpCode' => 200,
            'body' => $this->payload,
            'exceptionCode' => null,
            'exceptionMessage' => null,
        ]);
    }

    /**
     * Handle creation and customer login
     *
     * @param string $email
     * @param string $firstName
     * @param string $lastName
     *
     * @throws PrestaShopException
     */
    private function createAndLoginCustomer(
        $email,
        $firstName,
        $lastName
    ) {
        /** @var int $idCustomerExists */
        $idCustomerExists = Customer::customerExists($email, true);

        if (0 === $idCustomerExists) {
            // @todo Extract factory in a Service.
            $customer = $this->createCustomer(
                $email,
                $firstName,
                $lastName
            );
        } else {
            $customer = new Customer($idCustomerExists);
        }

        if (method_exists($this->context, 'updateCustomer')) {
            $this->context->updateCustomer($customer);
        } else {
            CustomerUpdater::updateContextCustomer($this->context, $customer);
        }
    }

    /**
     * Create a customer
     *
     * @todo Extract factory in a Service.
     *
     * @param string $email
     * @param string $firstName
     * @param string $lastName
     *
     * @return Customer
     *
     * @throws PsCheckoutException
     */
    private function createCustomer($email, $firstName, $lastName)
    {
        $customer = new Customer();
        $customer->email = $email;
        $customer->firstname = $firstName;
        $customer->lastname = $lastName;

        if (Configuration::get('PS_CHECKOUT_EXPRESS_USE_GUEST')) {
            $customer->is_guest = true;
            $customer->id_default_group = (int) Configuration::get('PS_GUEST_GROUP');
        }

        if (class_exists('PrestaShop\PrestaShop\Core\Crypto\Hashing')) {
            $crypto = new PrestaShop\PrestaShop\Core\Crypto\Hashing();
            $customer->passwd = $crypto->hash(
                time() . _COOKIE_KEY_,
                _COOKIE_KEY_
            );
        } else {
            $customer->passwd = md5(time() . _COOKIE_KEY_);
        }

        try {
            $customer->save();
        } catch (Exception $exception) {
            throw new PsCheckoutException($exception->getMessage(), PsCheckoutException::PSCHECKOUT_EXPRESS_CHECKOUT_CANNOT_SAVE_CUSTOMER, $exception);
        }

        return $customer;
    }

    /**
     * Create address
     *
     * @todo Extract factory in a Service.
     *
     * @param string $firstName
     * @param string $lastName
     * @param string $address1
     * @param string $address2
     * @param string $postcode
     * @param string $state
     * @param string $city
     * @param string $countryIsoCode
     * @param string $phone
     *
     * @return bool
     *
     * @throws PsCheckoutException
     */
    private function createAddress(
        $firstName,
        $lastName,
        $address1,
        $address2,
        $postcode,
        $state,
        $city,
        $countryIsoCode,
        $phone,
        $idPaypalOrder
    ) {
        // check if country is available for delivery
        $psIsoCode = (new PaypalCountryCodeMatrice())->getPrestashopIsoCode($countryIsoCode);
        $idCountry = Country::getByIso($psIsoCode);
        $idState = 0;
        $country = new Country((int) $idCountry, null, (int) $this->context->shop->id);

        if (!Validate::isLoadedObject($country)
            || !$country->active
            || !$country->isAssociatedToShop((int) $this->context->shop->id)
            || Country::isNeedDniByCountryId($idCountry)
        ) {
            return false;
        }

        if ($country->contains_states) {
            /** @var CountryRepository $countryRepository */
            $countryRepository = $this->module->getService(CountryRepository::class);

            $idState = $countryRepository->getStateId((int) $idCountry, $state);
        }

        // check if a PayPal address already exist for the customer and not used
        $paypalAddressAlias = 'Paypal ' . $idPaypalOrder;
        $paypalAddressId = $this->addressAlreadyExist($paypalAddressAlias, $this->context->customer->id);
        $paypalAddress = new Address($paypalAddressId);
        $isPaypalValidAddressAndNotUsed = Validate::isLoadedObject($paypalAddress) && !$paypalAddress->isUsed();

        if ($isPaypalValidAddressAndNotUsed) {
            $address = $paypalAddress; // if yes, update it with the new address
        } else {
            $address = new Address(); // otherwise create a new address
        }

        $address->alias = $paypalAddressAlias;
        $address->id_customer = $this->context->customer->id;
        $address->firstname = $firstName;
        $address->lastname = $lastName;
        $address->address1 = $address1;
        $address->address2 = $address2;
        $address->postcode = $postcode;
        $address->city = $city;
        $address->id_country = $idCountry;
        $address->phone = $phone;
        $address->id_state = $idState;

        if (true !== $address->validateFields(false)) {
            return false;
        }

        try {
            $address->save();
        } catch (Exception $exception) {
            throw new PsCheckoutException($exception->getMessage(), PsCheckoutException::PSCHECKOUT_EXPRESS_CHECKOUT_CANNOT_SAVE_ADDRESS, $exception);
        }

        $this->context->cart->id_address_delivery = $address->id;
        $this->context->cart->id_address_invoice = $address->id;

        $products = $this->context->cart->getProducts();
        foreach ($products as $product) {
            $this->context->cart->setProductAddressDelivery($product['id_product'], $product['id_product_attribute'], $product['id_address_delivery'], $address->id);
        }

        return $this->context->cart->save();
    }

    /**
     * Check if address already exist, if yes return the id_address
     *
     * @param string $alias
     * @param int $id_customer
     *
     * @return int
     */
    private function addressAlreadyExist($alias, $id_customer)
    {
        $query = new DbQuery();
        $query->select('id_address');
        $query->from('address');
        $query->where('alias = \'' . pSQL($alias) . '\'');
        $query->where('id_customer = ' . (int) $id_customer);
        $query->where('deleted = 0');

        return (int) Db::getInstance()->getValue($query);
    }

    /**
     * Reset current cart address
     */
    private function resetContextCartAddresses()
    {
        $this->context->cart->id_address_delivery = 0;
        $this->context->cart->id_address_invoice = 0;
        $this->context->cart->save();
    }
}