From 3910048ce1b8e91b65fccfb5f7395e4fa3369427 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Sun, 12 Jul 2015 17:26:24 +1200 Subject: [PATCH] CRM-16808 standardisation of paypal express --- CRM/Contribute/BAO/Contribution/Utils.php | 28 ++--- CRM/Contribute/Form/Contribution/Confirm.php | 1 - CRM/Contribute/Form/Contribution/Main.php | 116 +++++++++++-------- CRM/Core/Payment.php | 41 ++++++- CRM/Core/Payment/PayPalImpl.php | 36 ++++++ 5 files changed, 150 insertions(+), 72 deletions(-) diff --git a/CRM/Contribute/BAO/Contribution/Utils.php b/CRM/Contribute/BAO/Contribution/Utils.php index 503480b808..a92cda7943 100644 --- a/CRM/Contribute/BAO/Contribution/Utils.php +++ b/CRM/Contribute/BAO/Contribution/Utils.php @@ -184,29 +184,17 @@ class CRM_Contribute_BAO_Contribution_Utils { } } } - elseif ($form->_contributeMode == 'express') { - if ($form->_values['is_monetary'] && $form->_amount > 0.0) { - // determine if express + recurring and direct accordingly - if (!empty($paymentParams['is_recur']) && $paymentParams['is_recur'] == 1) { - if (is_object($payment)) { - $result = $payment->createRecurringPayments($paymentParams); - } - else { - CRM_Core_Error::fatal($paymentObjError); - } + + elseif ($isPaymentTransaction && $form->_contributeMode) { + if ($form->_contributeMode == 'express' && !empty($paymentParams['is_recur']) && $paymentParams['is_recur'] == 1) { + if (is_object($payment)) { + $result = $payment->createRecurringPayments($paymentParams); } else { - if (is_object($payment)) { - $result = $payment->doExpressCheckout($paymentParams); - } - else { - CRM_Core_Error::fatal($paymentObjError); - } + CRM_Core_Error::fatal($paymentObjError); } } - } - elseif ($isPaymentTransaction) { - if ($form->_contributeMode == 'direct') { + else { $paymentParams['contactID'] = $contactID; // Fix for CRM-14354. If the membership is recurring, don't create a @@ -296,7 +284,7 @@ class CRM_Contribute_BAO_Contribution_Utils { } //Do not send an email if Recurring contribution is done via Direct Mode //We will send email once the IPN is received. - if ($form->_contributeMode == 'direct') { + if (!$isPayLater) { return $result; } diff --git a/CRM/Contribute/Form/Contribution/Confirm.php b/CRM/Contribute/Form/Contribution/Confirm.php index dcd9cce58f..6506549f7a 100644 --- a/CRM/Contribute/Form/Contribution/Confirm.php +++ b/CRM/Contribute/Form/Contribution/Confirm.php @@ -916,7 +916,6 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr ) { $transaction = new CRM_Core_Transaction(); $contribSoftContactId = $addressID = NULL; - $contributeMode = $form->_contributeMode; $isMonetary = !empty($form->_values['is_monetary']); $isEmailReceipt = !empty($form->_values['is_email_receipt']); // How do these vary from params? These are currently passed to diff --git a/CRM/Contribute/Form/Contribution/Main.php b/CRM/Contribute/Form/Contribution/Main.php index f1ca8f54d2..ac7490095f 100644 --- a/CRM/Contribute/Form/Contribution/Main.php +++ b/CRM/Contribute/Form/Contribution/Main.php @@ -1293,82 +1293,100 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu // generate and set an invoiceID for this transaction $invoiceID = md5(uniqid(rand(), TRUE)); $this->set('invoiceID', $invoiceID); + $params['invoiceID'] = $invoiceID; + $params['description'] = ts('Online Contribution') . ': ' . (($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $this->_values['title']); // required only if is_monetary and valid positive amount if ($this->_values['is_monetary'] && is_array($this->_paymentProcessor) && ((float ) $params['amount'] > 0.0 || $memFee > 0.0) ) { - - // default mode is direct - $this->set('contributeMode', 'direct'); + $this->setContributeMode(); if ($this->_paymentProcessor && - $this->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_BUTTON + $this->_paymentProcessor['object']->supportsPreApproval() ) { - //get the button name - $buttonName = $this->controller->getButtonName(); - if (in_array($buttonName, - array($this->_expressButtonName, $this->_expressButtonName . '_x', $this->_expressButtonName . '_y') - ) && empty($params['is_pay_later']) - ) { - $this->handlePaypalExpress($invoiceID, $params); + $this->handlePreApproval($params); } } - elseif ($this->_paymentProcessor && - $this->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_NOTIFY - ) { - $this->set('contributeMode', 'notify'); - } - } - // should we skip the confirm page? if (empty($this->_values['is_confirm_enabled'])) { - // call the post process hook for the main page before we switch to confirm - $this->postProcessHook(); + $this->skipToThankYouPage(); + } - // build the confirm page - $confirmForm = &$this->controller->_pages['Confirm']; - $confirmForm->preProcess(); - $confirmForm->buildQuickForm(); + } - // the confirmation page is valid - $data = &$this->controller->container(); - $data['valid']['Confirm'] = 1; + /** + * Assign the billing mode to the template. + * + * This is required for legacy support for contributeMode in templates. + * + * The goal is to remove this parameter & use more relevant parameters. + */ + protected function setContributeMode() { + switch ($this->_paymentProcessor['billing_mode']) { + case CRM_Core_Payment::BILLING_MODE_FORM: + $this->set('contributeMode', 'direct'); + break; - // confirm the contribution - // mainProcess calls the hook also - $confirmForm->mainProcess(); - $qfKey = $this->controller->_key; + case CRM_Core_Payment::BILLING_MODE_BUTTON: + $this->set('contributeMode', 'express'); + break; - // redirect to thank you page - CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_ThankYou_display=1&qfKey=$qfKey", TRUE, NULL, FALSE)); + case CRM_Core_Payment::BILLING_MODE_NOTIFY: + $this->set('contributeMode', 'notify'); + break; } } /** - * @param $invoiceID - * @param $params + * Handle pre approval for processors. + * + * This fits with the flow where a pre-approval is done and then confirmed in the next stage when confirm is hit. * - * @return mixed + * This applies to processors that + * @param array $params */ - protected function handlePaypalExpress($invoiceID, $params) { - $this->set('contributeMode', 'express'); - - $params['invoiceID'] = $invoiceID; - $params['description'] = ts('Online Contribution') . ': ' . (($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $this->_values['title']); - - $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); - $token = $payment->setExpressCheckout($params); - if (is_a($token, 'CRM_Core_Error')) { - CRM_Core_Error::displaySessionError($token); + protected function handlePreApproval(&$params) { + try { + $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); + $result = $payment->doPreApproval($params); + } + catch (\Civi\Payment\Exception\PaymentProcessorException $e) { + CRM_Core_Error::displaySessionError($e->getMessage()); CRM_Utils_System::redirect($params['cancelURL']); } - $this->set('token', $token); - $paymentURL = $this->_paymentProcessor['url_site'] . "/cgi-bin/webscr?cmd=_express-checkout&token=$token"; - CRM_Utils_System::redirect($paymentURL); + $this->set('pre_approval_parameters', $result['pre_approval_parameters']); + if (!empty($result['redirect_url'])) { + CRM_Utils_System::redirect($result['redirect_url']); + } + } + + /** + * Process confirm function and pass browser to the thank you page. + */ + protected function skipToThankYouPage() { + // call the post process hook for the main page before we switch to confirm + $this->postProcessHook(); + + // build the confirm page + $confirmForm = &$this->controller->_pages['Confirm']; + $confirmForm->preProcess(); + $confirmForm->buildQuickForm(); + + // the confirmation page is valid + $data = &$this->controller->container(); + $data['valid']['Confirm'] = 1; + + // confirm the contribution + // mainProcess calls the hook also + $confirmForm->mainProcess(); + $qfKey = $this->controller->_key; + + // redirect to thank you page + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_ThankYou_display=1&qfKey=$qfKey", TRUE, NULL, FALSE)); } } diff --git a/CRM/Core/Payment.php b/CRM/Core/Payment.php index 12cb1e9048..9410f7f168 100644 --- a/CRM/Core/Payment.php +++ b/CRM/Core/Payment.php @@ -222,6 +222,35 @@ abstract class CRM_Core_Payment { return FALSE; } + /** + * Does this processor support pre-approval. + * + * This would generally look like a redirect to enter credentials which can then be used in a later payment call. + * + * Currently Paypal express supports this, with a redirect to paypal after the 'Main' form is submitted in the + * contribution page. This token can then be processed at the confirm phase. Although this flow 'looks' like the + * 'notify' flow a key difference is that in the notify flow they don't have to return but in this flow they do. + * + * @return bool + */ + protected function supportsPreApproval() { + return FALSE; + } + + /** + * Function to action pre-approval if supported + * + * @param array $params + * Parameters from the form + * @param string component + * contribution or event. + * + * This function returns an array which should contain + * - pre_approval_parameters (this will be stored on the calling form & available later) + * - redirect_url (if set the browser will be redirected to this. + */ + protected function doPreApproval($params) {} + /** * Default payment instrument validation. * @@ -631,13 +660,16 @@ abstract class CRM_Core_Payment { * The function ensures an exception is thrown & moves some of this logic out of the form layer and makes the forms * more agnostic. * - * Payment processors should set contribution_status_id. This function adds some historical defaults ie. the + * Payment processors should set payment_status_id. This function adds some historical defaults ie. the * assumption that if a 'doDirectPayment' processors comes back it completed the transaction & in fact * doTransferCheckout would not traditionally come back. * * doDirectPayment does not do an immediate payment for Authorize.net or Paypal so the default is assumed * to be Pending. * + * Once this function is fully rolled out then it will be preferred for processors to throw exceptions than to + * return Error objects + * * @param array $params * * @param string $component @@ -656,7 +688,12 @@ abstract class CRM_Core_Payment { } } else { - $result = $this->doDirectPayment($params, $component); + if ($this->_paymentProcessor['billing_mode'] ==1) { + $result = $this->doDirectPayment($params, $component); + } + else { + $result = $this->doExpressCheckout($params); + } if (is_array($result) && !isset($result['payment_status_id'])) { if (!empty($params['is_recur'])) { // See comment block. diff --git a/CRM/Core/Payment/PayPalImpl.php b/CRM/Core/Payment/PayPalImpl.php index 87e04c1de0..7cc8755026 100644 --- a/CRM/Core/Payment/PayPalImpl.php +++ b/CRM/Core/Payment/PayPalImpl.php @@ -82,6 +82,24 @@ class CRM_Core_Payment_PayPalImpl extends CRM_Core_Payment { return FALSE; } + /** + * Does this processor support pre-approval. + * + * This would generally look like a redirect to enter credentials which can then be used in a later payment call. + * + * Currently Paypal express supports this, with a redirect to paypal after the 'Main' form is submitted in the + * contribution page. This token can then be processed at the confirm phase. Although this flow 'looks' like the + * 'notify' flow a key difference is that in the notify flow they don't have to return but in this flow they do. + * + * @return bool + */ + protected function supportsPreApproval() { + if ($this->_processorName == ts('PayPal_Express')) { + return TRUE; + } + return FALSE; + } + /** * Opportunity for the payment processor to override the entire form build. * @@ -598,6 +616,24 @@ class CRM_Core_Payment_PayPalImpl extends CRM_Core_Payment { return FALSE; } + /** + * Function to action pre-approval if supported + * + * @param array $params + * Parameters from the form + * + * @return array + * - pre_approval_parameters (this will be stored on the calling form & available later) + * - redirect_url (if set the browser will be redirected to this. + */ + public function doPreApproval(&$params) { + $token = $this->setExpressCheckOut($params); + return array( + 'pre_approval_parameters' => array('token' => $token), + 'redirect_url' => $this->_paymentProcessor['url_site'] . "/cgi-bin/webscr?cmd=_express-checkout&token=$token", + ); + } + /** * @param array $params * @param string $component -- 2.25.1