payment_info_rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 .