X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=CRM%2FCore%2FPayment%2FBaseIPN.php;h=3976560052fb1a937debca4a8caadf044746a742;hb=9d058543716e160743499ebe0e396daa8bc42afa;hp=f72bbd073834427e280086e9d0273585ed1e68a2;hpb=d626f2f1850dd331da9f8e6941b70d7e6b630cfd;p=civicrm-core.git diff --git a/CRM/Core/Payment/BaseIPN.php b/CRM/Core/Payment/BaseIPN.php index f72bbd0738..3976560052 100644 --- a/CRM/Core/Payment/BaseIPN.php +++ b/CRM/Core/Payment/BaseIPN.php @@ -1,7 +1,7 @@ _inputParameters = $parameters; + } + /** + * Validate incoming data. This function is intended to ensure that incoming data matches + * It provides a form of pseudo-authentication - by checking the calling fn already knows + * the correct contact id & contribution id (this can be problematic when that has changed in + * the meantime for transactions that are delayed & contacts are merged in-between. e.g + * Paypal allows you to resend Instant Payment Notifications if you, for example, moved site + * and didn't update your IPN URL. + * + * @param array $input interpreted values from the values returned through the IPN + * @param array $ids more interpreted values (ids) from the values returned through the IPN + * @param array $objects an empty array that will be populated with loaded object + * @param boolean $required boolean Return FALSE if the relevant objects don't exist + * @param integer $paymentProcessorID Id of the payment processor ID in use + * @return boolean + */ function validateData(&$input, &$ids, &$objects, $required = TRUE, $paymentProcessorID = NULL) { // make sure contact exists and is valid - $contact = new CRM_Contact_DAO_Contact(); + $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)); @@ -51,7 +88,7 @@ class CRM_Core_Payment_BaseIPN { } // make sure contribution exists and is valid - $contribution = new CRM_Contribute_DAO_Contribution(); + $contribution = new CRM_Contribute_BAO_Contribution(); $contribution->id = $ids['contribution']; if (!$contribution->find(TRUE)) { CRM_Core_Error::debug_log_message("Could not find contribution record: {$contribution->id} in IPN request: ".print_r($input, TRUE)); @@ -69,33 +106,16 @@ class CRM_Core_Payment_BaseIPN { return TRUE; } - function createContact(&$input, &$ids, &$objects) { - $params = array(); - $billingID = $ids['billing']; - $lookup = array( - 'first_name', - 'last_name', - "street_address-{$billingID}", - "city-{$billingID}", - "state-{$billingID}", - "postal_code-{$billingID}", - "country-{$billingID}", - ); - foreach ($lookup as $name) { - $params[$name] = $input[$name]; - } - if (!empty($params)) { - // update contact record - $contact = CRM_Contact_BAO_Contact::createProfileContact($params, CRM_Core_DAO::$_nullArray, $ids['contact']); - } - - return TRUE; - } - - /* + /** * Load objects related to contribution * * @input array information from Payment processor + * @param array $ids + * @param array $objects + * @param boolean $required + * @param integer $paymentProcessorID + * @param array $error_handling + * @return multitype:number NULL |boolean */ function loadObjects(&$input, &$ids, &$objects, $required, $paymentProcessorID, $error_handling = NULL) { if (empty($error_handling)) { @@ -121,7 +141,8 @@ class CRM_Core_Payment_BaseIPN { try { $success = $contribution->loadRelatedObjects($input, $ids, $required); } - catch(Exception$e) { + catch(Exception $e) { + $success = FALSE; if (CRM_Utils_Array::value('log_error', $error_handling)) { CRM_Core_Error::debug_log_message($e->getMessage()); } @@ -139,6 +160,13 @@ class CRM_Core_Payment_BaseIPN { return $success; } + /** + * Set contribution to failed + * @param array $objects + * @param object $transaction + * @param array $input + * @return boolean + */ function failed(&$objects, &$transaction, $input = array()) { $contribution = &$objects['contribution']; $memberships = array(); @@ -156,6 +184,9 @@ class CRM_Core_Payment_BaseIPN { $participant = &$objects['participant']; $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); + $contribution->receive_date = CRM_Utils_Date::isoToMysql($contribution->receive_date); + $contribution->receipt_date = CRM_Utils_Date::isoToMysql($contribution->receipt_date); + $contribution->thankyou_date = CRM_Utils_Date::isoToMysql($contribution->thankyou_date); $contribution->contribution_status_id = array_search('Failed', $contributionStatus); $contribution->save(); @@ -164,15 +195,22 @@ class CRM_Core_Payment_BaseIPN { $this->addrecurLineItems($objects['contributionRecur']->id, $contribution->id, CRM_Core_DAO::$_nullArray); } + //copy initial contribution custom fields for recurring contributions + if (CRM_Utils_Array::value('contributionRecur', $objects) && $objects['contributionRecur']->id) { + $this->copyCustomValues($objects['contributionRecur']->id, $contribution->id); + } + if (!CRM_Utils_Array::value('skipComponentSync', $input)) { if (!empty($memberships)) { + // if transaction is failed then set "Cancelled" as membership status + $cancelStatusId = array_search('Cancelled', CRM_Member_PseudoConstant::membershipStatus()); foreach ($memberships as $membership) { if ($membership) { - $membership->status_id = 4; + $membership->status_id = $cancelStatusId; $membership->save(); //update related Memberships. - $params = array('status_id' => 4); + $params = array('status_id' => $cancelStatusId); CRM_Member_BAO_Membership::updateRelatedMemberships($membership->id, $params); } } @@ -190,6 +228,12 @@ class CRM_Core_Payment_BaseIPN { return TRUE; } + /** + * Handled pending contribution status + * @param array $objects + * @param object $transaction + * @return boolean + */ function pending(&$objects, &$transaction) { $transaction->commit(); CRM_Core_Error::debug_log_message("returning since contribution status is pending"); @@ -222,6 +266,11 @@ class CRM_Core_Payment_BaseIPN { $this->addrecurLineItems($objects['contributionRecur']->id, $contribution->id, CRM_Core_DAO::$_nullArray); } + //copy initial contribution custom fields for recurring contributions + if (CRM_Utils_Array::value('contributionRecur', $objects) && $objects['contributionRecur']->id) { + $this->copyCustomValues($objects['contributionRecur']->id, $contribution->id); + } + if (!CRM_Utils_Array::value('skipComponentSync', $input)) { if (!empty($memberships)) { foreach ($memberships as $membership) { @@ -278,11 +327,14 @@ class CRM_Core_Payment_BaseIPN { $values['amount'] = $recurContrib->amount; $values['financial_type_id'] = $objects['contributionType']->id; $values['title'] = $source = ts('Offline Recurring Contribution'); - $values['is_email_receipt'] = $recurContrib->is_email_receipt; $domainValues = CRM_Core_BAO_Domain::getNameAndEmail(); $values['receipt_from_name'] = $domainValues[0]; $values['receipt_from_email'] = $domainValues[1]; } + if($recurContrib && $recurContrib->id){ + //CRM-13273 - is_email_receipt setting on recurring contribution should take precedence over contribution page setting + $values['is_email_receipt'] = $recurContrib->is_email_receipt; + } $contribution->source = $source; if (CRM_Utils_Array::value('is_email_receipt', $values)) { @@ -417,6 +469,9 @@ LIMIT 1;"; $values['custom_pre_id'] = $custom_pre_id; $values['custom_post_id'] = $custom_post_ids; + //for tasks 'Change Participant Status' and 'Batch Update Participants Via Profile' case + //and cases involving status updation through ipn + $values['totalAmount'] = $input['amount']; $contribution->source = ts('Online Event Registration') . ': ' . $values['event']['title']; @@ -447,6 +502,7 @@ LIMIT 1;"; $contribution->trxn_id = $input['trxn_id']; $contribution->receive_date = CRM_Utils_Date::isoToMysql($contribution->receive_date); $contribution->thankyou_date = CRM_Utils_Date::isoToMysql($contribution->thankyou_date); + $contribution->receipt_date = CRM_Utils_Date::isoToMysql($contribution->receipt_date); $contribution->cancel_date = 'null'; if (CRM_Utils_Array::value('check_number', $input)) { @@ -468,6 +524,11 @@ LIMIT 1;"; $this->addrecurLineItems($objects['contributionRecur']->id, $contribution->id, $input); } + //copy initial contribution custom fields for recurring contributions + if ($recurContrib && $recurContrib->id) { + $this->copyCustomValues($recurContrib->id, $contribution->id); + } + // next create the transaction record $paymentProcessor = $paymentProcessorId = ''; if (isset($objects['paymentProcessor'])) { @@ -480,12 +541,15 @@ LIMIT 1;"; $paymentProcessorId = $objects['paymentProcessor']->id; } } - + //it's hard to see how it could reach this point without a contributon id as it is saved in line 511 above + // which raised the question as to whether this check preceded line 511 & if so whether something could be broken + // From a lot of code reading /debugging I'm still not sure the intent WRT first & subsequent payments in this code + // it would be good if someone added some comments or refactored this if ($contribution->id) { $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - if ((!$input['prevContribution'] && $paymentProcessorId) || (!$input['prevContribution']->is_pay_later && - $input['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatuses))) { - $input['payment_processor'] = $paymentProcessorId; + if ((empty($input['prevContribution']) && $paymentProcessorId) || (!$input['prevContribution']->is_pay_later && +- $input['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatuses))) { + $input['payment_processor'] = $paymentProcessorId; } $input['contribution_status_id'] = array_search('Completed', $contributionStatuses); $input['total_amount'] = $input['amount']; @@ -497,7 +561,15 @@ LIMIT 1;"; $input['participant_id'] = $contribution->_relatedObjects['participant']->id; $input['skipLineItem'] = 1; } - + //@todo writing a unit test I was unable to create a scenario where this line did not fatal on second + // and subsequent payments. In this case the line items are created at $this->addrecurLineItems + // and since the contribution is saved prior to this line there is always a contribution-id, + // however there is never a prevContribution (which appears to mean original contribution not previous + // contribution - or preUpdateContributionObject most accurately) + // so, this is always called & only appears to succeed when prevContribution exists - which appears + // to mean "are we updating an exisitng pending contribution" + //I was able to make the unit test complete as fataling here doesn't prevent + // the contribution being created - but activities would not be created or emails sent CRM_Contribute_BAO_Contribution::recordFinancialAccounts($input, NULL); } @@ -535,7 +607,7 @@ LIMIT 1;"; function getBillingID(&$ids) { // get the billing location type - $locationTypes = CRM_Core_PseudoConstant::locationType(); + $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', array(), 'validate'); // CRM-8108 remove the ts around the Billing locationtype //$ids['billing'] = array_search( ts('Billing'), $locationTypes ); $ids['billing'] = array_search('Billing', $locationTypes); @@ -574,6 +646,13 @@ LIMIT 1;"; return $contribution->composeMessageArray($input, $ids, $values, $recur, $returnMessageText); } + /** + * Update contribution status - this is only called from one place in the code & + * it is unclear whether it is a function on the way in or on the way out + * + * @param unknown_type $params + * @return void|Ambigous + */ function updateContributionStatus(&$params) { // get minimum required values. $statusId = CRM_Utils_Array::value('contribution_status_id', $params); @@ -759,7 +838,7 @@ LIMIT 1;"; $lineItems = CRM_Price_BAO_LineItem::getLineItems($contriID, 'contribution'); if (!empty($lineItems)) { foreach ($lineItems as $key => $value) { - $pricesetID = new CRM_Price_DAO_Field(); + $pricesetID = new CRM_Price_DAO_PriceField(); $pricesetID->id = $value['price_field_id']; $pricesetID->find(TRUE); $lineSets[$pricesetID->price_set_id][] = $value; @@ -773,5 +852,45 @@ LIMIT 1;"; } } } -} + // function to copy custom data of the + // initial contribution into its recurring contributions + function copyCustomValues($recurId, $targetContributionId) { + if ($recurId && $targetContributionId) { + // get the initial contribution id of recur id + $sourceContributionId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $recurId, 'id', 'contribution_recur_id'); + + // if the same contribution is being proccessed then return + if ($sourceContributionId == $targetContributionId) { + return; + } + // check if proper recurring contribution record is being processed + $targetConRecurId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $targetContributionId, 'contribution_recur_id'); + if ($targetConRecurId != $recurId) { + return; + } + + // copy custom data + $extends = array('Contribution'); + $groupTree = CRM_Core_BAO_CustomGroup::getGroupDetail(NULL, NULL, $extends); + if ($groupTree) { + foreach ($groupTree as $groupID => $group) { + $table[$groupTree[$groupID]['table_name']] = array('entity_id'); + foreach ($group['fields'] as $fieldID => $field) { + $table[$groupTree[$groupID]['table_name']][] = $groupTree[$groupID]['fields'][$fieldID]['column_name']; + } + } + + foreach ($table as $tableName => $tableColumns) { + $insert = 'INSERT INTO ' . $tableName . ' (' . implode(', ', $tableColumns) . ') '; + $tableColumns[0] = $targetContributionId; + $select = 'SELECT ' . implode(', ', $tableColumns); + $from = ' FROM ' . $tableName; + $where = " WHERE {$tableName}.entity_id = {$sourceContributionId}"; + $query = $insert . $select . $from . $where; + $dao = CRM_Core_DAO::executeQuery($query, CRM_Core_DAO::$_nullArray); + } + } + } + } +}