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) ||
$processor['billing_mode'] == 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
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 * 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
187 static function setCreditCardFields(&$form) {
188 CRM_Core_Payment_Form
::setBillingDetailsFields($form);
190 $form->_paymentFields
['credit_card_number'] = array(
191 'htmlType' => 'text',
192 'name' => 'credit_card_number',
193 'title' => ts('Card Number'),
195 'attributes' => array('size' => 20, 'maxlength' => 20, 'autocomplete' => 'off'),
196 'is_required' => TRUE,
199 $form->_paymentFields
['cvv2'] = array(
200 'htmlType' => 'text',
202 'title' => ts('Security Code'),
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',
212 $form->_paymentFields
['credit_card_exp_date'] = array(
213 'htmlType' => 'date',
214 'name' => 'credit_card_exp_date',
215 'title' => ts('Expiration Date'),
217 'attributes' => CRM_Core_SelectValues
::date('creditCard'),
218 'is_required' => TRUE,
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'),
227 'attributes' => $creditCardType,
228 'is_required' => FALSE,
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
));
239 * @param CRM_Core_Form $form
240 * @param bool $useRequired
241 * @param array $paymentFields
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']));
250 $form->add($field['htmlType'],
253 $field['attributes'],
254 $useRequired ?
$field['is_required'] : FALSE
262 * create all fields needed for direct debit transaction
263 * @deprecated - use the setPaymentFieldsByProcessor which leverages the processor to determine the fields
269 static function setDirectDebitFields(&$form) {
270 CRM_Core_Payment_Form
::setBillingDetailsFields($form);
272 $form->_paymentFields
['account_holder'] = array(
273 'htmlType' => 'text',
274 'name' => 'account_holder',
275 'title' => ts('Account Holder'),
277 'attributes' => array('size' => 20, 'maxlength' => 34, 'autocomplete' => 'on'),
278 'is_required' => TRUE,
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'),
287 'attributes' => array('size' => 20, 'maxlength' => 34, 'autocomplete' => 'off'),
288 'is_required' => TRUE,
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'),
297 'attributes' => array('size' => 20, 'maxlength' => 11, 'autocomplete' => 'off'),
298 'is_required' => TRUE,
301 $form->_paymentFields
['bank_name'] = array(
302 'htmlType' => 'text',
303 'name' => 'bank_name',
304 'title' => ts('Bank Name'),
306 'attributes' => array('size' => 20, 'maxlength' => 64, 'autocomplete' => 'off'),
307 'is_required' => TRUE,
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
));
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?
325 static function getPaymentFields($paymentProcessor) {
326 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
327 return $paymentProcessorObject->getPaymentFormFields();
331 * @param array $paymentProcessor
335 static function getPaymentFieldMetadata($paymentProcessor) {
336 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
337 return $paymentProcessorObject->getPaymentFormFieldsMetadata();
341 * @param array $paymentProcessor
345 static function getPaymentTypeName($paymentProcessor) {
346 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
347 return $paymentProcessorObject->getPaymentTypeName();
351 * @param array $paymentProcessor
355 static function getPaymentTypeLabel($paymentProcessor) {
356 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
357 return ts(($paymentProcessorObject->getPaymentTypeLabel()) . ' Information');
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)
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);
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);
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
));
391 * @param CRM_Core_Form $form
392 * @param array $paymentFields array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors
394 * @param $paymentFields
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'],
403 $rule['rule_parameters']
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
416 protected static function addPaypalExpressCode(&$form) {
417 if (empty($form->isBackOffice
)) {
418 if ($form->_paymentProcessor
['billing_mode'] &
419 CRM_Core_Payment
::BILLING_MODE_BUTTON
421 $form->_expressButtonName
= $form->getButtonName('upload', 'express');
422 $form->assign('expressButtonName', $form->_expressButtonName
);
424 $form->_expressButtonName
,
425 $form->_paymentProcessor
['url_button'],
426 array('class' => 'crm-form-submit')
432 * Function to add all the credit card fields
433 * @deprecated Use BuildPaymentForm
435 * @param bool $useRequired
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
);
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.'),
450 $form->addRule('credit_card_exp_date',
451 ts('Card expiration date cannot be a past date.'),
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
);
462 $form->_expressButtonName
,
463 $form->_paymentProcessor
['url_button'],
464 array('class' => 'crm-form-submit')
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.
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;
484 return $creditCardTypes;
488 * Function to add all the direct debit fields
489 * @deprecated use buildPaymentForm
492 * @param bool $useRequired
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
);
501 $form->addRule('bank_identification_number',
502 ts('Please enter a valid Bank Identification Number (value must not contain punctuation characters).'),
506 $form->addRule('bank_account_number',
507 ts('Please enter a valid Bank Account Number (value must not contain punctuation characters).'),
514 * Make sure that credit card number and cvv are valid
515 * Called within the scope of a QF formRule function
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'])
522 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
524 if (!empty($values['cvv2']) &&
525 !CRM_Utils_Rule
::cvv($values['cvv2'], $values['credit_card_type'])
527 $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
530 elseif (!empty($values['credit_card_number'])) {
531 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
536 * function to map address fields
541 * @param bool $reverse
546 static function mapParams($id, &$src, &$dst, $reverse = FALSE) {
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",
563 foreach ($map as $n => $v) {
565 if (isset($src[$n])) {
570 if (isset($src[$v])) {
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)
587 static function getCreditCardExpirationMonth($src) {
588 if ($month = CRM_Utils_Array
::value('M', $src['credit_card_exp_date'])) {
592 return CRM_Utils_Array
::value('m', $src['credit_card_exp_date']);
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
605 static function getCreditCardExpirationYear($src) {
606 return CRM_Utils_Array
::value('Y', $src['credit_card_exp_date']);