CRM-20264 support card_type_id and pan_truncation in all backoffice form
authordeb.monish <monish.deb@jmaconsulting.biz>
Wed, 19 Apr 2017 12:25:47 +0000 (17:55 +0530)
committereileen <emcnaughton@wikimedia.org>
Thu, 20 Apr 2017 21:44:58 +0000 (09:44 +1200)
16 files changed:
CRM/Contribute/BAO/Contribution.php
CRM/Contribute/Form/AbstractEditPayment.php
CRM/Contribute/Form/Contribution.php
CRM/Contribute/Form/Contribution/Confirm.php
CRM/Core/Payment.php
CRM/Core/Payment/Form.php
CRM/Core/Payment/Manual.php
CRM/Financial/Form/Payment.php
CRM/Member/BAO/Membership.php
CRM/Member/Form/Membership.php
api/v3/Contribution.php
templates/CRM/Contribute/Form/Contribution.tpl
templates/CRM/Core/BillingBlock.tpl
templates/CRM/Member/Form/Membership.tpl
templates/CRM/Member/Form/MembershipCommon.tpl
tests/phpunit/CRM/Contribute/Form/ContributionTest.php

index fe49c31bca32ad47f16d9241b324900012efdd82..c18b70b871fb0df722af5a7b4bf7f3e10d999d96 100644 (file)
@@ -3135,6 +3135,8 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
         $balanceTrxnParams['status_id'] = $statusId;
         $balanceTrxnParams['payment_instrument_id'] = $params['contribution']->payment_instrument_id;
         $balanceTrxnParams['check_number'] = CRM_Utils_Array::value('check_number', $params);
+        $balanceTrxnParams['pan_truncation'] = CRM_Utils_Array::value('pan_truncation', $params);
+        $balanceTrxnParams['card_type_id'] = CRM_Utils_Array::value('card_type_id', $params);
         if (!empty($balanceTrxnParams['from_financial_account_id']) &&
           ($statusId == array_search('Completed', $contributionStatuses) || $statusId == array_search('Partially paid', $contributionStatuses))
         ) {
@@ -3199,6 +3201,8 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
         'status_id' => $statusId,
         'payment_instrument_id' => CRM_Utils_Array::value('payment_instrument_id', $params, $params['contribution']->payment_instrument_id),
         'check_number' => CRM_Utils_Array::value('check_number', $params),
+        'pan_truncation' => CRM_Utils_Array::value('pan_truncation', $params),
+        'card_type_id' => CRM_Utils_Array::value('card_type_id', $params),
       );
       if ($contributionStatus == 'Refunded' || $contributionStatus == 'Chargeback' || $contributionStatus == 'Cancelled') {
         $trxnParams['trxn_date'] = !empty($params['contribution']->cancel_date) ? $params['contribution']->cancel_date : date('YmdHis');
@@ -3559,6 +3563,7 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
         return;
       }
     }
+
     $trxn = CRM_Core_BAO_FinancialTrxn::create($params['trxnParams']);
     $params['entity_id'] = $trxn->id;
     if ($context != 'changePaymentInstrument') {
@@ -4464,6 +4469,8 @@ WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
       'receive_date',
       'receipt_date',
       'contribution_status_id',
+      'card_type_id',
+      'pan_truncation',
     );
     if (self::isSingleLineItem($primaryContributionID)) {
       $inputContributionWhiteList[] = 'financial_type_id';
index 4e0801e91d67b06246fe6fe1f903f5efe9e661d1..62e05145317ca4df92ef4cbe0ceb258e19c79157 100644 (file)
@@ -221,6 +221,7 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Contact_Form_Task {
     CRM_Core_Resources::singleton()->addVars('coreForm', array('contact_id' => (int) $this->_contactID));
     $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'add');
     $this->_mode = empty($this->_mode) ? CRM_Utils_Request::retrieve('mode', 'String', $this) : $this->_mode;
+    $this->assign('isBackOffice', $this->isBackOffice);
     $this->assignPaymentRelatedVariables();
   }
 
@@ -573,15 +574,27 @@ WHERE  contribution_id = {$id}
       $this->assign('credit_card_type', CRM_Utils_Array::value('credit_card_type', $this->_params));
     }
     $this->_params['ip_address'] = CRM_Utils_System::ipAddress();
