We have been following a strange pattern where we take the label of a credit card field and munge it into
a css compatible string and then expect that to be a match with an array based on lcasing the
name field for the credit card. This means that if label !== name the house of cards falls.
This is particularly a problem for Amex which needs to be set correctly for the
4 digit cvv not to trigger a validation fail. Notably Amex is probably also the most likely for
people to want to rename it since Amex is an abbreviation.
This PR backs out of all of that and assigns an array of data to js - including the pattern itself
and then updates the js script to refer to the right variable name for each purpose and to stop munging
$payment->validatePaymentInstrument($values, $errors);
}
- /**
- * The credit card pseudo constant results only the CC label, not the key ID
- * So we normalize the name to use it as a CSS class.
- */
- public static function getCreditCardCSSNames($creditCards = array()) {
- $creditCardTypes = array();
- if (empty($creditCards)) {
- $creditCards = CRM_Contribute_PseudoConstant::creditCard();
- }
- foreach ($creditCards as $key => $name) {
- // Replace anything not css-friendly by an underscore
- // Non-latin names will not like this, but so many things are wrong with
- // the credit-card type configurations already.
- $key = str_replace(' ', '', $key);
- $key = preg_replace('/[^a-zA-Z0-9]/', '_', $key);
- $key = strtolower($key);
- $creditCardTypes[$key] = $name;
- }
- return $creditCardTypes;
- }
-
/**
* Set default values for the form.
*
*/
public static function addCreditCardJs($paymentProcessorID = NULL, $region = 'billing-block') {
$creditCards = CRM_Financial_BAO_PaymentProcessor::getCreditCards($paymentProcessorID);
- $creditCardTypes = CRM_Core_Payment_Form::getCreditCardCSSNames($creditCards);
+ if (empty($creditCards)) {
+ $creditCards = CRM_Contribute_PseudoConstant::creditCard();
+ }
+ $creditCardTypes = [];
+ foreach ($creditCards as $name => $label) {
+ $creditCardTypes[$name] = [
+ 'label' => $label,
+ 'name' => $name,
+ 'css_key' => self::getCssLabelFriendlyName($name),
+ 'pattern' => self::getCardPattern($name),
+ ];
+ }
+
CRM_Core_Resources::singleton()
// CRM-20516: add BillingBlock script on billing-block region
// to support this feature in payment form snippet too.
->addScript('CRM.config.creditCardTypes = ' . json_encode($creditCardTypes) . ';', '-9999', $region);
}
+ /**
+ * Get css friendly labels for credit cards.
+ *
+ * We add the icons based on these css names which are lower cased
+ * and only AlphaNumeric (+ _).
+ *
+ * @param $key
+ *
+ * @return string
+ */
+ protected static function getCssLabelFriendlyName($key) {
+ $key = str_replace(' ', '', $key);
+ $key = preg_replace('/[^a-zA-Z0-9]/', '_', $key);
+ $key = strtolower($key);
+
+ return $key;
+ }
+
+ /**
+ * Get the pattern that can be used to determine the card type.
+ *
+ * We do a strotolower comparison as we don't know what case people might have if they
+ * are using a non-std one like dinersclub.
+ *
+ * @param $key
+ *
+ * Based on http://davidwalsh.name/validate-credit-cards
+ * See also https://en.wikipedia.org/wiki/Credit_card_numbers
+ *
+ * @return string
+ */
+ protected static function getCardPattern($key) {
+ $cardMappings = [
+ 'mastercard' => '(5[1-5][0-9]{2}|2[3-6][0-9]{2}|22[3-9][0-9]|222[1-9]|27[0-1][0-9]|2720)[0-9]{12}',
+ 'visa' => '4(?:[0-9]{12}|[0-9]{15})',
+ 'amex' => '3[47][0-9]{13}',
+ 'dinersclub' => '3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
+ 'carteblanche' => '3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
+ 'discover' => '6011[0-9]{12}',
+ 'jcb' => '(?:3[0-9]{15}|(2131|1800)[0-9]{11})',
+ 'unionpay' => '62(?:[0-9]{14}|[0-9]{17})',
+ ];
+ return isset($cardMappings[strtolower($key)]) ? $cardMappings[strtolower($key)] : '';
+ }
+
}
function civicrm_billingblock_creditcard_helper() {
$(function() {
$.each(CRM.config.creditCardTypes, function(key, val) {
- var html = '<a href="#" title="' + val + '" class="crm-credit_card_type-icon-' + key + '"><span>' + val + '</span></a>';
+ var html = '<a href="#" data-card_type=" + key + " title="' + val + '" class="crm-credit_card_type-icon-' + val.css_key + '"><span>' + val.label + '</span></a>';
$('.crm-credit_card_type-icons').append(html);
- $('.crm-credit_card_type-icon-' + key).click(function() {
- $('#credit_card_type').val(val);
+ $('.crm-credit_card_type-icon-' + val.css_key).click(function() {
+ $('#credit_card_type').val(key);
$('.crm-container .credit_card_type-section a').css('opacity', 0.25);
- $('.crm-container .credit_card_type-section .crm-credit_card_type-icon-' + key).css('opacity', 1);
+ $('.crm-container .credit_card_type-section .crm-credit_card_type-icon-' + val.css_key).css('opacity', 1);
return false;
});
});
// set the card type value as default if any found
var cardtype = $('#credit_card_type').val();
if (cardtype) {
- $.each(CRM.config.creditCardTypes, function(key, value) {
+ $.each(CRM.config.creditCardTypes, function(key, val) {
// highlight the selected card type icon
- if (value == cardtype) {
- $('.crm-container .credit_card_type-section .crm-credit_card_type-icon-' + key).css('opacity', 1);
+ if (key === cardtype) {
+ $('.crm-container .credit_card_type-section .crm-credit_card_type-icon-' + val.css_key).css('opacity', 1);
}
else {
- $('.crm-container .credit_card_type-section .crm-credit_card_type-icon-' + key).css('opacity', 0.25);
+ $('.crm-container .credit_card_type-section .crm-credit_card_type-icon-' + val.css_key).css('opacity', 0.25);
}
});
}
}
function civicrm_billingblock_set_card_type(ccnumber) {
- // Based on http://davidwalsh.name/validate-credit-cards
- // See also https://en.wikipedia.org/wiki/Credit_card_numbers
- var card_types = {
- 'mastercard': '(5[1-5][0-9]{2}|2[3-6][0-9]{2}|22[3-9][0-9]|222[1-9]|27[0-1][0-9]|2720)[0-9]{12}',
- 'visa': '4(?:[0-9]{12}|[0-9]{15})',
- 'amex': '3[47][0-9]{13}',
- 'dinersclub': '3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
- 'carteblanche': '3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
- 'discover': '6011[0-9]{12}',
- 'jcb': '(?:3[0-9]{15}|(2131|1800)[0-9]{11})',
- 'unionpay': '62(?:[0-9]{14}|[0-9]{17})'
- };
-
var card_values = CRM.config.creditCardTypes;
-
- $.each(card_types, function(key, pattern) {
- if (ccnumber.match('^' + pattern + '$')) {
- var value = card_values[key];
- //$.each(CRM.config.creditCardTypes, function(key2, val) {
- // if (value == val) {
- $('.crm-container .credit_card_type-section .crm-credit_card_type-icon-' + key).css('opacity', 1);
- $('select#credit_card_type').val(value);
- return false;
- // }
- // else {
- // $
- // });
+ $.each(card_values, function(key, spec) {
+ if (ccnumber.match('^' + spec.pattern + '$')) {
+ $('.crm-container .credit_card_type-section .crm-credit_card_type-icon-' + spec.css_key).css('opacity', 1);
+ $('select#credit_card_type').val(key);
+ return false;
}
});
}
$this->assertEquals($cards, $expectedCards, 'Verify correct credit card types are returned');
}
- public function testCreditCardCSSName() {
- $params = array(
- 'name' => 'API_Test_PP_Type',
- 'title' => 'API Test Payment Processor Type',
- 'class_name' => 'CRM_Core_Payment_APITest',
- 'billing_mode' => 'form',
- 'payment_processor_type_id' => 1,
- 'is_recur' => 0,
- 'domain_id' => 1,
- 'accepted_credit_cards' => json_encode(array(
- 'Visa' => 'Visa',
- 'Mastercard' => 'Mastercard',
- 'Amex' => 'Amex',
- )),
- );
- $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::create($params);
- $cards = CRM_Financial_BAO_PaymentProcessor::getCreditCards($paymentProcessor->id);
- $CSSCards = CRM_Core_Payment_Form::getCreditCardCSSNames($cards);
- $expectedCSSCards = array(
- 'visa' => 'Visa',
- 'mastercard' => 'Mastercard',
- 'amex' => 'Amex',
- );
- $this->assertEquals($CSSCards, $expectedCSSCards, 'Verify correct credit card types are returned');
- $CSSCards2 = CRM_Core_Payment_Form::getCreditCardCSSNames(array());
- $allCards = array(
- 'visa' => 'Visa',
- 'mastercard' => 'MasterCard',
- 'amex' => 'Amex',
- 'discover' => 'Discover',
- );
- $this->assertEquals($CSSCards2, $allCards, 'Verify correct credit card types are returned');
- }
-
}