| Server IP : 213.186.33.4 / Your IP : 216.73.216.193 Web Server : Apache System : Linux webm006.cluster103.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64 User : awebpaca ( 35430) PHP Version : 8.5.0 Disable Function : _dyuweyrj4,_dyuweyrj4r,dl MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/awebpaca/boutiques/app/code/core/Mage/Paypal/Model/Express/ |
Upload File : |
<?php
/**
* Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* 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@magentocommerce.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade Magento to newer
* versions in the future. If you wish to customize Magento for your
* needs please refer to http://www.magentocommerce.com for more information.
*
* @category Mage
* @package Mage_Paypal
* @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
/**
* Wrapper that performs Paypal Express and Checkout communication
* Use current Paypal Express method instance
*/
class Mage_Paypal_Model_Express_Checkout
{
/**
* Cache ID prefix for "pal" lookup
* @var string
*/
const PAL_CACHE_ID = 'paypal_express_checkout_pal';
/**
* Keys for passthrough variables in sales/quote_payment and sales/order_payment
* Uses additional_information as storage
* @var string
*/
const PAYMENT_INFO_TRANSPORT_TOKEN = 'paypal_express_checkout_token';
const PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDEN = 'paypal_express_checkout_shipping_overriden';
const PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD = 'paypal_express_checkout_shipping_method';
const PAYMENT_INFO_TRANSPORT_PAYER_ID = 'paypal_express_checkout_payer_id';
const PAYMENT_INFO_TRANSPORT_REDIRECT = 'paypal_express_checkout_redirect_required';
const PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT = 'paypal_ec_create_ba';
/**
* @var Mage_Sales_Model_Quote
*/
protected $_quote = null;
/**
* Config instance
* @var Mage_Paypal_Model_Config
*/
protected $_config = null;
/**
* API instance
* @var Mage_Paypal_Model_Api_Nvp
*/
protected $_api = null;
/**
* Api Model Type
*
* @var string
*/
protected $_apiType = 'paypal/api_nvp';
/**
* Payment method type
*
* @var unknown_type
*/
protected $_methodType = Mage_Paypal_Model_Config::METHOD_WPP_EXPRESS;
/**
* State helper variables
* @var string
*/
protected $_redirectUrl = '';
protected $_pendingPaymentMessage = '';
protected $_checkoutRedirectUrl = '';
/**
* Redirect urls supposed to be set to support giropay
*
* @var array
*/
protected $_giropayUrls = array();
/**
* Create Billing Agreement flag
*
* @var bool
*/
protected $_isBARequested = false;
/**
* Customer ID
*
* @var int
*/
protected $_customerId = null;
/**
* Recurring payment profiles
*
* @var array
*/
protected $_recurringPaymentProfiles = array();
/**
* Billing agreement that might be created during order placing
*
* @var Mage_Sales_Model_Billing_Agreement
*/
protected $_billingAgreement = null;
/**
* Order
*
* @var Mage_Sales_Model_QuoteMage_Sales_Model_Quote
*/
protected $_order = null;
/**
* Set quote and config instances
* @param array $params
*/
public function __construct($params = array())
{
if (isset($params['quote']) && $params['quote'] instanceof Mage_Sales_Model_Quote) {
$this->_quote = $params['quote'];
} else {
throw new Exception('Quote instance is required.');
}
if (isset($params['config']) && $params['config'] instanceof Mage_Paypal_Model_Config) {
$this->_config = $params['config'];
} else {
throw new Exception('Config instance is required.');
}
}
/**
* Checkout with PayPal image URL getter
* Spares API calls of getting "pal" variable, by putting it into cache per store view
* @return string
*/
public function getCheckoutShortcutImageUrl()
{
// get "pal" thing from cache or lookup it via API
$pal = null;
if ($this->_config->areButtonsDynamic()) {
$cacheId = self::PAL_CACHE_ID . Mage::app()->getStore()->getId();
$pal = Mage::app()->loadCache($cacheId);
if (-1 == $pal) {
$pal = null;
} elseif (!$pal) {
$pal = null;
$this->_getApi();
try {
$this->_api->callGetPalDetails();
$pal = $this->_api->getPal();
Mage::app()->saveCache($pal, $cacheId, array(Mage_Core_Model_Config::CACHE_TAG));
} catch (Exception $e) {
Mage::app()->saveCache(-1, $cacheId, array(Mage_Core_Model_Config::CACHE_TAG));
Mage::logException($e);
}
}
}
return $this->_config->getExpressCheckoutShortcutImageUrl(
Mage::app()->getLocale()->getLocaleCode(),
$this->_quote->getBaseGrandTotal(),
$pal
);
}
/**
* Setter that enables giropay redirects flow
*
* @param string $successUrl - payment success result
* @param string $cancelUrl - payment cancellation result
* @param string $pendingUrl - pending payment result
*/
public function prepareGiropayUrls($successUrl, $cancelUrl, $pendingUrl)
{
$this->_giropayUrls = array($successUrl, $cancelUrl, $pendingUrl);
return $this;
}
/**
* Set create billing agreement flag
*
* @param bool $flag
* @return Mage_Paypal_Model_Express_Checkout
*/
public function setIsBillingAgreementRequested($flag)
{
$this->_isBARequested = $flag;
return $this;
}
/**
* Setter for customer Id
*
* @param int $id
* @return Mage_Paypal_Model_Express_Checkout
*/
public function setCustomerId($id)
{
$this->_customerId = $id;
return $this;
}
/**
* Reserve order ID for specified quote and start checkout on PayPal
* @return string
*/
public function start($returnUrl, $cancelUrl)
{
$this->_quote->collectTotals();
$this->_quote->reserveOrderId()->save();
// prepare API
$this->_getApi();
$this->_api->setAmount($this->_quote->getBaseGrandTotal())
->setCurrencyCode($this->_quote->getBaseCurrencyCode())
->setInvNum($this->_quote->getReservedOrderId())
->setReturnUrl($returnUrl)
->setCancelUrl($cancelUrl)
->setSolutionType($this->_config->solutionType)
->setPaymentAction($this->_config->paymentAction)
;
if ($this->_giropayUrls) {
list($successUrl, $cancelUrl, $pendingUrl) = $this->_giropayUrls;
$this->_api->addData(array(
'giropay_cancel_url' => $cancelUrl,
'giropay_success_url' => $successUrl,
'giropay_bank_txn_pending_url' => $pendingUrl,
));
}
$this->_setBillingAgreementRequest();
// supress or export shipping address
if ($this->_quote->getIsVirtual()) {
$this->_api->setSuppressShipping(true);
} else {
$address = $this->_quote->getShippingAddress();
$isOverriden = 0;
if (true === $address->validate()) {
$isOverriden = 1;
$this->_api->setAddress($address);
}
$this->_quote->getPayment()->setAdditionalInformation(
self::PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDEN, $isOverriden
);
$this->_quote->getPayment()->save();
}
// add line items
if ($this->_config->lineItemsEnabled) {
list($items, $totals) = Mage::helper('paypal')->prepareLineItems($this->_quote);
if (Mage::helper('paypal')->areCartLineItemsValid($items, $totals, $this->_quote->getBaseGrandTotal())) {
$this->_api->setLineItems($items)->setLineItemTotals($totals);
}
// add shipping options
if ($this->_config->transferShippingOptions
&& !$this->_quote->getIsVirtual() && !$this->_quote->hasNominalItems()) {
if ($options = $this->_prepareShippingOptions($address, true)) {
$this->_api->setShippingOptionsCallbackUrl(
Mage::getUrl('*/*/shippingOptionsCallback', array('quote_id' => $this->_quote->getId()))
)->setShippingOptions($options);
}
}
}
// add recurring payment profiles information
if ($profiles = $this->_quote->prepareRecurringPaymentProfiles()) {
foreach ($profiles as $profile) {
$profile->setMethodCode(Mage_Paypal_Model_Config::METHOD_WPP_EXPRESS);
if (!$profile->isValid()) {
Mage::throwException($profile->getValidationErrors(true, true));
}
}
$this->_api->addRecurringPaymentProfiles($profiles);
}
$this->_config->exportExpressCheckoutStyleSettings($this->_api);
// call API and redirect with token
$this->_api->callSetExpressCheckout();
$token = $this->_api->getToken();
$this->_redirectUrl = $this->_config->getExpressCheckoutStartUrl($token);
$this->_quote->getPayment()->unsAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT);
$this->_quote->getPayment()->save();
return $token;
}
/**
* Update quote when returned from PayPal
* @param string $token
*/
public function returnFromPaypal($token)
{
$this->_getApi();
$this->_api->setToken($token)
->callGetExpressCheckoutDetails();
// import billing address
$billingAddress = $this->_quote->getBillingAddress();
$exportedBillingAddress = $this->_api->getExportedBillingAddress();
foreach ($exportedBillingAddress->getExportedKeys() as $key) {
$billingAddress->setDataUsingMethod($key, $exportedBillingAddress->getData($key));
}
// import shipping address
$exportedShippingAddress = $this->_api->getExportedShippingAddress();
if (!$this->_quote->getIsVirtual()) {
$shippingAddress = $this->_quote->getShippingAddress();
if ($shippingAddress) {
if ($exportedShippingAddress) {
foreach ($exportedShippingAddress->getExportedKeys() as $key) {
$shippingAddress->setDataUsingMethod($key, $exportedShippingAddress->getData($key));
}
$shippingAddress->setCollectShippingRates(true)->collectShippingRates();
}
// import shipping method
$code = '';
if ($this->_api->getShippingRateCode()) {
if ($code = $this->_matchShippingMethodCode($shippingAddress, $this->_api->getShippingRateCode())) {
// possible bug of double collecting rates :-/
$shippingAddress->setShippingMethod($code)->setCollectShippingRates(true);
}
}
$this->_quote->getPayment()->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD, $code);
}
}
$this->_ignoreAddressValidation();
// import payment info
$payment = $this->_quote->getPayment();
$payment->setMethod($this->_methodType);
Mage::getSingleton('paypal/info')->importToPayment($this->_api, $payment);
$payment->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID, $this->_api->getPayerId())
->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_TOKEN, $token)
;
$this->_quote->collectTotals()->save();
}
/**
* Check whether order review has enough data to initialize
*
* @param $token
* @throws Mage_Core_Exception
*/
public function prepareOrderReview($token = null)
{
$payment = $this->_quote->getPayment();
if (!$payment || !$payment->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID)) {
Mage::throwException(Mage::helper('paypal')->__('Payer is not identified.'));
}
$this->_quote->setMayEditShippingAddress(
1 != $this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDEN)
);
$this->_quote->setMayEditShippingMethod(
'' == $this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD)
);
$this->_ignoreAddressValidation();
$this->_quote->collectTotals()->save();
}
/**
* Return callback response with shipping options
*
* @param array $request
* @return string
*/
public function getShippingOptionsCallbackResponse(array $request)
{
// prepare debug data
$logger = Mage::getModel('core/log_adapter', 'payment_' . $this->_methodType . '.log');
$debugData = array('request' => $request, 'response' => array());
try {
// obtain addresses
$this->_getApi();
$address = $this->_api->prepareShippingOptionsCallbackAddress($request);
$quoteAddress = $this->_quote->getShippingAddress();
// compare addresses, calculate shipping rates and prepare response
$options = array();
if ($address && $quoteAddress && !$this->_quote->getIsVirtual()) {
foreach ($address->getExportedKeys() as $key) {
$quoteAddress->setDataUsingMethod($key, $address->getData($key));
}
$quoteAddress->setCollectShippingRates(true)->collectShippingRates();
$options = $this->_prepareShippingOptions($quoteAddress, false);
}
$response = $this->_api->setShippingOptions($options)->formatShippingOptionsCallback();
// log request and response
$debugData['response'] = $response;
$logger->log($debugData);
return $response;
} catch (Exception $e) {
$logger->log($debugData);
throw $e;
}
}
/**
* Set shipping method to quote, if needed
* @param string $methodCode
*/
public function updateShippingMethod($methodCode)
{
if (!$this->_quote->getIsVirtual() && $shippingAddress = $this->_quote->getShippingAddress()) {
if ($methodCode != $shippingAddress->getShippingMethod()) {
$this->_ignoreAddressValidation();
$shippingAddress->setShippingMethod($methodCode)->setCollectShippingRates(true);
$this->_quote->collectTotals()->save();
}
}
}
/**
* Place the order and recurring payment profiles when customer returned from paypal
* Until this moment all quote data must be valid
*
* @param string $token
* @param string $shippingMethodCode
*/
public function place($token, $shippingMethodCode = null)
{
if ($shippingMethodCode) {
$this->updateShippingMethod($shippingMethodCode);
}
if (!$this->_quote->getCustomerId()) {
$this->_quote->setCustomerIsGuest(true)
->setCustomerGroupId(Mage_Customer_Model_Group::NOT_LOGGED_IN_ID)
->setCustomerEmail($this->_quote->getBillingAddress()->getEmail());
}
$this->_ignoreAddressValidation();
$this->_quote->collectTotals();
$service = Mage::getModel('sales/service_quote', $this->_quote);
$service->submitAll();
$this->_quote->save();
$this->_recurringPaymentProfiles = $service->getRecurringPaymentProfiles();
// TODO: send recurring profile emails
$order = $service->getOrder();
if (!$order) {
return;
}
$this->_billingAgreement = $order->getPayment()->getBillingAgreement();
// commence redirecting to finish payment, if paypal requires it
if ($order->getPayment()->getAdditionalInformation(Mage_Paypal_Model_Express_Checkout::PAYMENT_INFO_TRANSPORT_REDIRECT)) {
$this->_redirectUrl = $this->_config->getExpressCheckoutCompleteUrl($token);
}
switch ($order->getState()) {
// even after placement paypal can disallow to authorize/capture, but will wait until bank transfers money
case Mage_Sales_Model_Order::STATE_PENDING_PAYMENT:
// TODO
break;
// regular placement, when everything is ok
case Mage_Sales_Model_Order::STATE_PROCESSING:
case Mage_Sales_Model_Order::STATE_COMPLETE:
case Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW:
$order->sendNewOrderEmail();
break;
}
$this->_order = $order;
}
/**
* Make sure addresses will be saved without validation errors
*/
private function _ignoreAddressValidation()
{
$this->_quote->getBillingAddress()->setShouldIgnoreValidation(true);
if (!$this->_quote->getIsVirtual()) {
$this->_quote->getShippingAddress()->setShouldIgnoreValidation(true);
}
}
/**
* Determine whether redirect somewhere specifically is required
*
* @param string $action
* @return string
*/
public function getRedirectUrl()
{
return $this->_redirectUrl;
}
/**
* Return recurring payment profiles
*
* @return array
*/
public function getRecurringPaymentProfiles()
{
return $this->_recurringPaymentProfiles;
}
/**
* Get created billing agreement
*
* @return Mage_Sales_Model_Billing_Agreement|null
*/
public function getBillingAgreement()
{
return $this->_billingAgreement;
}
/**
* Return order
*
* @return Mage_Sales_Model_Order
*/
public function getOrder()
{
return $this->_order;
}
/**
* Set create billing agreement flag to api call
*
* @return Mage_Paypal_Model_Express_Checkout
*/
protected function _setBillingAgreementRequest()
{
if (!$this->_customerId || $this->_quote->hasNominalItems()) {
return $this;
}
$isRequested = $this->_isBARequested || $this->_quote->getPayment()
->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT);
if (!($this->_config->allow_ba_signup == Mage_Paypal_Model_Config::EC_BA_SIGNUP_AUTO
|| $isRequested && $this->_config->shouldAskToCreateBillingAgreement())) {
return $this;
}
if (!Mage::getModel('sales/billing_agreement')->needToCreateForCustomer($this->_customerId)) {
return $this;
}
$this->_api->setBillingType($this->_api->getBillingAgreementType());
return $this;
}
/**
* @return Mage_Paypal_Model_Api_Nvp
*/
protected function _getApi()
{
if (null === $this->_api) {
$this->_api = Mage::getModel($this->_apiType)->setConfigObject($this->_config);
}
return $this->_api;
}
/**
* Attempt to collect address shipping rates and return them for further usage in instant update API
* Returns empty array if it was impossible to obtain any shipping rate
* If there are shipping rates obtained, the method must return one of them as default.
*
* @param Mage_Sales_Model_Quote_Address $address
* @param bool $mayReturnEmpty
* @return array|false
*/
protected function _prepareShippingOptions(Mage_Sales_Model_Quote_Address $address, $mayReturnEmpty = false)
{
$options = array(); $i = 0; $iMin = false; $min = false; $iDefault = false;
foreach ($address->getGroupedAllShippingRates() as $group) {
foreach ($group as $rate) {
$amount = (float)$rate->getPrice();
if (!$rate->getMethodTitle() || 0.00 == $amount) {
continue;
}
$isDefault = $address->getShippingMethod() === $rate->getCode();
if ($isDefault) {
$iDefault = $i;
}
$options[$i] = new Varien_Object(array(
'is_default' => $isDefault,
'name' => "{$rate->getCarrierTitle()} - {$rate->getMethodTitle()}",
'code' => $rate->getCode(),
'amount' => $amount,
));
if (false === $min || $amount < $min) {
$min = $amount; $iMin = $i;
}
$i++;
}
}
if ($mayReturnEmpty) {
$options[] = new Varien_Object(array(
'is_default' => (false === $iDefault ? true : false),
'name' => 'N/A',
'code' => 'no_rate',
'amount' => 0.00,
));
} elseif (false === $iDefault && isset($options[$iMin])) {
$options[$iMin]->setIsDefault(true);
}
return $options;
}
/**
* Try to find whether the code provided by PayPal corresponds to any of possible shipping rates
* This method was created only because PayPal has issues with returning the selected code.
* If in future the issue is fixed, we don't need to attempt to match it. It would be enough to set the method code
* before collecting shipping rates
*
* @param Mage_Sales_Model_Quote_Address $address
* @param string $selectedCode
* @return string
*/
protected function _matchShippingMethodCode(Mage_Sales_Model_Quote_Address $address, $selectedCode)
{
$options = $this->_prepareShippingOptions($address, false);
foreach ($options as $option) {
if ($selectedCode === $option['code'] // the proper case as outlined in documentation
|| $selectedCode === $option['name'] // workaround: PayPal may return name instead of the code
// workaround: PayPal may concatenate code and name, and return it instead of the code:
|| $selectedCode === "{$option['code']} {$option['name']}"
) {
return $option['code'];
}
}
return '';
}
}