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