3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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-2015
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
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.
55 static public function setPaymentFieldsByProcessor(&$form, $processor, $forceBillingFieldsForPayLater = FALSE, $isBackOffice = FALSE) {
56 $form->billingFieldSets
= array();
57 if ($processor != NULL) {
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);
66 $form->billingFieldSets
[$paymentTypeName]['fields'] = $form->_paymentFields
= array_intersect_key(self
::getPaymentFieldMetadata($processor), array_flip($paymentFields));
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;
73 $form->billingPane
= array($paymentTypeName => $paymentTypeLabel);
74 $form->assign('paymentFields', $paymentFields);
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)
80 if (($processor == NULL && !$forceBillingFieldsForPayLater) || CRM_Utils_Array
::value('billing_mode', $processor) == 4) {
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();
92 * Add general billing fields.
93 * @todo set these like processor fields & let payment processors alter them
95 * @param CRM_Core_Form $form
99 static protected function setBillingDetailsFields(&$form) {
100 $bltID = $form->_bltID
;
102 $form->_paymentFields
['billing_first_name'] = array(
103 'htmlType' => 'text',
104 'name' => 'billing_first_name',
105 'title' => ts('Billing First Name'),
107 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
108 'is_required' => TRUE,
111 $form->_paymentFields
['billing_middle_name'] = array(
112 'htmlType' => 'text',
113 'name' => 'billing_middle_name',
114 'title' => ts('Billing Middle Name'),
116 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
117 'is_required' => FALSE,
120 $form->_paymentFields
['billing_last_name'] = array(
121 'htmlType' => 'text',
122 'name' => 'billing_last_name',
123 'title' => ts('Billing Last Name'),
125 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
126 'is_required' => TRUE,
129 $form->_paymentFields
["billing_street_address-{$bltID}"] = array(
130 'htmlType' => 'text',
131 'name' => "billing_street_address-{$bltID}",
132 'title' => ts('Street Address'),
134 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
135 'is_required' => TRUE,
138 $form->_paymentFields
["billing_city-{$bltID}"] = array(
139 'htmlType' => 'text',
140 'name' => "billing_city-{$bltID}",
141 'title' => ts('City'),
143 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
144 'is_required' => TRUE,
147 $form->_paymentFields
["billing_state_province_id-{$bltID}"] = array(
148 'htmlType' => 'chainSelect',
149 'title' => ts('State/Province'),
150 'name' => "billing_state_province_id-{$bltID}",
152 'is_required' => TRUE,
155 $form->_paymentFields
["billing_postal_code-{$bltID}"] = array(
156 'htmlType' => 'text',
157 'name' => "billing_postal_code-{$bltID}",
158 'title' => ts('Postal Code'),
160 'attributes' => array('size' => 30, 'maxlength' => 60, 'autocomplete' => 'off'),
161 'is_required' => TRUE,
164 $form->_paymentFields
["billing_country_id-{$bltID}"] = array(
165 'htmlType' => 'select',
166 'name' => "billing_country_id-{$bltID}",
167 'title' => ts('Country'),
169 'attributes' => array(
170 '' => ts('- select -'),
172 CRM_Core_PseudoConstant
::country(),
173 'is_required' => TRUE,
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',
181 "billing_street_address-{$bltID}",
182 "billing_city-{$bltID}",
183 "billing_country_id-{$bltID}",
184 "billing_state_province_id-{$bltID}",
185 "billing_postal_code-{$bltID}",
190 * @param CRM_Core_Form $form
191 * @param bool $useRequired
192 * @param array $paymentFields
194 protected static function addCommonFields(&$form, $useRequired, $paymentFields) {
195 foreach ($paymentFields as $name => $field) {
196 if (!empty($field['cc_field'])) {
197 if ($field['htmlType'] == 'chainSelect') {
198 $form->addChainSelect($field['name'], array('required' => $useRequired && $field['is_required']));
201 $form->add($field['htmlType'],
204 $field['attributes'],
205 $useRequired ?
$field['is_required'] : FALSE
213 * @param array $paymentProcessor
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?
219 public static function getPaymentFields($paymentProcessor) {
220 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
221 return $paymentProcessorObject->getPaymentFormFields();
225 * @param array $paymentProcessor
229 public static function getPaymentFieldMetadata($paymentProcessor) {
230 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
231 return $paymentProcessorObject->getPaymentFormFieldsMetadata();
235 * @param array $paymentProcessor
239 public static function getPaymentTypeName($paymentProcessor) {
240 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
241 return $paymentProcessorObject->getPaymentTypeName();
245 * @param array $paymentProcessor
249 public static function getPaymentTypeLabel($paymentProcessor) {
250 $paymentProcessorObject = CRM_Core_Payment
::singleton(($paymentProcessor['is_test'] ?
'test' : 'live'), $paymentProcessor);
251 return ts(($paymentProcessorObject->getPaymentTypeLabel()) . ' Information');
255 * @param CRM_Contribute_Form_AbstractEditPayment|CRM_Contribute_Form_Contribution_Main|CRM_Core_Payment_ProcessorForm|CRM_Contribute_Form_UpdateBilling $form
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.
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)
265 public static function buildPaymentForm(&$form, $processor, $isBillingDataOptional, $isBackOffice) {
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);
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);
279 self
::setPaymentFieldsByProcessor($form, $processor, empty($isBillingDataOptional), $isBackOffice);
280 self
::addCommonFields($form, !$isBillingDataOptional, $form->_paymentFields
);
281 self
::addRules($form, $form->_paymentFields
);
282 self
::addPaypalExpressCode($form);
283 return (!empty($form->_paymentFields
));
287 * @param CRM_Core_Form $form
288 * @param array $paymentFields
289 * Array of properties including 'object' as loaded from CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors.
290 * @param $paymentFields
292 protected static function addRules(&$form, $paymentFields) {
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'],
299 $rule['rule_parameters']
307 * Billing mode button is basically synonymous with paypal express - this is probably a good example of 'odds & sods' code we
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
312 protected static function addPaypalExpressCode(&$form) {
313 if (empty($form->isBackOffice
)) {
314 if (in_array(CRM_Utils_Array
::value('billing_mode', $form->_paymentProcessor
), array(2, 3))) {
315 $form->_expressButtonName
= $form->getButtonName('upload', 'express');
316 $form->assign('expressButtonName', $form->_expressButtonName
);
318 $form->_expressButtonName
,
319 $form->_paymentProcessor
['url_button'],
320 array('class' => 'crm-form-submit')
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).
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) {
334 $paymentProcessor = CRM_Financial_BAO_PaymentProcessor
::getPayment($payment_processor_id, 'live');
335 $payment = CRM_Core_Payment
::singleton('live', $paymentProcessor, $form);
336 $payment->validatePaymentInstrument($values, $errors);
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.
344 public static function getCreditCardCSSNames() {
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;
355 return $creditCardTypes;
359 * Make sure that credit card number and cvv are valid.
360 * Called within the scope of a QF formRule function
362 * @param array $values
363 * @param array $errors
365 public static function validateCreditCard($values, &$errors) {
366 if (!empty($values['credit_card_type']) ||
!empty($values['credit_card_number'])) {
367 if (!empty($values['credit_card_number']) &&
368 !CRM_Utils_Rule
::creditCardNumber($values['credit_card_number'], $values['credit_card_type'])
370 $errors['credit_card_number'] = ts('Please enter a valid Card Number');
372 if (!empty($values['cvv2']) &&
373 !CRM_Utils_Rule
::cvv($values['cvv2'], $values['credit_card_type'])
375 $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
381 * Map address fields.
386 * @param bool $reverse
388 public static function mapParams($id, $src, &$dst, $reverse = FALSE) {
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",
402 'contactID' => 'contact_id',
406 foreach ($map as $n => $v) {
408 if (isset($src[$n])) {
413 if (isset($src[$v])) {
421 * Get the credit card expiration month.
422 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
429 public static function getCreditCardExpirationMonth($src) {
430 if ($month = CRM_Utils_Array
::value('M', $src['credit_card_exp_date'])) {
434 return CRM_Utils_Array
::value('m', $src['credit_card_exp_date']);
438 * Get the credit card expiration year.
439 * The date format for this field should typically be "M Y" (ex: Feb 2011) or "m Y" (02 2011)
440 * This function exists only to make it consistent with getCreditCardExpirationMonth
446 public static function getCreditCardExpirationYear($src) {
447 return CRM_Utils_Array
::value('Y', $src['credit_card_exp_date']);