Add new Payment.sendconfirmation api
authoreileen <emcnaughton@wikimedia.org>
Mon, 11 Feb 2019 03:39:55 +0000 (16:39 +1300)
committereileen <emcnaughton@wikimedia.org>
Wed, 13 Feb 2019 23:24:12 +0000 (12:24 +1300)
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
CRM/Core/BAO/FinancialTrxn.php
CRM/Financial/BAO/Payment.php
api/v3/Payment.php
tests/phpunit/api/v3/PaymentTest.php

index 88ef2e87180d8f905a1bb68b76ca666b031dddce..dc7a4f208f41552a06e6d8ea51b3d3b869d788b6 100644 (file)
@@ -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';
index e6070ebfb1f454d8f74ca7421710c4b327154201..edef5913887baf7cff6095729decd6bb56789f23 100644 (file)
@@ -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');
     }
index 656f2a03e4dd1f0561479d974d7f78874f0f8cdc..2897ee1526ea4be1864c07af265020d73c53c5e3 100644 (file)
@@ -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;
+  }
+
 }
index 355ea7ad50c5e2cc7da99f7685e8fc91f5e4a426..a4292a5b6b719e52c24ba52e565cce07fdc5c1c3 100644 (file)
@@ -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,
+  );
+}
index 85a3786745ff74aec7ffd422254ba33db0e0ac93..5585a58f6b82db4c34b82d8b834ebae4c01352d6 100644 (file)
@@ -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
    */