CRM-17114 move setting of billing address data onto payment form
authoreileenmcnaugton <eileen@fuzion.co.nz>
Mon, 31 Aug 2015 00:34:02 +0000 (12:34 +1200)
committereileenmcnaugton <eileen@fuzion.co.nz>
Mon, 31 Aug 2015 02:11:19 +0000 (14:11 +1200)
CRM/Core/Payment.php
CRM/Core/Payment/Form.php

index 39f5aa34a251b82564fd3e388497c08491c37592..a288080f7ca4d305dd65cb0d1e5f64dd3d6d5d6b 100644 (file)
@@ -536,6 +536,141 @@ abstract class CRM_Core_Payment {
     );
   }
 
+  /**
+   * Get billing fields required for this processor.
+   *
+   * We apply the existing default of returning fields only for payment processor type 1. Processors can override to
+   * alter.
+   *
+   * @param int $billingLocationID
+   *
+   * @return array
+   */
+  public function getBillingAddressFields($billingLocationID) {
+    if ($this->_paymentProcessor['billing_mode'] != 1 && $this->_paymentProcessor['billing_mode'] != 3) {
+      return array();
+    }
+    return array(
+      'first_name' => 'billing_first_name',
+      'middle_name' => 'billing_middle_name',
+      'last_name' => 'billing_last_name',
+      'street_address' => "billing_street_address-{$billingLocationID}",
+      'city' => "billing_city-{$billingLocationID}",
+      'country' => "billing_country_id-{$billingLocationID}",
+      'state_province' => "billing_state_province_id-{$billingLocationID}",
+      'postal_code' => "billing_postal_code-{$billingLocationID}",
+    );
+  }
+
+  /**
+   * Get form metadata for billing address fields.
+   *
+   * @param int $billingLocationID
+   *
+   * @return array
+   *    Array of metadata for address fields.
+   */
+  public function getBillingAddressFieldsMetadata($billingLocationID) {
+    $metadata = array();
+    $metadata['billing_first_name'] = array(
+      'htmlType' => 'text',
+      'name' => 'billing_first_name',
+      'title' => ts('Billing First Name'),
+      'cc_field' => TRUE,
+      'attributes' => array(
+        'size' => 30,
+        'maxlength' => 60,
+        'autocomplete' => 'off',
+      ),
+      'is_required' => TRUE,
+    );
+
+    $metadata['billing_middle_name'] = array(
+      'htmlType' => 'text',
+      'name' => 'billing_middle_name',
+      'title' => ts('Billing Middle Name'),
+      'cc_field' => TRUE,
+      'attributes' => array(
+        'size' => 30,
+        'maxlength' => 60,
+        'autocomplete' => 'off',
+      ),
+      'is_required' => FALSE,
+    );
+
+    $metadata['billing_last_name'] = array(
+      'htmlType' => 'text',
+      'name' => 'billing_last_name',
+      'title' => ts('Billing Last Name'),
+      'cc_field' => TRUE,
+      'attributes' => array(
+        'size' => 30,
+        'maxlength' => 60,
+        'autocomplete' => 'off',
+      ),
+      'is_required' => TRUE,
+    );
+
+    $metadata["billing_street_address-{$billingLocationID}"] = array(
+      'htmlType' => 'text',
+      'name' => "billing_street_address-{$billingLocationID}",
+      'title' => ts('Street Address'),
+      'cc_field' => TRUE,
+      'attributes' => array(
+        'size' => 30,
+        'maxlength' => 60,
+        'autocomplete' => 'off',
+      ),
+      'is_required' => TRUE,
+    );
+
+    $metadata["billing_city-{$billingLocationID}"] = array(
+      'htmlType' => 'text',
+      'name' => "billing_city-{$billingLocationID}",
+      'title' => ts('City'),
+      'cc_field' => TRUE,
+      'attributes' => array(
+        'size' => 30,
+        'maxlength' => 60,
+        'autocomplete' => 'off',
+      ),
+      'is_required' => TRUE,
+    );
+
+    $metadata["billing_state_province_id-{$billingLocationID}"] = array(
+      'htmlType' => 'chainSelect',
+      'title' => ts('State/Province'),
+      'name' => "billing_state_province_id-{$billingLocationID}",
+      'cc_field' => TRUE,
+      'is_required' => TRUE,
+    );
+
+    $metadata["billing_postal_code-{$billingLocationID}"] = array(
+      'htmlType' => 'text',
+      'name' => "billing_postal_code-{$billingLocationID}",
+      'title' => ts('Postal Code'),
+      'cc_field' => TRUE,
+      'attributes' => array(
+        'size' => 30,
+        'maxlength' => 60,
+        'autocomplete' => 'off',
+      ),
+      'is_required' => TRUE,
+    );
+
+    $metadata["billing_country_id-{$billingLocationID}"] = array(
+      'htmlType' => 'select',
+      'name' => "billing_country_id-{$billingLocationID}",
+      'title' => ts('Country'),
+      'cc_field' => TRUE,
+      'attributes' => array(
+        '' => ts('- select -'),
+      ) + CRM_Core_PseudoConstant::country(),
+      'is_required' => TRUE,
+    );
+    return $metadata;
+  }
+
   /**
    * Get base url dependent on component.
    *
index c5ad956590b24e13c0a3c64c6e151409e6ba628c..476d266ac3f6f86ceaf84fc0ac20202354553e63 100644 (file)
@@ -26,6 +26,7 @@
  */
 
 /**
+ * Class for constructing the payment processor block.
  *
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2015
@@ -54,41 +55,67 @@ class CRM_Core_Payment_Form {
    */
   static public function setPaymentFieldsByProcessor(&$form, $processor, $forceBillingFieldsForPayLater = FALSE, $isBackOffice = FALSE) {
     $form->billingFieldSets = array();
-    if ($processor != NULL) {
-      // ie it is pay later
-      $paymentFields = self::getPaymentFields($processor);
-      $paymentTypeName = self::getPaymentTypeName($processor);
-      $paymentTypeLabel = self::getPaymentTypeLabel($processor);
-      //@todo if we switch to iterating through $form->billingFieldSets we won't need to assign these directly
-      $form->assign('paymentTypeName', $paymentTypeName);
-      $form->assign('paymentTypeLabel', $paymentTypeLabel);
-
-      $form->billingFieldSets[$paymentTypeName]['fields'] = $form->_paymentFields = array_intersect_key(self::getPaymentFieldMetadata($processor), array_flip($paymentFields));
-      $form->assign('paymentFields', $paymentFields);
-    }
-
-    // @todo - replace this section with one similar to above per discussion - probably use a manual processor shell class to stand in for that capability
-    //return without adding billing fields if billing_mode = 4 (@todo - more the ability to set that to the payment processor)
-    // or payment processor is NULL (pay later)
-    if (($processor == NULL && !$forceBillingFieldsForPayLater) || CRM_Utils_Array::value('billing_mode', $processor) == 4) {
+    if (empty($processor)) {
+      self::hackyHandlePayLaterInPaymentProcessorFunction($form, $forceBillingFieldsForPayLater);
       return;
     }
-    //@todo setPaymentFields defines the billing fields - this should be moved to the processor class & renamed getBillingFields
-    // potentially pay later would also be a payment processor
-    //also set the billingFieldSet to hold all the details required to render the fieldset so we can iterate through the fieldset - making
-    // it easier to re-order in hooks etc. The billingFieldSets param is used to determine whether to show the billing pane
-    CRM_Core_Payment_Form::setBillingDetailsFields($form);
+
+    $paymentTypeName = self::getPaymentTypeName($processor);
+    $paymentTypeLabel = self::getPaymentTypeLabel($processor);
+    $form->assign('paymentTypeName', $paymentTypeName);
+    $form->assign('paymentTypeLabel', $paymentTypeLabel);
+    $form->_paymentFields = $form->billingFieldSets[$paymentTypeName]['fields'] = self::getPaymentFieldMetadata($processor);
+    $form->_paymentFields = array_merge($form->_paymentFields, self::getBillingAddressMetadata($processor, $form->_bltID));
+    $form->assign('paymentFields', self::getPaymentFields($processor));
+    self::setBillingAddressFields($form, $processor);
+    // @todo - this may be obsolete - although potentially it could be used to re-order things in the form.
     $form->billingFieldSets['billing_name_address-group']['fields'] = array();
   }
 
   /**
    * Add general billing fields.
-   * @todo set these like processor fields & let payment processors alter them
    *
    * @param CRM_Core_Form $form
+   * @param CRM_Core_Payment $processor
    *
    * @return void
    */
+  static protected function setBillingAddressFields(&$form, $processor) {
+    $billingID = $form->_bltID;
+    $smarty = CRM_Core_Smarty::singleton();
+    $smarty->assign('billingDetailsFields', self::getBillingAddressFields($processor, $billingID));
+  }
+
+  /**
+   * Add pay later billing fields
+   *
+   * @deprecated
+   *
+   * This is here to preserve the old flow for pay-later requiring billing as I am unsure how to replicate it or what to
+   * expect from it/ whether it even works.
+   *
+   * Including the pay-later flow in this form is pretty hacky unless we adopt the proposed process of adding
+   * an offline / pay later processor (CRM_Core_Payment_Offline). In which case it would either be implemented
+   * (preferably) like other processors or (possibly) as a pseudo-processor with the Civi\Payment\System->getById
+   * turning that class if $id === 0 or getByProcessor returning it when $processor === array(); If we go down the path
+   * we probably also want to add the default pay-later text into the signature field of the pay later processor and
+   * implement a function similar to the dummy class where the payment processor outcome class can be set.
+   *
+   * Then doPayment could be called regardless of whether the flow is paylater or not - it wouldn't do much although
+   * people might leverage it's hook - but it would simplify the main postProcess flow as it would look like
+   *
+   * if ($paymentStatus === Completed) {
+   *   $processor->setPaymentResult = array('payment_status_id', 1);
+   * }
+   * $processor->doDirectPayment();
+   * etc
+   *
+   * And the postProcess code would not need to distinguish between pay later/ offline & online payments.
+   *
+   * Alternatively enforcing certain fields for pay later in some cases would be a candidate for an extension.
+   *
+   * @param CRM_Core_Form $form
+   */
   static protected function setBillingDetailsFields(&$form) {
     $bltID = $form->_bltID;
 
@@ -229,9 +256,12 @@ class CRM_Core_Payment_Form {
   }
 
   /**
+   * Get the payment fields that apply to this processor.
+   *
    * @param array $paymentProcessor
-   * @todo it will be necessary to set details that affect it - mostly likely take Country as a param. Should we add generic
-   * setParams on processor class or just setCountry which we know we need?
+   *
+   * @todo sometimes things like the country alter the required fields (e.g direct debit fields). We should possibly
+   * set these before calling getPaymentFormFields (as we identify them).
    *
    * @return array
    */
@@ -247,7 +277,37 @@ class CRM_Core_Payment_Form {
    */
   public static function getPaymentFieldMetadata($paymentProcessor) {
     $paymentProcessorObject = Civi\Payment\System::singleton()->getByProcessor($paymentProcessor);
-    return $paymentProcessorObject->getPaymentFormFieldsMetadata();
+    return array_intersect_key($paymentProcessorObject->getPaymentFormFieldsMetadata(), array_flip(self::getPaymentFields($paymentProcessor)));
+  }
+
+  /**
+   * Get the billing fields that apply to this processor.
+   *
+   * @param array $paymentProcessor
+   * @param int $billingLocationID
+   *   ID of billing location type.
+   *
+   * @todo sometimes things like the country alter the required fields (e.g postal code). We should possibly
+   * set these before calling getPaymentFormFields (as we identify them).
+   *
+   * @return array
+   */
+  public static function getBillingAddressFields($paymentProcessor, $billingLocationID) {
+    $paymentProcessorObject = Civi\Payment\System::singleton()->getByProcessor($paymentProcessor);
+    return $paymentProcessorObject->getBillingAddressFields($billingLocationID);
+  }
+
+  /**
+   * @param array $paymentProcessor
+   *
+   * @param int $billingLocationID
+   *
+   * @return array
+   * @throws \CRM_Core_Exception
+   */
+  public static function getBillingAddressMetadata($paymentProcessor, $billingLocationID) {
+    $paymentProcessorObject = Civi\Payment\System::singleton()->getByProcessor($paymentProcessor);
+    return $paymentProcessorObject->getBillingAddressFieldsMetadata($billingLocationID);
   }
 
   /**
@@ -471,4 +531,22 @@ class CRM_Core_Payment_Form {
     return CRM_Utils_Array::value('Y', $src['credit_card_exp_date']);
   }
 
+  /**
+   * Set billing fields for pay later.
+   *
+   * This is considered hacky because pay later has basically been cludged onto the payment processor form.
+   *
+   * See notes on the deprecated function as to how this could be restructured. Alternatively this pay later
+   * handling could be moved out of the payment processor form all together.
+   *
+   * @param CRM_Core_Form $form
+   * @param int $forceBillingFieldsForPayLater
+   */
+  protected static function hackyHandlePayLaterInPaymentProcessorFunction(&$form, $forceBillingFieldsForPayLater) {
+    if ($forceBillingFieldsForPayLater) {
+      CRM_Core_Payment_Form::setBillingDetailsFields($form);
+      $form->billingFieldSets['billing_name_address-group']['fields'] = array();
+    }
+  }
+
 }