/** * Complete an order. * * Do not call this directly - use the contribution.completetransaction api as this function is being refactored. * * Currently overloaded to complete a transaction & repeat a transaction - fix! * * Moving it out of the BaseIPN class is just the first step. * * @param array $input * @param array $ids * @param array $objects * @param CRM_Core_Transaction $transaction * @param int $recur * @param CRM_Contribute_BAO_Contribution $contribution * @param bool $isRecurring * Duplication of param needs review. Only used by AuthorizeNetIPN * @param int $isFirstOrLastRecurringPayment * Deprecated param only used by AuthorizeNetIPN. */ public static function completeOrder(&$input, &$ids, $objects, $transaction, $recur, $contribution, $isRecurring, $isFirstOrLastRecurringPayment) { $primaryContributionID = isset($contribution->id) ? $contribution->id : $objects['first_contribution']->id; // The previous details are used when calculating line items so keep it before any code that 'does something' if (!empty($contribution->id)) { $input['prevContribution'] = CRM_Contribute_BAO_Contribution::getValues(array('id' => $contribution->id), CRM_Core_DAO::$_nullArray, CRM_Core_DAO::$_nullArray); } $inputContributionWhiteList = array('fee_amount', 'net_amount', 'trxn_id', 'check_number', 'payment_instrument_id', 'is_test', 'campaign_id', 'receive_date'); $contributionParams = array_merge(array('contribution_status_id' => 'Completed'), array_intersect_key($input, array_fill_keys($inputContributionWhiteList, 1))); $participant = CRM_Utils_Array::value('participant', $objects); $memberships = CRM_Utils_Array::value('membership', $objects); $recurContrib = CRM_Utils_Array::value('contributionRecur', $objects); if (!empty($recurContrib->id)) { $contributionParams['contribution_recur_id'] = $recurContrib->id; } self::repeatTransaction($contribution, $input, $contributionParams); if (is_numeric($memberships)) { $memberships = array($objects['membership']); } $changeDate = CRM_Utils_Array::value('trxn_date', $input, date('YmdHis')); $values = array(); if (isset($input['is_email_receipt'])) { $values['is_email_receipt'] = $input['is_email_receipt']; } if ($input['component'] == 'contribute') { if ($contribution->contribution_page_id) { CRM_Contribute_BAO_ContributionPage::setValues($contribution->contribution_page_id, $values); $contributionParams['source'] = ts('Online Contribution') . ': ' . $values['title']; } elseif ($recurContrib && $recurContrib->id) { $contributionParams['contribution_page_id'] = NULL; $values['amount'] = $recurContrib->amount; $values['financial_type_id'] = $objects['contributionType']->id; $values['title'] = $source = ts('Offline Recurring Contribution'); $domainValues = CRM_Core_BAO_Domain::getNameAndEmail(); $values['receipt_from_name'] = $domainValues[0]; $values['receipt_from_email'] = $domainValues[1]; } if ($recurContrib && $recurContrib->id && !isset($input['is_email_receipt'])) { //CRM-13273 - is_email_receipt setting on recurring contribution should take precedence over contribution page setting // but CRM-16124 if $input['is_email_receipt'] is set then that should not be overridden. $values['is_email_receipt'] = $recurContrib->is_email_receipt; } if (!empty($values['is_email_receipt'])) { $contributionParams['receipt_date'] = $changeDate; } if (!empty($memberships)) { foreach ($memberships as $membershipTypeIdKey => $membership) { if ($membership) { $membershipParams = array('id' => $membership->id, 'contact_id' => $membership->contact_id, 'is_test' => $membership->is_test, 'membership_type_id' => $membership->membership_type_id); $currentMembership = CRM_Member_BAO_Membership::getContactMembership($membershipParams['contact_id'], $membershipParams['membership_type_id'], $membershipParams['is_test'], $membershipParams['id']); // CRM-8141 update the membership type with the value recorded in log when membership created/renewed // this picks up membership type changes during renewals $sql = "\nSELECT membership_type_id\nFROM civicrm_membership_log\nWHERE membership_id={$membershipParams['id']}\nORDER BY id DESC\nLIMIT 1;"; $dao = CRM_Core_DAO::executeQuery($sql); if ($dao->fetch()) { if (!empty($dao->membership_type_id)) { $membershipParams['membership_type_id'] = $dao->membership_type_id; } } $dao->free(); $membershipParams['num_terms'] = $contribution->getNumTermsByContributionAndMembershipType($membershipParams['membership_type_id'], $primaryContributionID); $dates = array_fill_keys(array('join_date', 'start_date', 'end_date'), NULL); if ($currentMembership) { /* * Fixed FOR CRM-4433 * In BAO/Membership.php(renewMembership function), we skip the extend membership date and status * when Contribution mode is notify and membership is for renewal ) */ CRM_Member_BAO_Membership::fixMembershipStatusBeforeRenew($currentMembership, $changeDate); // @todo - we should pass membership_type_id instead of null here but not // adding as not sure of testing $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membershipParams['id'], $changeDate, NULL, $membershipParams['num_terms']); $dates['join_date'] = $currentMembership['join_date']; } //get the status for membership. $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($dates['start_date'], $dates['end_date'], $dates['join_date'], 'today', TRUE, $membershipParams['membership_type_id'], $membershipParams); $membershipParams['status_id'] = CRM_Utils_Array::value('id', $calcStatus, 'New'); //we might be renewing membership, //so make status override false. $membershipParams['is_override'] = FALSE; civicrm_api3('Membership', 'create', $membershipParams); //update related Memberships. CRM_Member_BAO_Membership::updateRelatedMemberships($membership->id, $membershipParams); } } } } else { if (empty($input['IAmAHorribleNastyBeyondExcusableHackInTheCRMEventFORMTaskClassThatNeedsToBERemoved'])) { $eventDetail = civicrm_api3('Event', 'getsingle', array('id' => $objects['event']->id)); $contributionParams['source'] = ts('Online Event Registration') . ': ' . $eventDetail['title']; if ($eventDetail['is_email_confirm']) { // @todo this should be set by the function that sends the mail after sending. $contributionParams['receipt_date'] = $changeDate; } $participantParams['id'] = $participant->id; $participantParams['status_id'] = 'Registered'; civicrm_api3('Participant', 'create', $participantParams); } } $contributionParams['id'] = $contribution->id; civicrm_api3('Contribution', 'create', $contributionParams); // Add new soft credit against current $contribution. if (CRM_Utils_Array::value('contributionRecur', $objects) && $objects['contributionRecur']->id) { CRM_Contribute_BAO_ContributionRecur::addrecurSoftCredit($objects['contributionRecur']->id, $contribution->id); } $paymentProcessorId = ''; if (isset($objects['paymentProcessor'])) { if (is_array($objects['paymentProcessor'])) { $paymentProcessorId = $objects['paymentProcessor']['id']; } else { $paymentProcessorId = $objects['paymentProcessor']->id; } } $contributionStatuses = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'contribution_status_id', array('labelColumn' => 'name', 'flip' => 1)); if (empty($input['prevContribution']) && $paymentProcessorId || !$input['prevContribution']->is_pay_later && $input['prevContribution']->contribution_status_id == $contributionStatuses['Pending']) { $input['payment_processor'] = $paymentProcessorId; } $input['contribution_status_id'] = $contributionStatuses['Completed']; $input['total_amount'] = $input['amount']; $input['contribution'] = $contribution; $input['financial_type_id'] = $contribution->financial_type_id; if (!empty($contribution->_relatedObjects['participant'])) { $input['contribution_mode'] = 'participant'; $input['participant_id'] = $contribution->_relatedObjects['participant']->id; $input['skipLineItem'] = 1; } elseif (!empty($contribution->_relatedObjects['membership'])) { $input['skipLineItem'] = TRUE; $input['contribution_mode'] = 'membership'; } //@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 // CRM_Contribute_BAO_ContributionRecur::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); CRM_Core_Error::debug_log_message("Contribution record updated successfully"); $transaction->commit(); CRM_Contribute_BAO_ContributionRecur::updateRecurLinkedPledge($contribution); // create an activity record if ($input['component'] == 'contribute') { //CRM-4027 $targetContactID = NULL; if (!empty($ids['related_contact'])) { $targetContactID = $contribution->contact_id; $contribution->contact_id = $ids['related_contact']; } CRM_Activity_BAO_Activity::addActivity($contribution, NULL, $targetContactID); // event } else { CRM_Activity_BAO_Activity::addActivity($participant); } // CRM-9132 legacy behaviour was that receipts were sent out in all instances. Still sending // when array_key 'is_email_receipt doesn't exist in case some instances where is needs setting haven't been set if (!array_key_exists('is_email_receipt', $values) || $values['is_email_receipt'] == 1) { self::sendMail($input, $ids, $objects['contribution'], $values, $recur, FALSE); CRM_Core_Error::debug_log_message("Receipt sent"); } CRM_Core_Error::debug_log_message("Success: Database updated"); if ($isRecurring) { CRM_Contribute_BAO_ContributionRecur::sendRecurringStartOrEndNotification($ids, $recur, $isFirstOrLastRecurringPayment); } }
function completeTransaction(&$input, &$ids, &$objects, &$transaction, $recur = FALSE) { $contribution =& $objects['contribution']; $primaryContributionID = isset($contribution->id) ? $contribution->id : $objects['first_contribution']->id; $memberships =& $objects['membership']; if (is_numeric($memberships)) { $memberships = array($objects['membership']); } $participant =& $objects['participant']; $event =& $objects['event']; $changeToday = CRM_Utils_Array::value('trxn_date', $input, self::$_now); $recurContrib =& $objects['contributionRecur']; $values = array(); if (isset($input['is_email_receipt'])) { $values['is_email_receipt'] = $input['is_email_receipt']; } $source = NULL; if ($input['component'] == 'contribute') { if ($contribution->contribution_page_id) { CRM_Contribute_BAO_ContributionPage::setValues($contribution->contribution_page_id, $values); $source = ts('Online Contribution') . ': ' . $values['title']; } elseif ($recurContrib && $recurContrib->id) { $contribution->contribution_page_id = NULL; $values['amount'] = $recurContrib->amount; $values['financial_type_id'] = $objects['contributionType']->id; $values['title'] = $source = ts('Offline Recurring Contribution'); $domainValues = CRM_Core_BAO_Domain::getNameAndEmail(); $values['receipt_from_name'] = $domainValues[0]; $values['receipt_from_email'] = $domainValues[1]; } if ($recurContrib && $recurContrib->id && !isset($input['is_email_receipt'])) { //CRM-13273 - is_email_receipt setting on recurring contribution should take precedence over contribution page setting // but CRM-16124 if $input['is_email_receipt'] is set then that should not be overridden. $values['is_email_receipt'] = $recurContrib->is_email_receipt; } $contribution->source = $source; if (CRM_Utils_Array::value('is_email_receipt', $values)) { $contribution->receipt_date = self::$_now; } if (!empty($memberships)) { $membershipsUpdate = array(); foreach ($memberships as $membershipTypeIdKey => $membership) { if ($membership) { $format = '%Y%m%d'; $currentMembership = CRM_Member_BAO_Membership::getContactMembership($membership->contact_id, $membership->membership_type_id, $membership->is_test, $membership->id); // CRM-8141 update the membership type with the value recorded in log when membership created/renewed // this picks up membership type changes during renewals $sql = "\nSELECT membership_type_id\nFROM civicrm_membership_log\nWHERE membership_id={$membership->id}\nORDER BY id DESC\nLIMIT 1;"; $dao = new CRM_Core_DAO(); $dao->query($sql); if ($dao->fetch()) { if (!empty($dao->membership_type_id)) { $membership->membership_type_id = $dao->membership_type_id; $membership->save(); } // else fall back to using current membership type } // else fall back to using current membership type $dao->free(); $num_terms = $contribution->getNumTermsByContributionAndMembershipType($membership->membership_type_id, $primaryContributionID); if ($currentMembership) { /* * Fixed FOR CRM-4433 * In BAO/Membership.php(renewMembership function), we skip the extend membership date and status * when Contribution mode is notify and membership is for renewal ) */ CRM_Member_BAO_Membership::fixMembershipStatusBeforeRenew($currentMembership, $changeToday); // @todo - we should pass membership_type_id instead of null here but not // adding as not sure of testing $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membership->id, $changeToday, NULL, $num_terms); $dates['join_date'] = CRM_Utils_Date::customFormat($currentMembership['join_date'], $format); } else { $dates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($membership->membership_type_id, NULL, NULL, NULL, $num_terms); } //get the status for membership. $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($dates['start_date'], $dates['end_date'], $dates['join_date'], 'today', TRUE, $membership->membership_type_id, (array) $membership); $formatedParams = array('status_id' => CRM_Utils_Array::value('id', $calcStatus, 2), 'join_date' => CRM_Utils_Date::customFormat(CRM_Utils_Array::value('join_date', $dates), $format), 'start_date' => CRM_Utils_Date::customFormat(CRM_Utils_Array::value('start_date', $dates), $format), 'end_date' => CRM_Utils_Date::customFormat(CRM_Utils_Array::value('end_date', $dates), $format)); //we might be renewing membership, //so make status override false. $formatedParams['is_override'] = FALSE; $membership->copyValues($formatedParams); $membership->save(); //updating the membership log $membershipLog = array(); $membershipLog = $formatedParams; $logStartDate = $formatedParams['start_date']; if (CRM_Utils_Array::value('log_start_date', $dates)) { $logStartDate = CRM_Utils_Date::customFormat($dates['log_start_date'], $format); $logStartDate = CRM_Utils_Date::isoToMysql($logStartDate); } $membershipLog['start_date'] = $logStartDate; $membershipLog['membership_id'] = $membership->id; $membershipLog['modified_id'] = $membership->contact_id; $membershipLog['modified_date'] = date('Ymd'); $membershipLog['membership_type_id'] = $membership->membership_type_id; CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray); //update related Memberships. CRM_Member_BAO_Membership::updateRelatedMemberships($membership->id, $formatedParams); //update the membership type key of membership relatedObjects array //if it has changed after membership update if ($membershipTypeIdKey != $membership->membership_type_id) { $membershipsUpdate[$membership->membership_type_id] = $membership; $contribution->_relatedObjects['membership'][$membership->membership_type_id] = $membership; unset($contribution->_relatedObjects['membership'][$membershipTypeIdKey]); unset($memberships[$membershipTypeIdKey]); } } } //update the memberships object with updated membershipTypeId data //if membershipTypeId has changed after membership update if (!empty($membershipsUpdate)) { $memberships = $memberships + $membershipsUpdate; } } } else { // event $eventParams = array('id' => $objects['event']->id); $values['event'] = array(); CRM_Event_BAO_Event::retrieve($eventParams, $values['event']); //get location details $locationParams = array('entity_id' => $objects['event']->id, 'entity_table' => 'civicrm_event'); $values['location'] = CRM_Core_BAO_Location::getValues($locationParams); $ufJoinParams = array('entity_table' => 'civicrm_event', 'entity_id' => $ids['event'], 'module' => 'CiviEvent'); list($custom_pre_id, $custom_post_ids) = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams); $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']; if ($values['event']['is_email_confirm']) { $contribution->receipt_date = self::$_now; $values['is_email_receipt'] = 1; } if (!CRM_Utils_Array::value('skipComponentSync', $input)) { $participant->status_id = 1; } $participant->save(); } if (CRM_Utils_Array::value('net_amount', $input, 0) == 0 && CRM_Utils_Array::value('fee_amount', $input, 0) != 0) { $input['net_amount'] = $input['amount'] - $input['fee_amount']; } // This complete transaction function is being overloaded to create new contributions too. // here we record if it is a new contribution. // @todo separate the 2 more appropriately. $isNewContribution = FALSE; if (empty($contribution->id)) { $isNewContribution = TRUE; if (!empty($input['amount']) && $input['amount'] != $contribution->total_amount) { $contribution->total_amount = $input['amount']; // The BAO does this stuff but we are actually kinda bypassing it here (bad code! go sit in the corner) // so we have to handle net_amount in this (naughty) code. if (isset($input['fee_amount']) && is_numeric($input['fee_amount'])) { $contribution->fee_amount = $input['fee_amount']; } $contribution->net_amount = $contribution->total_amount - $contribution->fee_amount; } if (!empty($input['campaign_id'])) { $contribution->campaign_id = $input['campaign_id']; } } $contributionStatuses = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'contribution_status_id', array('labelColumn' => 'name', 'flip' => 1)); // @todo this section should call the api in order to have hooks called & // because all this 'messiness' setting variables could be avoided // by letting the api resolve pseudoconstants & copy set values and format dates. $contribution->contribution_status_id = $contributionStatuses['Completed']; $contribution->is_test = $input['is_test']; // CRM-15960 If we don't have a value we 'want' for the amounts, leave it to the BAO to sort out. if (isset($input['net_amount'])) { $contribution->fee_amount = CRM_Utils_Array::value('fee_amount', $input, 0); } if (isset($input['net_amount'])) { $contribution->net_amount = $input['net_amount']; } $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)) { $contribution->check_number = $input['check_number']; } if (CRM_Utils_Array::value('payment_instrument_id', $input)) { $contribution->payment_instrument_id = $input['payment_instrument_id']; } if (!empty($contribution->id)) { $contributionId['id'] = $contribution->id; $input['prevContribution'] = CRM_Contribute_BAO_Contribution::getValues($contributionId, CRM_Core_DAO::$_nullArray, CRM_Core_DAO::$_nullArray); } $contribution->save(); //add line items for recurring payments if (!empty($contribution->contribution_recur_id)) { if ($isNewContribution) { $input['line_item'] = $this->addRecurLineItems($contribution->contribution_recur_id, $contribution); } else { // this is just to prevent e-notices when we call recordFinancialAccounts - per comments on that line - intention is somewhat unclear $input['line_item'] = array(); } if (!empty($memberships) && $primaryContributionID != $contribution->id) { foreach ($memberships as $membership) { try { $membershipPayment = array('membership_id' => $membership->id, 'contribution_id' => $contribution->id); if (!civicrm_api3('membership_payment', 'getcount', $membershipPayment)) { civicrm_api3('membership_payment', 'create', $membershipPayment); } } catch (CiviCRM_API3_Exception $e) { echo $e->getMessage(); // we are catching & ignoring errors as an extra precaution since lost IPNs may be more serious that lost membership_payment data // this fn is unit-tested so risk of changes elsewhere breaking it are otherwise mitigated } } } } //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'])) { if (is_array($objects['paymentProcessor'])) { $paymentProcessor = $objects['paymentProcessor']['payment_processor_type']; $paymentProcessorId = $objects['paymentProcessor']['id']; } else { $paymentProcessor = $objects['paymentProcessor']->payment_processor_type; $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 (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']; $input['contribution'] = $contribution; $input['financial_type_id'] = $contribution->financial_type_id; if (CRM_Utils_Array::value('participant', $contribution->_relatedObjects)) { $input['contribution_mode'] = 'participant'; $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); } self::updateRecurLinkedPledge($contribution); // create an activity record if ($input['component'] == 'contribute') { //CRM-4027 $targetContactID = NULL; if (CRM_Utils_Array::value('related_contact', $ids)) { $targetContactID = $contribution->contact_id; $contribution->contact_id = $ids['related_contact']; } CRM_Activity_BAO_Activity::addActivity($contribution, NULL, $targetContactID); // event } else { CRM_Activity_BAO_Activity::addActivity($participant); } CRM_Core_Error::debug_log_message("Contribution record updated successfully"); $transaction->commit(); // CRM-9132 legacy behaviour was that receipts were sent out in all instances. Still sending // when array_key 'is_email_receipt doesn't exist in case some instances where is needs setting haven't been set if (!array_key_exists('is_email_receipt', $values) || $values['is_email_receipt'] == 1) { self::sendMail($input, $ids, $objects, $values, $recur, FALSE); CRM_Core_Error::debug_log_message("Receipt sent"); } CRM_Core_Error::debug_log_message("Success: Database updated"); }
/** * Function to record additional payment for partial and refund contributions. * * @param int $contributionId * is the invoice contribution id (got created after processing participant payment). * @param array $trxnsData * to take user provided input of transaction details. * @param string $paymentType * 'owed' for purpose of recording partial payments, 'refund' for purpose of recording refund payments. * @param int $participantId * * @return null|object */ public static function recordAdditionalPayment($contributionId, $trxnsData, $paymentType = 'owed', $participantId = NULL) { $statusId = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name'); $getInfoOf['id'] = $contributionId; $defaults = array(); $contributionDAO = CRM_Contribute_BAO_Contribution::retrieve($getInfoOf, $defaults, CRM_Core_DAO::$_nullArray); if ($paymentType == 'owed') { // build params for recording financial trxn entry $params['contribution'] = $contributionDAO; $params = array_merge($defaults, $params); $params['skipLineItem'] = TRUE; $params['partial_payment_total'] = $contributionDAO->total_amount; $params['partial_amount_pay'] = $trxnsData['total_amount']; $trxnsData['trxn_date'] = !empty($trxnsData['trxn_date']) ? $trxnsData['trxn_date'] : date('YmdHis'); $trxnsData['net_amount'] = !empty($trxnsData['net_amount']) ? $trxnsData['net_amount'] : $trxnsData['total_amount']; // record the entry $financialTrxn = CRM_Contribute_BAO_Contribution::recordFinancialAccounts($params, $trxnsData); $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' ")); $toFinancialAccount = CRM_Contribute_PseudoConstant::financialAccountType($contributionDAO->financial_type_id, $relationTypeId); $trxnId = CRM_Core_BAO_FinancialTrxn::getBalanceTrxnAmt($contributionId, $contributionDAO->financial_type_id); if (!empty($trxnId)) { $trxnId = $trxnId['trxn_id']; } elseif (!empty($contributionDAO->payment_instrument_id)) { $trxnId = CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($contributionDAO->payment_instrument_id); } else { $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Asset' ")); $queryParams = array(1 => array($relationTypeId, 'Integer')); $trxnId = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_financial_account WHERE is_default = 1 AND financial_account_type_id = %1", $queryParams); } // update statuses // criteria for updates contribution total_amount == financial_trxns of partial_payments $sql = "SELECT SUM(ft.total_amount) as sum_of_payments, SUM(ft.net_amount) as net_amount_total\nFROM civicrm_financial_trxn ft\nLEFT JOIN civicrm_entity_financial_trxn eft\n ON (ft.id = eft.financial_trxn_id)\nWHERE eft.entity_table = 'civicrm_contribution'\n AND eft.entity_id = {$contributionId}\n AND ft.to_financial_account_id != {$toFinancialAccount}\n AND ft.status_id = {$statusId}\n"; $query = CRM_Core_DAO::executeQuery($sql); $query->fetch(); $sumOfPayments = $query->sum_of_payments; // update statuses if ($contributionDAO->total_amount == $sumOfPayments) { // update contribution status and // clean cancel info (if any) if prev. contribution was updated in case of 'Refunded' => 'Completed' $contributionDAO->contribution_status_id = $statusId; $contributionDAO->cancel_date = 'null'; $contributionDAO->cancel_reason = NULL; $netAmount = !empty($trxnsData['net_amount']) ? NULL : $trxnsData['total_amount']; $contributionDAO->net_amount = $query->net_amount_total + $netAmount; $contributionDAO->fee_amount = $contributionDAO->total_amount - $contributionDAO->net_amount; $contributionDAO->save(); //Change status of financial record too $financialTrxn->status_id = $statusId; $financialTrxn->save(); // note : not using the self::add method, // the reason because it performs 'status change' related code execution for financial records // which in 'Partial Paid' => 'Completed' is not useful, instead specific financial record updates // are coded below i.e. just updating financial_item status to 'Paid' if ($participantId) { // update participant status $participantStatuses = CRM_Event_PseudoConstant::participantStatus(); $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId); foreach ($ids as $val) { $participantUpdate['id'] = $val; $participantUpdate['status_id'] = array_search('Registered', $participantStatuses); CRM_Event_BAO_Participant::add($participantUpdate); } } // update financial item statuses $financialItemStatus = CRM_Core_PseudoConstant::get('CRM_Financial_DAO_FinancialItem', 'status_id'); $paidStatus = array_search('Paid', $financialItemStatus); $baseTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contributionId); $sqlFinancialItemUpdate = "\nUPDATE civicrm_financial_item fi\n LEFT JOIN civicrm_entity_financial_trxn eft\n ON (eft.entity_id = fi.id AND eft.entity_table = 'civicrm_financial_item')\nSET status_id = {$paidStatus}\nWHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})\n"; CRM_Core_DAO::executeQuery($sqlFinancialItemUpdate); } } elseif ($paymentType == 'refund') { // build params for recording financial trxn entry $params['contribution'] = $contributionDAO; $params = array_merge($defaults, $params); $params['skipLineItem'] = TRUE; $trxnsData['trxn_date'] = !empty($trxnsData['trxn_date']) ? $trxnsData['trxn_date'] : date('YmdHis'); $trxnsData['total_amount'] = -$trxnsData['total_amount']; $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' ")); $trxnsData['from_financial_account_id'] = CRM_Contribute_PseudoConstant::financialAccountType($contributionDAO->financial_type_id, $relationTypeId); $trxnsData['status_id'] = CRM_Core_OptionGroup::getValue('contribution_status', 'Refunded', 'name'); // record the entry $financialTrxn = CRM_Contribute_BAO_Contribution::recordFinancialAccounts($params, $trxnsData); // note : not using the self::add method, // the reason because it performs 'status change' related code execution for financial records // which in 'Pending Refund' => 'Completed' is not useful, instead specific financial record updates // are coded below i.e. just updating financial_item status to 'Paid' $contributionDetails = CRM_Core_DAO::setFieldValue('CRM_Contribute_BAO_Contribution', $contributionId, 'contribution_status_id', $statusId); // add financial item entry $financialItemStatus = CRM_Core_PseudoConstant::get('CRM_Financial_DAO_FinancialItem', 'status_id'); $getLine['entity_id'] = $contributionDAO->id; $getLine['entity_table'] = 'civicrm_contribution'; $lineItemId = CRM_Price_BAO_LineItem::retrieve($getLine, CRM_Core_DAO::$_nullArray); if (!empty($lineItemId->id)) { $addFinancialEntry = array('transaction_date' => $financialTrxn->trxn_date, 'contact_id' => $contributionDAO->contact_id, 'amount' => $financialTrxn->total_amount, 'status_id' => array_search('Paid', $financialItemStatus), 'entity_id' => $lineItemId->id, 'entity_table' => 'civicrm_line_item'); $trxnIds['id'] = $financialTrxn->id; CRM_Financial_BAO_FinancialItem::create($addFinancialEntry, NULL, $trxnIds); } if ($participantId) { // update participant status $participantStatuses = CRM_Event_PseudoConstant::participantStatus(); $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId); foreach ($ids as $val) { $participantUpdate['id'] = $val; $participantUpdate['status_id'] = array_search('Registered', $participantStatuses); CRM_Event_BAO_Participant::add($participantUpdate); } } } // activity creation if (!empty($financialTrxn)) { if ($participantId) { $inputParams['id'] = $participantId; $values = array(); $ids = array(); $component = 'event'; $entityObj = CRM_Event_BAO_Participant::getValues($inputParams, $values, $ids); $entityObj = $entityObj[$participantId]; } $activityType = $paymentType == 'refund' ? 'Refund' : 'Payment'; self::addActivityForPayment($entityObj, $financialTrxn, $activityType, $component, $contributionId); } return $financialTrxn; }