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