51fb59e933c5e075c3cb186fdb13822df3c2c2f2
[civicrm-core.git] / CRM / Core / Payment / Form.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2014
32 * $Id$
33 *
34 */
35 class CRM_Core_Payment_Form {
36
37
38 /**
39 * Add payment fields depending on payment processor. The payment processor can implement the following functions to override the built in fields.
40 *
41 * - getPaymentFormFields()
42 * - getPaymentFormFieldsMetadata()
43 * (planned - getBillingDetailsFormFields(), getBillingDetailsFormFieldsMetadata()
44 *
45 * Note that this code is written to accommodate the possibility CiviCRM will switch to implementing pay later as a manual processor in future
46 *
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
50 */
51 static public function setPaymentFieldsByProcessor(&$form, $processor, $forceBillingFieldsForPayLater = FALSE) {
52 $form->billingFieldSets = array();
53 if ($processor != NULL) {
54 // ie it is pay later
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);
61
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);
65 }
66
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) || $processor['billing_mode'] == 4) {
71 return;
72 }
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();
79 }
80
81 /**
82 * add general billing fields
83 * @todo set these like processor fields & let payment processors alter them
84 *
85 * @param $form
86 *
87 * @return void
88 * @access protected
89 */
90 static protected function setBillingDetailsFields(&$form) {
91 $bltID = $form->_bltID;
92
93 $form->_paymentFields['billing_first_name'] = array(
94 'htmlType' => 'text',
95 'name' => 'billing_first_name',
96 'title' => ts('Billing First Name'),
97 'cc_field' => TRUE,
98 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
99 'is_required' => TRUE,
100 );
101
102 $form->_paymentFields['billing_middle_name'] = array(
103 'htmlType' => 'text',
104 'name' => 'billing_middle_name',
105 'title' => ts('Billing Middle Name'),
106 'cc_field' => TRUE,
107 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
108 'is_required' => FALSE,
109 );
110
111 $form->_paymentFields['billing_last_name'] = array(
112 'htmlType' => 'text',
113 'name' => 'billing_last_name',
114 'title' => ts('Billing Last Name'),
115 'cc_field' => TRUE,
116 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
117 'is_required' => TRUE,
118 );
119
120 $form->_paymentFields["billing_street_address-{$bltID}"] = array(
121 'htmlType' => 'text',
122 'name' => "billing_street_address-{$bltID}",
123 'title' => ts('Street Address'),
124 'cc_field' => TRUE,
125 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
126 'is_required' => TRUE,
127 );
128
129 $form->_paymentFields["billing_city-{$bltID}"] = array(
130 'htmlType' => 'text',
131 'name' => "billing_city-{$bltID}",
132 'title' => ts('City'),
133 'cc_field' => TRUE,
134 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
135 'is_required' => TRUE,
136 );
137
138 $form->_paymentFields["billing_state_province_id-{$bltID}"] = array(
139 'htmlType' => 'chainSelect',
140 'title' => ts('State/Province'),
141 'name' => "billing_state_province_id-{$bltID}",
142 'cc_field' => TRUE,
143 'is_required' => TRUE,
144 );
145
146 $form->_paymentFields["billing_postal_code-{$bltID}"] = array(
147 'htmlType' => 'text',
148 'name' => "billing_postal_code-{$bltID}",
149 'title' => ts('Postal Code'),
150 'cc_field' => TRUE,
151 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
152 'is_required' => TRUE,
153 );
154
155 $form->_paymentFields["billing_country_id-{$bltID}"] = array(
156 'htmlType' => 'select',
157 'name' => "billing_country_id-{$bltID}",
158 'title' => ts('Country'),
159 'cc_field' => TRUE,
160 'attributes' => array(
161 '' => ts('- select -')) +
162 CRM_Core_PseudoConstant::country(),
163 'is_required' => TRUE,
164 );
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',
170 'billing_last_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}",
176 ));
177 }
178
179 /**
180 * create all fields needed for a credit card transaction
181 * @deprecated - use the setPaymentFieldsByProcessor which leverages the processor to determine the fields
182 * @param CRM_Core_Form $form
183 *
184 * @return void
185 * @access public
186 */
187 static function setCreditCardFields(&$form) {
188 CRM_Core_Payment_Form::setBillingDetailsFields($form);
189
190 $form->_paymentFields['credit_card_number'] = array(
191 'htmlType' => 'text',
192 'name' => 'credit_card_number',
193 'title' => ts('Card Number'),
194 'cc_field' => TRUE,
195 'attributes' => array('size' => 20, 'maxlength' => 20, 'autocomplete' => 'off'),
196 'is_required' => TRUE,
197 );
198
199 $form->_paymentFields['cvv2'] = array(
200 'htmlType' => 'text',
201 'name' => 'cvv2',
202 'title' => ts('Security Code'),
203 'cc_field' => TRUE,
204 'attributes' => array('size' => 5, 'maxlength' => 10, 'autocomplete' => 'off'),
205 'is_required' => CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME,
206 'cvv_backoffice_required',
207 NULL
208 ,1
209 ),
210 );
211
212 $form->_paymentFields['credit_card_exp_date'] = array(
213 'htmlType' => 'date',
214 'name' => 'credit_card_exp_date',
215 'title' => ts('Expiration Date'),
216 'cc_field' => TRUE,
217 'attributes' => CRM_Core_SelectValues::date('creditCard'),
218 'is_required' => TRUE,
219 );
220
221 $creditCardType = array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::creditCard();
222 $form->_paymentFields['credit_card_type'] = array(
223 'htmlType' => 'select',
224 'name' => 'credit_card_type',
225 'title' => ts('Card Type'),
226 'cc_field' => TRUE,
227 'attributes' => $creditCardType,
228 'is_required' => FALSE,
229 );
230 //CRM-15509 this is probably a temporary resting place for these form assignments but we are working towards putting this
231 // in an option group & having php / payment processors define the billing form rather than the tpl
232 $smarty = CRM_Core_Smarty::singleton();
233 $smarty->assign('paymentTypeName', 'credit_card');
234 $smarty->assign('paymentTypeLabel', ts('Credit Card Information'));
235 $smarty->assign('paymentFields', self::getPaymentFields($form->_paymentProcessor));
236 }
237
238 /**
239 * @param CRM_Core_Form $form
240 * @param bool $useRequired
241 * @param array $paymentFields
242 */
243 protected static function addCommonFields(&$form, $useRequired, $paymentFields) {
244 foreach ($paymentFields as $name => $field) {
245 if (!empty($field['cc_field'])) {
246 if ($field['htmlType'] == 'chainSelect') {
247 $form->addChainSelect($field['name'], array('required' => $useRequired && $field['is_required']));
248 }
249 else {
250 $form->add($field['htmlType'],
251 $field['name'],
252 $field['title'],
253 $field['attributes'],
254 $useRequired ? $field['is_required'] : FALSE
255 );
256 }
257 }
258 }
259 }
260
261 /**
262 * create all fields needed for direct debit transaction
263 * @deprecated - use the setPaymentFieldsByProcessor which leverages the processor to determine the fields
264 * @param $form
265 *
266 * @return void
267 * @access public
268 */
269 static function setDirectDebitFields(&$form) {
270 CRM_Core_Payment_Form::setBillingDetailsFields($form);
271
272 $form->_paymentFields['account_holder'] = array(
273 'htmlType' => 'text',
274 'name' => 'account_holder',
275 'title' => ts('Account Holder'),
276 'cc_field' => TRUE,
277 'attributes' => array('size' => 20, 'maxlength' => 34, 'autocomplete' => 'on'),
278 'is_required' => TRUE,
279 );
280
281 //e.g. IBAN can have maxlength of 34 digits
282 $form->_paymentFields['bank_account_number'] = array(
283 'htmlType' => 'text',
284 'name' => 'bank_account_number',
285 'title' => ts('Bank Account Number'),
286 'cc_field' => TRUE,
287 'attributes' => array('size' => 20, 'maxlength' => 34, 'autocomplete' => 'off'),
288 'is_required' => TRUE,
289 );
290
291 //e.g. SWIFT-BIC can have maxlength of 11 digits
292 $form->_paymentFields['bank_identification_number'] = array(
293 'htmlType' => 'text',
294 'name' => 'bank_identification_number',
295 'title' => ts('Bank Identification Number'),
296 'cc_field' => TRUE,
297 'attributes' => array('size' => 20, 'maxlength' => 11, 'autocomplete' => 'off'),
298 'is_required' => TRUE,
299 );
300
301 $form->_paymentFields['bank_name'] = array(
302 'htmlType' => 'text',
303 'name' => 'bank_name',
304 'title' => ts('Bank Name'),
305 'cc_field' => TRUE,
306 'attributes' => array('size' => 20, 'maxlength' => 64, 'autocomplete' => 'off'),
307 'is_required' => TRUE,
308 );
309 //CRM-15509 this is probably a temporary resting place for these form assignments but we are working towards putting this
310 // in an option group & having php / payment processors define the billing form rather than the tpl
311 $smarty = CRM_Core_Smarty::singleton();
312 // replace these payment type names with an option group - moving name & label assumptions out of the tpl is a step towards that
313 $smarty->assign('paymentTypeName', 'direct_debit');
314 $smarty->assign('paymentTypeLabel', ts('Direct Debit Information'));
315 $smarty->assign('paymentFields', self::getPaymentFields($form->_paymentProcessor));
316 }
317
318 /**
319 * @param array $paymentProcessor
320 * @todo it will be necessary to set details that affect it - mostly likely take Country as a param. Should we add generic
321 * setParams on processor class or just setCountry which we know we need?
322 *
323 * @return array
324 */
325 static function getPaymentFields($paymentProcessor) {
326 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
327 return $paymentProcessorObject->getPaymentFormFields();
328 }
329
330 /**
331 * @param array $paymentProcessor
332 *
333 * @return array
334 */
335 static function getPaymentFieldMetadata($paymentProcessor) {
336 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
337 return $paymentProcessorObject->getPaymentFormFieldsMetadata();
338 }
339
340 /**
341 * @param array $paymentProcessor
342 *
343 * @return string
344 */
345 static function getPaymentTypeName($paymentProcessor) {
346 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
347 return $paymentProcessorObject->getPaymentTypeName();
348 }
349
350 /**
351 * @param array $paymentProcessor
352 *
353 * @return string
354 */
355 static function getPaymentTypeLabel($paymentProcessor) {
356 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
357 return ts(($paymentProcessorObject->getPaymentTypeLabel()) . ' Information');
358 }
359
360 /**
361 * @param CRM_Contribute_Form_AbstractEditPayment|CRM_Contribute_Form_Contribution_Main|CRM_Core_Payment_ProcessorForm $form
362 * @param array $processor array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors
363 * @param bool $isBillingDataOptional This manifests for 'NULL' (pay later) payment processor as the addition of billing fields to the form and
364 * for payment processors that gather payment data on site as rendering the fields as not being required. (not entirely sure why but this
365 * is implemented for back office forms)
366 *
367 * @return bool
368 */
369 static function buildPaymentForm($form, $processor, $isBillingDataOptional){
370 //if the form has address fields assign to the template so the js can decide what billing fields to show
371 $profileAddressFields = $form->get('profileAddressFields');
372 if (!empty($profileAddressFields)) {
373 $form->assign('profileAddressFields', $profileAddressFields);
374 }
375
376 // $processor->buildForm appears to be an undocumented (possibly unused) option for payment processors
377 // which was previously available only in some form flows
378 if (!empty($form->_paymentProcessor) && !empty($form->_paymentProcessor['object']) && $form->_paymentProcessor['object']->isSupported('buildForm')) {
379 $form->_paymentProcessor['object']->buildForm($form);
380 return;
381 }
382
383 self::setPaymentFieldsByProcessor($form, $processor, empty($isBillingDataOptional));
384 self::addCommonFields($form, !$isBillingDataOptional, $form->_paymentFields);
385 self::addRules($form, $form->_paymentFields);
386 self::addPaypalExpressCode($form);
387 return (!empty($form->_paymentFields));
388 }
389
390 /**
391 * @param CRM_Core_Form $form
392 * @param array $paymentFields array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors
393
394 * @param $paymentFields
395 */
396 protected static function addRules(&$form, $paymentFields) {
397 foreach ($paymentFields as $paymentField => $fieldSpecs) {
398 if (!empty($fieldSpecs['rules'])) {
399 foreach ($fieldSpecs['rules'] as $rule) {
400 $form->addRule($paymentField,
401 $rule['rule_message'],
402 $rule['rule_name'],
403 $rule['rule_parameters']
404 );
405 }
406 }
407 }
408 }
409
410 /**
411 * billing mode button is basically synonymous with paypal express - this is probably a good example of 'odds & sods' code we
412 * 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
413 *
414 * @param $form
415 */
416 protected static function addPaypalExpressCode(&$form) {
417 if (empty($form->isBackOffice)) {
418 if ($form->_paymentProcessor['billing_mode'] &
419 CRM_Core_Payment::BILLING_MODE_BUTTON
420 ) {
421 $form->_expressButtonName = $form->getButtonName('upload', 'express');
422 $form->assign('expressButtonName', $form->_expressButtonName);
423 $form->add('image',
424 $form->_expressButtonName,
425 $form->_paymentProcessor['url_button'],
426 array('class' => 'crm-form-submit')
427 );
428 }
429 }
430 }
431 /**
432 * Function to add all the credit card fields
433 * @deprecated Use BuildPaymentForm
434 * @param $form
435 * @param bool $useRequired
436 *
437 * @return void
438 * @access public
439 */
440 static function buildCreditCard(&$form, $useRequired = FALSE) {
441 if ($form->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_FORM) {
442 self::setCreditCardFields($form);
443 self::addCommonFields($form, $useRequired, $form->_paymentFields);
444
445 $form->addRule('cvv2',
446 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.'),
447 'integer'
448 );
449
450 $form->addRule('credit_card_exp_date',
451 ts('Card expiration date cannot be a past date.'),
452 'currentDate', TRUE
453 );
454
455 }
456
457
458 if ($form->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_BUTTON) {
459 $form->_expressButtonName = $form->getButtonName('upload', 'express');
460 $form->assign('expressButtonName', $form->_expressButtonName);
461 $form->add('image',
462 $form->_expressButtonName,
463 $form->_paymentProcessor['url_button'],
464 array('class' => 'crm-form-submit')
465 );
466 }
467 }
468
469 /**
470 * The credit card pseudo constant results only the CC label, not the key ID
471 * So we normalize the name to use it as a CSS class.
472 */
473 static function getCreditCardCSSNames() {
474 $creditCardTypes = array();
475 foreach (CRM_Contribute_PseudoConstant::creditCard() as $key => $name) {
476 // Replace anything not css-friendly by an underscore
477 // Non-latin names will not like this, but so many things are wrong with
478 // the credit-card type configurations already.
479 $key = str_replace(' ', '', $key);
480 $key = preg_replace('/[^a-zA-Z0-9]/', '_', $key);
481 $key = strtolower($key);
482 $creditCardTypes[$key] = $name;
483 }
484 return $creditCardTypes;
485 }
486
487 /**
488 * Function to add all the direct debit fields
489 * @deprecated use buildPaymentForm
490 *
491 * @param $form
492 * @param bool $useRequired
493 * @return void
494 * @access public
495 */
496 static function buildDirectDebit(&$form, $useRequired = FALSE) {
497 if ($form->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_FORM) {
498 self::setDirectDebitFields($form);
499 self::addCommonFields($form, $useRequired, $form->_paymentFields);
500
501 $form->addRule('bank_identification_number',
502 ts('Please enter a valid Bank Identification Number (value must not contain punctuation characters).'),
503 'nopunctuation'
504 );
505
506 $form->addRule('bank_account_number',
507 ts('Please enter a valid Bank Account Number (value must not contain punctuation characters).'),
508 'nopunctuation'
509 );
510 }
511 }
512
513 /**
514 * Make sure that credit card number and cvv are valid
515 * Called within the scope of a QF formRule function
516 */
517 static function validateCreditCard($values, &$errors) {
518 if (!empty($values['credit_card_type'])) {
519 if (!empty($values['credit_card_number']) &&
520 !CRM_Utils_Rule::creditCardNumber($values['credit_card_number'], $values['credit_card_type'])
521 ) {
522 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
523 }
524 if (!empty($values['cvv2']) &&
525 !CRM_Utils_Rule::cvv($values['cvv2'], $values['credit_card_type'])
526 ) {
527 $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
528 }
529 }
530 elseif (!empty($values['credit_card_number'])) {
531 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
532 }
533 }
534
535 /**
536 * function to map address fields
537 *
538 * @param $id
539 * @param $src
540 * @param $dst
541 * @param bool $reverse
542 *
543 * @return void
544 * @static
545 */
546 static function mapParams($id, &$src, &$dst, $reverse = FALSE) {
547 static $map = NULL;
548 if (!$map) {
549 $map = array(
550 'first_name' => 'billing_first_name',
551 'middle_name' => 'billing_middle_name',
552 'last_name' => 'billing_last_name',
553 'email' => "email-$id",
554 'street_address' => "billing_street_address-$id",
555 'supplemental_address_1' => "billing_supplemental_address_1-$id",
556 'city' => "billing_city-$id",
557 'state_province' => "billing_state_province-$id",
558 'postal_code' => "billing_postal_code-$id",
559 'country' => "billing_country-$id",
560 );
561 }
562
563 foreach ($map as $n => $v) {
564 if (!$reverse) {
565 if (isset($src[$n])) {
566 $dst[$v] = $src[$n];
567 }
568 }
569 else {
570 if (isset($src[$v])) {
571 $dst[$n] = $src[$v];
572 }
573 }
574 }
575 }
576
577 /**
578 * function to get the credit card expiration month
579 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
580 * See CRM-9017
581 *
582 * @param $src
583 *
584 * @return int
585 * @static
586 */
587 static function getCreditCardExpirationMonth($src) {
588 if ($month = CRM_Utils_Array::value('M', $src['credit_card_exp_date'])) {
589 return $month;
590 }
591
592 return CRM_Utils_Array::value('m', $src['credit_card_exp_date']);
593 }
594
595 /**
596 * function to get the credit card expiration year
597 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
598 * This function exists only to make it consistent with getCreditCardExpirationMonth
599 *
600 * @param $src
601 *
602 * @return int
603 * @static
604 */
605 static function getCreditCardExpirationYear($src) {
606 return CRM_Utils_Array::value('Y', $src['credit_card_exp_date']);
607 }
608 }