From dc9130737842986fec128e5ecd8e2402d150b975 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Mon, 3 Nov 2014 17:26:56 +1300 Subject: [PATCH] CRM-15555 towards allowing processor fields to be set by processors --- CRM/Contribute/Form/AbstractEditPayment.php | 29 +++- CRM/Contribute/Form/Contribution.php | 32 ++-- CRM/Core/Payment.php | 156 +++++++++++++++++++- CRM/Core/Payment/Form.php | 140 ++++++++++++++---- CRM/Financial/BAO/PaymentProcessor.php | 10 +- CRM/Utils/String.php | 20 +++ api/api.php | 13 +- templates/CRM/Core/BillingBlock.tpl | 4 +- 8 files changed, 332 insertions(+), 72 deletions(-) diff --git a/CRM/Contribute/Form/AbstractEditPayment.php b/CRM/Contribute/Form/AbstractEditPayment.php index 34f031ce6e..5612dae1ae 100644 --- a/CRM/Contribute/Form/AbstractEditPayment.php +++ b/CRM/Contribute/Form/AbstractEditPayment.php @@ -49,10 +49,22 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Core_Form { public $_paymentProcessor; public $_recurPaymentProcessors; + /** + * array of processor options in the format id => array($id => $label) + * WARNING it appears that the format used to differ to this and there are places in the code that + * expect the old format. $this->_paymentProcessors provides the additional data which this + * array seems to have provided in the past + * @var array + */ public $_processors; /** - * the id of the contribution that we are proceessing + * available payment processors with full details including the key 'object' indexed by their id + * @var array + */ + protected $_paymentProcessors = array(); + /** + * the id of the contribution that we are processing * * @var int * @public @@ -341,10 +353,7 @@ LEFT JOIN civicrm_contribution on (civicrm_contribution.contact_id = civicrm_co } /** - * @return array (0 => array(int $ppId => string $label), 1 => array(...payproc details...)) - */ - /** - * @return array of valid processors + * @return array of valid processors. The array resembles the DB table but also has 'object' as a key * @throws Exception */ public function getValidProcessors() { @@ -383,12 +392,16 @@ LEFT JOIN civicrm_contribution on (civicrm_contribution.contact_id = civicrm_co if ($this->_mode) { $this->assign(CRM_Financial_BAO_PaymentProcessor::hasPaymentProcessorSupporting(array('supportsFutureRecurStartDate')), TRUE); - $processors = $this->getValidProcessors(); - if (empty($processors)) { + $this->_paymentProcessors = $this->getValidProcessors(); + if (!isset($this->_paymentProcessor['id'])) { + // if the payment processor isn't set yet (as indicated by the presence of an id,) we'll grab the first one which should be the default + $this->_paymentProcessor = reset($this->_paymentProcessors); + } + if (empty($this->_paymentProcessors)) { throw new CRM_Core_Exception(ts('You will need to configure the %1 settings for your Payment Processor before you can submit a credit card transactions.', array(1 => $this->_mode))); } $this->_processors = array(); - foreach ($processors as $id => $processor) { + foreach ($this->_paymentProcessors as $id => $processor) { $this->_processors[$id] = ts($processor['name']); } //get the valid recurring processors. diff --git a/CRM/Contribute/Form/Contribution.php b/CRM/Contribute/Form/Contribution.php index cca070f757..d34144b289 100644 --- a/CRM/Contribute/Form/Contribution.php +++ b/CRM/Contribute/Form/Contribution.php @@ -287,7 +287,7 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP $this->assignBillingType(); $this->_fields = array(); - CRM_Core_Payment_Form::setPaymentFieldsByType(CRM_Utils_Array::value('payment_type', $this->_processors), $this); + CRM_Core_Payment_Form::setPaymentFieldsByProcessor($this, $this->_paymentProcessor); } catch (CRM_Core_Exception $e) { CRM_Core_Error::fatal($e->getMessage()); @@ -496,6 +496,7 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP * @access public */ public function buildQuickForm() { + //@todo document the purpose of cdType (if still in use) if ($this->_cdType) { CRM_Custom_Form_CustomData::buildQuickForm($this); return; @@ -579,21 +580,16 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP $paneNames[ts('Premium Information')] = 'Premium'; } - $ccPane = NULL; if ($this->_mode) { - if (CRM_Utils_Array::value('payment_type', $this->_processors) & CRM_Core_Payment::PAYMENT_TYPE_DIRECT_DEBIT - ) { - $ccPane = array(ts('Direct Debit Information') => 'DirectDebit'); - } - else { - $ccPane = array(ts('Credit Card Information') => 'CreditCard'); + if (CRM_Core_Payment_Form::buildPaymentForm($this, $this->_paymentProcessor, FALSE) == TRUE) { + $buildRecurBlock = TRUE; + foreach ($this->billingPane as $name => $label) { + //@todo reduce variation so we don't have to convert 'credit_card' to 'CreditCard' + $paneNames[$label] = CRM_Utils_String::convertStringToCamel($name); + } } } - if (is_array($ccPane)) { - $paneNames = array_merge($ccPane, $paneNames); - } - $buildRecurBlock = FALSE; foreach ($paneNames as $name => $type) { $urlParams = "snippet=4&formType={$type}"; if ($this->_mode) { @@ -621,15 +617,9 @@ class CRM_Contribute_Form_Contribution extends CRM_Contribute_Form_AbstractEditP $allPanes[$name]['open'] = 'true'; } - if ($type == 'CreditCard') { - $buildRecurBlock = TRUE; - $this->add('hidden', 'hidden_CreditCard', 1); - CRM_Core_Payment_Form::buildCreditCard($this, TRUE); - } - elseif ($type == 'DirectDebit') { - $buildRecurBlock = TRUE; - $this->add('hidden', 'hidden_DirectDebit', 1); - CRM_Core_Payment_Form::buildDirectDebit($this, TRUE); + if ($type == 'CreditCard' || $type == 'DirectDebit') { + //@todo would be good to align tpl name with form name... + $this->add('hidden', 'hidden_' . $type, 1); } else { $additionalInfoFormFunction = 'build' . $type; diff --git a/CRM/Core/Payment.php b/CRM/Core/Payment.php index 432b1c21f1..bfc013bc9b 100644 --- a/CRM/Core/Payment.php +++ b/CRM/Core/Payment.php @@ -206,11 +206,32 @@ abstract class CRM_Core_Payment { /** * Getter for accessing member vars * + * @param $name + * + * @return null */ function getVar($name) { return isset($this->$name) ? $this->$name : NULL; } + /** + * get name for the payment information type + * + * @return string + */ + public function getPaymentTypeName() { + return $this->_paymentProcessor['payment_type'] == 1 ? 'credit_card' : 'debit_card'; + } + + /** + * get label for the payment information type + * + * @return string + */ + public function getPaymentTypeLabel() { + return $this->_paymentProcessor['payment_type'] == 1 ? 'Credit Card' : 'Debit Card'; + } + /** * get array of fields that should be displayed on the payment form * @todo make payment type an option value & use it in the function name - currently on debit & credit card work @@ -218,7 +239,7 @@ abstract class CRM_Core_Payment { * @throws CiviCRM_API3_Exception */ public function getPaymentFormFields() { - if ($this->_paymentProcessor['payment_type'] == 4) { + if ($this->_paymentProcessor['billing_mode'] == 4) { return array(); } return $this->_paymentProcessor['payment_type'] == 1 ? $this->getCreditCardFormFields() : $this->getDirectDebitFormFields(); @@ -226,6 +247,7 @@ abstract class CRM_Core_Payment { /** * get array of fields that should be displayed on the payment form for credit cards + * * @return array */ protected function getCreditCardFormFields() { @@ -239,6 +261,7 @@ abstract class CRM_Core_Payment { /** * get array of fields that should be displayed on the payment form for direct debits + * * @return array */ protected function getDirectDebitFormFields() { @@ -250,6 +273,137 @@ abstract class CRM_Core_Payment { ); } + /** + * return an array of all the details about the fields potentially required for payment fields + * Only those determined by getPaymentFormFields will actually be assigned to the form + * + * @return array field metadata + */ + public function getPaymentFormFieldsMetadata() { + //@todo convert credit card type into an option value + $creditCardType = array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::creditCard(); + return array( + 'credit_card_number' => array( + 'htmlType' => 'text', + 'name' => 'credit_card_number', + 'title' => ts('Card Number'), + 'cc_field' => TRUE, + 'attributes' => array( + 'size' => 20, + 'maxlength' => 20, + 'autocomplete' => 'off' + ), + 'is_required' => TRUE, + ), + 'cvv2' => array( + 'htmlType' => 'text', + 'name' => 'cvv2', + 'title' => ts('Security Code'), + 'cc_field' => TRUE, + 'attributes' => array( + 'size' => 5, + 'maxlength' => 10, + 'autocomplete' => 'off' + ), + 'is_required' => CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, + 'cvv_backoffice_required', + NULL, + 1 + ), + 'rules' => array( + array( + 'rule_message' => ts('Please enter a valid value for your card security code. This is usually the last 3-4 digits on the card\'s signature panel.'), + 'rule_name' => 'integer', + 'rule_parameters' => NULL, + )), + ), + 'credit_card_exp_date' => array( + 'htmlType' => 'date', + 'name' => 'credit_card_exp_date', + 'title' => ts('Expiration Date'), + 'cc_field' => TRUE, + 'attributes' => CRM_Core_SelectValues::date('creditCard'), + 'is_required' => TRUE, + 'rules' => array( + array( + 'rule_message' => ts('Card expiration date cannot be a past date.'), + 'rule_name' => 'currentDate', + 'rule_parameters' => TRUE, + )), + ), + 'credit_card_type' => array( + 'htmlType' => 'select', + 'name' => 'credit_card_type', + 'title' => ts('Card Type'), + 'cc_field' => TRUE, + 'attributes' => $creditCardType, + 'is_required' => FALSE, + ), + 'account_holder' => array( + 'htmlType' => 'text', + 'name' => 'account_holder', + 'title' => ts('Account Holder'), + 'cc_field' => TRUE, + 'attributes' => array( + 'size' => 20, + 'maxlength' => 34, + 'autocomplete' => 'on' + ), + 'is_required' => TRUE, + ), + //e.g. IBAN can have maxlength of 34 digits + 'bank_account_number' => array( + 'htmlType' => 'text', + 'name' => 'bank_account_number', + 'title' => ts('Bank Account Number'), + 'cc_field' => TRUE, + 'attributes' => array( + 'size' => 20, + 'maxlength' => 34, + 'autocomplete' => 'off' + ), + 'rules' => array( + array( + 'rule_message' => ts('Please enter a valid Bank Identification Number (value must not contain punctuation characters).'), + 'rule_name' => 'nopunctuation', + 'rule_parameters' => NULL, + )), + 'is_required' => TRUE, + ), + //e.g. SWIFT-BIC can have maxlength of 11 digits + 'bank_identification_number' => array( + 'htmlType' => 'text', + 'name' => 'bank_identification_number', + 'title' => ts('Bank Identification Number'), + 'cc_field' => TRUE, + 'attributes' => array( + 'size' => 20, + 'maxlength' => 11, + 'autocomplete' => 'off' + ), + 'is_required' => TRUE, + 'rules' => array( + array( + 'rule_message' => ts('Please enter a valid Bank Identification Number (value must not contain punctuation characters).'), + 'rule_name' => 'nopunctuation', + 'rule_parameters' => NULL, + )), + ), + 'bank_name' => array( + 'htmlType' => 'text', + 'name' => 'bank_name', + 'title' => ts('Bank Name'), + 'cc_field' => TRUE, + 'attributes' => array( + 'size' => 20, + 'maxlength' => 64, + 'autocomplete' => 'off' + ), + 'is_required' => TRUE, + + ) + ); + } /** * This function collects all the information from a web/api form and invokes diff --git a/CRM/Core/Payment/Form.php b/CRM/Core/Payment/Form.php index b7074407e8..ee191dbc2e 100644 --- a/CRM/Core/Payment/Form.php +++ b/CRM/Core/Payment/Form.php @@ -36,7 +36,7 @@ class CRM_Core_Payment_Form { /** * Add payment fields are depending on payment type - * + * @deprecated - use the setPaymentFieldsByPaymentType which leverages the processor to determine the fields * @param int $type eg CRM_Core_Payment::PAYMENT_TYPE_DIRECT_DEBIT * @param CRM_Core_Form $form */ @@ -49,6 +49,34 @@ class CRM_Core_Payment_Form { } } + + /** + * Add payment fields are depending on payment type + * + * @param CRM_Core_Form $form + * @param array $processor array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors + */ + static public function setPaymentFieldsByProcessor(&$form, $processor) { + $form->billingFieldSets = array(); + $paymentFields = self::getPaymentFields($processor); + dpm($paymentFields); + $paymentTypeName = self::getPaymentTypeName($processor); + $paymentTypeLabel = self::getPaymentTypeLabel($processor); + //@todo if we switch to iterating through the fieldsets 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->billingPane = array($paymentTypeName => $paymentTypeLabel); + $form->assign('paymentFields', $paymentFields); + if ($processor['billing_mode'] != 4) { + //@todo setPaymentFields defines the billing fields - this should be moved to the processor class & renamed getBillingFields + //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. For not the billingFieldSets param is used to determine whether to show the billing pane + CRM_Core_Payment_Form::_setPaymentFields($form); + $form->billingFieldSets['billing_name_address-group']['fields'] = array(); + } + } /** * create all common fields needed for a credit card or direct debit transaction * @@ -148,7 +176,7 @@ class CRM_Core_Payment_Form { /** * create all fields needed for a credit card transaction - * + * @deprecated - use the setPaymentFieldsByPaymentType which leverages the processor to determine the fields * @param CRM_Core_Form $form * * @return void @@ -208,9 +236,10 @@ class CRM_Core_Payment_Form { /** * @param CRM_Core_Form $form * @param bool $useRequired + * @param array $paymentFields */ - static function addCommonFields(&$form, $useRequired) { - foreach ($form->_paymentFields as $name => $field) { + static function addCommonFields(&$form, $useRequired, $paymentFields) { + foreach ($paymentFields as $name => $field) { if (!empty($field['cc_field'])) { if ($field['htmlType'] == 'chainSelect') { $form->addChainSelect($field['name'], array('required' => $useRequired && $field['is_required'])); @@ -229,7 +258,7 @@ class CRM_Core_Payment_Form { /** * create all fields needed for direct debit transaction - * + * @deprecated - use the setPaymentFieldsByPaymentType which leverages the processor to determine the fields * @param $form * * @return void @@ -286,37 +315,102 @@ class CRM_Core_Payment_Form { /** * @param array $paymentProcessor - * @todo it may be necessary to set details that affect it - mostly likely take Country as a param + * @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? * - * @return mixed + * @return array */ static function getPaymentFields($paymentProcessor) { - return array(); $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor); - return $paymentProcessorObject->getPaymentFields(); + return $paymentProcessorObject->getPaymentFormFields(); } /** - * @param $form + * @param array $paymentProcessor * + * @return array + */ + static function getPaymentFieldMetadata($paymentProcessor) { + $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor); + return $paymentProcessorObject->getPaymentFormFieldsMetadata(); + } + + /** * @param array $paymentProcessor - * @todo it may be necessary to set details that affect it - mostly likely take Country as a param * - * @return mixed + * @return string */ - protected static function setPaymentFields(&$form, $paymentProcessor) { - $fields = self::getPaymentFields($paymentProcessor); + static function getPaymentTypeName($paymentProcessor) { + $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor); + return $paymentProcessorObject->getPaymentTypeName(); + } + /** + * @param array $paymentProcessor + * + * @return string + */ + static function getPaymentTypeLabel($paymentProcessor) { + $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor); + return ts(($paymentProcessorObject->getPaymentTypeLabel()) . ' Information'); } - static function buildPaymentForm(&$form, $useRequired = FALSE) { + /** + * @param CRM_Core_Form $form + * @param array $processor array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors + * @param bool $isBillingDataOptional + * + * @return bool + */ + static function buildPaymentForm($form, $processor, $isBillingDataOptional){ + self::setPaymentFieldsByProcessor($form, $processor); + self::addCommonFields($form, !$isBillingDataOptional, $form->_paymentFields); + self::addRules($form, $form->_paymentFields); + self::addPaypalExpressCode($form); + return (!empty($form->_paymentFields)); + } + /** + * @param CRM_Core_Form $form + * @param array $paymentFields array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors + + * @param $paymentFields + */ + static function addRules(&$form, $paymentFields) { + foreach ($paymentFields as $paymentField => $fieldSpecs) { + if (!empty($fieldSpecs['rules'])) { + foreach ($fieldSpecs['rules'] as $rule) { + $form->addRule($paymentField, + $rule['rule_message'], + $rule['rule_name'], + $rule['rule_parameters'] + ); + } + } + } } /** - * Function to add all the credit card fields + * billing mode button is basically synonymous with paypal express - this is probably a good example of 'odds & sods' code we + * need to find a way for the payment processor to assign. A tricky aspect is that the payment processor may need to set the order * * @param $form + */ + static function addPaypalExpressCode(&$form) { + if ($form->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_BUTTON) { + $form->_expressButtonName = $form->getButtonName('upload', 'express'); + $form->assign('expressButtonName', $form->_expressButtonName); + $form->add('image', + $form->_expressButtonName, + $form->_paymentProcessor['url_button'], + array('class' => 'crm-form-submit') + ); + } + } + /** + * Function to add all the credit card fields + * @deprecated Use BuildPaymentForm + * @param $form * @param bool $useRequired * * @return void @@ -325,7 +419,7 @@ class CRM_Core_Payment_Form { static function buildCreditCard(&$form, $useRequired = FALSE) { if ($form->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_FORM) { self::setCreditCardFields($form); - self::addCommonFields($form, $useRequired); + self::addCommonFields($form, $useRequired, $form->_paymentFields); $form->addRule('cvv2', ts('Please enter a valid value for your card security code. This is usually the last 3-4 digits on the card\'s signature panel.'), @@ -339,6 +433,7 @@ class CRM_Core_Payment_Form { } + if ($form->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_BUTTON) { $form->_expressButtonName = $form->getButtonName('upload', 'express'); $form->assign('expressButtonName', $form->_expressButtonName); @@ -379,7 +474,7 @@ class CRM_Core_Payment_Form { static function buildDirectDebit(&$form, $useRequired = FALSE) { if ($form->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_FORM) { self::setDirectDebitFields($form); - self::addCommonFields($form, $useRequired); + self::addCommonFields($form, $useRequired, $form->_paymentFields); $form->addRule('bank_identification_number', ts('Please enter a valid Bank Identification Number (value must not contain punctuation characters).'), @@ -391,15 +486,6 @@ class CRM_Core_Payment_Form { 'nopunctuation' ); } - - if ($form->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_BUTTON) { - $form->_expressButtonName = $form->getButtonName($form->buttonType(), 'express'); - $form->add('image', - $form->_expressButtonName, - $form->_paymentProcessor['url_button'], - array('class' => 'crm-form-submit') - ); - } } /** diff --git a/CRM/Financial/BAO/PaymentProcessor.php b/CRM/Financial/BAO/PaymentProcessor.php index 1a72cbf6ab..c26e03780c 100644 --- a/CRM/Financial/BAO/PaymentProcessor.php +++ b/CRM/Financial/BAO/PaymentProcessor.php @@ -302,7 +302,7 @@ class CRM_Financial_BAO_PaymentProcessor extends CRM_Financial_DAO_PaymentProces } } * */ - $retrievalParameters = array('is_active' => TRUE, 'options' => array('sort' => 'is_default, name')); + $retrievalParameters = array('is_active' => TRUE, 'options' => array('sort' => 'is_default DESC, name')); if ($isExcludeTest) { $retrievalParameters['is_test'] = 0; } @@ -324,12 +324,18 @@ class CRM_Financial_BAO_PaymentProcessor extends CRM_Financial_DAO_PaymentProces * @param array $capabilities * @param bool $isIncludeTest * + * @param array $ids + * * @return array available processors */ - static function getPaymentProcessors($capabilities = array(), $isIncludeTest = FALSE, $reset = FALSE) { + static function getPaymentProcessors($capabilities = array(), $isIncludeTest = FALSE, $ids = array()) { $processors = self::getAllPaymentProcessors(!$isIncludeTest); if ($capabilities) { foreach ($processors as $index => $processor) { + if (!empty($ids) && !in_array($processor['id'], $ids)) { + unset ($processors[$index]); + continue; + } if (($error = $processor['object']->checkConfig()) != NULL) { unset ($processors[$index]); continue; diff --git a/CRM/Utils/String.php b/CRM/Utils/String.php index 1643491496..79f4f7af05 100644 --- a/CRM/Utils/String.php +++ b/CRM/Utils/String.php @@ -102,6 +102,26 @@ class CRM_Utils_String { } } + /** + * convert possibly underscore separated words to camel case with special handling for 'UF' + * e.g + * membership_payment returns MembershipPayment + * @param string $string + * + * @return string string + */ + static function convertStringToCamel($string) { + $fragments = explode('_', $string); + foreach ($fragments as & $fragment) { + $fragment = ucfirst($fragment); + } + // Special case: UFGroup, UFJoin, UFMatch, UFField + if ($fragments[0] === 'Uf') { + $fragments[0] = 'UF'; + } + return implode('', $fragments); + } + /** * * Takes a variable name and munges it randomly into another variable name diff --git a/api/api.php b/api/api.php index c09d220249..8882e8df90 100644 --- a/api/api.php +++ b/api/api.php @@ -100,20 +100,11 @@ function civicrm_error($result) { /** * @param $entity - * @param null $version * * @return string */ -function _civicrm_api_get_camel_name($entity, $version = NULL) { - $fragments = explode('_', $entity); - foreach ($fragments as & $fragment) { - $fragment = ucfirst($fragment); - } - // Special case: UFGroup, UFJoin, UFMatch, UFField - if ($fragments[0] === 'Uf') { - $fragments[0] = 'UF'; - } - return implode('', $fragments); +function _civicrm_api_get_camel_name($entity) { + return CRM_Utils_String::convertStringToCamel($entity); } /** diff --git a/templates/CRM/Core/BillingBlock.tpl b/templates/CRM/Core/BillingBlock.tpl index 56cad1d540..f172e866ba 100644 --- a/templates/CRM/Core/BillingBlock.tpl +++ b/templates/CRM/Core/BillingBlock.tpl @@ -25,7 +25,7 @@ *} {crmRegion name="billing-block"}
- {if $paymentFields} + {if $paymentFields|@count}
{$paymentTypeLabel} @@ -51,7 +51,7 @@
{/if} - {if $billingDetailsFields} + {if $billingDetailsFields|@count} {if $profileAddressFields} -- 2.25.1