*/
public function validateData(&$input, &$ids, &$objects, $required = TRUE, $paymentProcessorID = NULL) {
- // make sure contact exists and is valid
- $contact = new CRM_Contact_BAO_Contact();
- $contact->id = $ids['contact'];
- if (!$contact->find(TRUE)) {
- CRM_Core_Error::debug_log_message("Could not find contact record: {$ids['contact']} in IPN request: " . print_r($input, TRUE));
- echo "Failure: Could not find contact record: {$ids['contact']}<p>";
- return FALSE;
- }
-
+ // Check if the contribution exists
// make sure contribution exists and is valid
$contribution = new CRM_Contribute_BAO_Contribution();
$contribution->id = $ids['contribution'];
echo "Failure: Could not find contribution record for {$contribution->id}<p>";
return FALSE;
}
+
+ // 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;
+ }
+
+ 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;
+ }
+ }
+
$contribution->receive_date = CRM_Utils_Date::isoToMysql($contribution->receive_date);
$contribution->receipt_date = CRM_Utils_Date::isoToMysql($contribution->receipt_date);
$this->assertEquals('test12345', $contribution['values'][0]['custom_' . $this->_customFieldID]);
}
+ /**
+ * Allow IPNs to validate when the supplied contact_id has been deleted from the database but there is a valid contact id in the contribution recur object or contribution object
+ */
+ public function testPayPalIPNSuccessDeletedContact() {
+ $contactTobeDeleted = $this->individualCreate();
+ $pendingStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending');
+ $completedStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
+ $params = [
+ 'payment_processor_id' => $this->_paymentProcessorID,
+ 'contact_id' => $this->_contactID,
+ 'trxn_id' => NULL,
+ 'invoice_id' => $this->_invoiceID,
+ 'contribution_status_id' => $pendingStatusID,
+ 'is_email_receipt' => TRUE,
+ ];
+ $this->_contributionID = $this->contributionCreate($params);
+ $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $this->_contributionID, 'sequential' => 1]);
+ // assert that contribution created before handling payment via paypal standard has no transaction id set and pending status
+ $this->assertEquals(NULL, $contribution['values'][0]['trxn_id']);
+ $this->assertEquals($pendingStatusID, $contribution['values'][0]['contribution_status_id']);
+ $payPalIPNParams = $this->getPaypalTransaction();
+ $payPalIPNParams['contactID'] = $contactTobeDeleted;
+ $this->callAPISuccess('Contact', 'delete', ['id' => $contactTobeDeleted, 'skip_undelete' => 1]);
+ global $_REQUEST;
+ $_REQUEST = ['q' => CRM_Utils_System::url('civicrm/payment/ipn/' . $this->_paymentProcessorID)] + $payPalIPNParams;
+ // Now process the IPN noting that the contact id that was supplied with the IPN has been deleted but there is still a valid one on the contribution id
+ $payment = CRM_Core_Payment::handlePaymentMethod('PaymentNotification', ['processor_id' => $this->_paymentProcessorID]);
+
+ $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $this->_contributionID, 'sequential' => 1]);
+ // assert that contribution is completed after getting response from paypal standard which has transaction id set and completed status
+ $this->assertEquals($_REQUEST['txn_id'], $contribution['values'][0]['trxn_id']);
+ $this->assertEquals($completedStatusID, $contribution['values'][0]['contribution_status_id']);
+ }
+
/**
* Store Custom data passed in from the PayPalIPN in a custom field
*/