X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=CRM%2FCore%2FPayment%2FPayPalProIPN.php;h=5ff6e0d31749d9c1c039a93a8195b6e8d81ca2a1;hb=415a9951148804ba9a43d952483171d1e6a6cce8;hp=6a4771be7dd6cc2cb481339fd9ab39f1f9358b10;hpb=65e978281bfd3fce49e5a04f440c8992cd516852;p=civicrm-core.git diff --git a/CRM/Core/Payment/PayPalProIPN.php b/CRM/Core/Payment/PayPalProIPN.php index 6a4771be7d..5ff6e0d317 100644 --- a/CRM/Core/Payment/PayPalProIPN.php +++ b/CRM/Core/Payment/PayPalProIPN.php @@ -221,16 +221,16 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN { * Process recurring contributions. * * @param array $input - * @param \CRM_Contribute_BAO_ContributionRecur $recur - * @param \CRM_Contribute_BAO_Contribution $contribution - * @param bool $first * * @throws \API_Exception * @throws \CRM_Core_Exception * @throws \CiviCRM_API3_Exception * @throws \Civi\API\Exception\UnauthorizedException */ - public function recur($input, $recur, $contribution, $first) { + public function recur(array $input): void { + // check if first contribution is completed, else complete first contribution + $first = !$this->isContributionCompleted(); + $recur = $this->getContributionRecurObject(); if (!isset($input['txnType'])) { Civi::log()->debug('PayPalProIPN: Could not find txn_type in input request.'); echo 'Failure: Invalid parameters

'; @@ -288,10 +288,7 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN { break; case 'recurring_payment': - if ($first) { - $recur->start_date = $now; - } - else { + if (!$first) { if ($input['paymentStatus'] !== 'Completed') { throw new CRM_Core_Exception("Ignore all IPN payments that are not completed"); } @@ -319,11 +316,6 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN { $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_END; } - // make sure the contribution status is not done - // since order of ipn's is unknown - if ($recur->contribution_status_id != $contributionStatuses['Completed']) { - $recur->contribution_status_id = $contributionStatuses['In Progress']; - } break; } @@ -342,25 +334,18 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN { return; } - // CRM-13737 - am not aware of any reason why payment_date would not be set - this if is a belt & braces - $contribution->receive_date = !empty($input['payment_date']) ? date('YmdHis', strtotime($input['payment_date'])) : $now; - - $this->single($input, $contribution, TRUE, $first); + $this->single($input); } /** * @param array $input - * @param \CRM_Contribute_BAO_Contribution $contribution - * @param bool $recur - * @param bool $first * * @return void * @throws \API_Exception * @throws \CRM_Core_Exception * @throws \CiviCRM_API3_Exception - * @throws \Civi\API\Exception\UnauthorizedException */ - public function single($input, $contribution, $recur = FALSE, $first = FALSE) { + protected function single(array $input): void { // make sure the invoice is valid and matches what we have in the contribution record if (!$this->isContributionCompleted()) { @@ -390,7 +375,7 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN { 'cancel_date' => 'now', 'contribution_status_id:name' => 'Cancelled', ])->addWhere('id', '=', $this->getContributionID())->execute(); - Civi::log()->debug("Setting contribution status to Cancelled"); + Civi::log()->debug('Setting contribution status to Cancelled'); return; } if ($status !== 'Completed') { @@ -440,96 +425,38 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN { * @todo the references to POST throughout this class need to be removed * @return void */ - public function main() { + public function main(): void { CRM_Core_Error::debug_var('GET', $_GET, TRUE, TRUE); CRM_Core_Error::debug_var('POST', $_POST, TRUE, TRUE); - $ids = $input = []; + $input = []; try { if ($this->_isPaymentExpress) { $this->handlePaymentExpress(); return; } - $this->_component = $input['component'] = self::getValue('m'); - $input['invoice'] = self::getValue('i', TRUE); - // get the contribution and contact ids from the GET params - $ids['contact'] = $this->getContactID(); - $ids['contribution'] = $this->getContributionID(); - - $this->getInput($input); - - if ($this->_component == 'event') { - $ids['event'] = self::getValue('e', TRUE); - $ids['participant'] = self::getValue('p', TRUE); - $ids['contributionRecur'] = $this->getContributionRecurID(); - } - else { - // get the optional ids - //@ how can this not be broken retrieving from GET as we are dealing with a POST request? - // copy & paste? Note the retrieve function now uses data from _REQUEST so this will be included - $ids['membership'] = self::retrieve('membershipID', 'Integer', 'GET', FALSE); - $ids['contributionRecur'] = $this->getContributionRecurID(); - $ids['contributionPage'] = self::getValue('p', FALSE); - $ids['related_contact'] = self::retrieve('relatedContactID', 'Integer', 'GET', FALSE); - $ids['onbehalf_dupe_alert'] = self::retrieve('onBehalfDupeAlert', 'Integer', 'GET', FALSE); - } - - if (!$ids['membership'] && $this->getContributionRecurID()) { - $sql = " - SELECT m.id - FROM civicrm_membership m -INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contribution_id = %1 - WHERE m.contribution_recur_id = %2 - LIMIT 1"; - $sqlParams = [ - 1 => [$ids['contribution'], 'Integer'], - 2 => [$this->getContributionRecurID(), 'Integer'], - ]; - if ($membershipId = CRM_Core_DAO::singleValueQuery($sql, $sqlParams)) { - $ids['membership'] = $membershipId; - } - } - - $paymentProcessorID = CRM_Utils_Array::value('processor_id', $this->_inputParameters); - if (!$paymentProcessorID) { - $paymentProcessorID = self::getPayPalPaymentProcessorID(); + if ($this->getValue('m') === 'event') { + // Validate required params. + $this->getValue('e'); + $this->getValue('p'); } - $contribution = $this->getContributionObject(); - - // make sure contact exists and is valid - // use the contact id from the contribution record as the id in the IPN may not be valid anymore. - $contact = new CRM_Contact_BAO_Contact(); - $contact->id = $contribution->contact_id; - $contact->find(TRUE); - if ($contact->id != $ids['contact']) { + $input['invoice'] = $this->getValue('i'); + if ($this->getContributionObject()->contact_id !== $this->getContactID()) { // If the ids do not match then it is possible the contact id in the IPN has been merged into another contact which is why we use the contact_id from the contribution - CRM_Core_Error::debug_log_message("Contact ID in IPN {$ids['contact']} not found but contact_id found in contribution {$contribution->contact_id} used instead"); - echo "WARNING: Could not find contact record: {$ids['contact']}

"; - $ids['contact'] = $contribution->contact_id; + CRM_Core_Error::debug_log_message('Contact ID in IPN ' . $this->getContactID() . ' not found but contact_id found in contribution ' . $this->getContributionID() . ' used instead'); + echo 'WARNING: Could not find contact record: ' . $this->getContactID() . '

'; } - // CRM-19478: handle oddity when p=null is set in place of contribution page ID, - if (!empty($ids['contributionPage']) && !is_numeric($ids['contributionPage'])) { - // We don't need to worry if about removing contribution page id as it will be set later in - // CRM_Contribute_BAO_Contribution::loadRelatedObjects(..) using $objects['contribution']->contribution_page_id - unset($ids['contributionPage']); - } - - $ids['paymentProcessor'] = $paymentProcessorID; - $contribution->loadRelatedObjects($input, $ids); - - $input['payment_processor_id'] = $paymentProcessorID; + $this->getInput($input); + $input['payment_processor_id'] = $this->_inputParameters['processor_id'] ?? $this->getPayPalPaymentProcessorID(); if ($this->getContributionRecurID()) { - $contributionRecur = $this->getContributionRecurObject(); - // check if first contribution is completed, else complete first contribution - $first = !$this->isContributionCompleted(); - $this->recur($input, $contributionRecur, $contribution, $first); + $this->recur($input); return; } - $this->single($input, $contribution, FALSE, FALSE); + $this->single($input); } - catch (CRM_Core_Exception $e) { + catch (Exception $e) { Civi::log()->debug($e->getMessage() . ' input {input}', ['input' => $input]); echo 'Invalid or missing data'; } @@ -577,25 +504,20 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr * * For one off IPNS no actual response is required * Recurring is more difficult as we have limited confirmation material - * lets look up invoice id in recur_contribution & rely on the unique transaction id to ensure no - * duplicated - * this may not be acceptable to all sites - e.g. if they are shipping or delivering something in return - * then the quasi security of the ids array might be required - although better to + * lets look up invoice id in recur_contribution & rely on the unique + * transaction id to ensure no duplicated this may not be acceptable to all + * sites - e.g. if they are shipping or delivering something in return then + * the quasi security of the ids array might be required - although better to * http://stackoverflow.com/questions/4848227/validate-that-ipn-call-is-from-paypal * but let's assume knowledge on invoice id & schedule is enough for now esp * for donations only contribute is handled * + * @throws \API_Exception * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ - public function handlePaymentExpress() { - //@todo - loads of copy & paste / code duplication but as this not going into core need to try to - // keep discreet - // also note that a lot of the complexity above could be removed if we used - // http://stackoverflow.com/questions/4848227/validate-that-ipn-call-is-from-paypal - // as membership id etc can be derived by the load objects fn - $objects = $ids = $input = []; - $isFirst = FALSE; - $input['invoice'] = self::getValue('i', FALSE); + public function handlePaymentExpress(): void { + $input = ['invoice' => $this->getValue('i', FALSE)]; //Avoid return in case of unit test. if (empty($input['invoice']) && empty($this->_inputParameters['is_unit_test'])) { return; @@ -611,54 +533,14 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr throw new CRM_Core_Exception('Paypal IPNS not handled other than recurring_payments'); } - $this->getInput($input, $ids); + $this->getInput($input); if ($input['txnType'] === 'recurring_payment' && $this->transactionExists($input['trxn_id'])) { throw new CRM_Core_Exception('This transaction has already been processed'); } - - $ids['contact'] = $contributionRecur['contact_id']; - $ids['contributionRecur'] = $this->getContributionRecurID(); $result = civicrm_api3('contribution', 'getsingle', ['invoice_id' => $input['invoice'], 'contribution_test' => '']); - $this->setContributionID((int) $result['id']); - $ids['contribution'] = $this->getContributionID(); - //@todo hardcoding 'pending' for now - $pendingStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'); - if ($result['contribution_status_id'] == $pendingStatusId) { - $isFirst = TRUE; - } - // arg api won't get this - fix it - $ids['contributionPage'] = CRM_Core_DAO::singleValueQuery("SELECT contribution_page_id FROM civicrm_contribution WHERE invoice_id = %1", [ - 1 => [ - $ids['contribution'], - 'Integer', - ], - ]); - // only handle component at this stage - not terribly sure how a recurring event payment would arise - // & suspec main function may be a victom of copy & paste - // membership would be an easy add - but not relevant to my customer... - $this->_component = $input['component'] = 'contribute'; - $input['trxn_date'] = date('Y-m-d H:i:s', strtotime(self::retrieve('time_created', 'String'))); - $paymentProcessorID = $contributionRecur['payment_processor_id']; - - // Check if the contribution exists - // make sure contribution exists and is valid - $contribution = $this->getContributionObject(); - $objects['contribution'] = &$contribution; - - // CRM-19478: handle oddity when p=null is set in place of contribution page ID, - if (!empty($ids['contributionPage']) && !is_numeric($ids['contributionPage'])) { - // We don't need to worry if about removing contribution page id as it will be set later in - // CRM_Contribute_BAO_Contribution::loadRelatedObjects(..) using $objects['contribution']->contribution_page_id - unset($ids['contributionPage']); - } - - $contribution = &$objects['contribution']; - $ids['paymentProcessor'] = $paymentProcessorID; - $contribution->loadRelatedObjects($input, $ids); - $objects = array_merge($objects, $contribution->_relatedObjects); - - $this->recur($input, $this->getContributionRecurObject(), $objects['contribution'], $isFirst); + $input['trxn_date'] = date('Y-m-d H:i:s', strtotime($this->retrieve('time_created', 'String'))); + $this->recur($input); } /** @@ -707,6 +589,8 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr if (!$contribution->find(TRUE)) { throw new CRM_Core_Exception('Failure: Could not find contribution record'); } + // The DAO types it as int but doesn't return it as int. + $contribution->contact_id = (int) $contribution->contact_id; $this->contributionObject = $contribution; } return $this->contributionObject;