CRM-17256 fix for paylater billing_required
authoreileenmcnaugton <eileen@fuzion.co.nz>
Tue, 13 Oct 2015 13:39:58 +0000 (02:39 +1300)
committereileenmcnaugton <eileen@fuzion.co.nz>
Tue, 13 Oct 2015 13:46:44 +0000 (02:46 +1300)
This commit sets up pay-later as a payment processor class in limited capacity to allow it to cope
with the field loading for billing_required_for_pay_later contribution forms. I had hoped not to have to do this
in 4.7 but the address fields were not loading correctly on form-change :-(. This did allow me to remove quite a few
hacks that were in place to support the way the billing_for_pay_later was working though :-).

Note that a better solution than a check-box for billing-required-for-pay-later would be to allow joining a profile
of field that the paymnet processor should be 'encouraged' to provide  on the payment form. ie. the payment processor could
then build the form to include the profile fields and those required (if any) by it's processor.

The code is written on the assumption the billing profile might be changed to a real profile at some point

13 files changed:
CRM/Contribute/Form/Contribution.php
CRM/Contribute/Form/Contribution/Main.php
CRM/Contribute/Form/ContributionBase.php
CRM/Contribute/Form/UpdateBilling.php
CRM/Core/Form.php
CRM/Core/Payment.php
CRM/Core/Payment/Form.php
CRM/Core/Payment/Manual.php [new file with mode: 0644]
CRM/Core/Payment/ProcessorForm.php
CRM/Financial/BAO/PaymentProcessor.php
CRM/Financial/Form/Payment.php
Civi/Payment/System.php
templates/CRM/common/paymentBlock.tpl

index 074fe609e9151cdf9f772fe8ff39dc0e2a5bd69e..7b00a75e11592bc03b2937885b108173cca75f6c 100644 (file)
@@ -907,7 +907,7 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP
       }
       else {
         // validate payment instrument (e.g. credit card number)
-        CRM_Core_Payment_Form::validatePaymentInstrument($fields['payment_processor_id'], $fields, $errors, $self);
+        CRM_Core_Payment_Form::validatePaymentInstrument($fields['payment_processor_id'], $fields, $errors, NULL);
       }
     }
 
index 59d431f716dafc59bc26fcd97998ddaa11db6eeb..051786be2d595f243c90b6b55719d10ae679a68c 100644 (file)
@@ -893,19 +893,18 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu
       }
     }
 
-    // also return if paylater mode
-    if (CRM_Utils_Array::value('payment_processor_id', $fields) == 0 && $self->_isBillingAddressRequiredForPayLater == 0) {
-      return empty($errors) ? TRUE : $errors;
-    }
-
     // if the user has chosen a free membership or the amount is less than zero
-    // i.e. we skip calling the payment processor and hence dont need credit card
-    // info
+    // i.e. we don't need to validate payment related fields or profiles.
     if ((float) $amount <= 0.0) {
       return $errors;
     }
 
