Merge pull request #6269 from yashodha/CRM-15564
[civicrm-core.git] / CRM / Core / Payment / Form.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
e7112fa7 6 | Copyright CiviCRM LLC (c) 2004-2015 |
6a488035
TO
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
e7112fa7 31 * @copyright CiviCRM LLC (c) 2004-2015
6a488035
TO
32 * $Id$
33 *
34 */
35class CRM_Core_Payment_Form {
36
dc913073
EM
37
38 /**
dde5a0ef 39 * Add payment fields depending on payment processor. The payment processor can implement the following functions to override the built in fields.
dc913073 40 *
dde5a0ef
EM
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
6a0b768e
TO
48 * @param array $processor
49 * Array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors.
50 * @param bool $forceBillingFieldsForPayLater
51 * Display billing fields even for pay later.
dfc68e82
EM
52 * @param bool $isBackOffice
53 * Is this a back office function? If so the option to suppress the cvn needs to be evaluated.
dc913073 54 */
dfc68e82 55 static public function setPaymentFieldsByProcessor(&$form, $processor, $forceBillingFieldsForPayLater = FALSE, $isBackOffice = FALSE) {
dc913073 56 $form->billingFieldSets = array();
dde5a0ef
EM
57 if ($processor != NULL) {
58 // ie it is pay later
59 $paymentFields = self::getPaymentFields($processor);
60 $paymentTypeName = self::getPaymentTypeName($processor);
61 $paymentTypeLabel = self::getPaymentTypeLabel($processor);
62 //@todo if we switch to iterating through $form->billingFieldSets we won't need to assign these directly
63 $form->assign('paymentTypeName', $paymentTypeName);
64 $form->assign('paymentTypeLabel', $paymentTypeLabel);
65
66 $form->billingFieldSets[$paymentTypeName]['fields'] = $form->_paymentFields = array_intersect_key(self::getPaymentFieldMetadata($processor), array_flip($paymentFields));
2fa76c18
EM
67 if (in_array('cvv2', $paymentFields) && $isBackOffice) {
68 if (!civicrm_api3('setting', 'getvalue', array('name' => 'cvv_backoffice_required', 'group' => 'Contribute Preferences'))) {
69 $form->billingFieldSets[$paymentTypeName]['fields'][array_search('cvv2', $paymentFields)]['required'] = 0;
70 }
71 }
72
dde5a0ef
EM
73 $form->billingPane = array($paymentTypeName => $paymentTypeLabel);
74 $form->assign('paymentFields', $paymentFields);
75 }
76
77 // @todo - replace this section with one similar to above per discussion - probably use a manual processor shell class to stand in for that capability
78 //return without adding billing fields if billing_mode = 4 (@todo - more the ability to set that to the payment processor)
79 // or payment processor is NULL (pay later)
353ffa53 80 if (($processor == NULL && !$forceBillingFieldsForPayLater) || CRM_Utils_Array::value('billing_mode', $processor) == 4) {
dde5a0ef 81 return;
dc913073 82 }
dde5a0ef
EM
83 //@todo setPaymentFields defines the billing fields - this should be moved to the processor class & renamed getBillingFields
84 // potentially pay later would also be a payment processor
85 //also set the billingFieldSet to hold all the details required to render the fieldset so we can iterate through the fieldset - making
86 // it easier to re-order in hooks etc. The billingFieldSets param is used to determine whether to show the billing pane
87 CRM_Core_Payment_Form::setBillingDetailsFields($form);
88 $form->billingFieldSets['billing_name_address-group']['fields'] = array();
dc913073 89 }
9c39fb25 90
6a488035 91 /**
fe482240 92 * Add general billing fields.
9c39fb25
EM
93 * @todo set these like processor fields & let payment processors alter them
94 *
c490a46a 95 * @param CRM_Core_Form $form
77b97be7 96 *
6a488035 97 * @return void
6a488035 98 */
dde5a0ef 99 static protected function setBillingDetailsFields(&$form) {
6a488035
TO
100 $bltID = $form->_bltID;
101
102 $form->_paymentFields['billing_first_name'] = array(
103 'htmlType' => 'text',
104 'name' => 'billing_first_name',
105 'title' => ts('Billing First Name'),
106 'cc_field' => TRUE,
107 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
108 'is_required' => TRUE,
109 );
110
111 $form->_paymentFields['billing_middle_name'] = array(
112 'htmlType' => 'text',
113 'name' => 'billing_middle_name',
114 'title' => ts('Billing Middle Name'),
115 'cc_field' => TRUE,
116 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
117 'is_required' => FALSE,
118 );
119
120 $form->_paymentFields['billing_last_name'] = array(
121 'htmlType' => 'text',
122 'name' => 'billing_last_name',
123 'title' => ts('Billing Last Name'),
124 'cc_field' => TRUE,
125 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
126 'is_required' => TRUE,
127 );
128
129 $form->_paymentFields["billing_street_address-{$bltID}"] = array(
130 'htmlType' => 'text',
131 'name' => "billing_street_address-{$bltID}",
132 'title' => ts('Street Address'),
133 'cc_field' => TRUE,
134 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
135 'is_required' => TRUE,
136 );
137
138 $form->_paymentFields["billing_city-{$bltID}"] = array(
139 'htmlType' => 'text',
140 'name' => "billing_city-{$bltID}",
141 'title' => ts('City'),
142 'cc_field' => TRUE,
143 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
144 'is_required' => TRUE,
145 );
146
147 $form->_paymentFields["billing_state_province_id-{$bltID}"] = array(
c927c151 148 'htmlType' => 'chainSelect',
757069de 149 'title' => ts('State/Province'),
6a488035 150 'name' => "billing_state_province_id-{$bltID}",
6a488035 151 'cc_field' => TRUE,
bc999cd1 152 'is_required' => TRUE,
6a488035
TO
153 );
154
155 $form->_paymentFields["billing_postal_code-{$bltID}"] = array(
156 'htmlType' => 'text',
157 'name' => "billing_postal_code-{$bltID}",
158 'title' => ts('Postal Code'),
159 'cc_field' => TRUE,
160 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
161 'is_required' => TRUE,
162 );
163
164 $form->_paymentFields["billing_country_id-{$bltID}"] = array(
165 'htmlType' => 'select',
166 'name' => "billing_country_id-{$bltID}",
167 'title' => ts('Country'),
168 'cc_field' => TRUE,
169 'attributes' => array(
acb1052e
WA
170 '' => ts('- select -'),
171 ) +
172 CRM_Core_PseudoConstant::country(),
6a488035
TO
173 'is_required' => TRUE,
174 );
92ec1d7f
EM
175 //CRM-15509 working towards giving control over billing fields to payment processors. For now removing tpl hard-coding
176 $smarty = CRM_Core_Smarty::singleton();
177 $smarty->assign('billingDetailsFields', array(
178 'billing_first_name',
179 'billing_middle_name',
180 'billing_last_name',
181 "billing_street_address-{$bltID}",
182 "billing_city-{$bltID}",
183 "billing_country_id-{$bltID}",
95e19ac6 184 "billing_state_province_id-{$bltID}",
92ec1d7f
EM
185 "billing_postal_code-{$bltID}",
186 ));
6a488035
TO
187 }
188
c46f87cf
CW
189 /**
190 * @param CRM_Core_Form $form
92ec1d7f 191 * @param bool $useRequired
dc913073 192 * @param array $paymentFields
c46f87cf 193 */
9c39fb25 194 protected static function addCommonFields(&$form, $useRequired, $paymentFields) {
dc913073 195 foreach ($paymentFields as $name => $field) {
c46f87cf
CW
196 if (!empty($field['cc_field'])) {
197 if ($field['htmlType'] == 'chainSelect') {
198 $form->addChainSelect($field['name'], array('required' => $useRequired && $field['is_required']));
199 }
200 else {
201 $form->add($field['htmlType'],
202 $field['name'],
203 $field['title'],
204 $field['attributes'],
205 $useRequired ? $field['is_required'] : FALSE
206 );
207 }
208 }
209 }
210 }
211
44b6505d
EM
212 /**
213 * @param array $paymentProcessor
dc913073
EM
214 * @todo it will be necessary to set details that affect it - mostly likely take Country as a param. Should we add generic
215 * setParams on processor class or just setCountry which we know we need?
44b6505d 216 *
dc913073 217 * @return array
44b6505d 218 */
00be9182 219 public static function getPaymentFields($paymentProcessor) {
44b6505d 220 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
dc913073 221 return $paymentProcessorObject->getPaymentFormFields();
44b6505d
EM
222 }
223
224 /**
dc913073 225 * @param array $paymentProcessor
44b6505d 226 *
dc913073
EM
227 * @return array
228 */
00be9182 229 public static function getPaymentFieldMetadata($paymentProcessor) {
dc913073
EM
230 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
231 return $paymentProcessorObject->getPaymentFormFieldsMetadata();
232 }
233
234 /**
44b6505d 235 * @param array $paymentProcessor
44b6505d 236 *
dc913073 237 * @return string
44b6505d 238 */
00be9182 239 public static function getPaymentTypeName($paymentProcessor) {
dc913073
EM
240 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
241 return $paymentProcessorObject->getPaymentTypeName();
242 }
44b6505d 243
dc913073
EM
244 /**
245 * @param array $paymentProcessor
246 *
247 * @return string
248 */
00be9182 249 public static function getPaymentTypeLabel($paymentProcessor) {
dc913073
EM
250 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
251 return ts(($paymentProcessorObject->getPaymentTypeLabel()) . ' Information');
44b6505d
EM
252 }
253
dc913073 254 /**
a6513ad5 255 * @param CRM_Contribute_Form_AbstractEditPayment|CRM_Contribute_Form_Contribution_Main|CRM_Core_Payment_ProcessorForm|CRM_Contribute_Form_UpdateBilling $form
6a0b768e
TO
256 * @param array $processor
257 * Array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors.
258 * @param bool $isBillingDataOptional
259 * This manifests for 'NULL' (pay later) payment processor as the addition of billing fields to the form and.
dde5a0ef
EM
260 * for payment processors that gather payment data on site as rendering the fields as not being required. (not entirely sure why but this
261 * is implemented for back office forms)
dc913073
EM
262 *
263 * @return bool
264 */
dfc68e82 265 public static function buildPaymentForm(&$form, $processor, $isBillingDataOptional, $isBackOffice) {
dde5a0ef
EM
266 //if the form has address fields assign to the template so the js can decide what billing fields to show
267 $profileAddressFields = $form->get('profileAddressFields');
268 if (!empty($profileAddressFields)) {
269 $form->assign('profileAddressFields', $profileAddressFields);
270 }
271
272 // $processor->buildForm appears to be an undocumented (possibly unused) option for payment processors
273 // which was previously available only in some form flows
274 if (!empty($form->_paymentProcessor) && !empty($form->_paymentProcessor['object']) && $form->_paymentProcessor['object']->isSupported('buildForm')) {
275 $form->_paymentProcessor['object']->buildForm($form);
acb1052e 276 return NULL;
dde5a0ef
EM
277 }
278
dfc68e82 279 self::setPaymentFieldsByProcessor($form, $processor, empty($isBillingDataOptional), $isBackOffice);
dc913073
EM
280 self::addCommonFields($form, !$isBillingDataOptional, $form->_paymentFields);
281 self::addRules($form, $form->_paymentFields);
282 self::addPaypalExpressCode($form);
283 return (!empty($form->_paymentFields));
284 }
44b6505d 285
dc913073
EM
286 /**
287 * @param CRM_Core_Form $form
6a0b768e
TO
288 * @param array $paymentFields
289 * Array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors.
dc913073
EM
290 * @param $paymentFields
291 */
9c39fb25 292 protected static function addRules(&$form, $paymentFields) {
dc913073
EM
293 foreach ($paymentFields as $paymentField => $fieldSpecs) {
294 if (!empty($fieldSpecs['rules'])) {
295 foreach ($fieldSpecs['rules'] as $rule) {
296 $form->addRule($paymentField,
297 $rule['rule_message'],
298 $rule['rule_name'],
299 $rule['rule_parameters']
300 );
301 }
302 }
303 }
6a488035
TO
304 }
305
306 /**
100fef9d 307 * Billing mode button is basically synonymous with paypal express - this is probably a good example of 'odds & sods' code we
dc913073 308 * 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
6a488035 309 *
fd31fa4c 310 * @param $form
dc913073 311 */
9c39fb25 312 protected static function addPaypalExpressCode(&$form) {
0be0b79d 313 if (empty($form->isBackOffice)) {
342ffc6c 314 if (in_array(CRM_Utils_Array::value('billing_mode', $form->_paymentProcessor), array(2, 3))) {
0be0b79d
EM
315 $form->_expressButtonName = $form->getButtonName('upload', 'express');
316 $form->assign('expressButtonName', $form->_expressButtonName);
317 $form->add('image',
318 $form->_expressButtonName,
319 $form->_paymentProcessor['url_button'],
320 array('class' => 'crm-form-submit')
321 );
322 }
dc913073
EM
323 }
324 }
6a488035 325
a479fe60 326 /**
327 * Validate the payment instrument values before passing it to the payment processor
328 * We want this to be overrideable by the payment processor, and default to using
329 * this object's validCreditCard for credit cards (implemented as the default in the Payment class).
330 */
331 public static function validatePaymentInstrument($payment_processor_id, $values, &$errors, $form) {
332 // ignore if we don't have a payment instrument to validate (e.g. backend payments)
333 if ($payment_processor_id > 0) {
d1bd65af 334 $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($payment_processor_id, 'live');
a479fe60 335 $payment = CRM_Core_Payment::singleton('live', $paymentProcessor, $form);
336 $payment->validatePaymentInstrument($values, $errors);
337 }
338 }
339
bef9421f
CW
340 /**
341 * The credit card pseudo constant results only the CC label, not the key ID
342 * So we normalize the name to use it as a CSS class.
343 */
00be9182 344 public static function getCreditCardCSSNames() {
bef9421f
CW
345 $creditCardTypes = array();
346 foreach (CRM_Contribute_PseudoConstant::creditCard() as $key => $name) {
347 // Replace anything not css-friendly by an underscore
348 // Non-latin names will not like this, but so many things are wrong with
349 // the credit-card type configurations already.
350 $key = str_replace(' ', '', $key);
351 $key = preg_replace('/[^a-zA-Z0-9]/', '_', $key);
352 $key = strtolower($key);
353 $creditCardTypes[$key] = $name;
354 }
355 return $creditCardTypes;
356 }
357
7cb3d4f0 358 /**
fe482240 359 * Make sure that credit card number and cvv are valid.
7cb3d4f0 360 * Called within the scope of a QF formRule function
431c430b
EM
361 *
362 * @param array $values
363 * @param array $errors
7cb3d4f0 364 */
00be9182 365 public static function validateCreditCard($values, &$errors) {
4d1fd569 366 if (!empty($values['credit_card_type']) || !empty($values['credit_card_number'])) {
7cb3d4f0
CW
367 if (!empty($values['credit_card_number']) &&
368 !CRM_Utils_Rule::creditCardNumber($values['credit_card_number'], $values['credit_card_type'])
369 ) {
8543f7c1 370 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
7cb3d4f0
CW
371 }
372 if (!empty($values['cvv2']) &&
373 !CRM_Utils_Rule::cvv($values['cvv2'], $values['credit_card_type'])
374 ) {
8543f7c1 375 $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
7cb3d4f0
CW
376 }
377 }
378 }
379
6a488035 380 /**
fe482240 381 * Map address fields.
6a488035 382 *
100fef9d 383 * @param int $id
431c430b
EM
384 * @param array $src
385 * @param array $dst
77b97be7 386 * @param bool $reverse
6a488035 387 */
431c430b 388 public static function mapParams($id, $src, &$dst, $reverse = FALSE) {
6a488035
TO
389 static $map = NULL;
390 if (!$map) {
391 $map = array(
392 'first_name' => 'billing_first_name',
393 'middle_name' => 'billing_middle_name',
394 'last_name' => 'billing_last_name',
395 'email' => "email-$id",
396 'street_address' => "billing_street_address-$id",
397 'supplemental_address_1' => "billing_supplemental_address_1-$id",
398 'city' => "billing_city-$id",
399 'state_province' => "billing_state_province-$id",
400 'postal_code' => "billing_postal_code-$id",
401 'country' => "billing_country-$id",
e7309307 402 'contactID' => 'contact_id',
6a488035
TO
403 );
404 }
405
406 foreach ($map as $n => $v) {
407 if (!$reverse) {
408 if (isset($src[$n])) {
409 $dst[$v] = $src[$n];
410 }
411 }
412 else {
413 if (isset($src[$v])) {
414 $dst[$n] = $src[$v];
415 }
416 }
417 }
418 }
419
420 /**
fe482240 421 * Get the credit card expiration month.
6a488035
TO
422 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
423 * See CRM-9017
424 *
2a6da8d7
EM
425 * @param $src
426 *
6a488035 427 * @return int
6a488035 428 */
00be9182 429 public static function getCreditCardExpirationMonth($src) {
6a488035
TO
430 if ($month = CRM_Utils_Array::value('M', $src['credit_card_exp_date'])) {
431 return $month;
432 }
433
434 return CRM_Utils_Array::value('m', $src['credit_card_exp_date']);
435 }
436
437 /**
fe482240 438 * Get the credit card expiration year.
6a488035 439 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
c1cc3e0c 440 * This function exists only to make it consistent with getCreditCardExpirationMonth
6a488035 441 *
2a6da8d7
EM
442 * @param $src
443 *
6a488035 444 * @return int
6a488035 445 */
00be9182 446 public static function getCreditCardExpirationYear($src) {
6a488035
TO
447 return CRM_Utils_Array::value('Y', $src['credit_card_exp_date']);
448 }
96025800 449
6a488035 450}