-    if (in_array('credit_card_type', array_keys($this->_params))) {
-      $this->_params['card_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'card_type_id', $this->_params['credit_card_type']);
-    }
-    if (!empty($this->_params['credit_card_number']) && empty($this->_params['pan_truncation'])) {
-      $this->_params['pan_truncation'] = substr($this->_params['credit_card_number'], -4);
-    }
 
+    self::formatCreditCardDetails($this->_params);
   }
 
+  /**
+   * Format credit card details like:
+   *  1. Retrieve last 4 digit from credit card number as pan_truncation
+   *  2. Retrieve credit card type id from name
+   *
+   * @param array $params
+   *
+   * @return void
+   */
+  public static function formatCreditCardDetails(&$params) {
+    if (in_array('credit_card_type', array_keys($params))) {
+      $params['card_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'card_type_id', $params['credit_card_type']);
+    }
+    if (!empty($params['credit_card_number']) && empty($params['pan_truncation'])) {
+      $params['pan_truncation'] = substr($params['credit_card_number'], -4);
+    }
+  }
 
   /**
    * Add the billing address to the contact who paid.
index 7034a131a3b1e7dfe4f967558d46b189cb9fec62..f9209b94d102be9fbc70f7176b9f9261e6b556fd 100644 (file)
@@ -1274,6 +1274,8 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP
               'payment_processor_id' => $this->_paymentProcessor['id'],
               'is_transactional' => FALSE,
               'fee_amount' => CRM_Utils_Array::value('fee_amount', $result),
+              'card_type_id' => CRM_Utils_Array::value('card_type_id', $paymentParams),
+              'pan_truncation' => CRM_Utils_Array::value('pan_truncation', $paymentParams),
             ));
             // This has now been set to 1 in the DB - declare it here also
             $contribution->contribution_status_id = 1;
@@ -1432,10 +1434,11 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP
    * @throws \Exception
    */
   protected function submit($submittedValues, $action, $pledgePaymentID) {
-
     $pId = $contribution = $isRelatedId = FALSE;
     $this->_params = $submittedValues;
     $this->beginPostProcess();
+    // reassign submitted form values if the any information is formatted via beginPostProcess
+    $submittedValues = $this->_params;
 
     if (!empty($submittedValues['price_set_id']) && $action & CRM_Core_Action::UPDATE) {
       $line = CRM_Price_BAO_LineItem::getLineItems($this->_id, 'contribution');
@@ -1643,6 +1646,8 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP
         'cancel_reason',
         'source',
         'check_number',
+        'card_type_id',
+        'pan_truncation',
       );
       foreach ($fields as $f) {
         $params[$f] = CRM_Utils_Array::value($f, $formValues);
index 55844c1b68d906e028cd64edcb3d54d685a879a9..83ed30a6d97c2b85fcab76f068a5dbe86d0e47fd 100644 (file)
@@ -2037,6 +2037,8 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
     // fix currency ID
     $this->_params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency;
 
+    CRM_Contribute_Form_AbstractEditPayment::formatCreditCardDetails($this->_params);
+
     // CRM-18854
     if (CRM_Utils_Array::value('is_pledge', $this->_params) && !CRM_Utils_Array::value('pledge_id', $this->_values) && CRM_Utils_Array::value('adjust_recur_start_date', $this->_values)) {
       $pledgeBlock = CRM_Pledge_BAO_PledgeBlock::getPledgeBlock($this->_id);
@@ -2434,14 +2436,15 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
     if (CRM_Utils_Array::value('payment_status_id', $result) == 1) {
       try {
         civicrm_api3('contribution', 'completetransaction', array(
-            'id' => $contributionID,
-            'trxn_id' => CRM_Utils_Array::value('trxn_id', $result),
-            'payment_processor_id' => $this->_paymentProcessor['id'],
-            'is_transactional' => FALSE,
-            'fee_amount' => CRM_Utils_Array::value('fee_amount', $result),
-            'receive_date' => CRM_Utils_Array::value('receive_date', $result),
-          )
-        );
+          'id' => $contributionID,
+          'trxn_id' => CRM_Utils_Array::value('trxn_id', $result),
+          'payment_processor_id' => $this->_paymentProcessor['id'],
+          'is_transactional' => FALSE,
+          'fee_amount' => CRM_Utils_Array::value('fee_amount', $result),
+          'receive_date' => CRM_Utils_Array::value('receive_date', $result),
+          'card_type_id' => CRM_Utils_Array::value('card_type_id', $result),
+          'pan_truncation' => CRM_Utils_Array::value('pan_truncation', $result),
+        ));
       }
       catch (CiviCRM_API3_Exception $e) {
         if ($e->getErrorCode() != 'contribution_completed') {
index c26f8f4a0ca217940f26ef72f25b27908fa5685f..63657464ad731cb30b45112dc8c149053dd287b6 100644 (file)
@@ -60,6 +60,18 @@ abstract class CRM_Core_Payment {
     BILLING_MODE_BUTTON = 2,
     BILLING_MODE_NOTIFY = 4;
 
+  /**
+   * Which payment type(s) are we using?
+   *
+   * credit card
+   * direct debit
+   * or both
+   * @todo create option group - nb omnipay uses a 3rd type - transparent redirect cc
+   */
+  const
+    PAYMENT_TYPE_CREDIT_CARD = 1,
+    PAYMENT_TYPE_DIRECT_DEBIT = 2;
+
   /**
    * Subscription / Recurring payment Status
    * START, END
@@ -781,6 +793,13 @@ abstract class CRM_Core_Payment {
           'maxlength' => 4,
           'autocomplete' => 'off',
         ),
+        'rules' => array(
+          array(
+            'rule_message' => ts('Please enter valid last 4 digit card number.'),
+            'rule_name' => 'numeric',
+            'rule_parameters' => NULL,
+          ),
+        ),
       ),
     );
   }
index eadec63d937c6ba52f9a9f95dc1c96c31248ad14..a2286be5689c131ceec057bbf7386ea49bdfb4aa 100644 (file)
@@ -67,8 +67,8 @@ class CRM_Core_Payment_Form {
     $processor['object']->setBackOffice($isBackOffice);
     $processor['object']->setPaymentInstrumentID($paymentInstrumentID);
     $paymentTypeName = self::getPaymentTypeName($processor);
-    $paymentTypeLabel = self::getPaymentTypeLabel($processor);
     $form->assign('paymentTypeName', $paymentTypeName);
+    $paymentTypeLabel = self::getPaymentTypeLabel($processor);
     $form->assign('paymentTypeLabel', $paymentTypeLabel);
     $form->assign('isBackOffice', $isBackOffice);
     $form->_paymentFields = $form->billingFieldSets[$paymentTypeName]['fields'] = self::getPaymentFieldMetadata($processor);
index 13657435c306f87db947bc48f834a3637525d9e5..9024f10cab53741fe2c0578b6d548ef063339ffd 100644 (file)
@@ -95,7 +95,7 @@ class CRM_Core_Payment_Manual extends CRM_Core_Payment {
       // However there is an ambiguity as that field is an integer & should hence be called
       // credit_card_type_id, or it should store 'visa' It probably makes sense to fix that before going much
       // further as the code I've seen makes it clear that it will require work arounds.
-      return array('pan_truncation');
+      return array('credit_card_type', 'pan_truncation');
     }
     elseif ($paymentInstrument === 'Check') {
       // Really we should render check_number here, but we need to review how we edit
index 0830426248e041cf051e3ca0bd2debc0d3b2413c..996e45abc040e2077910694730e4ce0ffe9d56e1 100644 (file)
@@ -37,6 +37,7 @@ class CRM_Financial_Form_Payment extends CRM_Core_Form {
    */
   protected $_paymentProcessorID;
   protected $currency;
+
   public $_values = array();
 
   /**
index ad9338a4d3a53be76fb3afd4bec96924ae7d9d95..746d738d4201ff39b314dc203ef75b16e2cd515c 100644 (file)
@@ -2408,6 +2408,8 @@ WHERE      civicrm_membership.is_test = 0";
       'tax_amount',
       'skipLineItem',
       'contribution_recur_id',
+      'pan_truncation',
+      'card_type_id',
     );
     foreach ($recordContribution as $f) {
       $contributionParams[$f] = CRM_Utils_Array::value($f, $params);
index 4bc23750031573c74a8e89480b389602c97f46f6..cfb5a743ca132ff6c8c47670f0af9fe2c41dfe28 100644 (file)
@@ -1308,6 +1308,8 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
         'check_number',
         'campaign_id',
         'receive_date',
+        'card_type_id',
+        'pan_truncation',
       );
 
       foreach ($recordContribution as $f) {
index 2fa6ba7205113a1b238c4351f83e204c9fc65d52..aa58066f25127da0ddc479387ddb04f87d8a2efa 100644 (file)
@@ -160,6 +160,14 @@ function _civicrm_api3_contribution_create_spec(&$params) {
     'type' => CRM_Utils_Type::T_STRING,
     'description' => 'Transaction ID specific to the refund taking place',
   );
+  $params['card_type_id'] = array(
+    'title' => 'Card Type ID',
+    'description' => 'Providing Credit Card Type ID',
+    'type' => CRM_Utils_Type::T_INT,
+    'pseudoconstant' => array(
+      'optionGroupName' => 'accept_creditcard',
+    ),
+  );
 }
 
 /**
@@ -511,7 +519,6 @@ function _civicrm_api3_contribution_sendconfirmation_spec(&$params) {
  * @throws \Exception
  */
 function civicrm_api3_contribution_completetransaction(&$params) {
-
   $input = $ids = array();
   if (isset($params['payment_processor_id'])) {
     $input['payment_processor_id'] = $params['payment_processor_id'];
@@ -581,6 +588,14 @@ function _civicrm_api3_contribution_completetransaction_spec(&$params) {
     'description' => 'Date this transaction occurred',
     'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
   );
+  $params['card_type_id'] = array(
+    'title' => 'Card Type ID',
+    'description' => 'Providing Credit Card Type ID',
+    'type' => CRM_Utils_Type::T_INT,
+    'pseudoconstant' => array(
+      'optionGroupName' => 'accept_creditcard',
+    ),
+  );
 }
 
 /**
@@ -693,6 +708,8 @@ function _ipn_process_transaction(&$params, $contribution, $input, $ids, $firstC
     $input['receipt_from_name'] = CRM_Utils_Array::value('receipt_from_name', $params, $domainFromName);
     $input['receipt_from_email'] = CRM_Utils_Array::value('receipt_from_email', $params, $domainFromEmail);
   }
+  $input['card_type_id'] = CRM_Utils_Array::value('card_type_id', $params);
+  $input['pan_truncation'] = CRM_Utils_Array::value('pan_truncation', $params);
   $transaction = new CRM_Core_Transaction();
   return CRM_Contribute_BAO_Contribution::completeOrder($input, $ids, $objects, $transaction, !empty
   ($contribution->contribution_recur_id), $contribution,
index 3191ccf6e9a6eee2e2343f3a7fdb4e51e6d6eb98..193f2a417788e5b4030a743cff2c67f656723b34 100644 (file)
   </table>
 
   {if !$contributionMode}
+    <fieldset class="payment-details_group">
+      <legend>
+        {ts}Payment Details{/ts}
+      </legend>
         <table class="form-layout-compressed" >
           <tr class="crm-contribution-form-block-receive_date">
             <td class="label">{$form.receive_date.label}</td>
             <td>{$form.from_email_address.html}</td>
           </tr>
         </table>
+      </fieldset>
   {/if}
 
   {include file='CRM/Core/BillingBlockWrapper.tpl'}
index dda84773bd7248e32cf37cb9b46598dfc8ddfc31..1f4d394a89afb3e6dbc198677d15235c3cac5493 100644 (file)
   {* Payment processors sometimes need to append something to the end of the billing block. We create a region for
      clarity  - the plan is to move to assigning this through the payment processor to this region *}
 {/crmRegion}
-
index b153be9bfd375ab23a98b15f1938af1bab7e0ac0..acad1a90004ac0b268480bc8f5b91534793b5341 100644 (file)
     field_type          ="radio"
     invert              = 0
     }
-    {include file="CRM/common/showHideByFieldValue.tpl"
-    trigger_field_id    ="payment_instrument_id"
-    trigger_value       = '4'
-    target_element_id   ="checkNumber"
-    target_element_type ="table-row"
-    field_type          ="select"
-    invert              = 0
-    }
     {/if}
 
     {literal}
index 2ef6c8f194c7b19b337cf129e2f1508bbec3b46b..52e0d89c2096bc3378e99b501e7f50e3b62d8c1d 100644 (file)
       </table>
     </td>
   </tr>
-  <tr class="crm-membership-form-block-billing">
-    <td colspan="2">
-      {include file='CRM/Core/BillingBlockWrapper.tpl'}
-    </td>
-  </tr>
 
   <div class="spacer"></div>
 {/if}
+
+<tr class="crm-membership-form-block-billing">
+  <td colspan="2">
+    {include file='CRM/Core/BillingBlockWrapper.tpl'}
+  </td>
+</tr>
index 62cc9ec37be40d63e7061dcb6a184c14bb997a13..6d9710542b50ec7ae9b1e16bae3ae3a90b0421a4 100644 (file)
@@ -922,4 +922,101 @@ Price Field - Price Field 1        1   $ 100.00      $ 100.00
     $this->callAPISuccessGetCount('FinancialItem', array(), 2);
   }
 
+  /**
+   * function to test card_type and pan truncation.
+   */
+  public function testCardTypeAndPanTruncation() {
+    $form = new CRM_Contribute_Form_Contribution();
+    $form->testSubmit(
+      array(
+        'total_amount' => 100,
+        'financial_type_id' => 3,
+        'receive_date' => '04/21/2015',
+        'receive_date_time' => '11:27PM',
+        'contact_id' => $this->_individualId,
+        'payment_instrument_id' => array_search('Credit Card', $this->paymentInstruments),
+        'contribution_status_id' => 1,
+        'credit_card_type' => 'Visa',
+        'pan_truncation' => 4567,
+        'price_set_id' => 0,
+      ),
+      CRM_Core_Action::ADD
+    );
+    $contribution = $this->callAPISuccessGetSingle('Contribution',
+      array(
+        'contact_id' => $this->_individualId,
+        'return' => array('id'),
+      )
+    );
+    $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contribution['id'], 'DESC');
+    $financialTrxn = $this->callAPISuccessGetSingle(
+      'FinancialTrxn',
+      array(
+        'id' => $lastFinancialTrxnId['financialTrxnId'],
+        'return' => array('card_type_id.label', 'pan_truncation'),
+      )
+    );
+    $this->assertEquals(CRM_Utils_Array::value('card_type_id.label', $financialTrxn), 'Visa');
+    $this->assertEquals(CRM_Utils_Array::value('pan_truncation', $financialTrxn), 4567);
+  }
+
+  /**
+   * function to test card_type and pan truncation.
+   */
+  public function testCardTypeAndPanTruncationLiveMode() {
+    $visaID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'card_type_id', 'Visa');
+    $form = new CRM_Contribute_Form_Contribution();
+    $form->_mode = 'Live';
+    $error = FALSE;
+    $form->testSubmit(
+      array(
+        'total_amount' => 50,
+        'financial_type_id' => 1,
+        'receive_date' => '04/21/2015',
+        'receive_date_time' => '11:27PM',
+        'contact_id' => $this->_individualId,
+        'credit_card_number' => 4444333322221111,
+        'payment_instrument_id' => array_search('Credit Card', $this->paymentInstruments),
+        'cvv2' => 123,
+        'credit_card_exp_date' => array(
+          'M' => 9,
+          'Y' => date('Y', strtotime('+5 years')),
+        ),
+        'credit_card_type' => 'Visa',
+        'billing_first_name' => 'Junko',
+        'billing_middle_name' => '',
+        'billing_last_name' => 'Adams',
+        'billing_street_address-5' => '790L Lincoln St S',
+        'billing_city-5' => 'Maryknoll',
+        'billing_state_province_id-5' => 1031,
+        'billing_postal_code-5' => 10545,
+        'billing_country_id-5' => 1228,
+        'frequency_interval' => 1,
+        'frequency_unit' => 'month',
+        'installments' => '',
+        'hidden_AdditionalDetail' => 1,
+        'hidden_Premium' => 1,
+        'from_email_address' => '"civi45" <civi45@civicrm.com>',
+        'receipt_date' => '',
+        'receipt_date_time' => '',
+        'payment_processor_id' => $this->paymentProcessorID,
+        'currency' => 'USD',
+        'source' => 'bob sled race',
+      ),
+      CRM_Core_Action::ADD
+    );
+    $contribution = $this->callAPISuccessGetSingle('Contribution', array('contact_id' => $this->_individualId));
+    $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contribution['id'], 'DESC');
+    $financialTrxn = $this->callAPISuccessGetSingle(
+      'FinancialTrxn',
+      array(
+        'id' => $lastFinancialTrxnId['financialTrxnId'],
+        'return' => array('card_type_id', 'pan_truncation'),
+      )
+    );
+    // @todo in test environment FinancialTrxn.getsingle doesn't fetch card_type_id value
+    //$this->assertEquals(CRM_Utils_Array::value('card_type_id', $financialTrxn), $visaID);
+    $this->assertEquals(CRM_Utils_Array::value('pan_truncation', $financialTrxn), 1111);
+  }
+
 }