payment_info_rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,514 @@
1
+ // Payment Info Component
2
+ // Author: Zachary Forrest, modified by Nora Alvarado
3
+ // Requires: jQuery, Modernizer
4
+
5
+ (function ($) {
6
+
7
+ "use strict";
8
+
9
+ $.fn.paymentInfo = function (method) {
10
+
11
+ // Global variables.
12
+ var methods,
13
+ helpers,
14
+ events,
15
+ ccDefinitions,
16
+ opts,
17
+ pluginName = "paymentInfo";
18
+
19
+ // Events map for the matchNumbers() function.
20
+ events = $.map(['change', 'blur', 'keyup', 'keypress', 'keydown'], function (v) {
21
+ return v + '.' + pluginName;
22
+ }).join(' ');
23
+
24
+ // Credit card regex patterns.
25
+ ccDefinitions = {
26
+ 'visa': /^4/,
27
+ 'mc': /^5[1-5]/,
28
+ 'amex': /^3(4|7)/,
29
+ 'disc': /^6011/
30
+ };
31
+
32
+ helpers = {
33
+
34
+ // Determine if the number being give corresponds to a credit card
35
+ // regex pattern. If it does, break the loop and return the credit card type.
36
+
37
+ getCreditCardType: function (number) {
38
+
39
+ var ccType;
40
+
41
+ $.each(ccDefinitions, function (i, v) {
42
+
43
+ if (v.test(number)) {
44
+ ccType = i;
45
+ return false;
46
+ }
47
+
48
+ });
49
+
50
+ return ccType;
51
+
52
+ },
53
+
54
+ // Our matchNumbers function. Probably does more than it should.
55
+ // Will revisit.
56
+
57
+ matchNumbers: function (el) {
58
+
59
+ // Check to see if the the credit card number stored in data(). If not,
60
+ // grab the current value of the credit card field and then get the type
61
+ // of the card.
62
+
63
+ var cardNumber = el.data("ccNumber") || el.val(),
64
+ ccType = helpers.getCreditCardType(cardNumber);
65
+
66
+ // If the credit card field has a value and we know what type of card it is,
67
+ // then add the appropriate class to the card image <span>. This will allow us
68
+ // to see the correct card. If we don't know the type, then revert back to the
69
+ // default image. If the credit card field doesn't have a value, then we revert
70
+ // back to the default image and erase any credit card number we might have stored.
71
+
72
+ if (el.val() !== "") {
73
+ if (ccType !== "") {
74
+ $("." + opts.cardImageClass)
75
+ .addClass(ccType);
76
+ } else {
77
+ $("." + opts.cardImageClass)
78
+ .removeClass()
79
+ .addClass(opts.cardImageClass);
80
+ }
81
+ } else {
82
+ $("." + opts.cardImageClass)
83
+ .removeClass()
84
+ .addClass(opts.cardImageClass)
85
+ .data("ccNumber", "");
86
+ }
87
+
88
+ // Check the card type to see if it's an Amex. If it is, update the mask to reflect
89
+ // the number groupings that Amex uses. Also update the CVV. Amex requires four digits
90
+ // instead of three.
91
+
92
+ if (ccType === "amex") {
93
+ el.mask("0000 000000 00000", { onComplete: helpers.creditCardComplete });
94
+ $("." + opts.cardCvvClass).mask("000", { onComplete: helpers.cvvComplete });
95
+ } else {
96
+ el.mask("0000 0000 0000 0000", { onComplete: helpers.creditCardComplete });
97
+ $("." + opts.cardCvvClass).mask("000", { onComplete: helpers.cvvComplete });
98
+ }
99
+
100
+ // If the credit card value ever becomes empty, make sure the corresponding expiration date,
101
+ // CVV, and Zip Code values are also empty.
102
+
103
+ if (el.val() === "") {
104
+ $("." + opts.fieldsetClass)
105
+ .find("input:gt(0)")
106
+ .val("");
107
+ }
108
+
109
+ if (ccType !== undefined) {
110
+ $(el)
111
+ .parents("." + opts.fieldsetClass)
112
+ .removeClass("invalid shake");
113
+
114
+ $('.' + opts.cardInstructionClass).removeClass("invalid");
115
+ }
116
+
117
+ },
118
+
119
+ creditCardComplete: function () {
120
+
121
+ // We need to get the credit card field and the unmasked value of the field.
122
+ var element = $("." + opts.cardNumberClass),
123
+ uvalue = element.cleanVal(),
124
+ ccType = helpers.getCreditCardType(uvalue);
125
+
126
+ // Let's make sure the card is valid
127
+ if (ccType === undefined) {
128
+ $(element)
129
+ .parents("." + opts.fieldsetClass)
130
+ .addClass("invalid shake");
131
+
132
+ // Update instruction class to reflect the error
133
+ $('.' + opts.cardInstructionClass).addClass("invalid");
134
+
135
+ // Update instruction message
136
+ helpers.updateInstruction(opts.messageCardNumberError);
137
+
138
+ return;
139
+ }
140
+
141
+ // Let's make sure the number entered checks against the Luhn Algorithm
142
+ if (helpers.ccLuhnCheck(uvalue) === false) {
143
+ $(element)
144
+ .parents("." + opts.fieldsetClass)
145
+ .addClass("invalid shake");
146
+
147
+ // Update instruction class to reflect the error
148
+ $('.' + opts.cardInstructionClass).addClass("invalid");
149
+
150
+ // Update instruction message
151
+ helpers.updateInstruction(opts.messageLuhnCheckError);
152
+
153
+ return;
154
+ }
155
+
156
+ // Store the credit card value in data(). Replace the value with the last
157
+ // four numbers of the card.
158
+
159
+ element.bind("saveValues", function () {
160
+
161
+ if ((ccType === "amex" && uvalue.length === 15) || (ccType !== "amex" && uvalue.length === 16)) {
162
+
163
+ element
164
+ .data("ccNumber", uvalue)
165
+ .val(uvalue.substr(uvalue.length - 4, uvalue.length));
166
+ }
167
+
168
+ });
169
+
170
+ // Once this function is fired, we need to add a "transitioning" class to credit
171
+ // card element so that we can take advantage of our CSS animations.
172
+ element.addClass("transitioning-out");
173
+
174
+ // We have to set a timeout so that we give our animations time to finish. We have to
175
+ // blur the element as well to fix a bug where our credit card field was losing its
176
+ // value prematurely.
177
+
178
+ setTimeout(function () {
179
+
180
+ element.removeClass("transitioning-out");
181
+
182
+ if (!Modernizr.touch) {
183
+
184
+ element.bind("blur", function () {
185
+ element.trigger("saveValues");
186
+ }).blur();
187
+
188
+ } else {
189
+ element
190
+ .trigger("saveValues")
191
+ .blur(function () {
192
+ element.trigger("saveValues");
193
+ });
194
+ }
195
+
196
+ element.addClass("full");
197
+
198
+ }, opts.animationWait);
199
+
200
+ // Setting another timeout so that we can wait for CSS animations to finish.
201
+ setTimeout(function () {
202
+
203
+ // Expose the rest of the payment info fields now that the credit card
204
+ // has been filled out.
205
+ $("." + opts.fieldsetClass)
206
+ .find("input:gt(0)")
207
+ .removeClass("hide");
208
+
209
+ }, opts.animationWait);
210
+
211
+ // After the credit card field is initially filled out, bind a click event
212
+ // that will allow us to edit the number again if we want to. We also bind
213
+ // a focus event (for mobile) and a keydown event in case of shift + tab
214
+
215
+ $(element)
216
+ .unbind("blur focus click keydown keypress")
217
+ .bind("focus click keydown", function (e) {
218
+ if (e.type === "focus" || e.type === "click" || (e.shiftKey && e.keyCode === 9)) {
219
+ helpers.beginCreditCard($(element));
220
+
221
+ // Update instruction message
222
+ helpers.updateInstruction(opts.messageEnterCardNumber);
223
+ }
224
+ });
225
+
226
+ if (window.navigator.standalone || !Modernizr.touch) {
227
+ // Focus on the credit card expiration input.
228
+ $("." + opts.cardExpirationClass).focus().val($.trim($("." + opts.cardExpirationClass).val()));
229
+
230
+ // Update instruction message
231
+ helpers.updateInstruction(opts.messageExpiration);
232
+ }
233
+
234
+ },
235
+
236
+ // This function is fired when the credit card expiration field's mask has been
237
+ // satisfied. We trigger an animation for CVV card, wait for it to finish, and then
238
+ // focus on the CVV field.
239
+
240
+ expirationComplete: function () {
241
+
242
+ $("." + opts.cardImageClass).addClass("cvv2");
243
+
244
+ $("." + opts.cardExpirationClass)
245
+ .addClass("full")
246
+ .unbind("keydown blur")
247
+ .bind("keydown", function (e) {
248
+ if (e.keyCode === 8 && $(this).val() === "") {
249
+ $(this).removeClass("full");
250
+ if (window.navigator.standalone || !Modernizr.touch) {
251
+ $("." + opts.cardNumberClass).focus();
252
+
253
+ // Update instruction message
254
+ helpers.updateInstruction(opts.messageEnterCardNumber);
255
+ }
256
+ }
257
+ });
258
+
259
+ if (window.navigator.standalone || !Modernizr.touch) {
260
+ setTimeout(function () {
261
+ $("." + opts.cardCvvClass).focus();
262
+
263
+ // Update instruction message
264
+ helpers.updateInstruction(opts.messageCVV);
265
+ }, 220);
266
+ }
267
+
268
+ // Update instruction message
269
+ helpers.updateInstruction(opts.messageCVV);
270
+
271
+ },
272
+
273
+ // This function is fired when the mask for CVV field is satisfied. We animate
274
+ // the credit card back from the CVV card image to the appropriate card type.
275
+ // We wait for the animation to finish and then focus on the zip field.
276
+
277
+ cvvComplete: function () {
278
+
279
+ $("." + opts.cardImageClass).removeClass("cvv2");
280
+
281
+ $("." + opts.cardCvvClass)
282
+ .addClass("full")
283
+ .unbind("keydown blur")
284
+ .bind("keydown", function (e) {
285
+ if (e.keyCode === 8 && $(this).val() === "") {
286
+ $(this).removeClass("full");
287
+ if (window.navigator.standalone || !Modernizr.touch) {
288
+ $("." + opts.cardExpirationClass).focus();
289
+
290
+ // Update instruction message
291
+ helpers.updateInstruction(opts.messageExpiration);
292
+ }
293
+ }
294
+ });
295
+
296
+ if (window.navigator.standalone || !Modernizr.touch) {
297
+ setTimeout(function () {
298
+ $("." + opts.cardZipClass).focus();
299
+
300
+ // Update instruction message
301
+ helpers.updateInstruction(opts.messageZip);
302
+ }, 220);
303
+ }
304
+ // Update instruction message
305
+ helpers.updateInstruction(opts.messageZip);
306
+
307
+ },
308
+
309
+ zipComplete: function () {
310
+
311
+ $("." + opts.cardZipClass)
312
+ .addClass("full")
313
+ .unbind("keydown blur")
314
+ .bind("keydown", function (e) {
315
+ if (e.keyCode === 8 && $(this).val() === "") {
316
+ $(this).removeClass("full");
317
+ if (window.navigator.standalone || !Modernizr.touch) {
318
+ $("." + opts.cardCvvClass).focus();
319
+
320
+ // Update instruction message
321
+ helpers.updateInstruction(opts.messageCVV);
322
+ }
323
+ }
324
+ })
325
+ .mask("00000-000", { onComplete: helpers.zipComplete });
326
+
327
+ $("." + opts.fieldsetClass)
328
+ .addClass('valid');
329
+
330
+ $("." + opts.cardInstructionClass)
331
+ .addClass('valid');
332
+
333
+ // Update instruction message with success message
334
+ helpers.updateInstruction(opts.messageSuccess);
335
+
336
+ },
337
+
338
+ // This function allows us to edit the credit card number once it's been entered.
339
+ beginCreditCard: function (element) {
340
+
341
+ // Set the value of the field to the original card number and apply our
342
+ // transition state.
343
+
344
+ $(element)
345
+ .val($(element).data("ccNumber"))
346
+ .addClass("transitioning-in");
347
+
348
+ // Wait for the animation to complete and then remove our classes.
349
+ setTimeout(function () {
350
+ element.removeClass("transitioning-in full");
351
+ }, 150);
352
+
353
+ // Attach a keypress handler that listens for the "enter" key. If the user
354
+ // clicks enter, then fire off our creditCardComplete() event.
355
+
356
+ $(element)
357
+ .unbind("keypress blur")
358
+ .bind("keypress blur", function (e) {
359
+
360
+ // Is it the enter key?
361
+ if (e.keyCode === 13 || e.type === "blur") {
362
+
363
+ var uvalue = $(element).cleanVal(),
364
+ ccType = helpers.getCreditCardType(uvalue);
365
+
366
+ // Make sure the number length is valid
367
+ if ((ccType === "amex" && uvalue.length === 15) || (ccType !== "amex" && uvalue.length === 16)) {
368
+ helpers.creditCardComplete();
369
+ }
370
+
371
+ }
372
+
373
+ })
374
+ .unbind("focus click keydown");
375
+
376
+ // Hide the extraneous inputs until the credit card is filled out again.
377
+ $("." + opts.fieldsetClass)
378
+ .find("input:gt(0)")
379
+ .addClass("hide");
380
+
381
+ },
382
+
383
+ updateInstruction: function (message) {
384
+ $('.card-instruction').html(message);
385
+ },
386
+
387
+ // This function returns true or false if
388
+ /**
389
+ * Luhn algorithm in JavaScript: validate credit card number supplied as string of numbers
390
+ * @author ShirtlessKirk. Copyright (c) 2012.
391
+ * @license WTFPL (http://www.wtfpl.net/txt/copying)
392
+ */
393
+ ccLuhnCheck: (function (arr) {
394
+ return function (ccNum) {
395
+ var
396
+ len = ccNum.length,
397
+ bit = 1,
398
+ sum = 0,
399
+ val;
400
+
401
+ while (len) {
402
+ val = parseInt(ccNum.charAt(--len), 10);
403
+ sum += (bit ^= 1) ? arr[val] : val;
404
+ }
405
+
406
+ return sum && sum % 10 === 0;
407
+ };
408
+ }([0, 2, 4, 6, 8, 1, 3, 5, 7, 9]))
409
+
410
+ };
411
+
412
+ methods = {
413
+
414
+ init: function (options) {
415
+
416
+ // Get a copy of our configuration options
417
+ opts = $.extend({}, $.fn.paymentInfo.defaults, options);
418
+
419
+ var zipcodeOptions = {
420
+ onComplete: helpers.zipComplete,
421
+ onKeyPress: function(cep, e, field, options) {
422
+ var masks = ['00000-000', '0-00-00-00'];
423
+ var mask = (cep.length>7) ? masks[1] : masks[0];
424
+ $(this).find("." + opts.cardZipClass)
425
+ .mask(mask, zipcodeOptions)
426
+ }
427
+ }
428
+
429
+ // Loop through our fieldset, find our inputs and initialize them.
430
+ return this.each(function () {
431
+
432
+ $(this)
433
+ .find("label")
434
+ .addClass("hide")
435
+ .end()
436
+ .find("." + opts.cardNumberClass)
437
+ .mask("0000 0000 0000 0000", { onComplete: helpers.creditCardComplete })
438
+ .before("<span class='" + opts.cardImageClass + "'></span>")
439
+ .end()
440
+ .find("." + opts.cardExpirationClass)
441
+ .mask("00/00", { onComplete: helpers.expirationComplete })
442
+ .addClass("hide")
443
+ .end()
444
+ .find("." + opts.cardCvvClass)
445
+ .mask("000", { onComplete: helpers.cvvComplete } )
446
+ .addClass("hide")
447
+ .focus(function () {
448
+ $("." + opts.cardImageClass).addClass("cvv2");
449
+ })
450
+ .blur(function () {
451
+ $("." + opts.cardImageClass).removeClass("cvv2");
452
+ })
453
+ .end()
454
+ .find("." + opts.cardZipClass)
455
+ .mask('00000-000', zipcodeOptions)
456
+ .addClass("hide")
457
+ .end();
458
+
459
+ if(opts.cardInstruction) {
460
+ $(this).
461
+ after("<span class='" + opts.cardInstructionClass + "'>"+ opts.messageEnterCardNumber + "</span>");
462
+ }
463
+
464
+ helpers.matchNumbers($(this).find("." + opts.cardNumberClass));
465
+
466
+ }).unbind('.' + pluginName).bind(events, function () {
467
+ helpers.matchNumbers($(this).find("." + opts.cardNumberClass));
468
+ });
469
+ },
470
+
471
+ // Need to add this later.
472
+ destroy: function () {
473
+ return this.unbind('.' + pluginName);
474
+ }
475
+
476
+ };
477
+
478
+ // Plugin methods API
479
+ if (methods[method]) {
480
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
481
+ }
482
+ if (typeof method === "object" || !method) {
483
+ return methods.init.apply(this, arguments);
484
+ }
485
+ return $.error("Method does not exist in plugin");
486
+
487
+ };
488
+
489
+ // Plugin config options.
490
+ $.fn.paymentInfo.defaults = {
491
+ fieldsetClass: "credit-card-group",
492
+ cardImageClass: "card-image",
493
+ cardCvvClass: "card-cvv",
494
+ cardExpirationClass: "card-expiration",
495
+ cardZipClass: "card-zip",
496
+ cardNumberClass: "card-number",
497
+ cardInstruction : true,
498
+ cardInstructionClass: "card-instruction",
499
+ animationWait: 600,
500
+ focusDelay: 200,
501
+ messageEnterCardNumber : "Please enter your credit card number",
502
+ messageCardNumberError : "Please enter a valid credit card number",
503
+ messageLuhnCheckError : "Please double check your credit card number",
504
+ messageExpiration : "Please enter your card's expiration month and year",
505
+ messageExpirationError : "Please enter a valid month and year",
506
+ messageCVV : "Please enter the three-digit CVV number found on the back of your card",
507
+ messageCVVAmEx : "Please enter your four-digit CVV number on the front of your card",
508
+ messageZip : "Please enter your billing zip code",
509
+ messageSuccess : "Hooray! You've successfully filled out your credit card information."
510
+ };
511
+
512
+ }(jQuery));
513
+
514
+ $(".credit-card-group").paymentInfo();
@@ -0,0 +1,3 @@
1
+ $(function() {
2
+ $('fieldset').paymentInfo();
3
+ });
@@ -0,0 +1 @@
1
+ //= require_tree .