}
}
+ //set defaults in create mode
+ if (!$contributionID) {
+ CRM_Core_DAO::setCreateDefaults($params, self::getDefaults());
+ }
+
//if contribution is created with cancelled or refunded status, add credit note id
if (!empty($params['contribution_status_id'])) {
$contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
if (($params['contribution_status_id'] == array_search('Refunded', $contributionStatus)
- || $params['contribution_status_id'] == array_search('Cancelled', $contributionStatus))
+ || $params['contribution_status_id'] == array_search('Cancelled', $contributionStatus))
) {
if (empty($params['creditnote_id']) || $params['creditnote_id'] == "null") {
$params['creditnote_id'] = self::createCreditNoteId();
}
}
}
-
- //set defaults in create mode
- if (!$contributionID) {
- CRM_Core_DAO::setCreateDefaults($params, self::getDefaults());
+ else {
+ // Since the fee amount is expecting this (later on) ensure it is always set.
+ // It would only not be set for an update where it is unchanged.
+ $params['contribution_status_id'] = civicrm_api3('Contribution', 'getvalue', array('id' => $contributionID, 'return' => 'contribution_status_id'));
}
+
self::calculateMissingAmountParams($params, $contributionID);
if (!empty($params['payment_instrument_id'])) {
return $null;
}
+ /**
+ * Get the values and resolve the most common mappings.
+ *
+ * Since contribution status is resolved in almost every function that calls getValues it makes
+ * sense to have an extra function to resolve it rather than repeat the code.
+ *
+ * Think carefully before adding more mappings to be resolved as there could be performance implications
+ * if this function starts to be called from more iterative functions.
+ *
+ * @param array $params
+ * Input parameters to find object.
+ *
+ * @return array
+ * Array of the found contribution.
+ * @throws CRM_Core_Exception
+ */
+ public static function getValuesWithMappings($params) {
+ $values = $ids = array();
+ $contribution = self::getValues($params, $values, $ids);
+ if (is_null($contribution)) {
+ throw new CRM_Core_Exception('No contribution found');
+ }
+ $values['contribution_status'] = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $values['contribution_status_id']);
+ return $values;
+ }
+
/**
* Calculate net_amount & fee_amount if they are not set.
*
),
);
+ // CRM-17787
+ $campaignTitle = array(
+ 'contribution_campaign_title' => array(
+ 'title' => ts('Campaign Title'),
+ 'name' => 'campaign_title',
+ 'where' => 'civicrm_campaign.title',
+ 'data_type' => CRM_Utils_Type::T_STRING,
+ ),
+ );
$softCreditFields = array(
'contribution_soft_credit_name' => array(
'name' => 'contribution_soft_credit_name',
);
$fields = array_merge($impFields, $typeField, $contributionStatus, $contributionPage, $optionField, $expFieldProduct,
- $expFieldsContrib, $contributionNote, $contributionRecurId, $extraFields, $softCreditFields, $financialAccount, $premiums,
+ $expFieldsContrib, $contributionNote, $contributionRecurId, $extraFields, $softCreditFields, $financialAccount, $premiums, $campaignTitle,
CRM_Core_BAO_CustomField::getFieldsForImport('Contribution', FALSE, FALSE, FALSE, $checkPermission)
);
if (!empty($input['amount'])) {
$contribution->total_amount = $contributionParams['total_amount'] = $input['amount'];
}
- $templateContribution = civicrm_api3('Contribution', 'getsingle', array(
- 'contribution_recur_id' => $contributionParams['contribution_recur_id'],
- 'options' => array('limit' => 1),
- ));
+ $templateContribution = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($contributionParams['contribution_recur_id']);
+ if (!empty($contributionParams['contribution_recur_id'])) {
+ $recurringContribution = civicrm_api3('ContributionRecur', 'getsingle', array(
+ 'id' => $contributionParams['contribution_recur_id'],
+ ));
+ if (!empty($recurringContribution['campaign_id'])) {
+ // CRM-17718 the campaign id on the contribution recur record should get precedence.
+ $contributionParams['campaign_id'] = $recurringContribution['campaign_id'];
+ }
+ if (!empty($recurringContribution['financial_type_id'])) {
+ // CRM-17718 the campaign id on the contribution recur record should get precedence.
+ $contributionParams['financial_type_id'] = $recurringContribution['financial_type_id'];
+ }
+ }
$contributionParams['skipLineItem'] = TRUE;
$contributionParams['status_id'] = 'Pending';
- $contributionParams['financial_type_id'] = $templateContribution['financial_type_id'];
+ if (isset($contributionParams['financial_type_id'])) {
+ // Give precedence to passed in type.
+ $contribution->financial_type_id = $contributionParams['financial_type_id'];
+ }
+ else {
+ $contributionParams['financial_type_id'] = $templateContribution['financial_type_id'];
+ }
$contributionParams['contact_id'] = $templateContribution['contact_id'];
$contributionParams['source'] = empty($templateContribution['source']) ? ts('Recurring contribution') : $templateContribution['source'];
$createContribution = civicrm_api3('Contribution', 'create', $contributionParams);
$entityID = $ids['membership'][0];
}
- $template->assign('cancelSubscriptionUrl', $paymentObject->subscriptionURL($entityID, $entity));
+ $template->assign('cancelSubscriptionUrl', $paymentObject->subscriptionURL($entityID, $entity, 'cancel'));
$template->assign('updateSubscriptionBillingUrl', $paymentObject->subscriptionURL($entityID, $entity, 'billing'));
$template->assign('updateSubscriptionUrl', $paymentObject->subscriptionURL($entityID, $entity, 'update'));
$template->assign('is_separate_payment', 0);
if ($recur && $paymentObject) {
- $url = $paymentObject->subscriptionURL($membership->id, 'membership');
+ $url = $paymentObject->subscriptionURL($membership->id, 'membership', 'cancel');
$template->assign('cancelSubscriptionUrl', $url);
$url = $paymentObject->subscriptionURL($membership->id, 'membership', 'billing');
$template->assign('updateSubscriptionBillingUrl', $url);
$paymentObject = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($contributionId, 'contribute', 'obj');
if (!empty($paymentObject)) {
- $supportsCancel[$cacheKeyString] = $paymentObject->isSupported('cancelSubscription') && !$isCancelled;
+ $supportsCancel[$cacheKeyString] = $paymentObject->supports('cancelRecurring') && !$isCancelled;
}
}
return $supportsCancel[$cacheKeyString];
);
if ($contributionStatus == 'Refunded') {
$trxnParams['trxn_date'] = !empty($params['contribution']->cancel_date) ? $params['contribution']->cancel_date : date('YmdHis');
+ if (isset($params['refund_trxn_id'])) {
+ // CRM-17751 allow a separate trxn_id for the refund to be passed in via api & form.
+ $trxnParams['trxn_id'] = $params['refund_trxn_id'];
+ }
}
//CRM-16259, set is_payment flag for non pending status
if (!in_array($contributionStatus, $pendingStatus)) {
$params['trxnParams'] = $trxnParams;
if (!empty($params['prevContribution'])) {
+ $updated = FALSE;
$params['trxnParams']['total_amount'] = $trxnParams['total_amount'] = $params['total_amount'] = $params['prevContribution']->total_amount;
$params['trxnParams']['fee_amount'] = $params['prevContribution']->fee_amount;
$params['trxnParams']['net_amount'] = $params['prevContribution']->net_amount;
- $params['trxnParams']['trxn_id'] = $params['prevContribution']->trxn_id;
+ if (!isset($params['trxnParams']['trxn_id'])) {
+ // Actually I have no idea why we are overwriting any values from the previous contribution.
+ // (filling makes sense to me). However, only protecting this value as I really really know we
+ // don't want this one overwritten.
+ // CRM-17751.
+ $params['trxnParams']['trxn_id'] = $params['prevContribution']->trxn_id;
+ }
$params['trxnParams']['status_id'] = $params['prevContribution']->contribution_status_id;
if (!(($params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatuses)
$params['total_amount'] = $params['trxnParams']['total_amount'] = $trxnParams['total_amount'];
self::updateFinancialAccounts($params);
$params['trxnParams']['to_financial_account_id'] = $trxnParams['to_financial_account_id'];
+ $updated = TRUE;
}
}
//Update contribution status
$params['trxnParams']['status_id'] = $params['contribution']->contribution_status_id;
- $params['trxnParams']['trxn_id'] = $params['contribution']->trxn_id;
+ if (!isset($params['refund_trxn_id'])) {
+ // CRM-17751 This has previously been deliberately set. No explanation as to why one variant
+ // gets preference over another so I am only 'protecting' a very specific tested flow
+ // and letting natural justice take care of the rest.
+ $params['trxnParams']['trxn_id'] = $params['contribution']->trxn_id;
+ }
if (!empty($params['contribution_status_id']) &&
$params['prevContribution']->contribution_status_id != $params['contribution']->contribution_status_id
) {
//Update Financial Records
self::updateFinancialAccounts($params, 'changedStatus');
+ $updated = TRUE;
}
// change Payment Instrument for a Completed contribution
self::updateFinancialAccounts($params, 'changePaymentInstrument');
$params['total_amount'] = $params['trxnParams']['total_amount'] = $trxnParams['total_amount'];
self::updateFinancialAccounts($params, 'changePaymentInstrument');
+ $updated = TRUE;
}
}
elseif ((!CRM_Utils_System::isNull($params['contribution']->payment_instrument_id) ||
self::updateFinancialAccounts($params, 'changePaymentInstrument');
$params['total_amount'] = $params['trxnParams']['total_amount'] = $trxnParams['total_amount'];
self::updateFinancialAccounts($params, 'changePaymentInstrument');
+ $updated = TRUE;
}
elseif (!CRM_Utils_System::isNull($params['contribution']->check_number) &&
$params['contribution']->check_number != $params['prevContribution']->check_number
$params['trxnParams']['check_number'] = $params['contribution']->check_number;
$params['total_amount'] = $params['trxnParams']['total_amount'] = $trxnParams['total_amount'];
self::updateFinancialAccounts($params, 'changePaymentInstrument');
+ $updated = TRUE;
}
}
//Update Financial Records
$params['trxnParams']['from_financial_account_id'] = NULL;
self::updateFinancialAccounts($params, 'changedAmount');
+ $updated = TRUE;
+ }
+
+ if (!$updated) {
+ // Looks like we might have a data correction update.
+ // This would be a case where a transaction id has been entered but it is incorrect &
+ // the person goes back in & fixes it, as opposed to a new transaction.
+ // Currently the UI doesn't support multiple refunds against a single transaction & we are only supporting
+ // the data fix scenario.
+ // CRM-17751.
+ if (isset($params['refund_trxn_id'])) {
+ $refundIDs = CRM_Core_BAO_FinancialTrxn::getRefundTransactionIDs($params['id']);
+ if ($refundIDs['trxn_id'] != $params['refund_trxn_id']) {
+ civicrm_api3('FinancialTrxn', 'create', array('id' => $refundIDs['financialTrxnId'], 'trxn_id' => $params['refund_trxn_id']));
+ }
+ }
}
}
*
* @return null|object
*/
- public static function recordAdditionalPayment($contributionId, $trxnsData, $paymentType = 'owed', $participantId = NULL) {
+ public static function recordAdditionalPayment($contributionId, $trxnsData, $paymentType = 'owed', $participantId = NULL, $updateStatus = TRUE) {
$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 (!$participantId) {
+ $participantId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $contributionId, 'participant_id');
+ }
if ($paymentType == 'owed') {
// build params for recording financial trxn entry
// 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);
-
+ if ($updateStatus) {
+ $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;
empty($params['skipLineItem']) && !$isLineItem
) {
$taxRateParams = $taxRates[$params['financial_type_id']];
- $taxAmount = CRM_Contribute_BAO_Contribution_Utils::calculateTaxAmount($params['total_amount'], $taxRateParams);
+ $taxAmount = CRM_Contribute_BAO_Contribution_Utils::calculateTaxAmount(CRM_Utils_Array::value('total_amount', $params), $taxRateParams);
$params['tax_amount'] = round($taxAmount['tax_amount'], 2);
// Get Line Item on update of contribution
$params['line_item'][$setID][$priceFieldID]['tax_amount'] = $params['tax_amount'];
}
}
- $params['total_amount'] = $params['total_amount'] + $params['tax_amount'];
+ $params['total_amount'] = CRM_Utils_Array::value('total_amount', $params) + $params['tax_amount'];
}
elseif (isset($params['api.line_item.create'])) {
// Update total amount of contribution using lineItem
}
}
+ /**
+ * Is there only one line item attached to the contribution.
+ *
+ * @param int $id
+ * Contribution ID.
+ *
+ * @return bool
+ * @throws \CiviCRM_API3_Exception
+ */
+ public static function isSingleLineItem($id) {
+ $lineItemCount = civicrm_api3('LineItem', 'getcount', array('contribution_id' => $id));
+ return ($lineItemCount == 1);
+ }
+
/**
* Complete an order.
*
'campaign_id',
'receive_date',
);
+ if (self::isSingleLineItem($primaryContributionID)) {
+ $inputContributionWhiteList[] = 'financial_type_id';
+ }
+
+ $participant = CRM_Utils_Array::value('participant', $objects);
+ $memberships = CRM_Utils_Array::value('membership', $objects);
+ $recurContrib = CRM_Utils_Array::value('contributionRecur', $objects);
+ $event = CRM_Utils_Array::value('event', $objects);
$contributionParams = array_merge(array(
'contribution_status_id' => 'Completed',
- 'financial_type_id' => $contribution->financial_type_id,
+ 'source' => self::getRecurringContributionDescription($contribution, $event),
), 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;
}
+ $changeDate = CRM_Utils_Array::value('trxn_date', $input, date('YmdHis'));
+
+ if (empty($contributionParams['receive_date']) && $changeDate) {
+ $contributionParams['receive_date'] = $changeDate;
+ }
+
self::repeatTransaction($contribution, $input, $contributionParams);
+ $contributionParams['financial_type_id'] = $contribution->financial_type_id;
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) {
+ // Figure out what we gain from this.
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');
$values['receipt_from_email'] = $domainValues[1];
}
- if (empty($contributionParams['receive_date']) && $changeDate) {
- $contributionParams['receive_date'] = $changeDate;
- }
-
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.
//we might be renewing membership,
//so make status override false.
$membershipParams['is_override'] = FALSE;
- $membershipParams['reset'] = TRUE;
+ //CRM-17723 - reset static $relatedContactIds array()
+ $var = TRUE;
+ CRM_Member_BAO_Membership::createRelatedMemberships($var, $var, TRUE);
civicrm_api3('Membership', 'create', $membershipParams);
-
- // unset/format unwanted keys
- $removeKeys = array('id', 'contact_id', 'reset');
- $membershipParams = array_diff_key($membershipParams, array_flip($removeKeys));
- if (!empty($membershipParams['status_id']) && is_string($membershipParams['status_id'])) {
- $membershipParams['status_id'] = array_search($membershipParams['status_id'], CRM_Member_PseudoConstant::membershipStatus());
- }
- //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']) {
+ if ($event->is_email_confirm) {
// @todo this should be set by the function that sends the mail after sending.
$contributionParams['receipt_date'] = $changeDate;
}
return CRM_Core_BAO_FinancialTrxn::create($balanceTrxnParams);
}
+ /**
+ * Get the description (source field) for the recurring contribution.
+ *
+ * @param CRM_Contribute_BAO_Contribution $contribution
+ * @param CRM_Event_DAO_Event|null $event
+ *
+ * @return array
+ * @throws \CiviCRM_API3_Exception
+ */
+ protected static function getRecurringContributionDescription($contribution, $event) {
+ if (!empty($contribution->contribution_page_id)) {
+ $contributionPageTitle = civicrm_api3('ContributionPage', 'getvalue', array(
+ 'id' => $contribution->contribution_page_id,
+ 'return' => 'title',
+ ));
+ return ts('Online Contribution') . ': ' . $contributionPageTitle;
+ }
+ elseif ($event) {
+ return ts('Online Event Registration') . ': ' . $event->title;
+ }
+ return 'recurring contribution';
+ }
+
+ /**
+ * Function to add payments for contribution
+ * for Partially Paid status
+ *
+ * @param array $lineItems
+ * @param array $contributions
+ *
+ */
+ public static function addPayments($lineItems, $contributions) {
+ // get financial trxn which is a payment
+ $ftSql = "SELECT ft.id
+ FROM civicrm_financial_trxn ft
+ INNER JOIN civicrm_entity_financial_trxn eft ON eft.financial_trxn_id = ft.id AND eft.entity_table = 'civicrm_contribution'
+ WHERE eft.entity_id = %1 AND ft.is_payment = 1";
+ $sql = "SELECT fi.id, li.price_field_value_id
+ FROM civicrm_financial_item fi
+ INNER JOIN civicrm_line_item li ON li.id = fi.entity_id
+ WHERE li.contribution_id = %1";
+
+ foreach ($contributions as $k => $contribution) {
+ if ($contribution->contribution_status_id != CRM_Core_OptionGroup::getValue('contribution_status', 'Partially paid', 'name')) {
+ continue;
+ }
+ $ftId = CRM_Core_DAO::singleValueQuery($ftSql, array(1 => array($contribution->id, 'Integer')));
+ // get financial item
+ $dao = CRM_Core_DAO::executeQuery($sql, array(1 => array($contribution->id, 'Integer')));
+ while ($dao->fetch()) {
+ $ftIds[$dao->price_field_value_id] = $dao->id;
+ }
+
+ $params = array(
+ 'entity_table' => 'civicrm_financial_item',
+ 'financial_trxn_id' => $ftId,
+ );
+ foreach ($lineItems as $key => $value) {
+ $paid = $value['line_total'] * ($contribution->net_amount / $contribution->total_amount);
+ // Record Entity Financial Trxn
+ $params['amount'] = round($paid, 2);
+ $params['entity_id'] = $ftIds[$value['price_field_value_id']];
+
+ civicrm_api3('EntityFinancialTrxn', 'create', $params);
+ }
+ }
+ }
+
+ /**
+ * Function use to store line item proportionaly in
+ * in entity financial trxn table
+ *
+ * @param array $params
+ * array of contribution params.
+ * @param object $trxn
+ * CRM_Financial_DAO_FinancialTrxn object
+ * @param array $contribution
+ *
+ */
+ public static function assignProportionalLineItems($params, $trxn, $contribution) {
+ $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($params['contribution_id']);
+ if (!empty($lineItems)) {
+ // get financial item
+ $sql = "SELECT fi.id, li.price_field_value_id
+ FROM civicrm_financial_item fi
+ INNER JOIN civicrm_line_item li ON li.id = fi.entity_id
+ WHERE li.contribution_id = %1";
+ $dao = CRM_Core_DAO::executeQuery($sql, array(1 => array($params['contribution_id'], 'Integer')));
+ while ($dao->fetch()) {
+ $ftIds[$dao->price_field_value_id] = $dao->id;
+ }
+ $eftParams = array(
+ 'entity_table' => 'civicrm_financial_item',
+ 'financial_trxn_id' => $trxn->id,
+ );
+ foreach ($lineItems as $key => $value) {
+ $paid = $value['line_total'] * ($params['total_amount'] / $contribution['total_amount']);
+ // Record Entity Financial Trxn
+ $eftParams['amount'] = round($paid, 2);
+ $eftParams['entity_id'] = $ftIds[$value['price_field_value_id']];
+
+ civicrm_api3('EntityFinancialTrxn', 'create', $eftParams);
+ }
+ }
+ }
+
+ /**
+ * Function use to check check line items
+ *
+ * @param array $params
+ * array of order params.
+ *
+ * @return string
+ */
+ public static function checkLineItems(&$params) {
+ $totalAmount = CRM_Utils_Array::value('total_amount', $params);
+ $lineItemAmount = 0;
+ foreach ($params['line_items'] as &$lineItems) {
+ foreach ($lineItems['line_item'] as &$item) {
+ if (empty($item['financial_type_id'])) {
+ $item['financial_type_id'] = $params['financial_type_id'];
+ }
+ $lineItemAmount += $item['line_total'];
+ }
+ }
+ if (!isset($totalAmount)) {
+ $params['total_amount'] = $lineItemAmount;
+ }
+ elseif ($totalAmount != $lineItemAmount) {
+ return "Line item total doesn't match with total amount.";
+ }
+ return NULL;
+ }
+
}