Skip to content

Commit d324fa2

Browse files
borisson_mglaman
borisson_
authored andcommitted
Issue #2790551 by steveoliver, bojanz, borisson_: Implement a JS library for the credit card form
1 parent 5d03652 commit d324fa2

File tree

3 files changed

+267
-0
lines changed

3 files changed

+267
-0
lines changed

modules/payment/commerce_payment.libraries.yml

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
credit_card_validation:
2+
version: VERSION
3+
js:
4+
js/credit-card-validation.js: {}
5+
dependencies:
6+
- core/jquery
7+
18
payment_method_form:
29
version: VERSION
310
css:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/**
2+
* @file
3+
* Attaches credit card validation logic.
4+
*/
5+
6+
(function ($) {
7+
8+
'use strict';
9+
10+
var types = {};
11+
var VISA = 'visa';
12+
var MASTERCARD = 'master-card';
13+
var AMEX = 'amex';
14+
var DINERSCLUB = 'dinersclub';
15+
var DISCOVER = 'discover';
16+
var MAESTRO = 'maestro';
17+
18+
types[VISA] = {
19+
niceType: 'Visa',
20+
type: VISA,
21+
pattern: ['4'],
22+
gaps: [4, 8, 12],
23+
lengths: [16]
24+
};
25+
26+
types[MASTERCARD] = {
27+
niceType: 'MasterCard',
28+
type: MASTERCARD,
29+
pattern: ['51-55', '222100-272099'],
30+
gaps: [4, 8, 12],
31+
lengths: [16]
32+
};
33+
34+
types[AMEX] = {
35+
niceType: 'American Express',
36+
type: AMEX,
37+
pattern: ['34', '37'],
38+
lengths: [15]
39+
};
40+
41+
types[DINERSCLUB] = {
42+
niceType: 'Diners Club',
43+
type: DINERSCLUB,
44+
pattern: ['300-305', '309', '36', '38', '39'],
45+
lengths: [14]
46+
};
47+
48+
types[DISCOVER] = {
49+
niceType: 'Discover Card',
50+
type: DISCOVER,
51+
pattern: ['6011', '622126-622925', '644-649', '65'],
52+
lengths: [16, 19]
53+
};
54+
55+
types[MAESTRO] = {
56+
niceType: 'Maestro',
57+
type: MAESTRO,
58+
pattern: [
59+
'5018',
60+
'5020',
61+
'5038',
62+
'5612',
63+
'5893',
64+
'6304',
65+
'6759',
66+
'6761',
67+
'6762',
68+
'6763',
69+
'0604',
70+
'6390'
71+
],
72+
lenghts: [12, 13, 14, 15, 16, 17, 18, 19]
73+
};
74+
75+
76+
/**
77+
* Detect the type of credit card.
78+
*
79+
* @param {string} number
80+
*
81+
* @return {object|boolean}
82+
*/
83+
var detectType = function (number) {
84+
// Loop over all available types.
85+
for (var x in types) {
86+
var type = types[x];
87+
88+
// Loop over all patterns in the type.
89+
for (var i in type.pattern) {
90+
var pattern = type.pattern[i];
91+
92+
// If the pattern has a dash, we should create a range of patterns.
93+
if (pattern.indexOf('-') >= 0) {
94+
var exploded_pattern;
95+
var ranges = [];
96+
var range;
97+
exploded_pattern = pattern.split('-');
98+
99+
while (exploded_pattern[0] <= exploded_pattern[1]) {
100+
ranges.push(exploded_pattern[0]);
101+
exploded_pattern[0]++;
102+
}
103+
104+
for (range in ranges) {
105+
if (validatePrefix(number, range)) {
106+
return type;
107+
}
108+
}
109+
}
110+
// No dashes, so just validate this pattern.
111+
else if (validatePrefix(number, pattern)) {
112+
return type;
113+
}
114+
}
115+
}
116+
117+
return false;
118+
};
119+
120+
/**
121+
* Validate the prefix is according to the expected prefix.
122+
*
123+
* @param {string} number
124+
* @param {string} prefix
125+
*
126+
* @return {boolean}
127+
*/
128+
var validatePrefix = function (number, prefix) {
129+
return number.substring(0, prefix.length) == prefix + '';
130+
};
131+
132+
/**
133+
* Validate credit card.
134+
*
135+
* @param {string} number
136+
* @param {object} type
137+
*
138+
* @return {boolean}
139+
*/
140+
var validateCreditCard = function (number, type) {
141+
// Make sure that the type is really the expected type.
142+
if (detectType(number) != type) {
143+
return false;
144+
}
145+
146+
// Test that the length of the card is actually one of the expected lengths
147+
// defined in the type.
148+
for (var x in type.lengths) {
149+
var expected_lenght = type.lengths[x];
150+
if (number.length === expected_lenght) {
151+
return true;
152+
}
153+
}
154+
155+
return false;
156+
};
157+
158+
/**
159+
* Trigger all other validations.
160+
*
161+
* @param {object} element
162+
*/
163+
var cardInputBlur = function (element) {
164+
var value = element.val();
165+
// Strip spaces from the value for all validations.
166+
value = value.replace(/ /gi, '');
167+
168+
// If the value is not filled in, don't do any validation.
169+
var empty_value = value.length === 0;
170+
if (empty_value) {
171+
element.addClass('invalid-cc');
172+
return;
173+
}
174+
175+
// Get the type of the card.
176+
var type = detectType(value);
177+
178+
// If no type is found, don't bother doing anything else.
179+
if (!type) {
180+
element.addClass('invalid-cc');
181+
return;
182+
}
183+
element.addClass('credit_card');
184+
element.addClass('credit_card--' + type.type);
185+
186+
var ValidationDiv = element.parent().parent().find('#cc-validation');
187+
ValidationDiv.html('');
188+
189+
var ccv_field = $("input[name='payment_information[add][payment_details][security_code]']");
190+
if (ccv_field.size() > 0) {
191+
var ccv_value = ccv_field.val();
192+
if (ccv_value.length == 0) {
193+
ValidationDiv.append('CCV is not filled in');
194+
return;
195+
}
196+
}
197+
198+
ValidationDiv.append('CC is of type: ' + type.niceType);
199+
200+
// Check if the card is actually valid as well.
201+
var is_valid = validateCreditCard(value, type);
202+
if (is_valid) {
203+
ValidationDiv.append(' CC is valid');
204+
element.removeClass('invalid-cc');
205+
element.addClass('valid-cc');
206+
}
207+
else {
208+
ValidationDiv.append(' CC is not valid');
209+
element.removeClass('valid-cc');
210+
element.addClass('invalid-cc');
211+
}
212+
};
213+
214+
/**
215+
* Element onkey upvalidation.
216+
*
217+
* @param {object} element
218+
*/
219+
var cardInputKeyup = function (element) {
220+
var value = element.val();
221+
// Strip spaces from the value for all validations.
222+
value = value.replace(/ /gi, '');
223+
224+
// If the value is not filled in, don't do any validation.
225+
var empty_value = value.length === 0;
226+
if (empty_value) {
227+
element.addClass('invalid-cc');
228+
return;
229+
}
230+
231+
// Get the type of the card.
232+
var type = detectType(value);
233+
234+
// If no type is found, don't bother doing anything else.
235+
if (!type) {
236+
element.addClass('invalid-cc');
237+
return;
238+
}
239+
element.removeClass('invalid-cc');
240+
element.addClass('credit_card');
241+
element.addClass('credit_card--' + type.type);
242+
};
243+
244+
Drupal.behaviors.creditCardValidation = {
245+
attach: function (context, settings) {
246+
$('#edit-payment-information-add-payment-details-number', context).each(function () {
247+
var element = $(this);
248+
$(element).on('blur', function () {
249+
cardInputBlur(element);
250+
});
251+
$(element).on('keyup', function() {
252+
cardInputKeyup(element);
253+
});
254+
});
255+
}
256+
};
257+
258+
})(jQuery);

modules/payment/src/PluginForm/PaymentMethodAddForm.php

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
4747
];
4848
if ($payment_method->bundle() == 'credit_card') {
4949
$form['payment_details'] = $this->buildCreditCardForm($form['payment_details'], $form_state);
50+
$form['#attached']['library'][] = 'commerce_payment/credit_card_validation';
5051
}
5152
elseif ($payment_method->bundle() == 'paypal') {
5253
$form['payment_details'] = $this->buildPayPalForm($form['payment_details'], $form_state);
@@ -168,6 +169,7 @@ protected function buildCreditCardForm(array $element, FormStateInterface $form_
168169
'#required' => TRUE,
169170
'#maxlength' => 19,
170171
'#size' => 20,
172+
'#suffix' => '<div id="cc-validation"></div>',
171173
];
172174
$element['expiration'] = [
173175
'#type' => 'container',

0 commit comments

Comments
 (0)