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