Source of file Paypal.php
Size: 62,940 Bytes - Last Modified: 2021-12-23T10:21:25+00:00
/var/www/docs.ssmods.com/process/src/src/Model/Paypal.php
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511 | <?php namespace SilverCart\Paypal\Model; use SilverCart\Dev\Tools; use SilverCart\Admin\Forms\GridField\GridFieldConfig_ExclusiveRelationEditor; use SilverCart\Forms\FormFields\FieldGroup; use SilverCart\Forms\FormFields\TextField; use SilverCart\Model\Customer\Customer; use SilverCart\Model\Order\Order; use SilverCart\Model\Payment\PaymentMethod; use SilverCart\Model\Payment\PaymentStatus; use SilverCart\ORM\Connect\DBMigration; use SilverCart\Paypal\Model\PaypalTranslation; use SilverStripe\Control\Director; use SilverStripe\Control\HTTPRequest; use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\ToggleCompositeField; use SilverStripe\Forms\GridField\GridField; /** * Paypal payment modul * * @package SilverCart * @subpackage Paypal_Model * @author Sebastian Diel <sdiel@pixeltricks.de>, * Sascha Koehler <skoehler@pixeltricks.de> * @since 24.04.2018 * @copyright 2018 pixeltricks GmbH * @license see license file in modules root directory */ class Paypal extends PaymentMethod { const SESSION_KEY = 'Silvercart.Paypal'; const TOKEN_SESSION_KEY = self::SESSION_KEY . '.Token'; const PAYERID_SESSION_KEY = self::SESSION_KEY . '.PayerID'; const BEFORE_PAYMENT_PROVIDER_IS_PROCESSED_SESSION_KEY = self::SESSION_KEY . '.ProcessedBeforePaymentProvider'; const AFTER_PAYMENT_PROVIDER_IS_PROCESSED_SESSION_KEY = self::SESSION_KEY . '.ProcessedAfterPaymentProvider'; /** * db field definitions * * @var array */ private static $db = [ 'paypalSharedSecret' => 'Varchar(255)', 'paypalCheckoutUrl_Dev' => 'Varchar(255)', 'paypalCheckoutUrl_Live' => 'Varchar(255)', 'paypalApiUsername_Dev' => 'Varchar(255)', 'paypalApiUsername_Live' => 'Varchar(255)', 'paypalApiPassword_Dev' => 'Varchar(255)', 'paypalApiPassword_Live' => 'Varchar(255)', 'paypalApiSignature_Dev' => 'Varchar(255)', 'paypalApiSignature_Live' => 'Varchar(255)', 'paypalNvpApiServerUrl_Dev' => 'Varchar(255)', 'paypalNvpApiServerUrl_Live' => 'Varchar(255)', 'paypalSoapApiServerUrl_Dev' => 'Varchar(255)', 'paypalSoapApiServerUrl_Live' => 'Varchar(255)', 'paypalApiVersion_Dev' => 'Varchar(255)', 'paypalApiVersion_Live' => 'Varchar(255)', 'PaymentStatusPaid' => 'Int', 'PaymentStatusCanceled' => 'Int', 'PaymentStatusPending' => 'Int', 'PaymentStatusRefunded' => 'Int' ]; /** * Casted attributes * * @var array */ private static $casting = [ 'CheckoutUrl' => 'Text', 'ApiUsername' => 'Text', 'ApiPassword' => 'Text', 'ApiSignature' => 'Text', 'NvpApiServerUrl' => 'Text', 'SoapApiServerUrl' => 'Text', 'ApiVersion' => 'Text', ]; /** * Default db values. * * @var array */ private static $defaults = [ 'paypalCheckoutUrl_Dev' => 'https://www.sandbox.paypal.com/cgi-bin/webscr?', 'paypalCheckoutUrl_Live' => 'https://www.paypal.com/cgi-bin/webscr?', 'paypalNvpApiServerUrl_Dev' => 'https://api-3t.sandbox.paypal.com/nvp', 'paypalNvpApiServerUrl_Live' => 'https://api-3t.paypal.com/nvp', 'paypalSoapApiServerUrl_Dev' => 'https://api-3t.sandbox.paypal.com/2.0', 'paypalSoapApiServerUrl_Live' => 'https://api-3t.paypal.com/2.0', 'paypalApiVersion_Dev' => '2.3', 'paypalApiVersion_Live' => '2.3', ]; /** * 1:n relationships. * * @var array */ private static $has_many = [ 'PaypalTranslations' => PaypalTranslation::class, ]; /** * DB table name * * @var string */ private static $table_name = 'SilvercartPaymentPaypal'; /** * contains module name for display in the admin backend * * @var string */ protected $moduleName = 'Paypal'; /** * contains name for the shared secret ID; Used by paypal at the IPN answer * * @var string */ protected $sharedSecretVariableName = 'sh'; /** * contains all strings of the paypal answer which declare the transaction status false * * @var array */ public $failedPaypalStatus = [ 'Denied', 'Expired', 'Failed', 'Voided', ]; /** * contains all strings of the paypal answer which declare the transaction status true * * @var array */ public $successPaypalStatus = [ 'Completed', 'Processed', 'Canceled-Reversal', ]; /** * contains all strings of the paypal answer of a withdrawn payment * * @var array */ public $refundedPaypalStatus = [ 'Refunded', 'Reversed', ]; /** * contains all strings of the paypal answer which declare the transaction status pending * * @var array */ public $pendingPaypalStatus = [ 'Pending', 'Created', ]; /** * i18n for field labels * * @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields * * @return array * * @author Sebastian Diel <sdiel@pixeltricks.de>, * Roland Lehmann <rlehmann@pixeltricks.de> * @since 24.04.2018 */ public function fieldLabels($includerelations = true) : array { $this->beforeUpdateFieldLabels(function(&$labels) { $labels = array_merge( $labels, Tools::field_labels_for(self::class), [ 'paypalSharedSecret' => _t(self::class . '.SHARED_SECRET', 'shared secret for secure communication'), 'paypalCheckoutUrl' => _t(self::class . '.CHECKOUT_URL', 'URL to the paypal checkout'), 'paypalCheckoutUrl_Dev' => _t(self::class . '.CHECKOUT_URL', 'URL to the paypal checkout'), 'paypalCheckoutUrl_Live' => _t(self::class . '.CHECKOUT_URL', 'URL to the paypal checkout'), 'paypalApiUsername' => _t(self::class . '.API_USERNAME', 'API username'), 'paypalApiUsername_Dev' => _t(self::class . '.API_USERNAME', 'API username'), 'paypalApiUsername_Live' => _t(self::class . '.API_USERNAME', 'API username'), 'paypalApiPassword' => _t(self::class . '.API_PASSWORD', 'API password'), 'paypalApiPassword_Dev' => _t(self::class . '.API_PASSWORD', 'API password'), 'paypalApiPassword_Live' => _t(self::class . '.API_PASSWORD', 'API password'), 'paypalApiSignature' => _t(self::class . '.API_SIGNATURE', 'API signature'), 'paypalApiSignature_Dev' => _t(self::class . '.API_SIGNATURE', 'API signature'), 'paypalApiSignature_Live' => _t(self::class . '.API_SIGNATURE', 'API signature'), 'paypalApiVersion' => _t(self::class . '.API_VERSION', 'API version'), 'paypalApiVersion_Dev' => _t(self::class . '.API_VERSION', 'API version'), 'paypalApiVersion_Live' => _t(self::class . '.API_VERSION', 'API version'), 'paypalNvpApiServerUrl' => _t(self::class . '.URL_API_NVP', 'URL to the paypal NVP API server'), 'paypalNvpApiServerUrl_Dev' => _t(self::class . '.URL_API_NVP', 'URL to the paypal NVP API server'), 'paypalNvpApiServerUrl_Live' => _t(self::class . '.URL_API_NVP', 'URL to the paypal NVP API server'), 'paypalSoapApiServerUrl' => _t(self::class . '.URL_API_SOAP', 'URL to the paypal SOAP API server'), 'paypalSoapApiServerUrl_Dev' => _t(self::class . '.URL_API_SOAP', 'URL to the paypal SOAP API server'), 'paypalSoapApiServerUrl_Live' => _t(self::class . '.URL_API_SOAP', 'URL to the paypal SOAP API server'), 'TabPaymentStatus' => _t(self::class . '.TabPaymentStatus', 'Attributed Payment Status'), 'TabApiDev' => _t(self::class . '.API_DEVELOPMENT_MODE', 'API development mode'), 'TabApiLive' => _t(self::class . '.API_LIVE_MODE', 'API live mode'), 'TabUrlsDev' => _t(self::class . '.URLS_DEV_MODE', 'URLs of dev mode'), 'TabUrlsLive' => _t(self::class . '.URLS_LIVE_MODE', 'URLs of live mode'), 'PaypalApiData' => _t(self::class . '.PaypalApiData', 'PayPal login data'), 'OrderConfirmationSubmitButtonTitle' => _t(self::class . '.ORDER_CONFIRMATION_SUBMIT_BUTTON_TITLE', 'Proceed to payment via PayPal'), 'PaypalTranslations' => PaypalTranslation::singleton()->plural_name(), 'StatusPaid' => _t(PaymentStatus::class . '.DefaultStatusPaid', 'Paid'), 'StatusPaypalRefunded' => _t(self::class . '.StatusPaypalRefunding', 'PayPal refunding'), 'StatusPaypalPending' => _t(self::class . '.StatusPaypalPending', 'PayPal pending'), 'StatusPaypalSuccess' => _t(self::class . '.StatusPaypalSuccess', 'Payment approved by PayPal'), 'StatusPaypalError' => _t(self::class . '.StatusPaypalError', 'PayPal error'), 'StatusPaypalCanceled' => _t(self::class . '.StatusPaypalCanceled', 'PayPal canceled'), 'AnErrorOccurredPaymentFailed' => _t(self::class . '.AnErrorOccurredPaymentFailed', 'PayPal payment failed (error 10417)'), ] ); }); return parent::fieldLabels($includerelations); } /** * Adds the fields for the PayPal API * * @param FieldList $fields FieldList to add fields to * @param bool $forDev Add fields for dev or live mode? * * @return void */ protected function getFieldsForAPI($fields, $forDev = false) : void { $mode = 'Live'; if ($forDev) { $mode = 'Dev'; } $apiGroup = FieldGroup::create('APIDevGroup', '', $fields); $apiGroup->push(TextField::create('paypalApiUsername_' . $mode, $this->fieldLabel('paypalApiUsername_' . $mode))); $apiGroup->push(TextField::create('paypalApiPassword_' . $mode, $this->fieldLabel('paypalApiPassword_' . $mode))); $apiGroup->push(TextField::create('paypalApiSignature_' . $mode, $this->fieldLabel('paypalApiSignature_' . $mode))); $apiGroup->push(TextField::create('paypalApiVersion_' . $mode, $this->fieldLabel('paypalApiVersion_' . $mode))); $fieldlist = [ $apiGroup, TextField::create('paypalCheckoutUrl_' . $mode, $this->fieldLabel('paypalCheckoutUrl_' . $mode)), TextField::create('paypalNvpApiServerUrl_' . $mode, $this->fieldLabel('paypalNvpApiServerUrl_' . $mode)), TextField::create('paypalSoapApiServerUrl_' . $mode, $this->fieldLabel('paypalSoapApiServerUrl_' . $mode)), ]; if (!$forDev) { $fieldlist[] = TextField::create('paypalSharedSecret', $this->fieldLabel('paypalSharedSecret')); } $apiDataToggle = ToggleCompositeField::create( 'PaypalAPI' . $mode, $this->fieldLabel('PaypalApiData') . ' "' . $this->fieldLabel('mode' . $mode) . '"', $fieldlist )->setHeadingLevel(4)->setStartClosed(true); $fields->addFieldToTab('Root.Basic', $apiDataToggle); } /** * Adds the fields for the PayPal order status * * @param FieldList $fields FieldList to add fields to * * @return void */ protected function getFieldsForPaymentStatus($fields) : void { $paymentStatus = PaymentStatus::get(); $fieldlist = [ $fields->dataFieldByName('PaymentStatusID'), DropdownField::create('PaymentStatusPaid', $this->fieldLabel('PaymentStatusPaid'), $paymentStatus->map('ID', 'Title'), $this->PaymentStatusPaid), DropdownField::create('PaymentStatusCanceled', $this->fieldLabel('PaymentStatusCanceled'), $paymentStatus->map('ID', 'Title'), $this->PaymentStatusCanceled), DropdownField::create('PaymentStatusPending', $this->fieldLabel('PaymentStatusPending'), $paymentStatus->map('ID', 'Title'), $this->PaymentStatusPending), DropdownField::create('PaymentStatusRefunded', $this->fieldLabel('PaymentStatusRefunded'), $paymentStatus->map('ID', 'Title'), $this->PaymentStatusRefunded) ]; $paymentStatusDataToggle = ToggleCompositeField::create( 'PaymentStatus', $this->fieldLabel('TabPaymentStatus'), $fieldlist )->setHeadingLevel(4)->setStartClosed(true); $fields->removeByName('PaymentStatusID'); $fields->addFieldToTab('Root.Basic', $paymentStatusDataToggle); } /** * returns CMS fields * * @return \SilverStripe\Forms\FieldList */ public function getCMSFields() : FieldList { $this->beforeUpdateCMSFields(function(FieldList $fields) { $this->getFieldsForPaymentStatus($fields); $this->getFieldsForAPI($fields, true); $this->getFieldsForAPI($fields); $translations = GridField::create( 'PaypalTranslations', $this->fieldLabel('PaypalTranslations'), $this->PaypalTranslations(), GridFieldConfig_ExclusiveRelationEditor::create() ); $fields->addFieldToTab('Root.Translations', $translations); }); return parent::getCMSFieldsForModules(); } /** * Creates and relates required order status and logo images. * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ public function requireDefaultRecords() : void { parent::requireDefaultRecords(); $requiredStatus = [ 'paid' => $this->fieldLabel('StatusPaid'), 'paypal_refunded' => $this->fieldLabel('StatusPaypalRefunded'), 'paypal_pending' => $this->fieldLabel('StatusPaypalPending'), 'paypal_success' => $this->fieldLabel('StatusPaypalSuccess'), 'paypal_error' => $this->fieldLabel('StatusPaypalError'), 'paypal_canceled' => $this->fieldLabel('StatusPaypalCanceled'), ]; $paymentLogos = [ 'Paypal' => SILVERCART_PAYPAL_IMG_PATH . DIRECTORY_SEPARATOR . 'paypal-payments.png', ]; parent::createRequiredPaymentStatus($requiredStatus); parent::createLogoImageObjects($paymentLogos, self::class); $paypalPayments = Paypal::get()->filter('PaymentStatusPaid', 0); if ($paypalPayments->exists()) { foreach ($paypalPayments as $paypalPayment) { $paypalPayment->PaymentStatusPaid = PaymentStatus::get()->filter('Code', 'paid')->first()->ID; $paypalPayment->successPaypalStatus = PaymentStatus::get()->filter('Code', 'paypal_success')->first()->ID; $paypalPayment->failedPaypalStatus = PaymentStatus::get()->filter('Code', 'paypal_error')->first()->ID; $paypalPayment->refundedPaypalStatus = PaymentStatus::get()->filter('Code', 'paypal_refunded')->first()->ID; $paypalPayment->pendingPaypalStatus = PaymentStatus::get()->filter('Code', 'paypal_pending')->first()->ID; $paypalPayment->write(); } } } /** * Renames some DB fields if necessary and then calls parent::requireTable(). * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 07.09.2018 */ public function requireTable() : void { DBMigration::rename_fields($this, [ 'PaidOrderStatus' => 'PaymentStatusPaid', 'CanceledOrderStatus' => 'PaymentStatusCanceled', 'PendingOrderStatus' => 'PaymentStatusPending', 'RefundedOrderStatus' => 'PaymentStatusRefunded', ]); parent::requireTable(); } /*********************************************************************************************** *********************************************************************************************** ** ** ** Mutator methods for casted attributes ** ** ** *********************************************************************************************** **********************************************************************************************/ /** * Set the title for the submit button on the order confirmation step. * * @return string */ public function getOrderConfirmationSubmitButtonTitle() : string { return $this->fieldLabel('OrderConfirmationSubmitButtonTitle'); } /** * Returns the PayPal checkout URL. * * @return string */ public function getPaypalCheckoutUrl() : string { return $this->getCheckoutUrl(); } /** * Returns the PayPal checkout URL. * * @return string */ public function getCheckoutUrl() : string { $paypalCheckoutUrl = ''; if ($this->mode == 'Live') { $paypalCheckoutUrl = "{$this->paypalCheckoutUrl_Live}cmd=_express-checkout&token={$this->getPaypalToken()}"; } else { $paypalCheckoutUrl = "{$this->paypalCheckoutUrl_Dev}cmd=_express-checkout&token={$this->getPaypalToken()}"; } return $paypalCheckoutUrl; } /** * Returns the PayPal API username. * * @return string */ public function getApiUsername() : string { if ($this->mode == 'Live') { return (string) $this->paypalApiUsername_Live; } else { return (string) $this->paypalApiUsername_Dev; } } /** * Returns the PayPal API password. * * @return string */ public function getApiPassword() : string { if ($this->mode == 'Live') { return (string) $this->paypalApiPassword_Live; } else { return (string) $this->paypalApiPassword_Dev; } } /** * Returns the PayPal API signature. * * @return string */ public function getApiSignature() : string { if ($this->mode == 'Live') { return (string) $this->paypalApiSignature_Live; } else { return (string) $this->paypalApiSignature_Dev; } } /** * Returns the PayPal NVP API server URL. * * @return string */ public function getNvpApiServerUrl() : string { if ($this->mode == 'Live') { return (string) $this->paypalNvpApiServerUrl_Live; } else { return (string) $this->paypalNvpApiServerUrl_Dev; } } /** * Returns the PayPal SOAP API server URL. * * @return string */ public function getSoapApiServerUrl() : string { if ($this->mode == 'Live') { return (string) $this->paypalSoapApiServerUrl_Live; } else { return (string) $this->paypalSoapApiServerUrl_Dev; } } /** * Returns the PayPal SOAP API server URL. * * @return string */ public function getApiVersion() : string { if ($this->mode == 'Live') { return (string) $this->paypalApiVersion_Live; } else { return (string) $this->paypalApiVersion_Dev; } } /** * Returns the PayPal IPN target URL. * * @return string */ public function getIPNTargetURL() : string { $url = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr'; if ($this->mode == 'Live') { $url = 'https://ipnpb.paypal.com/cgi-bin/webscr'; } return $url; } /** * accepts the variables and values sent via IPN and saves them to an * associative array. * * @return array */ public function getIPNRequestVariables() : array { $variables = []; $ipnKeysMap = [ 'txn_id' => 'TRANSACTIONID', 'txn_type' => 'TRANSACTIONTYPE', 'payment_type' => 'PAYMENTTYPE', 'payment_status' => 'PAYMENTSTATUS', 'payment_date' => 'ORDERTIME_CUSTOM', 'pending_reason' => 'PENDINGREASON', 'reason_code' => 'REASONCODE', 'mc_currency' => 'CURRENCYCODE', 'mc_fee' => 'FEEAMT', 'mc_gross' => 'AMT', 'tax' => 'TAXAMT', 'shipping' => 'SHIPPINGAMT', 'address_city' => 'SHIPTOCITY', 'address_country' => 'SHIPTOCOUNTRYNAME', 'address_country_code' => 'SHIPTOCOUNTRYCODE', 'address_name' => 'SHIPTONAME', 'address_state' => 'SHIPTOSTATE', 'address_status' => 'ADDRESSSTATUS', 'address_street' => 'SHIPTOADDRESS', 'address_zip' => 'SHIPTOZIP', 'first_name' => 'FIRSTNAME', 'last_name' => 'LASTNAME', 'payer_email' => 'PAYEREMAIL', 'payer_status' => 'PAYERSTATUS', 'verify_sign' => 'VERIFYSIGN', ]; foreach ($ipnKeysMap as $ipnVariable => $checkoutVariable) { if (isset($_REQUEST[$ipnVariable]) && $encoding = mb_detect_encoding($_REQUEST[$ipnVariable]) && $encoding != 'UTF-8' ) { $variables[$checkoutVariable] = iconv($encoding, 'UTF-8', $_REQUEST[$ipnVariable]); } else { $variables[$checkoutVariable] = utf8_encode($_REQUEST[$ipnVariable]); } } $variables['ORDERTIME_CUSTOM'] = date('Y-m-d H:i:s', strtotime($variables['ORDERTIME_CUSTOM'])); $this->Log('getIPNRequestVariables: Incoming Request Variables', var_export($_REQUEST, true)); $this->Log('getIPNRequestVariables: Translated Request Variables', var_export($variables, true)); return $variables; } /** * returns an associative array with data passed to the field "Custom". * * @return array */ public function getIPNCustomVariables() : array { $variables = []; if (isset($_REQUEST['custom'])) { if (strpos($_REQUEST['custom'], ',') !== false) { $pairStr = explode(',', $_REQUEST['custom']); } else { $pairStr = $_REQUEST['custom']; } $pairArr = explode('=', $pairStr); $variables[$pairArr[0]] = $pairArr[1]; } return $variables; } /** * retireves paypal PayerID from the URL; IPN notification variable is different * from the checkout notification. * * @return string */ public function getPayerID() : string { $payerID = ''; if (isset($_REQUEST['payer_id'])) { $payerID = $_REQUEST['payer_id']; } elseif (isset($_REQUEST['PayerID'])) { $payerID = $_REQUEST['PayerID']; } else { $payerID = Tools::Session()->get(self::PAYERID_SESSION_KEY); } return (string) $payerID; } /** * returns the Paypal token saved to the session * * @return string */ public function getPaypalToken() : string { return (string) Tools::Session()->get(self::TOKEN_SESSION_KEY); } /*********************************************************************************************** *********************************************************************************************** ** ** ** Payment processing section. SilverCart checkout will call these methods: ** ** ** ** - canProcessBeforePaymentProvider ** ** - canProcessAfterPaymentProvider ** ** - canProcessBeforeOrder ** ** - canProcessAfterOrder ** ** - canPlaceOrder ** ** - processBeforePaymentProvider ** ** - processAfterPaymentProvider ** ** - processBeforeOrder ** ** - processAfterOrder ** ** - processNotification ** ** - processConfirmationText ** ** ** *********************************************************************************************** **********************************************************************************************/ /** * Returns whether the checkout is ready to call self::processBeforePaymentProvider(). * * @param array $checkoutData Checkout data * * @return bool * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ public function canProcessBeforePaymentProvider(array $checkoutData) : bool { return !$this->beforePaymentProviderIsProcessed(); } /** * Returns whether the checkout is ready to call self::processAfterPaymentProvider(). * * @param array $checkoutData Checkout data * * @return bool * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ public function canProcessAfterPaymentProvider(array $checkoutData) : bool { $can = false; $request = $this->getController()->getRequest(); $token = $request->getVar('token'); $payerID = $request->getVar('PayerID'); if (!is_null($token) && !is_null($payerID) ) { $can = true; } return $can && $this->beforePaymentProviderIsProcessed() && !$this->afterPaymentProviderIsProcessed(); } /** * Is called by default checkout right before placing an order. * If this returns false, the order won't be placed and the checkout won't be finalized. * * @param array $checkoutData Checkout data * * @return bool * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ public function canPlaceOrder(array $checkoutData) : bool { return $this->beforePaymentProviderIsProcessed() && $this->afterPaymentProviderIsProcessed(); } /** * Returns whether the checkout is ready to call self::processAfterOrder(). * * @param array $checkoutData Checkout data * * @return bool * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ public function canProcessAfterOrder(Order $order, array $checkoutData) : bool { return $this->canPlaceOrder($checkoutData) && $order instanceof Order;// && $order->exists(); } /** * Is called by default checkout right before placing an order. * - fetches the Paypal token * - saves the Paypal token to the session * - redirects to Paypal checkout * * @param array $checkoutData Checkout data * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ protected function processBeforePaymentProvider(array $checkoutData) : void { $token = $this->fetchPaypalToken($checkoutData); if (!$this->errorOccured) { $this->saveToken($token); } if (!($token === false && $this->errorOccured) ) { $skip = false; $this->extend('skipProcessBeforePaymentProvider', $skip); if ($skip) { return; } $this->getController()->redirect($this->CheckoutUrl); Tools::Session()->set(self::BEFORE_PAYMENT_PROVIDER_IS_PROCESSED_SESSION_KEY, true); Tools::saveSession(); } } /** * Is called right after returning to the checkout after being redirected to PayPal. * PayPal sends the PayerID during this step which will be saved to the session. * * @param array $checkoutData Checkout data * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de>, * Sascha Koehler <skoehler@pixeltricks.de> * @since 09.04.2014 */ public function processAfterPaymentProvider(array $checkoutData) : void { $request = $this->getController()->getRequest(); $token = $request->getVar('token'); $payerID = $request->getVar('PayerID'); if (is_null($token)) { $this->Log('processAfterPaymentProvider', 'ERROR: paypal token is not set.'); $this->errorOccured = true; } if (is_null($payerID)) { $this->Log('processAfterPaymentProvider', 'ERROR: paypal payerID is not set.'); $this->errorOccured = true; } if ($this->errorOccured) { $this->Log('processAfterPaymentProvider', ' - request data:'); $this->Log('processAfterPaymentProvider', var_export($request->getVars(), true)); $this->Log('processAfterPaymentProvider', ''); } else { $this->savePayerID($payerID); Tools::Session()->set(self::AFTER_PAYMENT_PROVIDER_IS_PROCESSED_SESSION_KEY, true); Tools::saveSession(); } } /** * Is called by default checkout right after placing an order. * * @param \SilverCart\Model\Order\Order $order Order * @param array $checkoutData Checkout data * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 25.04.2018 */ protected function processAfterOrder(Order $order, array $checkoutData) : void { $this->doExpressCheckoutPayment($order); $this->clearSession(); } /** * Is called when a payment provider sends a background notification to the shop. * * @param HTTPRequest $request Request data * * @return string * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ protected function processNotification(HTTPRequest $request) : void { if ($this->validateSharedSecret($request) === false) { $this->Log('processNotification', ''); $this->Log('processNotification', 'ERROR: validation of the shared secret failed.'); $this->Log('processNotification', 'request details:'); $this->Log('processNotification', var_export($_REQUEST, true)); $this->Log('processNotification', ''); } if ($this->isValidPaypalIPNCall($request)) { $this->Log('processNotification', 'valid Paypal notification.'); $payerId = $this->getPayerID(); $ipnVariables = $this->getIPNRequestVariables(); $customVariables = $this->getIPNCustomVariables(); $order = Order::get()->byID($customVariables['order_id']); if ($order instanceof Order && $order->exists() ) { if (in_array($ipnVariables['PAYMENTSTATUS'], $this->successPaypalStatus)) { $order->setPaymentStatus(PaymentStatus::get()->byID($this->PaymentStatusPaid)); } elseif (in_array($ipnVariables['PAYMENTSTATUS'], $this->failedPaypalStatus)) { $order->setPaymentStatus(PaymentStatus::get()->byID($this->PaymentStatusCanceled)); } elseif (in_array($ipnVariables['PAYMENTSTATUS'], $this->refundedPaypalStatus)) { $order->setPaymentStatus(PaymentStatus::get()->byID($this->PaymentStatusRefunded)); } elseif (in_array($ipnVariables['PAYMENTSTATUS'], $this->pendingPaypalStatus)) { $order->setPaymentStatus(PaymentStatus::get()->byID($this->PaymentStatusPending)); } } $paypalOrder = PaypalOrder::get()->filter('orderId', $customVariables['order_id'])->first(); if ($paypalOrder instanceof PaypalOrder && $paypalOrder->exists() ) { $paypalOrder->updateOrder( $customVariables['order_id'], $payerId, $ipnVariables ); $this->Log('processNotification', 'updated order status for #' . $customVariables['order_id']); } else { $this->Log('processNotification', 'ERROR: PaypalOrder #' . $customVariables['order_id'] . ' not found.'); } } else { $this->Log('processNotification', 'ERROR: invalid IPN call.'); $this->Log('processNotification', 'request data:'); $this->Log('processNotification', var_export($_REQUEST, true)); $this->Log('processNotification', ''); } } /** * Resets the payment progress hold in session. * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 10.10.2018 */ public function resetProgress() : void { Tools::Session()->set(self::BEFORE_PAYMENT_PROVIDER_IS_PROCESSED_SESSION_KEY, false); Tools::Session()->set(self::AFTER_PAYMENT_PROVIDER_IS_PROCESSED_SESSION_KEY, false); Tools::saveSession(); } /*********************************************************************************************** *********************************************************************************************** ** ** ** Paypal handling methods ** ** ** *********************************************************************************************** **********************************************************************************************/ /** * Clears the PayPal session data. * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 25.04.2018 */ public function clearSession() : void { Tools::Session()->set(self::SESSION_KEY, null); Tools::saveSession(); } /** * Returns whether self::processBeforePaymentProvider() is already processed. * * @return bool */ protected function beforePaymentProviderIsProcessed() : bool { return (bool) Tools::Session()->get(self::BEFORE_PAYMENT_PROVIDER_IS_PROCESSED_SESSION_KEY); } /** * Returns whether self::processAfterPaymentProvider() is already processed. * * @return bool */ protected function afterPaymentProviderIsProcessed() : bool { return (bool) Tools::Session()->get(self::AFTER_PAYMENT_PROVIDER_IS_PROCESSED_SESSION_KEY); } /** * Fetches a paypal token via API-call (SetExpressCheckout) which is used for * identification in further steps; * * @param array $checkoutData Checkout data to inject * * @return string|boolean false * * @author Sebastian Diel <sdiel@pixeltricks.de>, * Sascha Koehler <skoehler@pixeltricks.de> * @since 24.04.2018 */ public function fetchPaypalToken(array $checkoutData = []) { if (empty($checkoutData)) { $checkoutData = $this->getController()->getCheckout()->getData(); } $parameters = $this->initPaypalTokenParameters($checkoutData); $apiCallResult = $this->callPaypalAPI('SetExpressCheckout', $this->generateUrlParams($parameters)); if (strtolower($apiCallResult['ACK']) != 'success' && strtolower($apiCallResult['ACK']) != 'successwithwarning' ) { $this->Log('fetchPaypalToken', 'ERROR: fetching Paypal token failed.'); $this->Log('fetchPaypalToken', ' - API call parameters:'); $this->Log('fetchPaypalToken', var_export($parameters, true)); $this->Log('fetchPaypalToken', ' - API call response:'); $this->Log('fetchPaypalToken', var_export($apiCallResult, true)); $this->Log('fetchPaypalToken', ''); $this->errorOccured = true; $this->addError('Die Kommunikation mit Paypal konnte nicht initialisiert werden.'); return false; } else { $this->Log('fetchPaypalToken', 'fetched paypal token ' . $apiCallResult['TOKEN']); $this->Log('fetchPaypalToken', ' - API call parameters:'); $this->Log('fetchPaypalToken', var_export($parameters, true)); $this->Log('fetchPaypalToken', ' - API call response:'); $this->Log('fetchPaypalToken', var_export($apiCallResult, true)); return $apiCallResult['TOKEN']; } } /** * Initializes the Paypal token parameters to send via API. * * @param array $checkoutData Checkout data * * @return array * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ protected function initPaypalTokenParameters(array $checkoutData) : array { $shippingAddress = $this->getShippingAddress(); $shoppingCart = $this->getShoppingCart(); $positionIndex = 0; $shoppingCart->setShippingMethodID($checkoutData['ShippingMethod']); $shoppingCart->setPaymentMethodID($checkoutData['PaymentMethod']); if ($shippingAddress->IsPackstation) { $streetInfo = $shippingAddress->Packstation . ' ' . $shippingAddress->PostNumber; } else { $streetInfo = $shippingAddress->Street . ' ' . $shippingAddress->StreetNumber; } $parameters = [ 'ADDROVERRIDE' => '1', 'VERSION' => '63', 'PAYMENTREQUEST_0_AMT' => round((float) $shoppingCart->getAmountTotal()->getAmount(), 2), 'PAYMENTREQUEST_0_ITEMAMT' => round((float) $shoppingCart->getTaxableAmountGrossWithoutFees()->getAmount(), 2), 'PAYMENTREQUEST_0_CURRENCYCODE' => $shoppingCart->getAmountTotal()->getCurrency(), 'PAYMENTREQUEST_0_SHIPPINGAMT' => round((float) $shoppingCart->HandlingCostShipment()->getAmount(), 2), 'PAYMENTREQUEST_0_HANDLINGAMT' => round((float) $shoppingCart->HandlingCostPayment()->getAmount(), 2), 'RETURNURL' => $this->getReturnLink(), 'CANCELURL' => $this->getCancelLink(), 'NOTIFYURL' => $this->getNotificationLink(), 'CUSTOM' => '', 'PAYMENTREQUEST_0_RETURNURL' => $this->getReturnLink(), 'PAYMENTREQUEST_0_CANCELURL' => $this->getCancelLink(), 'PAYMENTREQUEST_0_NOTIFYURL' => $this->getNotificationLink(), 'PAYMENTREQUEST_0_SHIPTONAME' => $shippingAddress->FirstName . ' ' . $shippingAddress->Surname, 'PAYMENTREQUEST_0_SHIPTOSTREET' => $streetInfo, 'PAYMENTREQUEST_0_SHIPTOCITY' => $shippingAddress->City, 'PAYMENTREQUEST_0_SHIPTOZIP' => $shippingAddress->Postcode, 'PAYMENTREQUEST_0_SHIPTOSTATE' => $shippingAddress->State, 'PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE' => $shippingAddress->Country()->ISO2, 'PAYMENTREQUEST_0_SHIPTOPHONENUM' => $shippingAddress->Phone, ]; $this->addPositionParameters($shoppingCart, $parameters, $positionIndex); $this->addChargesAndDiscountsForProductsParameters($shoppingCart, $parameters, $positionIndex); $this->addChargesAndDiscountsForTotalParameters($shoppingCart, $parameters, $positionIndex); $this->addTaxAmountParameters($shoppingCart, $parameters); return $parameters; } /** * Adds the shopping cart positions to the Paypal token parameters. * * @param \SilverCart\Model\Order\ShoppingCart $shoppingCart Shopping cart * @param array $parameters Token parameters * @param int &$positionIndex Current position index * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ protected function addPositionParameters($shoppingCart, &$parameters, &$positionIndex) : void { foreach ($shoppingCart->getTaxableShoppingcartPositions() as $position) { /* @var $position \SilverCart\Model\Order\ShoppingCartPosition */ if ($position->hasMethod('getTaxAmount')) { $taxAmount = round($position->getTaxAmount(), 2); } else { $taxAmount = round($position->Product()->getTaxAmount(), 2); } if ($position->hasMethod('getShortDescription')) { $description = substr($position->getShortDescription(), 0, 50); } else { $description = substr($position->Product()->getShortDescription(false), 0, 50); } $parameters['L_PAYMENTREQUEST_0_NAME' . $positionIndex] = $position->Quantity.' x ' . strip_tags($position->getTitle()); $parameters['L_PAYMENTREQUEST_0_DESC' . $positionIndex] = $description; $parameters['L_PAYMENTREQUEST_0_AMT' . $positionIndex] = round((float) $position->getPrice()->getAmount(), 2); $parameters['L_PAYMENTREQUEST_0_ITEMCATEGORY' . $positionIndex] = 'Physical'; $positionIndex++; } } /** * Adds the charges and discounts for cart positions to the Paypal token parameters. * * @param \SilverCart\Model\Order\ShoppingCart $shoppingCart Shopping cart * @param array $parameters Token parameters * @param int &$positionIndex Current position index * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ protected function addChargesAndDiscountsForProductsParameters($shoppingCart, &$parameters, &$positionIndex) : void { if ($shoppingCart->HasChargesAndDiscountsForProducts()) { $position = $shoppingCart->ChargesAndDiscountsForProducts(); $parameters['L_PAYMENTREQUEST_0_NAME' . $positionIndex] = $position->Name; $parameters['L_PAYMENTREQUEST_0_DESC' . $positionIndex] = ''; $parameters['L_PAYMENTREQUEST_0_AMT' . $positionIndex] = round((float) $position->Price->getAmount(), 2); $parameters['L_PAYMENTREQUEST_0_ITEMCATEGORY' . $positionIndex] = 'Physical'; $positionIndex++; } } /** * Adds the charges and discounts for the total amount to the Paypal token parameters. * * @param \SilverCart\Model\Order\ShoppingCart $shoppingCart Shopping cart * @param array $parameters Token parameters * @param int &$positionIndex Current position index * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ protected function addChargesAndDiscountsForTotalParameters($shoppingCart, &$parameters, &$positionIndex) : void { if ($shoppingCart->HasChargesAndDiscountsForTotal()) { $position = $shoppingCart->ChargesAndDiscountsForTotal(); $amount = round((float) $position->Price->getAmount(), 2); $parameters['L_PAYMENTREQUEST_0_NAME' . $positionIndex] = $position->Name; $parameters['L_PAYMENTREQUEST_0_DESC' . $positionIndex] = ''; $parameters['L_PAYMENTREQUEST_0_AMT' . $positionIndex] = $amount; $parameters['L_PAYMENTREQUEST_0_ITEMCATEGORY' . $positionIndex] = 'Physical'; $positionIndex++; // Charges and discounts for total are not possible as a single // position for paypal. // To workaround this, the amount will be added to or reduced from // item amount. $parameters['PAYMENTREQUEST_0_ITEMAMT'] += $amount; $parameters['PAYMENTREQUEST_0_CUSTOM'] = ''; } } /** * Adds the charges and discounts for the total amount to the Paypal token parameters. * * @param \SilverCart\Model\Order\ShoppingCart $shoppingCart Shopping cart * @param array $parameters Token parameters * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ protected function addTaxAmountParameters($shoppingCart, &$parameters) : void { if (!Customer::currentUser()->showPricesGross()) { // Add taxes as a special position when the current order is displayed in net price mode. $taxTotalList = $shoppingCart->getTaxTotal(); if ($taxTotalList instanceof ArrayList && $taxTotalList->exists() ) { $taxAmountTotal = 0; foreach ($taxTotalList as $tax) { $taxAmountTotal += $tax->AmountRaw; } $parameters['PAYMENTREQUEST_0_TAXAMT'] = round((float) $taxAmountTotal, 2); } } } /** * Validation of the Paypal shared secret. * * @param HTTPRequest $request Request data * * @return bool * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ public function validateSharedSecret(HTTPRequest $request) : bool { $secretIsValid = false; $sentSharedSecret = $request->getVar($this->sharedSecretVariableName); if (is_string($sentSharedSecret)) { $ownSharedSecret = mb_convert_encoding($this->paypalSharedSecret, 'UTF-8'); $encodedSharedSecret = mb_convert_encoding(urldecode($sentSharedSecret), 'UTF-8'); if (mb_strstr($ownSharedSecret, $encodedSharedSecret) === $ownSharedSecret) { $secretIsValid = true; } else { $this->Log('validateSharedSecret', ''); $this->Log('validateSharedSecret', 'ERROR: sent shared secret doesn\'t match with the own shared secret.'); $this->Log('validateSharedSecret', ' - requested secret: ' . $sentSharedSecret); $this->Log('validateSharedSecret', ' - own secret: ' . $ownSharedSecret); $this->Log('validateSharedSecret', ''); } } return $secretIsValid; } /** * called via IPN script; processes the request confirmation and adjusts the * order status; * paypal calls this IPN script and sends all data relevant for the request. * To check if the paypal answer is really from paypal, the answer plus an * additional parameter will be sent back to paypal. paypal answers "VERIFIED" * or "INVALID". * If the answer is "VERIFIED" we check if the order status must be adjusted. * * @param HTTPRequest $request Request data * * @return bool * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 05.09.2018 */ public function isValidPaypalIPNCall(HTTPRequest $request) : bool { $requestIsFromPaypal = false; $req = 'cmd=_notify-validate'; $url = $this->getIPNTargetURL(); // Combine request data to an URL encoded string foreach ($request->postVars() as $key => $value) { if (get_magic_quotes_gpc() == 1) { $value = urlencode(stripslashes($value)); } else { $value = urlencode($value); } $req .= "&{$key}={$value}"; } $ch = curl_init($url); curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); curl_setopt($ch, CURLOPT_POSTFIELDS, $req); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Connection: Close']); if (!($res = curl_exec($ch))) { $this->Log('isValidPaypalIPNCall', ''); $this->Log('isValidPaypalIPNCall', 'ERROR: couldn\'t connect to Paypal.'); $this->Log('isValidPaypalIPNCall', ' - URL: ' . $url); $this->Log('isValidPaypalIPNCall', ' - code: ' . curl_errno($ch)); $this->Log('isValidPaypalIPNCall', ' - message: ' . curl_error($ch)); $this->Log('isValidPaypalIPNCall', ''); curl_close($ch); } else { curl_close($ch); if (strcmp ($res, "VERIFIED") == 0) { // IPN call was verified and is valid $requestIsFromPaypal = true; } elseif (strcmp($res, "INVALID") == 0) { // IPN call is NOT valid $this->Log('isValidPaypalIPNCall', ''); $this->Log('isValidPaypalIPNCall', 'ERROR: payment notification was not sent by Paypal.'); $this->Log('isValidPaypalIPNCall', ' - Paypal request: ' . var_export($req, true)); $this->Log('isValidPaypalIPNCall', ' - Paypal response: ' . var_export($res, true)); $this->Log('isValidPaypalIPNCall', ''); } } return $requestIsFromPaypal; } /** * returns payment and shipping information from paypal * * @return void */ public function getExpressCheckoutDetails() { $parameters = [ 'TOKEN' => $this->getPaypalToken(), 'PAYERID' => $this->getPayerID() ]; $response = $this->callPaypalAPI('GetExpressCheckoutDetails', $this->generateUrlParams($parameters)); $this->Log('getExpressCheckoutDetails',''); $this->Log('getExpressCheckoutDetails','parameters:'); $this->Log('getExpressCheckoutDetails', var_export($parameters, true)); $this->Log('getExpressCheckoutDetails','response data:'); $this->Log('getExpressCheckoutDetails', var_export($response, true)); $this->Log('getExpressCheckoutDetails',''); return $response; } /** * Finalizes the PayPal expres payment. * * @param \SilverCart\Model\Order\Order $order Order * * @return boolean * * @author Sebastian Diel <sdiel@pixeltricks.de>, * Sascha Koehler <skoehler@pixeltricks.de> * @since 25.04.2018 */ public function doExpressCheckoutPayment(Order $order = null) { if (is_null($order)) { $order = $this->getOrder(); } $parameters = $this->initPaypalExpressPaymentParameters($order); $response = $this->callPaypalAPI('DoExpressCheckoutPayment', $this->generateUrlParams($parameters)); if (isset($response['ORDERTIME'])) { ['T','Z'];[' ','']; $response['ORDERTIME_CUSTOM'] = str_replace(['T','Z'], [' ',''], $response['ORDERTIME']); } else { $response['ORDERTIME_CUSTOM'] = ''; } $paypalOrder = PaypalOrder::create(); $paypalOrder->updateOrder($order->ID, $this->getPayerID(), $response); if (isset($response['PAYMENTSTATUS'])) { if (in_array($response['PAYMENTSTATUS'], $this->successPaypalStatus)) { $paymentStatusID = $this->PaymentStatusPaid; } elseif (in_array($response['PAYMENTSTATUS'], $this->failedPaypalStatus)) { $paymentStatusID = $this->PaymentStatusCanceled; } elseif (in_array($response['PAYMENTSTATUS'], $this->pendingPaypalStatus)) { $paymentStatusID = $this->PaymentStatusPending; } elseif (in_array($response['PAYMENTSTATUS'], $this->refundedPaypalStatus)) { $paymentStatusID = $this->PaymentStatusRefunded; } else { $paymentStatusID = $this->PaymentStatusCanceled; } } else { $paymentStatusID = $this->PaymentStatusCanceled; } if (is_numeric($paymentStatusID)) { $paymentStatus = PaymentStatus::get()->byID($paymentStatusID); if ($paymentStatus instanceof PaymentStatus) { $order->setPaymentStatus($paymentStatus); } } $this->Log('doExpressCheckoutPayment', ''); $this->Log('doExpressCheckoutPayment', 'done with status: ' . $response['ACK']); $this->Log('doExpressCheckoutPayment', ' - sent parameters:'); $this->Log('doExpressCheckoutPayment', var_export($parameters, true)); $this->Log('doExpressCheckoutPayment', ' - response:'); $this->Log('doExpressCheckoutPayment', var_export($response, true)); $this->Log('doExpressCheckoutPayment', ''); if (strtolower($response['ACK']) != 'success') { $this->errorOccured = true; $this->addError($this->fieldLabel('AnErrorOccurredPaymentFailed')); return false; } else { return true; } } /** * Initializes the Paypal token parameters to send via API. * * @param Order $order Order * * @return array * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ protected function initPaypalExpressPaymentParameters(Order $order) { $cartAmountGross = round((float) $order->AmountTotal->getAmount(), 2); $itemAmountGross = round((float) $order->getPositionsPriceGross()->getAmount(), 2); $itemAmountNet = round((float) $order->getPositionsPriceNet()->getAmount(), 2); $shippingAmount = round((float) $order->HandlingCostShipmentAmount, 2); $handlingAmount = round((float) $order->HandlingCostPaymentAmount, 2); $taxTotal = $itemAmountGross - $itemAmountNet; $notifyUrl = $this->getNotificationLink() . '?' . $this->sharedSecretVariableName . '=' . urlencode($this->paypalSharedSecret) . '&'; $this->Log('initPaypalExpressPaymentParameters', ''); $this->Log('initPaypalExpressPaymentParameters', 'finalizing PayPal payment with these amounts:'); $this->Log('initPaypalExpressPaymentParameters', ' - total (gross): ' . $cartAmountGross); $this->Log('initPaypalExpressPaymentParameters', ' - positions (gross): ' . $itemAmountGross); $this->Log('initPaypalExpressPaymentParameters', ' - positions (net): ' . $itemAmountNet); $this->Log('initPaypalExpressPaymentParameters', ' - shipping: ' . $shippingAmount); $this->Log('initPaypalExpressPaymentParameters', ' - handling: ' . $handlingAmount); $this->Log('initPaypalExpressPaymentParameters', ' - tax: ' . $taxTotal); $this->Log('initPaypalExpressPaymentParameters', ''); return [ 'TOKEN' => $this->getPaypalToken(), 'PAYERID' => $this->getPayerID(), 'PAYMENTACTION' => 'Sale', 'AMT' => $cartAmountGross, 'ITEMAMT' => $itemAmountNet, 'SHIPPINGAMT' => $shippingAmount, 'HANDLINGAMT' => $handlingAmount, 'TAXAMT' => $taxTotal, 'DESC' => 'Order Nr. ' . $order->OrderNumber, 'CURRENCYCODE' => $order->getPriceGross()->getCurrency(), 'CUSTOM' => 'order_id=' . $order->ID, 'NOTIFYURL' => Director::absoluteUrl($notifyUrl), ]; } /** * Creates and returns a string ("key=value&key=value&...") from an * associative array. * * @param array $parameters an associative array * * @return string * * @author Sebastian Diel <sdiel@pixeltricks.de>, * Sascha Koehler <skoehler@pixeltricks.de> * @since 06.11.2015 */ public function generateUrlParams($parameters) { $paramString = ''; foreach ($parameters as $key => $value) { $paramString .= '&' . urlencode($key) . '=' . urlencode($value); } return $paramString; } /** * processes a method call via paypals NVP-API * * Fuehrt einen Methodenaufruf ueber die NVP-API von Paypal durch. * * @param string $methodName method to be called * @param string $nvpStr the string to be sent to the NVP server * * @return array * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ public function callPaypalAPI($methodName, $nvpStr) { $nvpreq = "METHOD=" . urlencode($methodName) . "&VERSION=" . urlencode($this->ApiVersion) . "&PWD=" . urlencode($this->ApiPassword) . "&USER=" . urlencode($this->ApiUsername) . "&SIGNATURE=" . urlencode($this->ApiSignature) . $nvpStr; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->NvpApiServerUrl); curl_setopt($ch, CURLOPT_VERBOSE, 1); //turning off the server and peer verification(TrustManager Concept). curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $nvpreq); $response = curl_exec($ch); $nvpResArray = $this->deformatNVP($response); if (curl_errno($ch)) { $this->Log('callPaypalAPI', 'ERROR: CURL request failed.'); $this->Log('callPaypalAPI', ' - code: ' . curl_errno($ch)); $this->Log('callPaypalAPI', ' - message: ' . curl_error($ch)); $this->Log('callPaypalAPI', ''); return false; } curl_close($ch); return $nvpResArray; } /** * This method will take a NVPString and convert it to an Associative Array and it will decode the response. * It is usefull to search for a particular key and displaying arrays. * * @param string $nvpstr NVPString * * @return void * * @author Sascha Koehler <skoehler@pixeltricks.de> * @since 17.11.2010 */ protected function deformatNVP($nvpstr) { $intial = 0; $nvpArray = []; while (strlen($nvpstr)) { //postion of Key $keypos = strpos($nvpstr, '='); //position of value $valuepos = strpos($nvpstr, '&') ? strpos($nvpstr, '&') : strlen($nvpstr); /* getting the Key and Value values and storing in a Associative Array */ $keyval = substr($nvpstr, $intial, $keypos); $valval = substr($nvpstr, $keypos + 1, $valuepos - $keypos - 1); //decoding the respose $nvpArray[urldecode($keyval)] = urldecode($valval); $nvpstr = substr($nvpstr, $valuepos + 1, strlen($nvpstr)); } return $nvpArray; } /** * saves the paypal token to the session * * @param string $token Paypal token * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ public function saveToken($token) { Tools::Session()->set(self::TOKEN_SESSION_KEY, $token); Tools::saveSession(); } /** * writes the PayerID to the session * * @param string $payerID Payer ID * * @return void * * @author Sebastian Diel <sdiel@pixeltricks.de> * @since 24.04.2018 */ public function savePayerID($payerID) { Tools::Session()->set(self::PAYERID_SESSION_KEY, $payerID); Tools::saveSession(); } } |