diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index 88ef2e87180d..dc7a4f208f41 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -4000,7 +4000,8 @@ public static function addActivityForPayment($entityObj, $trxnObj, $activityType * * @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 e6070ebfb1f4..edef5913887b 100644 --- a/CRM/Core/BAO/FinancialTrxn.php +++ b/CRM/Core/BAO/FinancialTrxn.php @@ -454,6 +454,7 @@ public static function getPartialPaymentWithType($entityId, $entityName = 'parti 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 656f2a03e4dd..2897ee1526ea 100644 --- a/CRM/Financial/BAO/Payment.php +++ b/CRM/Financial/BAO/Payment.php @@ -117,4 +117,158 @@ public static function create($params) { 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 355ea7ad50c5..a4292a5b6b71 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 85a3786745ff..5585a58f6b82 100644 --- a/tests/phpunit/api/v3/PaymentTest.php +++ b/tests/phpunit/api/v3/PaymentTest.php @@ -104,6 +104,39 @@ public function testGetPayment() { )); } + /** + * 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 */