[REF] Move sendNotification out of recur, remove unused related_contact
[civicrm-core.git] / CRM / Core / Payment / PayPalProIPN.php
index 6a684216c90fb730f9efbcf0ebc1ea248263bda1..87b568654665899a9c937da608522eddf916e00c 100644 (file)
@@ -148,21 +148,22 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
    *
    * @param array $input
    * @param array $ids
-   * @param array $objects
+   * @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, $ids, $objects, $first) {
+  public function recur($input, $ids, $recur, $contribution, $first) {
     if (!isset($input['txnType'])) {
       Civi::log()->debug('PayPalProIPN: Could not find txn_type in input request.');
       echo 'Failure: Invalid parameters<p>';
       return;
     }
 
-    $recur = &$objects['contributionRecur'];
-
     // make sure the invoice ids match
     // make sure the invoice is valid and matches what we have in
     // the contribution record
@@ -256,47 +257,42 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
     $recur->save();
 
     if ($sendNotification) {
-      $autoRenewMembership = FALSE;
-      if ($recur->id &&
-        isset($ids['membership']) && $ids['membership']
-      ) {
-        $autoRenewMembership = TRUE;
-      }
       //send recurring Notification email for user
       CRM_Contribute_BAO_ContributionPage::recurringNotify($subscriptionPaymentStatus,
         $ids['contact'],
         $ids['contributionPage'],
         $recur,
-        $autoRenewMembership
+        !empty($ids['membership'])
       );
     }
 
-    if ($txnType != 'recurring_payment') {
+    if ($txnType !== 'recurring_payment') {
       return;
     }
 
     // CRM-13737 - am not aware of any reason why payment_date would not be set - this if is a belt & braces
-    $objects['contribution']->receive_date = !empty($input['payment_date']) ? date('YmdHis', strtotime($input['payment_date'])) : $now;
+    $contribution->receive_date = !empty($input['payment_date']) ? date('YmdHis', strtotime($input['payment_date'])) : $now;
 
     $this->single($input, [
-      'related_contact' => $ids['related_contact'] ?? NULL,
       'participant' => $ids['participant'] ?? NULL,
       'contributionRecur' => $recur->id ?? NULL,
-    ], $objects, TRUE, $first);
+    ], $contribution, TRUE, $first);
   }
 
   /**
    * @param array $input
    * @param array $ids
-   * @param array $objects
+   * @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, $ids, $objects, $recur = FALSE, $first = FALSE) {
-    $contribution = &$objects['contribution'];
+  public function single($input, $ids, $contribution, $recur = FALSE, $first = FALSE) {
 
     // make sure the invoice is valid and matches what we have in the contribution record
     if ((!$recur) || ($recur && $first)) {
@@ -355,7 +351,7 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
       return;
     }
 
-    CRM_Contribute_BAO_Contribution::completeOrder($input, $ids, $objects['contribution']);
+    CRM_Contribute_BAO_Contribution::completeOrder($input, $ids, $contribution);
   }
 
   /**
@@ -399,7 +395,7 @@ class CRM_Core_Payment_PayPalProIPN extends CRM_Core_Payment_BaseIPN {
         $this->handlePaymentExpress();
         return;
       }
-      $objects = $ids = $input = [];
+      $ids = $input = [];
       $this->_component = $input['component'] = self::getValue('m');
       $input['invoice'] = self::getValue('i', TRUE);
       // get the contribution and contact ids from the GET params
@@ -445,28 +441,60 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
         $paymentProcessorID = self::getPayPalPaymentProcessorID();
       }
 
-      if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
-        return;
+      // Check if the contribution exists
+      // make sure contribution exists and is valid
+      $contribution = new CRM_Contribute_BAO_Contribution();
+      $contribution->id = $ids['contribution'];
+      if (!$contribution->find(TRUE)) {
+        throw new CRM_Core_Exception('Failure: Could not find contribution record for ' . (int) $contribution->id, NULL, ['context' => "Could not find contribution record: {$contribution->id} in IPN request: " . print_r($input, TRUE)]);
+      }
+
+      // 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']) {
+        // 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']}<p>";
+        $ids['contact'] = $contribution->contact_id;
       }
 
+      // 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;
 
       if ($ids['contributionRecur']) {
+        $contributionRecur = new CRM_Contribute_BAO_ContributionRecur();
+        $contributionRecur->id = $ids['contributionRecur'];
+        if (!$contributionRecur->find(TRUE)) {
+          CRM_Core_Error::debug_log_message("Could not find contribution recur record: {$ids['ContributionRecur']} in IPN request: " . print_r($input, TRUE));
+          echo "Failure: Could not find contribution recur record: {$ids['ContributionRecur']}<p>";
+          return;
+        }
         // check if first contribution is completed, else complete first contribution
         $first = TRUE;
         $completedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
-        if ($objects['contribution']->contribution_status_id == $completedStatusId) {
+        if ($contribution->contribution_status_id == $completedStatusId) {
           $first = FALSE;
         }
-        $this->recur($input, $ids, $objects, $first);
+        $this->recur($input, $ids, $contributionRecur, $contribution, $first);
         return;
       }
 
       $this->single($input, [
-        'related_contact' => $ids['related_contact'] ?? NULL,
         'participant' => $ids['participant'] ?? NULL,
         'contributionRecur' => $ids['contributionRecur'] ?? NULL,
-      ], $objects, FALSE, FALSE);
+      ], $contribution, FALSE, FALSE);
     }
     catch (CRM_Core_Exception $e) {
       Civi::log()->debug($e->getMessage());
@@ -576,10 +604,39 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
     $input['trxn_date'] = date('Y-m-d H:i:s', strtotime(self::retrieve('time_created', 'String')));
     $paymentProcessorID = $contributionRecur['payment_processor_id'];
 
-    if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
-      throw new CRM_Core_Exception('Data did not validate');
+    // Check if the contribution exists
+    // make sure contribution exists and is valid
+    $contribution = new CRM_Contribute_BAO_Contribution();
+    $contribution->id = $ids['contribution'];
+    if (!$contribution->find(TRUE)) {
+      throw new CRM_Core_Exception('Failure: Could not find contribution record for ' . (int) $contribution->id, NULL, ['context' => "Could not find contribution record: {$contribution->id} in IPN request: " . print_r($input, TRUE)]);
+    }
+
+    if (!empty($ids['contributionRecur'])) {
+      $contributionRecur = new CRM_Contribute_BAO_ContributionRecur();
+      $contributionRecur->id = $ids['contributionRecur'];
+      if (!$contributionRecur->find(TRUE)) {
+        CRM_Core_Error::debug_log_message("Could not find contribution recur record: {$ids['ContributionRecur']} in IPN request: " . print_r($input, TRUE));
+        echo "Failure: Could not find contribution recur record: {$ids['ContributionRecur']}<p>";
+        return FALSE;
+      }
     }
-    $this->recur($input, $ids, $objects, $isFirst);
+
+    $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, $ids, $objects['contributionRecur'], $objects['contribution'], $isFirst);
   }
 
   /**