* @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(); } }