-    CRM_Core_Payment_Form::validatePaymentInstrument($fields['payment_processor_id'], $fields, $errors, $self);
+    CRM_Core_Payment_Form::validatePaymentInstrument(
+      $fields['payment_processor_id'],
+      $fields,
+      $errors,
+      (!$self->_isBillingAddressRequiredForPayLater ? NULL : 'billing')
+    );
 
     foreach (CRM_Contact_BAO_Contact::$_greetingTypes as $greeting) {
       if ($greetingType = CRM_Utils_Array::value($greeting, $fields)) {
@@ -1161,7 +1160,7 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu
     // be & by requiring $memFee down here we make it harder to do a sensible refactoring of the function
     // above (ie. extract the amount in a small function).
     if ($this->_values['is_monetary'] &&
-      is_array($this->_paymentProcessor) &&
+      !empty($this->_paymentProcessor) &&
       ((float ) $params['amount'] > 0.0 || $memFee > 0.0)
     ) {
       $this->setContributeMode();
index 0984975146baa66f06509b6bc4af394e4f249736..1bc1a51747bead475e49f7226e5cb5441135b5c4 100644 (file)
@@ -451,6 +451,17 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
       $this->_isBillingAddressRequiredForPayLater = CRM_Utils_Array::value('is_billing_required', $this->_values);
       $this->assign('isBillingAddressRequiredForPayLater', $this->_isBillingAddressRequiredForPayLater);
     }
+
+    // We save the fact that the profile 'billing' is required on the payment form.
+    // Currently pay-later is the only 'processor' that takes notice of this - but ideally
+    // 1) it would be possible to select the minimum_billing_profile_id for the contribution form
+    // 2) that profile_id would be set on the payment processor
+    // 3) the payment processor would return a billing form that combines these user-configured
+    // minimums with the payment processor minimums. This would lead to fields like 'postal_code'
+    // only being on the form if either the admin has configured it as wanted or the processor
+    // requires it.
+    $this->assign('billing_profile_id', ($this->_isBillingAddressRequiredForPayLater ? 'billing' : ''));
+
   }
 
   /**
index db3dd856f5718368d6244838a9dff959eb7c6252..588ac72a0e557204e85b28c60559908cb8cf08cb 100644 (file)
@@ -222,7 +222,7 @@ class CRM_Contribute_Form_UpdateBilling extends CRM_Core_Form {
     CRM_Core_Form::validateMandatoryFields($self->_fields, $fields, $errors);
 
     // validate the payment instrument values (e.g. credit card number)
-    CRM_Core_Payment_Form::validatePaymentInstrument($self->_paymentProcessor['id'], $fields, $errors, $self);
+    CRM_Core_Payment_Form::validatePaymentInstrument($self->_paymentProcessor['id'], $fields, $errors, NULL);
 
     return empty($errors) ? TRUE : $errors;
   }
index 9a99b67fc3191aeff7c6b841a5615f56ff91a7e2..f5ba798e2b0766b54e4b70afa213d9bdac900dd2 100644 (file)
@@ -733,6 +733,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
    */
   protected function preProcessPaymentOptions() {
     $this->_paymentProcessorID = NULL;
+    $this->_paymentProcessors[0] = CRM_Financial_BAO_PaymentProcessor::getPayment(0);
     if ($this->_paymentProcessors) {
       if (!empty($this->_submitValues)) {
         $this->_paymentProcessorID = CRM_Utils_Array::value('payment_processor_id', $this->_submitValues);
@@ -750,7 +751,9 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
           }
         }
       }
-      if ($this->_paymentProcessorID) {
+      if ($this->_paymentProcessorID
+        || (isset($this->_submitValues['payment_processor_id']) && $this->_submitValues['payment_processor_id'] == 0)
+      ) {
         CRM_Core_Payment_ProcessorForm::preProcess($this);
       }
       else {
index e80568779e088705f295c9afac1214c584605825..2d20cef15c005b8e0994cfab08a6c737d74f45c0 100644 (file)
@@ -91,6 +91,22 @@ abstract class CRM_Core_Payment {
    */
   protected $baseReturnUrl;
 
+  /**
+   * The profile configured to show on the billing form.
+   *
+   * Currently only the pseudo-profile 'billing' is supported but hopefully in time we will take an id and
+   * load that from the DB and the processor will be able to return a set of fields that combines it's minimum
+   * requirements with the configured requirements.
+   *
+   * Currently only the pseudo-processor 'manual' or 'pay-later' uses this setting to return a 'curated' set
+   * of fields.
+   *
+   * Note this change would probably include converting 'billing' to a reserved profile.
+   *
+   * @var int|string
+   */
+  protected $billingProfile;
+
   /**
    * Set Base return URL.
    *
@@ -101,6 +117,15 @@ abstract class CRM_Core_Payment {
     $this->baseReturnUrl = $url;
   }
 
+  /**
+   * Set the configured payment profile.
+   *
+   * @param int|string $value
+   */
+  public function setBillingProfile($value) {
+    $this->billingProfile = $value;
+  }
+
   /**
    * Opportunity for the payment processor to override the entire form build.
    *
index 6fe963ab1e7609d84f3ba6965e6c694b2a647d3f..3ac8637c5cb09c9f39ac12e29ac9cfe8794b5ee1 100644 (file)
@@ -48,18 +48,20 @@ class CRM_Core_Payment_Form {
    * @param CRM_Contribute_Form_AbstractEditPayment|CRM_Contribute_Form_Contribution_Main $form
    * @param array $processor
    *   Array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors.
-   * @param bool $forceBillingFieldsForPayLater
+   * @param int $billing_profile_id
    *   Display billing fields even for pay later.
    * @param bool $isBackOffice
    *   Is this a back office function? If so the option to suppress the cvn needs to be evaluated.
    */
-  static public function setPaymentFieldsByProcessor(&$form, $processor, $forceBillingFieldsForPayLater = FALSE, $isBackOffice = FALSE) {
+  static public function setPaymentFieldsByProcessor(&$form, $processor, $billing_profile_id = NULL, $isBackOffice = FALSE) {
     $form->billingFieldSets = array();
+    // Load the pay-later processor
+    // @todo load this right up where the other processors are loaded initially.
     if (empty($processor)) {
-      self::hackyHandlePayLaterInPaymentProcessorFunction($form, $forceBillingFieldsForPayLater);
-      return;
+      $processor = CRM_Financial_BAO_PaymentProcessor::getPayment(0);
     }
 
+    $processor['object']->setBillingProfile($billing_profile_id);
     $paymentTypeName = self::getPaymentTypeName($processor);
     $paymentTypeLabel = self::getPaymentTypeLabel($processor);
     $form->assign('paymentTypeName', $paymentTypeName);
@@ -84,126 +86,6 @@ class CRM_Core_Payment_Form {
     $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;
-
-    $form->_paymentFields['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,
-    );
-
-    $form->_paymentFields['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,
-    );
-
-    $form->_paymentFields['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,
-    );
-
-    $form->_paymentFields["billing_street_address-{$bltID}"] = array(
-      'htmlType' => 'text',
-      'name' => "billing_street_address-{$bltID}",
-      'title' => ts('Street Address'),
-      'cc_field' => TRUE,
-      'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
-      'is_required' => TRUE,
-    );
-
-    $form->_paymentFields["billing_city-{$bltID}"] = array(
-      'htmlType' => 'text',
-      'name' => "billing_city-{$bltID}",
-      'title' => ts('City'),
-      'cc_field' => TRUE,
-      'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
-      'is_required' => TRUE,
-    );
-
-    $form->_paymentFields["billing_state_province_id-{$bltID}"] = array(
-      'htmlType' => 'chainSelect',
-      'title' => ts('State/Province'),
-      'name' => "billing_state_province_id-{$bltID}",
-      'cc_field' => TRUE,
-      'is_required' => TRUE,
-    );
-
-    $form->_paymentFields["billing_postal_code-{$bltID}"] = array(
-      'htmlType' => 'text',
-      'name' => "billing_postal_code-{$bltID}",
-      'title' => ts('Postal Code'),
-      'cc_field' => TRUE,
-      'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
-      'is_required' => TRUE,
-    );
-
-    $form->_paymentFields["billing_country_id-{$bltID}"] = array(
-      'htmlType' => 'select',
-      'name' => "billing_country_id-{$bltID}",
-      'title' => ts('Country'),
-      'cc_field' => TRUE,
-      'attributes' => array(
-        '' => ts('- select -'),
-      ) +
-      CRM_Core_PseudoConstant::country(),
-      'is_required' => TRUE,
-    );
-    //CRM-15509 working towards giving control over billing fields to payment processors. For now removing tpl hard-coding
-    $smarty = CRM_Core_Smarty::singleton();
-    $smarty->assign('billingDetailsFields', array(
-      'billing_first_name',
-      'billing_middle_name',
-      'billing_last_name',
-      "billing_street_address-{$bltID}",
-      "billing_city-{$bltID}",
-      "billing_country_id-{$bltID}",
-      "billing_state_province_id-{$bltID}",
-      "billing_postal_code-{$bltID}",
-    ));
-  }
-
   /**
    * Add the payment fields to the template.
    *
@@ -287,8 +169,7 @@ class CRM_Core_Payment_Form {
    * @return array
    */
   public static function getBillingAddressFields($paymentProcessor, $billingLocationID) {
-    $paymentProcessorObject = Civi\Payment\System::singleton()->getByProcessor($paymentProcessor);
-    return $paymentProcessorObject->getBillingAddressFields($billingLocationID);
+    return $paymentProcessor['object']->getBillingAddressFields($billingLocationID);
   }
 
   /**
@@ -313,8 +194,7 @@ class CRM_Core_Payment_Form {
    * @return string
    */
   public static function getPaymentTypeName($paymentProcessor) {
-    $paymentProcessorObject = Civi\Payment\System::singleton()->getByProcessor($paymentProcessor);
-    return $paymentProcessorObject->getPaymentTypeName();
+    return $paymentProcessor['object']->getPaymentTypeName();
   }
 
   /**
@@ -331,10 +211,9 @@ class CRM_Core_Payment_Form {
    * @param CRM_Contribute_Form_AbstractEditPayment|CRM_Contribute_Form_Contribution_Main|CRM_Core_Payment_ProcessorForm|CRM_Contribute_Form_UpdateBilling $form
    * @param array $processor
    *   Array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors.
-   * @param bool $isBillingDataOptional
-   *   This manifests for 'NULL' (pay later) payment processor as the addition of billing fields to the form and.
-   *   for payment processors that gather payment data on site as rendering the fields as not being required. (not entirely sure why but this
-   *   is implemented for back office forms)
+   * @param int|string $billing_profile_id
+   *   Id of a profile to be passed to the processor for the processor to merge with it's required fields.
+   *   (currently only implemented by manual/ pay-later processor)
    *
    * @param bool $isBackOffice
    *   Is this a backoffice form. This could affect the display of the cvn or whether some processors show,
@@ -344,7 +223,7 @@ class CRM_Core_Payment_Form {
    *
    * @return bool
    */
-  public static function buildPaymentForm(&$form, $processor, $isBillingDataOptional, $isBackOffice) {
+  public static function buildPaymentForm(&$form, $processor, $billing_profile_id, $isBackOffice) {
     //if the form has address fields assign to the template so the js can decide what billing fields to show
     $profileAddressFields = $form->get('profileAddressFields');
     if (!empty($profileAddressFields)) {
@@ -355,7 +234,7 @@ class CRM_Core_Payment_Form {
       return NULL;
     }
 
-    self::setPaymentFieldsByProcessor($form, $processor, empty($isBillingDataOptional), $isBackOffice);
+    self::setPaymentFieldsByProcessor($form, $processor, $billing_profile_id, $isBackOffice);
     self::addCommonFields($form, $form->_paymentFields);
     self::addRules($form, $form->_paymentFields);
     return (!empty($form->_paymentFields));
@@ -386,12 +265,10 @@ class CRM_Core_Payment_Form {
    * We want this to be overrideable by the payment processor, and default to using
    * this object's validCreditCard for credit cards (implemented as the default in the Payment class).
    */
-  public static function validatePaymentInstrument($payment_processor_id, $values, &$errors, $form) {
-    // ignore if we don't have a payment instrument to validate (e.g. backend payments)
-    if ($payment_processor_id > 0) {
-      $payment = Civi\Payment\System::singleton()->getById($payment_processor_id);
-      $payment->validatePaymentInstrument($values, $errors);
-    }
+  public static function validatePaymentInstrument($payment_processor_id, $values, &$errors, $billing_profile_id) {
+    $payment = Civi\Payment\System::singleton()->getById($payment_processor_id);
+    $payment->setBillingProfile($billing_profile_id);
+    $payment->validatePaymentInstrument($values, $errors);
   }
 
   /**
@@ -528,22 +405,4 @@ 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();
-    }
-  }
-
 }
diff --git a/CRM/Core/Payment/Manual.php b/CRM/Core/Payment/Manual.php
new file mode 100644 (file)
index 0000000..b438b8f
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2015
+ */
+class CRM_Core_Payment_Manual extends CRM_Core_Payment {
+
+  protected $result;
+
+  /**
+   * This function checks to see if we have the right config values.
+   *
+   * @return string
+   *   the error message if any
+   */
+  public function checkConfig() {
+  }
+
+  /**
+   * 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 = NULL) {
+    if (!$billingLocationID) {
+      // Note that although the billing id is passed around the forms the idea that it would be anything other than
+      // the result of the function below doesn't seem to have eventuated.
+      // So taking this as a param is possibly something to be removed in favour of the standard default.
+      $billingLocationID = CRM_Core_BAO_LocationType::getBilling();
+    }
+
+    // Only handle pseudo-profile billing for now.
+    if ($this->billingProfile == 'billing') {
+      // @todo - use profile api to retrieve this - either as pseudo-profile or (better) set up billing
+      // as a reserved profile in the DB and (even better) allow the profile to be selected
+      // on the form instead of just 'billing for pay=later bool'
+      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}",
+      );
+    }
+    else {
+      return array();
+    }
+  }
+
+  /**
+   * Get array of fields that should be displayed on the payment form.
+   *
+   * @return array
+   */
+  public function getPaymentFormFields() {
+    return array();
+  }
+  /**
+   * Process payment.
+   *
+   * The function ensures an exception is thrown & moves some of this logic out of the form layer and makes the forms
+   * more agnostic.
+   *
+   * @param array $params
+   *
+   * @param string $component
+   *
+   * @return array
+   *   Result array
+   *
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public function doPayment(&$params, $component = 'contribute') {
+    $params['payment_status_id'] = $this->getResult();
+    return $params;
+  }
+
+  /**
+   * Get the result of the payment.
+   *
+   * Usually this will be pending but the calling layer has a chance to set the result.
+   *
+   * This would apply in particular when the form accepts status id.
+   *
+   * Note that currently this payment class is only being used to manage the 'billing block' aspect
+   * of pay later. However, a longer term idea is that by treating 'pay-later' as 'just another processor'
+   * will allow code simplification.
+   *
+   * @return int
+   */
+  protected function getResult() {
+    if (!$this->result) {
+      $this->setResult(CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'status_id', 'Pending'));
+    }
+    return $this->result;
+  }
+
+  /**
+   * Set the result to be returned.
+   *
+   * This would be set from outside the function where we want to pass on the status from the form.
+   *
+   * @param int $result
+   */
+  public function setResult($result) {
+    $this->result = $result;
+  }
+
+  /**
+   * Get the name of the payment type.
+   *
+   * @return string
+   */
+  public function getPaymentTypeName() {
+    return 'pay-later';
+  }
+
+  /**
+   * Get the name of the payment type.
+   *
+   * @return string
+   */
+  public function getPaymentTypeLabel() {
+    return '';
+  }
+}
index e90bedf7a699dd108d67f0a23317910f45cc337f..86706dd81b36d1ba5fd9697cb14c398cab56aee7 100644 (file)
@@ -70,7 +70,11 @@ class CRM_Core_Payment_ProcessorForm {
     //checks after setting $form->_paymentProcessor
     // we do this outside of the above conditional to avoid
     // saving the country/state list in the session (which could be huge)
-    CRM_Core_Payment_Form::setPaymentFieldsByProcessor($form, $form->_paymentProcessor);
+    CRM_Core_Payment_Form::setPaymentFieldsByProcessor(
+      $form,
+      $form->_paymentProcessor,
+      CRM_Utils_Request::retrieve('billing_profile_id', 'String')
+    );
 
     $form->assign_by_ref('paymentProcessor', $form->_paymentProcessor);
 
@@ -113,12 +117,14 @@ class CRM_Core_Payment_ProcessorForm {
     //CRM-15743 - we should not set/create hidden element for pay later
     // because payment processor is not selected
     $processorId = $form->getVar('_paymentProcessorID');
-    $isBillingAddressRequiredForPayLater = $form->_isBillingAddressRequiredForPayLater;
+    $billing_profile_id = CRM_Utils_Request::retrieve('billing_profile_id', 'String');
+    if (!empty($form->_values) && !empty($form->_values['is_billing_required'])) {
+      $billing_profile_id = 'billing';
+    }
     if (!empty($processorId)) {
-      $isBillingAddressRequiredForPayLater = FALSE;
       $form->addElement('hidden', 'hidden_processor', 1);
     }
-    CRM_Core_Payment_Form::buildPaymentForm($form, $form->_paymentProcessor, empty($isBillingAddressRequiredForPayLater), FALSE);
+    CRM_Core_Payment_Form::buildPaymentForm($form, $form->_paymentProcessor, $billing_profile_id, FALSE);
   }
 
 }
index e79722e603f24adf25a878c0a124e9d3fa5c1f87..4e9f92c0597d9143d3b01b7ad2b8b9457f244271 100644 (file)
@@ -303,6 +303,19 @@ class CRM_Financial_BAO_PaymentProcessor extends CRM_Financial_DAO_PaymentProces
       $processors['values'][$processor['id']]['object'] = Civi\Payment\System::singleton()->getByProcessor($processor);
     }
 
+    // Add the pay-later pseudo-processor.
+    $processors['values'][0] = array(
+      'object' =>  new CRM_Core_Payment_Manual(),
+      'id' => 0,
+      'payment_processor_type_id' => 0,
+      'class_name' => 'Payment_Manual',
+      'name' => 'pay_later',
+      'billing_mode' => '',
+      // Making this optionally recur would give lots of options -but it should
+      // be a row in the payment processor table before we do that.
+      'is_recur' => FALSE,
+    );
+
     CRM_Utils_Cache::singleton()->set($cacheKey, $processors['values']);
 
     return $processors['values'];
index 8089960eedb302925bf20af0b0289b0d8b900831..b3fca54abfe6cd3f2ea80233a12439e483b63d13 100644 (file)
  * @copyright CiviCRM LLC (c) 2004-2015
  */
 class CRM_Financial_Form_Payment extends CRM_Core_Form {
+
+  /**
+   * @var int
+   */
+  protected $_paymentProcessorID;
+
+  /**
+   * @var array
+   */
+  public $_paymentProcessor;
   /**
    * Set variables up before form is built.
    */
@@ -42,6 +52,7 @@ class CRM_Financial_Form_Payment extends CRM_Core_Form {
     $this->assignBillingType();
 
     $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($this->_paymentProcessorID);
+
     CRM_Core_Payment_ProcessorForm::preProcess($this);
 
     self::addCreditCardJs();
@@ -52,6 +63,9 @@ class CRM_Financial_Form_Payment extends CRM_Core_Form {
     $this->controller->_generateQFKey = FALSE;
   }
 
+  /**
+   * Build quickForm.
+   */
   public function buildQuickForm() {
     CRM_Core_Payment_ProcessorForm::buildQuickForm($this);
   }
@@ -66,7 +80,7 @@ class CRM_Financial_Form_Payment extends CRM_Core_Form {
   }
 
   /**
-   * Add JS to show icons for the accepted credit cards
+   * Add JS to show icons for the accepted credit cards.
    */
   public static function addCreditCardJs() {
     $creditCardTypes = CRM_Core_Payment_Form::getCreditCardCSSNames();
index 5bfa71b2a0e4152ac1387d24000300141324bcaf..9ce8cb196f4abb35e1e70eebc595e3b811f3848c 100644 (file)
@@ -73,12 +73,17 @@ class System {
   }
 
   /**
+   * Get payment processor by it's ID.
+   *
    * @param int $id
    *
    * @return \Civi\Payment\CRM_Core_Payment|NULL
    * @throws \CiviCRM_API3_Exception
    */
   public function getById($id) {
+    if ($id == 0) {
+      return new \CRM_Core_Payment_Manual();
+    }
     $processor = civicrm_api3('payment_processor', 'getsingle', array('id' => $id, 'is_test' => NULL));
     return self::getByProcessor($processor);
   }
index 79f43994ce47d2da8c485aef4d7fe4be25fdb6f6..042711b40c8cd58dbadaa94f2fdf3a19b0d65f5d 100644 (file)
   CRM.$(function($) {
     function buildPaymentBlock(type) {
       var $form = $('#billing-payment-block').closest('form');
-
-      {/literal}{if !$isBillingAddressRequiredForPayLater}{literal}
-      if (type == 0) {
-        $("#billing-payment-block").html('');
-        return;
-      }
-      {/literal}{/if}
-
+      {/literal}
       {if $contributionPageID}
         {capture assign='contributionPageID'}id={$contributionPageID}&{/capture}
       {else}
       {else}
         {capture assign='urlPathVar'}{/capture}
       {/if}
+      {if $billing_profile_id}
+        {capture assign='profilePathVar'}billing_profile_id={$billing_profile_id}&{/capture}
+      {else}
+        {capture assign='profilePathVar'}{/capture}
+      {/if}
 
-      var dataUrl = "{crmURL p='civicrm/payment/form' h=0 q="`$urlPathVar``$contributionPageID`processor_id="}" + type;
+      var dataUrl = "{crmURL p='civicrm/payment/form' h=0 q="`$urlPathVar``$profilePathVar``$contributionPageID`processor_id="}" + type;
       {literal}
       if (typeof(CRM.vars) != "undefined") {
         if (typeof(CRM.vars.coreForm) != "undefined") {