3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2014
35 class CRM_Core_Payment_Form
{
39 * Add payment fields depending on payment processor. The payment processor can implement the following functions to override the built in fields.
41 * - getPaymentFormFields()
42 * - getPaymentFormFieldsMetadata()
43 * (planned - getBillingDetailsFormFields(), getBillingDetailsFormFieldsMetadata()
45 * Note that this code is written to accommodate the possibility CiviCRM will switch to implementing pay later as a manual processor in future
47 * @param CRM_Contribute_Form_AbstractEditPayment|CRM_Contribute_Form_Contribution_Main $form
48 * @param array $processor array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors
49 * @param bool $forceBillingFieldsForPayLater display billing fields even for pay later
51 static public function setPaymentFieldsByProcessor(&$form, $processor, $forceBillingFieldsForPayLater = FALSE) {
52 $form->billingFieldSets
= array();
53 if ($processor != NULL) {
55 $paymentFields = self
::getPaymentFields($processor);
56 $paymentTypeName = self
::getPaymentTypeName($processor);
57 $paymentTypeLabel = self
::getPaymentTypeLabel($processor);
58 //@todo if we switch to iterating through $form->billingFieldSets we won't need to assign these directly
59 $form->assign('paymentTypeName', $paymentTypeName);
60 $form->assign('paymentTypeLabel', $paymentTypeLabel);
62 $form->billingFieldSets
[$paymentTypeName]['fields'] = $form->_paymentFields
= array_intersect_key(self
::getPaymentFieldMetadata($processor), array_flip($paymentFields));
63 $form->billingPane
= array($paymentTypeName => $paymentTypeLabel);
64 $form->assign('paymentFields', $paymentFields);
67 // @todo - replace this section with one similar to above per discussion - probably use a manual processor shell class to stand in for that capability
68 //return without adding billing fields if billing_mode = 4 (@todo - more the ability to set that to the payment processor)
69 // or payment processor is NULL (pay later)
70 if (($processor == NULL && !$forceBillingFieldsForPayLater) || CRM_Utils_Array
::value('billing_mode', $processor) == 4) {
73 //@todo setPaymentFields defines the billing fields - this should be moved to the processor class & renamed getBillingFields
74 // potentially pay later would also be a payment processor
75 //also set the billingFieldSet to hold all the details required to render the fieldset so we can iterate through the fieldset - making
76 // it easier to re-order in hooks etc. The billingFieldSets param is used to determine whether to show the billing pane
77 CRM_Core_Payment_Form
::setBillingDetailsFields($form);
78 $form->billingFieldSets
['billing_name_address-group']['fields'] = array();
82 * add general billing fields
83 * @todo set these like processor fields & let payment processors alter them
85 * @param CRM_Core_Form $form
90 static protected function setBillingDetailsFields(&$form) {
91 $bltID = $form->_bltID
;
93 $form->_paymentFields
['billing_first_name'] = array(
95 'name' => 'billing_first_name',
96 'title' => ts('Billing First Name'),
98 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
99 'is_required' => TRUE,
102 $form->_paymentFields
['billing_middle_name'] = array(
103 'htmlType' => 'text',
104 'name' => 'billing_middle_name',
105 'title' => ts('Billing Middle Name'),
107 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
108 'is_required' => FALSE,
111 $form->_paymentFields
['billing_last_name'] = array(
112 'htmlType' => 'text',
113 'name' => 'billing_last_name',
114 'title' => ts('Billing Last Name'),
116 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
117 'is_required' => TRUE,
120 $form->_paymentFields
["billing_street_address-{$bltID}"] = array(
121 'htmlType' => 'text',
122 'name' => "billing_street_address-{$bltID}",
123 'title' => ts('Street Address'),
125 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
126 'is_required' => TRUE,
129 $form->_paymentFields
["billing_city-{$bltID}"] = array(
130 'htmlType' => 'text',
131 'name' => "billing_city-{$bltID}",
132 'title' => ts('City'),
134 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
135 'is_required' => TRUE,
138 $form->_paymentFields
["billing_state_province_id-{$bltID}"] = array(
139 'htmlType' => 'chainSelect',
140 'title' => ts('State/Province'),
141 'name' => "billing_state_province_id-{$bltID}",
143 'is_required' => TRUE,
146 $form->_paymentFields
["billing_postal_code-{$bltID}"] = array(
147 'htmlType' => 'text',
148 'name' => "billing_postal_code-{$bltID}",
149 'title' => ts('Postal Code'),
151 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
152 'is_required' => TRUE,
155 $form->_paymentFields
["billing_country_id-{$bltID}"] = array(
156 'htmlType' => 'select',
157 'name' => "billing_country_id-{$bltID}",
158 'title' => ts('Country'),
160 'attributes' => array(
161 '' => ts('- select -')) +
162 CRM_Core_PseudoConstant
::country(),
163 'is_required' => TRUE,
165 //CRM-15509 working towards giving control over billing fields to payment processors. For now removing tpl hard-coding
166 $smarty = CRM_Core_Smarty
::singleton();
167 $smarty->assign('billingDetailsFields', array(
168 'billing_first_name',
169 'billing_middle_name',
171 "billing_street_address-{$bltID}",
172 "billing_city-{$bltID}",
173 "billing_country_id-{$bltID}",
174 "billing_state_province_id-{$bltID}",
175 "billing_postal_code-{$bltID}",
180 * @param CRM_Core_Form $form
181 * @param bool $useRequired
182 * @param array $paymentFields
184 protected static function addCommonFields(&$form, $useRequired, $paymentFields) {
185 foreach ($paymentFields as $name => $field) {
186 if (!empty($field['cc_field'])) {
187 if ($field['htmlType'] == 'chainSelect') {
188 $form->addChainSelect($field['name'], array('required' => $useRequired && $field['is_required']));
191 $form->add($field['htmlType'],
194 $field['attributes'],
195 $useRequired ?
$field['is_required'] : FALSE
203 * @param array $paymentProcessor
204 * @todo it will be necessary to set details that affect it - mostly likely take Country as a param. Should we add generic
205 * setParams on processor class or just setCountry which we know we need?
209 static function getPaymentFields($paymentProcessor) {
210 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
211 return $paymentProcessorObject->getPaymentFormFields();
215 * @param array $paymentProcessor
219 static function getPaymentFieldMetadata($paymentProcessor) {
220 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
221 return $paymentProcessorObject->getPaymentFormFieldsMetadata();
225 * @param array $paymentProcessor
229 static function getPaymentTypeName($paymentProcessor) {
230 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
231 return $paymentProcessorObject->getPaymentTypeName();
235 * @param array $paymentProcessor
239 static function getPaymentTypeLabel($paymentProcessor) {
240 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
241 return ts(($paymentProcessorObject->getPaymentTypeLabel()) . ' Information');
245 * @param CRM_Contribute_Form_AbstractEditPayment|CRM_Contribute_Form_Contribution_Main|CRM_Core_Payment_ProcessorForm|CRM_Contribute_Form_UpdateBilling $form
246 * @param array $processor array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors
247 * @param bool $isBillingDataOptional This manifests for 'NULL' (pay later) payment processor as the addition of billing fields to the form and
248 * for payment processors that gather payment data on site as rendering the fields as not being required. (not entirely sure why but this
249 * is implemented for back office forms)
253 static function buildPaymentForm($form, $processor, $isBillingDataOptional){
254 //if the form has address fields assign to the template so the js can decide what billing fields to show
255 $profileAddressFields = $form->get('profileAddressFields');
256 if (!empty($profileAddressFields)) {
257 $form->assign('profileAddressFields', $profileAddressFields);
260 // $processor->buildForm appears to be an undocumented (possibly unused) option for payment processors
261 // which was previously available only in some form flows
262 if (!empty($form->_paymentProcessor
) && !empty($form->_paymentProcessor
['object']) && $form->_paymentProcessor
['object']->isSupported('buildForm')) {
263 $form->_paymentProcessor
['object']->buildForm($form);
267 self
::setPaymentFieldsByProcessor($form, $processor, empty($isBillingDataOptional));
268 self
::addCommonFields($form, !$isBillingDataOptional, $form->_paymentFields
);
269 self
::addRules($form, $form->_paymentFields
);
270 self
::addPaypalExpressCode($form);
271 return (!empty($form->_paymentFields
));
275 * @param CRM_Core_Form $form
276 * @param array $paymentFields array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors
278 * @param $paymentFields
280 protected static function addRules(&$form, $paymentFields) {
281 foreach ($paymentFields as $paymentField => $fieldSpecs) {
282 if (!empty($fieldSpecs['rules'])) {
283 foreach ($fieldSpecs['rules'] as $rule) {
284 $form->addRule($paymentField,
285 $rule['rule_message'],
287 $rule['rule_parameters']
295 * billing mode button is basically synonymous with paypal express - this is probably a good example of 'odds & sods' code we
296 * 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
300 protected static function addPaypalExpressCode(&$form) {
301 if (empty($form->isBackOffice
)) {
302 if (CRM_Utils_Array
::value('billing_mode', $form->_paymentProcessor
) == 3
304 $form->_expressButtonName
= $form->getButtonName('upload', 'express');
305 $form->assign('expressButtonName', $form->_expressButtonName
);
307 $form->_expressButtonName
,
308 $form->_paymentProcessor
['url_button'],
309 array('class' => 'crm-form-submit')
316 * The credit card pseudo constant results only the CC label, not the key ID
317 * So we normalize the name to use it as a CSS class.
319 static function getCreditCardCSSNames() {
320 $creditCardTypes = array();
321 foreach (CRM_Contribute_PseudoConstant
::creditCard() as $key => $name) {
322 // Replace anything not css-friendly by an underscore
323 // Non-latin names will not like this, but so many things are wrong with
324 // the credit-card type configurations already.
325 $key = str_replace(' ', '', $key);
326 $key = preg_replace('/[^a-zA-Z0-9]/', '_', $key);
327 $key = strtolower($key);
328 $creditCardTypes[$key] = $name;
330 return $creditCardTypes;
334 * Make sure that credit card number and cvv are valid
335 * Called within the scope of a QF formRule function
337 static function validateCreditCard($values, &$errors) {
338 if (!empty($values['credit_card_type'])) {
339 if (!empty($values['credit_card_number']) &&
340 !CRM_Utils_Rule
::creditCardNumber($values['credit_card_number'], $values['credit_card_type'])
342 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
344 if (!empty($values['cvv2']) &&
345 !CRM_Utils_Rule
::cvv($values['cvv2'], $values['credit_card_type'])
347 $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
350 elseif (!empty($values['credit_card_number'])) {
351 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
361 * @param bool $reverse
366 static function mapParams($id, &$src, &$dst, $reverse = FALSE) {
370 'first_name' => 'billing_first_name',
371 'middle_name' => 'billing_middle_name',
372 'last_name' => 'billing_last_name',
373 'email' => "email-$id",
374 'street_address' => "billing_street_address-$id",
375 'supplemental_address_1' => "billing_supplemental_address_1-$id",
376 'city' => "billing_city-$id",
377 'state_province' => "billing_state_province-$id",
378 'postal_code' => "billing_postal_code-$id",
379 'country' => "billing_country-$id",
383 foreach ($map as $n => $v) {
385 if (isset($src[$n])) {
390 if (isset($src[$v])) {
398 * get the credit card expiration month
399 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
407 static function getCreditCardExpirationMonth($src) {
408 if ($month = CRM_Utils_Array
::value('M', $src['credit_card_exp_date'])) {
412 return CRM_Utils_Array
::value('m', $src['credit_card_exp_date']);
416 * get the credit card expiration year
417 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
418 * This function exists only to make it consistent with getCreditCardExpirationMonth
425 static function getCreditCardExpirationYear($src) {
426 return CRM_Utils_Array
::value('Y', $src['credit_card_exp_date']);