Merge remote-tracking branch 'upstream/4.5' into 4.5-master-2014-11-17-13-40-23
[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) || CRM_Utils_Array::value('billing_mode', $processor) == 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 * @param CRM_Core_Form $form
181 * @param bool $useRequired
182 * @param array $paymentFields
183 */
184 protected static function addCommonFields(&$form, $useRequired, $paymentFields) {
185 foreach ($paymentFields as $name => $field) {
186 if (!empty($field['cc_field'])) {
187 if ($field['htmlType'] == 'chainSelect') {
188 $form->addChainSelect($field['name'], array('required' => $useRequired && $field['is_required']));
189 }
190 else {
191 $form->add($field['htmlType'],
192 $field['name'],
193 $field['title'],
194 $field['attributes'],
195 $useRequired ? $field['is_required'] : FALSE
196 );
197 }
198 }
199 }
200 }
201
202 /**
203 * @param array $paymentProcessor
204 * @todo it will be necessary to set details that affect it - mostly likely take Country as a param. Should we add generic
205 * setParams on processor class or just setCountry which we know we need?
206 *
207 * @return array
208 */
209 static function getPaymentFields($paymentProcessor) {
210 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
211 return $paymentProcessorObject->getPaymentFormFields();
212 }
213
214 /**
215 * @param array $paymentProcessor
216 *
217 * @return array
218 */
219 static function getPaymentFieldMetadata($paymentProcessor) {
220 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
221 return $paymentProcessorObject->getPaymentFormFieldsMetadata();
222 }
223
224 /**
225 * @param array $paymentProcessor
226 *
227 * @return string
228 */
229 static function getPaymentTypeName($paymentProcessor) {
230 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
231 return $paymentProcessorObject->getPaymentTypeName();
232 }
233
234 /**
235 * @param array $paymentProcessor
236 *
237 * @return string
238 */
239 static function getPaymentTypeLabel($paymentProcessor) {
240 $paymentProcessorObject = CRM_Core_Payment::singleton(($paymentProcessor['is_test'] ? 'test' : 'live'), $paymentProcessor);
241 return ts(($paymentProcessorObject->getPaymentTypeLabel()) . ' Information');
242 }
243
244 /**
245 * @param CRM_Contribute_Form_AbstractEditPayment|CRM_Contribute_Form_Contribution_Main|CRM_Core_Payment_ProcessorForm|CRM_Contribute_Form_UpdateBilling $form
246 * @param array $processor array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors
247 * @param bool $isBillingDataOptional This manifests for 'NULL' (pay later) payment processor as the addition of billing fields to the form and
248 * for payment processors that gather payment data on site as rendering the fields as not being required. (not entirely sure why but this
249 * is implemented for back office forms)
250 *
251 * @return bool
252 */
253 static function buildPaymentForm($form, $processor, $isBillingDataOptional){
254 //if the form has address fields assign to the template so the js can decide what billing fields to show
255 $profileAddressFields = $form->get('profileAddressFields');
256 if (!empty($profileAddressFields)) {
257 $form->assign('profileAddressFields', $profileAddressFields);
258 }
259
260 // $processor->buildForm appears to be an undocumented (possibly unused) option for payment processors
261 // which was previously available only in some form flows
262 if (!empty($form->_paymentProcessor) && !empty($form->_paymentProcessor['object']) && $form->_paymentProcessor['object']->isSupported('buildForm')) {
263 $form->_paymentProcessor['object']->buildForm($form);
264 return;
265 }
266
267 self::setPaymentFieldsByProcessor($form, $processor, empty($isBillingDataOptional));
268 self::addCommonFields($form, !$isBillingDataOptional, $form->_paymentFields);
269 self::addRules($form, $form->_paymentFields);
270 self::addPaypalExpressCode($form);
271 return (!empty($form->_paymentFields));
272 }
273
274 /**
275 * @param CRM_Core_Form $form
276 * @param array $paymentFields array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors
277
278 * @param $paymentFields
279 */
280 protected static function addRules(&$form, $paymentFields) {
281 foreach ($paymentFields as $paymentField => $fieldSpecs) {
282 if (!empty($fieldSpecs['rules'])) {
283 foreach ($fieldSpecs['rules'] as $rule) {
284 $form->addRule($paymentField,
285 $rule['rule_message'],
286 $rule['rule_name'],
287 $rule['rule_parameters']
288 );
289 }
290 }
291 }
292 }
293
294 /**
295 * billing mode button is basically synonymous with paypal express - this is probably a good example of 'odds & sods' code we
296 * 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
297 *
298 * @param $form
299 */
300 protected static function addPaypalExpressCode(&$form) {
301 if (empty($form->isBackOffice)) {
302 if (CRM_Utils_Array::value('billing_mode', $form->_paymentProcessor) == 3
303 ) {
304 $form->_expressButtonName = $form->getButtonName('upload', 'express');
305 $form->assign('expressButtonName', $form->_expressButtonName);
306 $form->add('image',
307 $form->_expressButtonName,
308 $form->_paymentProcessor['url_button'],
309 array('class' => 'crm-form-submit')
310 );
311 }
312 }
313 }
314
315 /**
316 * The credit card pseudo constant results only the CC label, not the key ID
317 * So we normalize the name to use it as a CSS class.
318 */
319 static function getCreditCardCSSNames() {
320 $creditCardTypes = array();
321 foreach (CRM_Contribute_PseudoConstant::creditCard() as $key => $name) {
322 // Replace anything not css-friendly by an underscore
323 // Non-latin names will not like this, but so many things are wrong with
324 // the credit-card type configurations already.
325 $key = str_replace(' ', '', $key);
326 $key = preg_replace('/[^a-zA-Z0-9]/', '_', $key);
327 $key = strtolower($key);
328 $creditCardTypes[$key] = $name;
329 }
330 return $creditCardTypes;
331 }
332
333 /**
334 * Make sure that credit card number and cvv are valid
335 * Called within the scope of a QF formRule function
336 */
337 static function validateCreditCard($values, &$errors) {
338 if (!empty($values['credit_card_type'])) {
339 if (!empty($values['credit_card_number']) &&
340 !CRM_Utils_Rule::creditCardNumber($values['credit_card_number'], $values['credit_card_type'])
341 ) {
342 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
343 }
344 if (!empty($values['cvv2']) &&
345 !CRM_Utils_Rule::cvv($values['cvv2'], $values['credit_card_type'])
346 ) {
347 $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
348 }
349 }
350 elseif (!empty($values['credit_card_number'])) {
351 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
352 }
353 }
354
355 /**
356 * function to map address fields
357 *
358 * @param $id
359 * @param $src
360 * @param $dst
361 * @param bool $reverse
362 *
363 * @return void
364 * @static
365 */
366 static function mapParams($id, &$src, &$dst, $reverse = FALSE) {
367 static $map = NULL;
368 if (!$map) {
369 $map = array(
370 'first_name' => 'billing_first_name',
371 'middle_name' => 'billing_middle_name',
372 'last_name' => 'billing_last_name',
373 'email' => "email-$id",
374 'street_address' => "billing_street_address-$id",
375 'supplemental_address_1' => "billing_supplemental_address_1-$id",
376 'city' => "billing_city-$id",
377 'state_province' => "billing_state_province-$id",
378 'postal_code' => "billing_postal_code-$id",
379 'country' => "billing_country-$id",
380 );
381 }
382
383 foreach ($map as $n => $v) {
384 if (!$reverse) {
385 if (isset($src[$n])) {
386 $dst[$v] = $src[$n];
387 }
388 }
389 else {
390 if (isset($src[$v])) {
391 $dst[$n] = $src[$v];
392 }
393 }
394 }
395 }
396
397 /**
398 * function to get the credit card expiration month
399 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
400 * See CRM-9017
401 *
402 * @param $src
403 *
404 * @return int
405 * @static
406 */
407 static function getCreditCardExpirationMonth($src) {
408 if ($month = CRM_Utils_Array::value('M', $src['credit_card_exp_date'])) {
409 return $month;
410 }
411
412 return CRM_Utils_Array::value('m', $src['credit_card_exp_date']);
413 }
414
415 /**
416 * function to get the credit card expiration year
417 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
418 * This function exists only to make it consistent with getCreditCardExpirationMonth
419 *
420 * @param $src
421 *
422 * @return int
423 * @static
424 */
425 static function getCreditCardExpirationYear($src) {
426 return CRM_Utils_Array::value('Y', $src['credit_card_exp_date']);
427 }
428 }