From a79d2ec285ee3bce8ea8ca91e7b357a3cc2cea47 Mon Sep 17 00:00:00 2001 From: eileen Date: Mon, 11 Feb 2019 16:39:55 +1300 Subject: [PATCH] Add new Payment.sendconfirmation api This adds a new api Payment.sendconfirmation intended to be an equivalent of Contribution.sendconfirmation. The key thing this needs to be able to do is to load up all related information to assign to the template - this is not all covered in this PR & until it is functionally equivalent to AdditionalPayment::sendEmail we can't switch over but since it's new functionality we should be able to merge & keep working on it. Note that there is discussion on the PR this relates to (#13330) about what should happen if 'Payment.create' api accepts 'is_email_receipt'. I think the most logical outcome is that receipts would go out if it caused the contribution to be completed (ie. we pass is_email_receipt into completetransaction api). However, I don't feel like that is 'settled' yet - separating into a second api (Payment.sendnotification) means we will have 2 generic function which can be called from multiple places in the code rather than 2 functions tightly tied to the form layer. I think it's OK if it is 2 not one --- CRM/Contribute/BAO/Contribution.php | 3 +- CRM/Core/BAO/FinancialTrxn.php | 1 + CRM/Financial/BAO/Payment.php | 154 +++++++++++++++++++++++++++ api/v3/Payment.php | 46 ++++++++ tests/phpunit/api/v3/PaymentTest.php | 33 ++++++ 5 files changed, 236 insertions(+), 1 deletion(-) diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index 88ef2e8718..dc7a4f208f 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -4000,7 +4000,8 @@ WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']}) * * @return mixed */ - public static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $usingLineTotal = FALSE) { + public static function getPaymentInfo($id, $component = 'contribution', $getTrxnInfo = FALSE, $usingLineTotal = FALSE) { + // @todo deprecate passing in component - always call with contribution. if ($component == 'event') { $entity = 'participant'; $entityTable = 'civicrm_participant'; diff --git a/CRM/Core/BAO/FinancialTrxn.php b/CRM/Core/BAO/FinancialTrxn.php index e6070ebfb1..edef591388 100644 --- a/CRM/Core/BAO/FinancialTrxn.php +++ b/CRM/Core/BAO/FinancialTrxn.php @@ -454,6 +454,7 @@ WHERE ceft.entity_id = %1"; return $value; } + // @todo - deprecate passing in entity & type - just figure out contribution id FIRST if ($entityName == 'participant') { $contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $entityId, 'contribution_id', 'participant_id'); } diff --git a/CRM/Financial/BAO/Payment.php b/CRM/Financial/BAO/Payment.php index 656f2a03e4..2897ee1526 100644 --- a/CRM/Financial/BAO/Payment.php +++ b/CRM/Financial/BAO/Payment.php @@ -117,4 +117,158 @@ class CRM_Financial_BAO_Payment { return $trxn; } + /** + * Send an email confirming a payment that has been received. + * + * @param array $params + * + * @return array + */ + public static function sendConfirmation($params) { + + $entities = self::loadRelatedEntities($params['id']); + $sendTemplateParams = array( + 'groupName' => 'msg_tpl_workflow_contribution', + 'valueName' => 'payment_or_refund_notification', + 'PDFFilename' => ts('notification') . '.pdf', + 'contactId' => $entities['contact']['id'], + 'toName' => $entities['contact']['display_name'], + 'toEmail' => $entities['contact']['email'], + 'tplParams' => self::getConfirmationTemplateParameters($entities), + ); + return CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams); + } + + /** + * Load entities related to the current payment id. + * + * This gives us all the data we need to send an email confirmation but avoiding + * getting anything not tested for the confirmations. We retrieve the 'full' event as + * it has been traditionally assigned in full. + * + * @param int $id + * + * @return array + * - contact = ['id' => x, 'display_name' => y, 'email' => z] + * - event = [.... full event details......] + * - contribution = ['id' => x], + * - payment = [payment info + payment summary info] + */ + protected static function loadRelatedEntities($id) { + $entities = []; + $contributionID = (int) civicrm_api3('EntityFinancialTrxn', 'getvalue', [ + 'financial_trxn_id' => $id, + 'entity_table' => 'civicrm_contribution', + 'return' => 'entity_id', + ]); + $entities['contribution'] = ['id' => $contributionID]; + $entities['payment'] = array_merge(civicrm_api3('FinancialTrxn', 'getsingle', ['id' => $id]), + CRM_Contribute_BAO_Contribution::getPaymentInfo($contributionID) + ); + + $contactID = self::getPaymentContactID($contributionID); + list($displayName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactID); + $entities['contact'] = ['id' => $contactID, 'display_name' => $displayName, 'email' => $email]; + + $participantRecords = civicrm_api3('ParticipantPayment', 'get', [ + 'contribution_id' => $contributionID, + 'api.Participant.get' => ['return' => 'event_id'], + 'sequential' => 1, + ])['values']; + if (!empty($participantRecords)) { + $entities['event'] = civicrm_api3('Event', 'getsingle', ['id' => $participantRecords[0]['api.Participant.get']['values'][0]['event_id']]); + } + + return $entities; + } + + /** + * @param int $contributionID + * + * @return int + */ + public static function getPaymentContactID($contributionID) { + $contribution = civicrm_api3('Contribution', 'getsingle', [ + 'id' => $contributionID , + 'return' => ['contact_id'], + ]); + return (int) $contribution['contact_id']; + } + /** + * @param array $entities + * Related entities as an array keyed by the various entities. + * + * @return array + * Values required for the notification + * - contact_id + * - template_variables + * - event (DAO of event if relevant) + */ + public static function getConfirmationTemplateParameters($entities) { + $templateVariables = [ + 'contactDisplayName' => $entities['contact']['display_name'], + 'totalAmount' => $entities['payment']['total'], + 'amountOwed' => $entities['payment']['balance'], + 'paymentAmount' => $entities['payment']['total_amount'], + 'event' => NULL, + 'component' => 'contribution', + ]; + if (!empty($entities['event'])) { + $templateVariables['component'] = 'event'; + $templateVariables['event'] = $entities['event']; + } + + return self::filterUntestedTemplateVariables($templateVariables); + } + + /** + * Filter out any untested variables. + * + * This just serves to highlight if any variables are added without a unit test also being added. + * + * (if hit then add a unit test for the param & add to this array). + * + * @param array $params + * + * @return array + */ + public static function filterUntestedTemplateVariables($params) { + $testedTemplateVariables = [ + 'contactDisplayName', + 'totalAmount', + 'amountOwed', + 'paymentAmount', + 'event', + 'component', + ]; + // Need to do these before switching the form over... + $todoParams = [ + 'isRefund', + 'totalPaid', + 'refundAmount', + 'paymentsComplete', + 'receive_date', + 'paidBy', + 'checkNumber', + 'contributeMode', + 'isAmountzero', + 'billingName', + 'address', + 'credit_card_type', + 'credit_card_number', + 'credit_card_exp_date', + 'isShowLocation', + 'location', + 'eventEmail', + '$event.participant_role', + ]; + $filteredParams = []; + foreach ($testedTemplateVariables as $templateVariable) { + // This will cause an a-notice if any are NOT set - by design. Ensuring + // they are set prevents leakage. + $filteredParams[$templateVariable] = $params[$templateVariable]; + } + return $filteredParams; + } + } diff --git a/api/v3/Payment.php b/api/v3/Payment.php index 355ea7ad50..a4292a5b6b 100644 --- a/api/v3/Payment.php +++ b/api/v3/Payment.php @@ -235,3 +235,49 @@ function _civicrm_api3_payment_cancel_spec(&$params) { ), ); } + +/** + * Send a payment confirmation. + * + * @param array $params + * Input parameters. + * + * @return array + * @throws Exception + */ +function civicrm_api3_payment_sendconfirmation($params) { + $allowedParams = [ + 'receipt_from_email', + 'receipt_from_name', + 'cc_receipt', + 'bcc_receipt', + 'receipt_text', + 'id', + ]; + $input = array_intersect_key($params, array_flip($allowedParams)); + // use either the contribution or membership receipt, based on whether it’s a membership-related contrib or not + $result = CRM_Financial_BAO_Payment::sendConfirmation($input); + return civicrm_api3_create_success([ + $params['id'] => [ + 'is_sent' => $result[0], + 'subject' => $result[1], + 'message_txt' => $result[2], + 'message_html' => $result[3], + ]]); +} + +/** + * Adjust Metadata for sendconfirmation action. + * + * The metadata is used for setting defaults, documentation & validation. + * + * @param array $params + * Array of parameters determined by getfields. + */ +function _civicrm_api3_payment_sendconfirmation_spec(&$params) { + $params['id'] = array( + 'api.required' => 1, + 'title' => ts('Payment ID'), + 'type' => CRM_Utils_Type::T_INT, + ); +} diff --git a/tests/phpunit/api/v3/PaymentTest.php b/tests/phpunit/api/v3/PaymentTest.php index 85a3786745..5585a58f6b 100644 --- a/tests/phpunit/api/v3/PaymentTest.php +++ b/tests/phpunit/api/v3/PaymentTest.php @@ -104,6 +104,39 @@ class api_v3_PaymentTest extends CiviUnitTestCase { )); } + /** + * Test email receipt for partial payment. + */ + public function testPaymentEmailReceipt() { + $mut = new CiviMailUtils($this); + list($lineItems, $contribution) = $this->createParticipantWithContribution(); + $params = array( + 'contribution_id' => $contribution['id'], + 'total_amount' => 50, + ); + $payment = $this->callAPISuccess('payment', 'create', $params); + $this->checkPaymentResult($payment, [ + $payment['id'] => [ + 'from_financial_account_id' => 7, + 'to_financial_account_id' => 6, + 'total_amount' => 50, + 'status_id' => 1, + 'is_payment' => 1, + ], + ]); + + $this->callAPISuccess('Payment', 'sendconfirmation', ['id' => $payment['id']]); + $mut->assertSubjects(['Payment Receipt - Annual CiviCRM meet']); + $mut->checkMailLog(array( + 'Dear Mr. Anthony Anderson II', + 'Total Fees: $ 300.00', + 'This Payment Amount: $ 50.00', + 'Balance Owed: $ 100.00', //150 was paid in the 1st payment. + 'Event Information and Location', + )); + $mut->stop(); + } + /** * Test create payment api with no line item in params */ -- 2.25.1