recurly-js-rails 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in recurly-js-rails.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Chris McGrath
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # RecurlyJs::Rails
2
+
3
+ Early alpha version of a Rails 3.1+ asset gem for recurly-js
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'recurly-js-rails'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install recurly-js-rails
18
+
19
+ ## Usage
20
+
21
+ To include the js and css everywhere add:
22
+
23
+ `//= require recurly` to both your application.js and application.css
24
+
25
+ To just include on your subscription pages, add:
26
+
27
+ `config.assets.precompile += %w{recurly.css recurly.js}`
28
+
29
+ To your application.rb and use `stylesheet_link_tag "recurly"` and `javascript_include_tag "recurly"` where appropriate
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,6 @@
1
+ module RecurlyJs
2
+ module Rails
3
+ require "recurly-js-rails/engine"
4
+ require "recurly-js-rails/version"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RecurlyJs
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RecurlyJs
2
+ module Rails
3
+ VERSION = "0.0.2"
4
+ RECURLY_JS_VERSION="9c37716d43e0a86561e23cf06a89a542e101fabd"
5
+ end
6
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/recurly-js-rails/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Chris McGrath"]
6
+ gem.email = ["chris@octopod.info"]
7
+ gem.description = %q{Rails 3.1+ asset gem for recurly-js}
8
+ gem.summary = %q{Rails 3.1+ asset gem for recurly-js}
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "recurly-js-rails"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = RecurlyJs::Rails::VERSION
17
+ end
@@ -0,0 +1,2243 @@
1
+ // Recurly.js - v2.1.3
2
+ //
3
+ // Communicates with Recurly <https://recurly.com> via a JSONP API,
4
+ // generates UI, handles user error, and passes control to the client
5
+ // to handle the successful events such as subscription creation.
6
+ //
7
+ // Example Site: https://js.recurly.com
8
+ //
9
+ // (MIT License)
10
+ //
11
+ // Copyright (C) 2012 by Recurly, Inc.
12
+ //
13
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ // of this software and associated documentation files (the "Software"), to deal
15
+ // in the Software without restriction, including without limitation the rights
16
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ // copies of the Software, and to permit persons to whom the Software is
18
+ // furnished to do so, subject to the following conditions:
19
+ //
20
+ // The above copyright notice and this permission notice shall be included in
21
+ // all copies or substantial portions of the Software.
22
+ //
23
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
+ // THE SOFTWARE.
30
+
31
+
32
+ (function($) {
33
+ "use strict";
34
+
35
+ //////////////////////////////////////////////////
36
+ // Compiled from src/js/core.js
37
+ //////////////////////////////////////////////////
38
+
39
+ // Non-intrusive Object.create
40
+ function createObject(o) {
41
+ function F() {}
42
+ F.prototype = o || this;
43
+ return new F();
44
+ };
45
+
46
+ var R = {};
47
+ R.settings = {
48
+ enableGeoIP: true
49
+ , acceptedCards: ['american_express', 'discover', 'mastercard', 'visa']
50
+ , oneErrorPerField: true
51
+ };
52
+
53
+ R.version = '2.1.3';
54
+
55
+ R.dom = {};
56
+
57
+ R.Error = {
58
+ toString: function() {
59
+ return 'RecurlyJS Error: ' + this.message;
60
+ }
61
+ };
62
+
63
+ R.raiseError = function(message) {
64
+ var e = createObject(R.Error);
65
+ e.message = message;
66
+ throw e;
67
+ };
68
+
69
+
70
+ R.config = function(settings) {
71
+ $.extend(true, R.settings, settings);
72
+
73
+ if(!settings.baseURL) {
74
+ R.settings.baseURL = 'https://api.recurly.com/jsonp/';
75
+ var subdomain = R.settings.subdomain || R.raiseError('company subdomain not configured');
76
+ R.settings.baseURL += subdomain + '/';
77
+ }
78
+ };
79
+
80
+
81
+ function pluralize(count, term) {
82
+ if(count == 1) {
83
+ return term.substr(0,term.length-1);
84
+ }
85
+
86
+ return '' + count + ' ' + term;
87
+ }
88
+
89
+ // Immutable currency-amount object
90
+ // This will eventually handle multi-currency
91
+ // where it will store a list of costs per currency
92
+ // and accessors will return the appropriate one
93
+ // based on the current currency
94
+ //
95
+
96
+ (R.Cost = function(cents) {
97
+ this._cents = cents || 0;
98
+ }).prototype = {
99
+ toString: function() {
100
+ return R.formatCurrency(this.dollars());
101
+ }
102
+ , cents: function(val) {
103
+ if(val === undefined)
104
+ return this._cents;
105
+
106
+ return new Cost(val);
107
+ }
108
+ , dollars: function(val) {
109
+ if(val === undefined)
110
+ return this._cents/100;
111
+
112
+ return new R.Cost(val*100);
113
+ }
114
+ , mult: function(n) {
115
+ return new R.Cost(this._cents * n);
116
+ }
117
+ , add: function(n) {
118
+ if(n.cents) n = n.cents();
119
+ return new R.Cost(this._cents + n);
120
+ }
121
+ , sub: function(n) {
122
+ if(n.cents) n = n.cents();
123
+ return new R.Cost(this._cents - n);
124
+ }
125
+ };
126
+
127
+ R.Cost.FREE = new R.Cost(0);
128
+
129
+ (R.TimePeriod = function(length,unit) {
130
+ this.length = length;
131
+ this.unit = unit;
132
+ }).prototype = {
133
+ toString: function() {
134
+ return '' + pluralize(this.length, this.unit);
135
+ }
136
+ , toDate: function() {
137
+ var d = new Date();
138
+ switch(this.unit) {
139
+ case 'month':
140
+ d.setMonth( d.getMonth() + this.length );
141
+ break;
142
+ case 'day':
143
+ d.setDay( d.getDay() + this.length );
144
+ break;
145
+ }
146
+ return d;
147
+ }
148
+ , clone: function() {
149
+ return new R.TimePeriod(this.length,this.unit);
150
+ }
151
+ };
152
+
153
+ (R.RecurringCost = function(cost,interval) {
154
+ this.cost = cost;
155
+ this.interval = interval;
156
+ }).prototype = {
157
+ toString: function() {
158
+ return '' + this.cost + ' every ' + this.interval;
159
+ }
160
+ , clone: function() {
161
+ return new R.TimePeriod(this.length,this.unit);
162
+ }
163
+ };
164
+
165
+ R.RecurringCost.FREE = new R.RecurringCost(0,null);
166
+
167
+ (R.RecurringCostStage = function(recurringCost, duration) {
168
+ this.recurringCost = recurringCost;
169
+ this.duration = duration;
170
+ }).prototype = {
171
+ toString: function() {
172
+ this.recurringCost.toString() + ' for ' + this.duration.toString();
173
+ }
174
+ };
175
+
176
+
177
+
178
+
179
+ //////////////////////////////////////////////////
180
+ // Compiled from src/js/locale.js
181
+ //////////////////////////////////////////////////
182
+
183
+ R.locale = {};
184
+
185
+ R.locale.errors = {
186
+ emptyField: 'Required field'
187
+ , missingFullAddress: 'Please enter your full address.'
188
+ , invalidEmail: 'Invalid'
189
+ , invalidCC: 'Invalid'
190
+ , invalidCVV: 'Invalid'
191
+ , invalidCoupon: 'Invalid'
192
+ , cardDeclined: 'Transaction declined'
193
+ , acceptTOS: 'Please accept the Terms of Service.'
194
+ , invalidQuantity: 'Invalid quantity'
195
+ };
196
+
197
+ R.locale.currencies = {};
198
+
199
+ R.locale.currency = {
200
+ format: "%u%n"
201
+ , separator: "."
202
+ , delimiter: ","
203
+ , precision: 2
204
+ };
205
+
206
+ function C(key, def) {
207
+ var c = R.locale.currencies[key] = createObject(R.locale.currency);
208
+ for(var p in def) {
209
+ c[p] = def[p];
210
+ }
211
+ };
212
+
213
+ C('USD', {
214
+ symbol: '$'
215
+ });
216
+
217
+ C('AUD', {
218
+ symbol: '$'
219
+ });
220
+
221
+ C('CAD', {
222
+ symbol: '$'
223
+ });
224
+
225
+ C('EUR', {
226
+ symbol: '\u20ac'
227
+ });
228
+
229
+ C('GBP', {
230
+ symbol: '\u00a3'
231
+ });
232
+
233
+ C('CZK', {
234
+ symbol: '\u004b'
235
+ });
236
+
237
+ C('DKK', {
238
+ symbol: '\u006b\u0072'
239
+ });
240
+
241
+ C('HUF', {
242
+ symbol: 'Ft'
243
+ });
244
+
245
+ C('JPY', {
246
+ symbol: '\u00a5'
247
+ });
248
+
249
+ C('NOK', {
250
+ symbol: 'kr'
251
+ });
252
+
253
+ C('NZD', {
254
+ symbol: '$'
255
+ });
256
+
257
+ C('PLN', {
258
+ symbol: '\u007a'
259
+ });
260
+
261
+ C('SGD', {
262
+ symbol: '$'
263
+ });
264
+
265
+ C('SEK', {
266
+ symbol: 'kr'
267
+ });
268
+
269
+ C('CHF', {
270
+ symbol: 'Fr'
271
+ });
272
+
273
+ C('ZAR', {
274
+ symbol: 'R'
275
+ });
276
+
277
+
278
+
279
+ R.settings.locale = R.locale;
280
+
281
+
282
+ //////////////////////////////////////////////////
283
+ // Compiled from src/js/utils.js
284
+ //////////////////////////////////////////////////
285
+
286
+ R.knownCards = {
287
+ 'visa': {
288
+ prefixes: [4]
289
+ , name: 'Visa'
290
+ }
291
+ , 'mastercard': {
292
+ prefixes: [51, 52, 53, 54, 55]
293
+ , name: 'MasterCard'
294
+ }
295
+ , 'american_express': {
296
+ prefixes: [34, 37]
297
+ , name: 'American Express'
298
+ }
299
+ , 'discover': {
300
+ prefixes: [6011, 62, 64, 65]
301
+ , name: 'Discover'
302
+ }
303
+ , 'diners_club': {
304
+ prefixes: [305, 36, 38]
305
+ , name: 'Diners Club'
306
+ }
307
+ , 'carte_blanche': {
308
+ prefixes: [300, 301, 302, 303, 304, 305]
309
+ }
310
+ , 'jcb': {
311
+ prefixes: [35]
312
+ , name: 'JCB'
313
+ }
314
+ , 'enroute': {
315
+ prefixes: [2014, 2149]
316
+ , name: 'EnRoute'
317
+ }
318
+ , 'maestro': {
319
+ prefixes: [5018, 5020, 5038, 6304, 6759, 6761]
320
+ , name: 'Maestro'
321
+ }
322
+ , 'laser': {
323
+ prefixes: [6304, 6706, 6771, 6709]
324
+ , name: 'Laser'
325
+ }
326
+ };
327
+
328
+ // Credit card type functions
329
+ R.detectCardType = function(cardNumber) {
330
+ cardNumber = cardNumber.replace(/\D/g, '');
331
+ var cards = R.knownCards;
332
+
333
+ for(var ci in cards) {
334
+ if(cards.hasOwnProperty(ci)) {
335
+ var c = cards[ci];
336
+ for(var pi=0,pl=c.prefixes.length; pi < pl; ++pi) {
337
+ if(c.prefixes.hasOwnProperty(pi)) {
338
+ var p = c.prefixes[pi];
339
+ if (new RegExp('^' + p.toString()).test(cardNumber))
340
+ return ci;
341
+ }
342
+ }
343
+ }
344
+ }
345
+
346
+ return false;
347
+ };
348
+
349
+
350
+ // Formats currency amount in the current denomination or one provided
351
+ // based on R.locale.currencies rules
352
+ R.formatCurrency = function(num,denomination) {
353
+
354
+ if(num < 0) {
355
+ num = -num;
356
+ var negative = true;
357
+ }
358
+ else {
359
+ var negative = false;
360
+ }
361
+
362
+ denomination = denomination || R.settings.currency ||
363
+ R.raiseError('currency not configured');
364
+
365
+ var langspec = R.locale.currency;
366
+ var currencyspec = R.locale.currencies[denomination];
367
+
368
+ // Format to precision
369
+ var str = num.toFixed(currencyspec.precision);
370
+
371
+ // Replace default period with format separator
372
+ if(langspec.separator != '.') {
373
+ str = str.replace(/\./g, langspec.separator);
374
+ }
375
+
376
+ function insertDelimiters(str) {
377
+ var sRegExp = new RegExp('(-?[0-9]+)([0-9]{3})');
378
+ while(sRegExp.test(str)) {
379
+ str = str.replace(sRegExp, '$1'+langspec.delimiter+'$2');
380
+ }
381
+ return str;
382
+ }
383
+
384
+ // Apply thousands delimiter
385
+ str = insertDelimiters(str);
386
+
387
+ // Format unit/number order
388
+ var format = langspec.format;
389
+ format = format.replace(/%u/g, currencyspec.symbol);
390
+ format = format.replace(/%n/g, str);
391
+ str = format;
392
+
393
+ if(negative) {
394
+ str = '-' + str;
395
+ }
396
+
397
+ return str;
398
+ };
399
+
400
+ var euCountries = ["AT","BE","BG","CY","CZ","DK","EE","FI","FR","DE","GR","HU","IE","IT","LV","LT","LU","MT","NL","PL","PT","RO","SK","SI","ES","SE","GB"];
401
+ R.isCountryInEU = function(country) {
402
+ return $.inArray(country, euCountries) !== -1;
403
+ }
404
+
405
+ R.isVATNumberApplicable = function(buyerCountry, sellerCountry) {
406
+ if(!R.settings.VATPercent) return false;
407
+
408
+ if(!R.settings.country) {
409
+ R.raiseError('you must configure a country for VAT to work');
410
+ }
411
+
412
+ if(!R.isCountryInEU(R.settings.country)) {
413
+ R.raiseError('you cannot charge VAT outside of the EU');
414
+ }
415
+
416
+ // Outside of EU don't even show the number
417
+ if(!R.isCountryInEU(buyerCountry)) {
418
+ return false;
419
+ }
420
+
421
+ return true;
422
+ }
423
+
424
+ R.isVATChargeApplicable = function(buyerCountry, vatNumber) {
425
+ // We made it so the VAT Number is collectable in any case
426
+ // where it could be charged, so this is logically sound:
427
+ if(!R.isVATNumberApplicable(buyerCountry)) return false;
428
+
429
+ var sellerCountry = R.settings.country;
430
+
431
+ // 1) Outside EU never pays
432
+ // 2) Same country in EU always pays
433
+ // 3) Different countries in EU, pay only without vatNumber
434
+ return (sellerCountry == buyerCountry || !vatNumber);
435
+ };
436
+
437
+ R.flattenErrors = function(obj, attr) {
438
+ var arr = [];
439
+
440
+ var baseErrorKeys = ['base','account_id'];
441
+
442
+ var attr = attr || '';
443
+
444
+ if( typeof obj == 'string'
445
+ || typeof obj == 'number'
446
+ || typeof obj == 'boolean') {
447
+
448
+ /*
449
+ * erroneous code, commented out here in case similar logic is needed later
450
+ if($.inArray(baseErrorKeys, attr)) {
451
+ return [obj];
452
+ }
453
+ */
454
+
455
+ return ['' + attr + ' ' + obj];
456
+ }
457
+
458
+ for(var k in obj) {
459
+ // console.log(k);
460
+ if(obj.hasOwnProperty(k)) {
461
+ // Inherit parent attribute names when property key
462
+ // is a numeric string; how we deal with arrays
463
+ attr = (parseInt(k).toString() == k) ? attr : k;
464
+ var children = R.flattenErrors(obj[k], attr);
465
+ for(var i=0, l=children.length; i < l; ++i) {
466
+ arr.push(children[i]);
467
+ }
468
+ }
469
+ }
470
+
471
+ return arr;
472
+ };
473
+
474
+ // Very small function, but defining for D.R.Y.ness
475
+ R.getToken = function(response) {
476
+ var token = response.token || 'INVALIDTOKEN';
477
+ return token;
478
+ }
479
+
480
+ // POST the results from Recurly to the merchant's webserver
481
+ R.postResult = function(url, originalResponse, options) {
482
+ var token = R.getToken(originalResponse);
483
+
484
+ var form = $('<form />').hide();
485
+ form.attr('action', url)
486
+ .attr('method', 'POST')
487
+ .attr('enctype', 'application/x-www-form-urlencoded');
488
+
489
+ $('<input type="hidden" />').attr({name: 'recurly_token', value: token}).appendTo(form);
490
+
491
+ $('body').append(form);
492
+ form.submit();
493
+ };
494
+
495
+ function jsonToSelect(obj) {
496
+ var $select = $('<select>');
497
+
498
+ for(var k in obj) {
499
+ if(obj.hasOwnProperty(k)) {
500
+ var v = obj[k];
501
+ $select.append('<option value='+k+'>'+v+'</option>');
502
+ }
503
+ }
504
+
505
+ return $select;
506
+ };
507
+
508
+ R.enforce = function(obj) {
509
+ return {
510
+ enforced: obj
511
+ , hidden: false
512
+ , hide: function() {
513
+ this.hidden = true;
514
+ return this;
515
+ }
516
+ };
517
+ };
518
+
519
+
520
+ function cc2lcu(obj) {
521
+ obj = obj || this;
522
+
523
+ if(typeof obj == 'string') {
524
+ return obj.replace(/([a-z])([A-Z])/g, function (a, l, u) {
525
+ return l+'_'+u;
526
+ }).toLowerCase();
527
+ }
528
+ else {
529
+ for(var k in obj) {
530
+ if(obj.hasOwnProperty(k)) {
531
+
532
+ }
533
+ }
534
+ }
535
+ }
536
+
537
+
538
+ R.ajax = function(options) {
539
+ options.data = $.extend({js_version: R.version}, options.data);
540
+ return $.ajax(options);
541
+ };
542
+
543
+
544
+ function errorDialog(message) {
545
+ $('body').append(R.dom.error_dialog);
546
+ }
547
+
548
+
549
+ //////////////////////////////////////////////////
550
+ // Compiled from src/js/validators.js
551
+ //////////////////////////////////////////////////
552
+
553
+
554
+ (R.isValidCC = function($input) {
555
+ var v = $input.val();
556
+
557
+ // Strip out all non digits
558
+ v = v.replace(/\D/g, "");
559
+
560
+ if(v == "") return false;
561
+
562
+ var nCheck = 0,
563
+ nDigit = 0,
564
+ bEven = false;
565
+
566
+
567
+ for (var n = v.length - 1; n >= 0; n--) {
568
+ var cDigit = v.charAt(n);
569
+ var nDigit = parseInt(cDigit, 10);
570
+ if (bEven) {
571
+ if ((nDigit *= 2) > 9)
572
+ nDigit -= 9;
573
+ }
574
+ nCheck += nDigit;
575
+ bEven = !bEven;
576
+ }
577
+
578
+ return (nCheck % 10) == 0;
579
+ }).defaultErrorKey = 'invalidCC';
580
+
581
+ (R.isValidEmail = function($input) {
582
+ var v = $input.val();
583
+ return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(v);
584
+ }).defaultErrorKey = 'invalidEmail';
585
+
586
+ function wholeNumber(val) {
587
+ return /^[0-9]+$/.test(val);
588
+ }
589
+
590
+ (R.isValidCVV = function($input) {
591
+ var v = $input.val();
592
+ return (v.length == 3 || v.length == 4) && wholeNumber(v);
593
+ }).defaultErrorKey = 'invalidCVV';
594
+
595
+ (R.isNotEmpty = function($input) {
596
+ var v = $input.val();
597
+ if($input.is('select')) {
598
+ if(v == '-' || v == '--') return false;
599
+ }
600
+ return !!v;
601
+ }).defaultErrorKey = 'emptyField';
602
+ // State is required if its a dropdown, it is not required if it is an input box
603
+ (R.isNotEmptyState = function($input) {
604
+ var v = $input.val();
605
+ if($input.is('select')) {
606
+ if(v == '-' || v == '--') return false;
607
+ }
608
+ return true;
609
+ }).defaultErrorKey = 'emptyField';
610
+
611
+ (R.isChecked = function($input) {
612
+ return $input.is(':checked');
613
+ }).defaultErrorKey = 'acceptTOS';
614
+
615
+ (R.isValidQuantity = function($input) {
616
+ return /^[0-9]*$/.test($input.val());
617
+ }).defaultErrorKey = 'invalidQuantity';
618
+
619
+
620
+
621
+ //////////////////////////////////////////////////
622
+ // Compiled from src/js/plan.js
623
+ //////////////////////////////////////////////////
624
+
625
+ R.Plan = {
626
+ create: createObject
627
+ , fromJSON: function(json) {
628
+ var p = this.create();
629
+
630
+ p.name = json.name;
631
+ p.code = json.plan_code;
632
+ p.currency = json.currency;
633
+ p.cost = new R.Cost(json.unit_amount_in_cents);
634
+
635
+ p.displayQuantity = json.display_quantity;
636
+
637
+ p.interval = new R.TimePeriod(
638
+ json.plan_interval_length,
639
+ json.plan_interval_unit
640
+ );
641
+
642
+ if(json.trial_interval_length) {
643
+ p.trial = new R.TimePeriod(
644
+ json.trial_interval_length,
645
+ json.trial_interval_unit
646
+ );
647
+ }
648
+
649
+ if(json.setup_fee_in_cents) {
650
+ p.setupFee = new R.Cost(json.setup_fee_in_cents);
651
+ }
652
+
653
+ if (json.vat_percentage) {
654
+ R.settings.VATPercent = parseFloat(json.vat_percentage);
655
+ }
656
+
657
+ if (json.merchant_country) {
658
+ R.settings.country = json.merchant_country;
659
+ }
660
+
661
+ p.addOns = [];
662
+ if(json.add_ons) {
663
+ for(var l=json.add_ons.length, i=0; i < l; ++i) {
664
+ var a = json.add_ons[i];
665
+ p.addOns.push(R.AddOn.fromJSON(a));
666
+ }
667
+ }
668
+
669
+ return p;
670
+ }
671
+ , get: function(plan_code, currency, callback) {
672
+ $.ajax({
673
+ url: R.settings.baseURL+'plans/'+plan_code+"?currency="+currency,
674
+ // data: params,
675
+ dataType: "jsonp",
676
+ jsonp: "callback",
677
+ timeout: 10000,
678
+ success: function(data) {
679
+ var plan = R.Plan.fromJSON(data);
680
+ callback(plan);
681
+ }
682
+ });
683
+ }
684
+ , createSubscription: function() {
685
+ var s = createObject(R.Subscription);
686
+ s.plan = createObject(this);
687
+ s.plan.quantity = 1;
688
+ s.addOns = [];
689
+ return s;
690
+ }
691
+ };
692
+
693
+ R.AddOn = {
694
+ fromJSON: function(json) {
695
+ var a = createObject(R.AddOn);
696
+ a.name = json.name;
697
+ a.code = json.add_on_code;
698
+ a.cost = new R.Cost(json.default_unit_amount_in_cents);
699
+ a.displayQuantity = json.display_quantity;
700
+ return a;
701
+ }
702
+
703
+ , toJSON: function() {
704
+ return {
705
+ name: this.name
706
+ , add_on_code: this.code
707
+ , default_unit_amount_in_cents: this.default_unit_amount_in_cents
708
+ };
709
+ }
710
+ };
711
+
712
+
713
+ //////////////////////////////////////////////////
714
+ // Compiled from src/js/account.js
715
+ //////////////////////////////////////////////////
716
+
717
+ R.Account = {
718
+ create: createObject
719
+ , toJSON: function() {
720
+ return {
721
+ first_name: this.firstName
722
+ , last_name: this.lastName
723
+ , company_name: this.companyName
724
+ , account_code: this.code
725
+ , email: this.email
726
+ };
727
+ }
728
+ };
729
+
730
+
731
+
732
+ //////////////////////////////////////////////////
733
+ // Compiled from src/js/billing_info.js
734
+ //////////////////////////////////////////////////
735
+
736
+ R.BillingInfo = {
737
+ create: createObject
738
+ , toJSON: function() {
739
+ return {
740
+ first_name: this.firstName
741
+ , last_name: this.lastName
742
+ , month: this.month
743
+ , year: this.year
744
+ , number: this.number
745
+ , verification_value: this.cvv
746
+ , address1: this.address1
747
+ , address2: this.address2
748
+ , city: this.city
749
+ , state: this.state
750
+ , zip: this.zip
751
+ , country: this.country
752
+ , phone: this.phone
753
+ , vat_number: this.vatNumber
754
+ };
755
+ }
756
+ , save: function(options) {
757
+ var json = {
758
+ billing_info: this.toJSON()
759
+ , signature: options.signature
760
+ };
761
+
762
+ // Save first/last name on the account
763
+ // if not distinguished
764
+ if(!options.distinguishContactFromBillingInfo) {
765
+ json.account = {
766
+ account_code: options.accountCode
767
+ , first_name: this.firstName
768
+ , last_name: this.lastName
769
+ };
770
+ }
771
+
772
+ R.ajax({
773
+ url: R.settings.baseURL+'accounts/'+options.accountCode+'/billing_info/update'
774
+ , data: json
775
+ , dataType: 'jsonp'
776
+ , jsonp: 'callback'
777
+ , timeout: 60000
778
+ , success: function(data) {
779
+ if(data.success && options.success) {
780
+ options.success(data.success);
781
+ }
782
+ else if(data.errors && options.error) {
783
+ options.error( R.flattenErrors(data.errors) );
784
+ }
785
+ }
786
+ , error: function() {
787
+ if(options.error) {
788
+ options.error(['Unknown error processing transaction. Please try again later.']);
789
+ }
790
+ }
791
+ , complete: options.complete || $.noop
792
+ });
793
+ }
794
+ };
795
+
796
+
797
+
798
+ //////////////////////////////////////////////////
799
+ // Compiled from src/js/subscription.js
800
+ //////////////////////////////////////////////////
801
+
802
+ // Base Subscription prototype
803
+ R.Subscription = {
804
+ create: createObject
805
+ , plan: R.Plan
806
+ , addOns: []
807
+
808
+ , calculateTotals: function() {
809
+ var totals = {
810
+ stages: {}
811
+ };
812
+
813
+ // PLAN
814
+ totals.plan = this.plan.cost.mult(this.plan.quantity);
815
+
816
+ // ADD-ONS
817
+ totals.allAddOns = new R.Cost(0);
818
+ totals.addOns = {};
819
+ for(var l=this.addOns.length, i=0; i < l; ++i) {
820
+ var a = this.addOns[i],
821
+ c = a.cost.mult(a.quantity);
822
+ totals.addOns[a.code] = c;
823
+ totals.allAddOns = totals.allAddOns.add(c);
824
+ }
825
+
826
+ totals.stages.recurring = totals.plan.add(totals.allAddOns);
827
+
828
+ totals.stages.now = totals.plan.add(totals.allAddOns);
829
+
830
+ // FREE TRIAL
831
+ if(this.plan.trial) {
832
+ totals.stages.now = R.Cost.FREE;
833
+ }
834
+
835
+ // COUPON
836
+ if(this.coupon) {
837
+ var beforeDiscount = totals.stages.now;
838
+ var afterDiscount = totals.stages.now.discount(this.coupon);
839
+ totals.coupon = afterDiscount.sub(beforeDiscount);
840
+ totals.stages.now = afterDiscount;
841
+ }
842
+
843
+ // SETUP FEE
844
+ if(this.plan.setupFee) {
845
+ totals.stages.now = totals.stages.now.add(this.plan.setupFee);
846
+ }
847
+
848
+ // VAT
849
+ if(this.billingInfo && R.isVATChargeApplicable(this.billingInfo.country,this.billingInfo.vatNumber)) {
850
+ totals.vat = totals.stages.now.mult( (R.settings.VATPercent/100) );
851
+ totals.stages.now = totals.stages.now.add(totals.vat);
852
+ }
853
+
854
+ return totals;
855
+ }
856
+ , redeemAddOn: function(addOn) {
857
+ var redemption = addOn.createRedemption();
858
+ this.addOns.push(redemption);
859
+ return redemption;
860
+ }
861
+
862
+ , removeAddOn: function(code) {
863
+ for(var a=this.addOns, l=a.length, i=0; i < l; ++i) {
864
+ if(a[i].code == code) {
865
+ return a.splice(i,1);
866
+ }
867
+ }
868
+ }
869
+
870
+ , findAddOnByCode: function(code) {
871
+ for(var l=this.addOns.length, i=0; i < l; ++i) {
872
+ if(this.addOns[i].code == code) {
873
+ return this.addOns[i];
874
+ }
875
+ }
876
+ return false;
877
+ }
878
+
879
+ , toJSON: function() {
880
+ var json = {
881
+ plan_code: this.plan.code
882
+ , quantity: this.plan.quantity
883
+ , currency: this.plan.currency
884
+ , coupon_code: this.coupon ? this.coupon.code : undefined
885
+ , add_ons: []
886
+ };
887
+
888
+ for(var i=0, l=this.addOns.length, a=json.add_ons, b=this.addOns; i < l; ++i) {
889
+ a.push({
890
+ add_on_code: b[i].code
891
+ , quantity: b[i].quantity
892
+ });
893
+ }
894
+
895
+ return json;
896
+ }
897
+
898
+ , save: function(options) {
899
+ var json = {
900
+ subscription: this.toJSON()
901
+ , account: this.account.toJSON()
902
+ , billing_info: this.billingInfo.toJSON()
903
+ , signature: options.signature
904
+ };
905
+
906
+ R.ajax({
907
+ url: R.settings.baseURL+'subscribe',
908
+ data: json,
909
+ dataType: "jsonp",
910
+ jsonp: "callback",
911
+ timeout: 60000,
912
+ success: function(data) {
913
+ if(data.success && options.success) {
914
+ options.success(data.success);
915
+ }
916
+ else if(data.errors && options.error) {
917
+ var errorCode = data.errors.error_code;
918
+ delete data.errors.error_code;
919
+ options.error( R.flattenErrors(data.errors), errorCode );
920
+ }
921
+ },
922
+ error: function() {
923
+ if(options.error) {
924
+ options.error(['Unknown error processing transaction. Please try again later.']);
925
+ }
926
+ },
927
+ complete: options.complete
928
+ });
929
+
930
+ }
931
+ };
932
+
933
+ R.AddOn.createRedemption = function(qty) {
934
+ var r = createObject(this);
935
+ r.quantity = qty || 1;
936
+ return r;
937
+ };
938
+
939
+ R.Coupon = {
940
+ fromJSON: function(json) {
941
+ var c = createObject(R.Coupon);
942
+
943
+ if(json.discount_in_cents)
944
+ c.discountCost = new R.Cost(-json.discount_in_cents);
945
+ else if(json.discount_percent)
946
+ c.discountRatio = json.discount_percent/100;
947
+
948
+ c.description = json.description;
949
+
950
+ return c;
951
+ }
952
+
953
+ , toJSON: function() {
954
+ }
955
+ };
956
+
957
+ R.Cost.prototype.discount = function(coupon){
958
+ if(coupon.discountCost)
959
+ return this.add(coupon.discountCost);
960
+
961
+ var ret = this.sub( this.mult(coupon.discountRatio) );
962
+ if(ret.cents() < 0) {
963
+ return R.Cost.FREE;
964
+ }
965
+
966
+ return ret;
967
+ };
968
+
969
+ R.Subscription.getCoupon = function(couponCode, successCallback, errorCallback) {
970
+
971
+ if(!R.settings.baseURL) { R.raiseError('Company subdomain not configured'); }
972
+
973
+ var couponCurrencyQuery = (R.settings.currency !== undefined ? '?currency='+R.settings.currency : '');
974
+
975
+ return R.ajax({
976
+ url: R.settings.baseURL+'plans/'+this.plan.code+'/coupons/'+couponCode+couponCurrencyQuery,
977
+ // data: params,
978
+ dataType: "jsonp",
979
+ jsonp: "callback",
980
+ timeout: 10000,
981
+ success: function(data) {
982
+ if(data.valid) {
983
+ var coupon = R.Coupon.fromJSON(data);
984
+ coupon.code = couponCode;
985
+ successCallback(coupon);
986
+ }
987
+ else {
988
+ errorCallback();
989
+ }
990
+ },
991
+ error: function() {
992
+ errorCallback();
993
+ }
994
+ });
995
+ };
996
+
997
+
998
+
999
+ //////////////////////////////////////////////////
1000
+ // Compiled from src/js/transaction.js
1001
+ //////////////////////////////////////////////////
1002
+
1003
+
1004
+ R.Transaction = {
1005
+ // Note - No toJSON function for this object, all parameters must be signed.
1006
+ create: createObject
1007
+ , save: function(options) {
1008
+ var json = {
1009
+ account: this.account ? this.account.toJSON() : undefined
1010
+ , billing_info: this.billingInfo.toJSON()
1011
+ , signature: options.signature
1012
+ };
1013
+
1014
+ R.ajax({
1015
+ url: R.settings.baseURL+'transactions/create'
1016
+ , data: json
1017
+ , dataType: 'jsonp'
1018
+ , jsonp: 'callback'
1019
+ , timeout: 60000
1020
+ , success: function(data) {
1021
+ if(data.success && options.success) {
1022
+ options.success(data.success);
1023
+ }
1024
+ else if(data.errors && options.error) {
1025
+ options.error( R.flattenErrors(data.errors) );
1026
+ }
1027
+ }
1028
+ , error: function() {
1029
+ if(options.error) {
1030
+ options.error(['Unknown error processing transaction. Please try again later.']);
1031
+ }
1032
+ }
1033
+ , complete: options.complete || $.noop
1034
+ });
1035
+ }
1036
+ };
1037
+
1038
+
1039
+
1040
+
1041
+ //////////////////////////////////////////////////
1042
+ // Compiled from src/js/ui.js
1043
+ //////////////////////////////////////////////////
1044
+
1045
+ R.UserError = {};
1046
+
1047
+ function raiseUserError(validation, elem) {
1048
+ var e = createObject(R.UserError);
1049
+ e.validation = validation;
1050
+ e.element = elem;
1051
+ throw e;
1052
+ }
1053
+
1054
+ function invalidMode(e) {
1055
+ var $input = e.element;
1056
+ var message = R.locale.errors[e.validation.errorKey];
1057
+ var validator = e.validation.validator;
1058
+
1059
+ var $e = $('<div class="error">');
1060
+ $e.text(message);
1061
+ $e.appendTo($input.parent());
1062
+
1063
+ $input.addClass('invalid');
1064
+ $input.bind('change keyup', function handler(e) {
1065
+
1066
+ if(validator($input)) {
1067
+ $input.removeClass('invalid');
1068
+ $e.remove();
1069
+ $input.unbind(e);
1070
+ }
1071
+ });
1072
+ }
1073
+
1074
+ function validationGroup(pull,success) {
1075
+ var anyErrors = false;
1076
+ var puller = {
1077
+ field: function($form, fieldSel, validations) {
1078
+ validations = Array.prototype.slice.call(arguments, 2);
1079
+ return pullField($form, fieldSel, validations, function onError(error) {
1080
+ if(!anyErrors) error.element.focus();
1081
+ invalidMode(error);
1082
+ anyErrors = true;
1083
+
1084
+ if(R.settings.oneErrorPerForm)
1085
+ throw {stopPulling:true};
1086
+ });
1087
+ }
1088
+ };
1089
+
1090
+
1091
+ try {
1092
+ pull(puller);
1093
+ }
1094
+ catch(e) {
1095
+ if(!e.stopPulling) {
1096
+ throw e;
1097
+ }
1098
+ }
1099
+
1100
+ if(!anyErrors) {
1101
+ success();
1102
+ }
1103
+ }
1104
+
1105
+
1106
+ function pullField($form, fieldSel, validations, onError) {
1107
+ // Try text input
1108
+ var $input = $form.find(fieldSel + ' input');
1109
+
1110
+ // Try as select
1111
+ if($input.length == 0) {
1112
+ $input = $form.find(fieldSel + ' select');
1113
+ }
1114
+
1115
+ // Treat nonexistence as removed deliberately
1116
+ if($input.length == 0) return undefined;
1117
+
1118
+ var val = $input.val();
1119
+
1120
+ for(var i=0,l=validations.length; i < l; ++i) {
1121
+ var v = validations[i];
1122
+
1123
+ if(!v.validator($input)) {
1124
+ onError({
1125
+ element: $input
1126
+ , validation: v
1127
+ });
1128
+
1129
+ if(R.settings.oneErrorPerField)
1130
+ break;
1131
+ }
1132
+ }
1133
+
1134
+ return val;
1135
+ }
1136
+
1137
+
1138
+ // Make a 'validation' from validator / errorKey
1139
+ function V(v,k) {
1140
+ return {
1141
+ validator: v,
1142
+ errorKey: k || v.defaultErrorKey
1143
+ };
1144
+ }
1145
+
1146
+
1147
+ // == SERVER ERROR UI METHODS
1148
+
1149
+ function clearServerErrors($form) {
1150
+ var $serverErrors = $form.find('.server_errors');
1151
+ $serverErrors.removeClass('any').addClass('none');
1152
+ $serverErrors.empty();
1153
+ }
1154
+
1155
+ function displayServerErrors($form, errors) {
1156
+ var $serverErrors = $form.find('.server_errors');
1157
+ clearServerErrors($form);
1158
+
1159
+ var l = errors.length;
1160
+ if(l) {
1161
+ $serverErrors.removeClass('none').addClass('any');
1162
+ for(var i=0; i < l; ++i) {
1163
+ var $e = $('<div class="error">');
1164
+ $e.text(errors[i]);
1165
+ $serverErrors.append($e);
1166
+ }
1167
+ }
1168
+ }
1169
+
1170
+
1171
+ var preFillMap = {
1172
+ account: {
1173
+ firstName: '.contact_info > .full_name > .first_name > input'
1174
+ , lastName: '.contact_info > .full_name > .last_name > input'
1175
+ , email: '.contact_info > .email > input'
1176
+ , phone: '.contact_info > .phone > input'
1177
+ , companyName: '.contact_info > .company_name > input'
1178
+ }
1179
+ , billingInfo: {
1180
+ firstName: '.billing_info > .credit_card > .first_name > input'
1181
+ , lastName: '.billing_info > .credit_card > .last_name > input'
1182
+ , address1: '.billing_info > .address > .address1 > input'
1183
+ , address2: '.billing_info > .address > .address2 > input'
1184
+ , country: '.billing_info > .address > .country > select'
1185
+ , city: '.billing_info > .address > .city > input'
1186
+ , state: '.billing_info > .address > .state_zip > .state > input'
1187
+ , zip: '.billing_info > .address > .state_zip > .zip > input'
1188
+ , vatNumber: '.billing_info > .vat_number > input'
1189
+
1190
+ , cardNumber: '.billing_info .card_number > input'
1191
+ , CVV: '.billing_info .cvv > input'
1192
+ }
1193
+ , subscription: {
1194
+ couponCode: '.subscription > .coupon > .coupon_code > input'
1195
+ }
1196
+ };
1197
+
1198
+ function preFillValues($form, options, mapObject) {
1199
+
1200
+ (function recurse(preFill,mapObject,keypath) {
1201
+
1202
+ if(!preFill) return;
1203
+
1204
+ for(var k in preFill) {
1205
+ if(preFill.hasOwnProperty(k) && mapObject.hasOwnProperty(k)) {
1206
+
1207
+ var v = preFill[k];
1208
+ var selectorOrNested = mapObject[k];
1209
+ var lcuk = cc2lcu(k);
1210
+ var keypath2 = keypath ? (keypath+'.'+lcuk) : lcuk;
1211
+
1212
+
1213
+ // jquery selector
1214
+ if(typeof selectorOrNested == 'string') {
1215
+
1216
+ var $input = $form.find(selectorOrNested);
1217
+ $input.val(v).change();
1218
+ }
1219
+ // nested mapping
1220
+ else if(typeof selectorOrNested == 'object') {
1221
+ recurse(v, selectorOrNested, keypath2);
1222
+ }
1223
+ }
1224
+ }
1225
+ })(options,mapObject);
1226
+ }
1227
+
1228
+
1229
+ function initCommonForm($form, options) {
1230
+
1231
+ if(!options.collectPhone) {
1232
+ $form.find('.phone').remove();
1233
+ }
1234
+
1235
+ if(!options.collectCompany) {
1236
+ $form.find('.company_name').remove();
1237
+ }
1238
+
1239
+ $form.delegate('.placeholder', 'click', function() {
1240
+ var $label = $(this);
1241
+ var $li = $(this).parent();
1242
+ $li.find('input').focus();
1243
+ });
1244
+
1245
+ $form.delegate('input', 'change keyup', function() {
1246
+ var $input = $(this);
1247
+ var $li = $(this).parent();
1248
+
1249
+ if($input.val().length > 0) {
1250
+ $li.find('.placeholder').hide();
1251
+ }
1252
+ else {
1253
+ $li.find('.placeholder').show();
1254
+ }
1255
+ });
1256
+
1257
+
1258
+ $form.delegate('input', 'focus', function() {
1259
+ $(this).parent().addClass('focus');
1260
+ });
1261
+
1262
+ $form.delegate('input', 'blur', function() {
1263
+ $(this).parent().removeClass('focus');
1264
+ });
1265
+
1266
+ $form.delegate('input', 'keydown', function(e) {
1267
+ if(e.keyCode >= 48 && e.keyCode <= 90) {
1268
+ $(this).parent().find('.placeholder').hide();
1269
+ }
1270
+ });
1271
+
1272
+ preFillValues($form, options, preFillMap);
1273
+ }
1274
+
1275
+ function initContactInfoForm($form, options) {
1276
+
1277
+ // == FIRSTNAME / LASTNAME REDUNDANCY
1278
+ if(options.distinguishContactFromBillingInfo) {
1279
+ var $contactFirstName = $form.find('.contact_info .first_name input');
1280
+ var $contactLastName = $form.find('.contact_info .last_name input');
1281
+ var prevFirstName = $contactFirstName.val();
1282
+ var prevLastName = $contactLastName.val();
1283
+ $form.find('.contact_info .first_name input').change(function() {
1284
+ var $billingFirstName = $form.find('.billing_info .first_name input');
1285
+ if($billingFirstName.val() == prevFirstName) {
1286
+ $billingFirstName.val( $(this).val() ).change();
1287
+ }
1288
+ prevFirstName = $contactFirstName.val();
1289
+ });
1290
+ $form.find('.contact_info .last_name input').change(function() {
1291
+ var $billingLastName = $form.find('.billing_info .last_name input');
1292
+ if($billingLastName.val() == prevLastName) {
1293
+ $billingLastName.val( $(this).val() ).change();
1294
+ }
1295
+ prevLastName = $contactLastName.val();
1296
+ });
1297
+
1298
+ }
1299
+ else {
1300
+ $form.find('.billing_info .first_name, .billing_info .last_name').remove();
1301
+ }
1302
+
1303
+ }
1304
+
1305
+ function initBillingInfoForm($form, options) {
1306
+
1307
+ // == SWAPPING OF STATE INPUT WITH SELECT
1308
+ var $countrySelect = $form.find('.country select');
1309
+ var $state = $form.find('.state');
1310
+ var $stateInput = $state.find('input');
1311
+ var $manualStateInput = $state.children();
1312
+ var $savedStateSelect = {};
1313
+ var knownStates = R.states;
1314
+ var prevCountry = $countrySelect.val();
1315
+
1316
+ function matchKnownStateWithInput(country, stateStr) {
1317
+ var ref = knownStates[country];
1318
+ // Normalize stateStr
1319
+ stateStr = $.trim(stateStr.toUpperCase());
1320
+
1321
+ // Is a state code
1322
+ if(ref.hasOwnProperty(stateStr)) {
1323
+ return stateStr;
1324
+ }
1325
+
1326
+ // Search through state names to find the code
1327
+ for(var k in ref) {
1328
+ if(ref.hasOwnProperty(k)) {
1329
+ var v = ref[k];
1330
+ if(stateStr == v.toUpperCase()) {
1331
+ return k;
1332
+ }
1333
+ }
1334
+ }
1335
+
1336
+ return false;
1337
+ }
1338
+
1339
+ function swapStateSelectOrInput(country, state) {
1340
+ var inSelectMode = $state.hasClass('select_mode');
1341
+ if(country == 'US' || country == 'CA') {
1342
+ if(!inSelectMode || prevCountry != country) {
1343
+ var manualVal = $state.find('input').val();
1344
+
1345
+ if(manualVal != undefined && manualVal != '') {
1346
+ state = matchKnownStateWithInput(country, manualVal);
1347
+
1348
+ if(!state) return false;
1349
+ }
1350
+
1351
+ // Change to select mode
1352
+ $state.addClass('select_mode');
1353
+ // Detatch manual-input children from field
1354
+ $state.children().detach();
1355
+ // Instantiate HTML DOM only now, and cache it
1356
+ $savedStateSelect[country] = $savedStateSelect[country] || jsonToSelect(knownStates[country]);
1357
+ // Insert select into field
1358
+ $state.append($savedStateSelect[country]);
1359
+ // Set known state, if provided
1360
+ if(state) $state.find('select').val(state);
1361
+ }
1362
+
1363
+ }
1364
+ else if(inSelectMode) {
1365
+ // Restore original manual state input field
1366
+ $state.empty().append($manualStateInput).removeClass('select_mode');
1367
+ }
1368
+ }
1369
+
1370
+ $stateInput.bind('change keyup', function() {
1371
+ swapStateSelectOrInput(prevCountry);
1372
+ });
1373
+
1374
+ $countrySelect.change(function() {
1375
+ var country = $(this).val();
1376
+ swapStateSelectOrInput(country);
1377
+ prevCountry = country;
1378
+ });
1379
+
1380
+ // == GEOIP
1381
+ function niceSet($jq, v) {
1382
+ var cur = $jq.val();
1383
+ if(!v || v == '') return false;
1384
+ if(cur && cur != '' && cur != '-') return false;
1385
+
1386
+ return $jq.val(v);
1387
+ }
1388
+
1389
+ if(options.enableGeoIP) {
1390
+ $.ajax({
1391
+ url: R.settings.baseURL+'location',
1392
+ dataType: "jsonp",
1393
+ jsonp: "callback",
1394
+ success: function(data) {
1395
+ if(data.country) {
1396
+ niceSet($countrySelect, data.country);
1397
+ swapStateSelectOrInput(data.country, data.state);
1398
+ }
1399
+ }
1400
+ });
1401
+ }
1402
+ else {
1403
+ // == DEFAULT BUYER TO SELLER COUNTRY
1404
+ if(R.settings.country) {
1405
+ var $countryOpt = $form.find('.country option[value='+R.settings.country+']');
1406
+ if($countryOpt.length) {
1407
+ $countryOpt.attr('selected', true).change();
1408
+ }
1409
+ }
1410
+ }
1411
+
1412
+ var now = new Date();
1413
+ var year = now.getFullYear();
1414
+ var month = now.getMonth();
1415
+ var $yearSelect = $form.find('.year select');
1416
+ var $monthSelect = $form.find('.month select');
1417
+
1418
+ // == GENERATE YEAR SELECT OPTIONS
1419
+ for(var i=year; i <= year+10; ++i) {
1420
+ var $yearOpt = $('<option name="'+i+'">'+i+'</option>');
1421
+ $yearOpt.appendTo($yearSelect);
1422
+ }
1423
+ $yearSelect.val(year+1);
1424
+
1425
+
1426
+ // == DISABLE INVALID MONTHS, SELECT CURRENT
1427
+ function updateMonths() {
1428
+ if($yearSelect.val() == year) {
1429
+ var foundSelected = false; // If we've set a selection yet
1430
+
1431
+ if($monthSelect.val() > month) {
1432
+ // We know the current selection is already valid
1433
+ foundSelected = true;
1434
+ }
1435
+
1436
+ $monthSelect.find('option').each(function(){
1437
+ if($(this).val() <= month) {
1438
+ $(this).attr('disabled', true);
1439
+ }
1440
+ else {
1441
+ $(this).removeAttr('disabled');
1442
+
1443
+ if(!foundSelected) {
1444
+ $(this).attr('selected', true);
1445
+ foundSelected = true;
1446
+ }
1447
+ }
1448
+ });
1449
+ }
1450
+ else {
1451
+ $monthSelect.find('option').removeAttr('disabled');
1452
+ }
1453
+ };
1454
+ updateMonths();
1455
+ $yearSelect.change(updateMonths);
1456
+
1457
+
1458
+ // == HIDE UNNECESSARY ADDRESS FIELDS
1459
+
1460
+ if(options.addressRequirement == 'none') {
1461
+ $form.find('.address').remove();
1462
+ }
1463
+ else if(options.addressRequirement == 'zip') {
1464
+ $form.find('.address').addClass('only_zip');
1465
+ $form.find('.address1, .address2, .city, .state').remove();
1466
+
1467
+ // Only remove country if no VAT support
1468
+ if(!R.settings.VATPercent) {
1469
+ $form.find('.country').remove();
1470
+ }
1471
+ }
1472
+ else if(options.addressRequirement == 'zipstreet') {
1473
+ $form.find('.address').addClass('only_zipstreet');
1474
+ $form.find('.city, .state').remove();
1475
+
1476
+ // Only remove country if no VAT support
1477
+ if(!R.settings.VATPercent) {
1478
+ $form.find('.country').remove();
1479
+ }
1480
+ }
1481
+ else if(options.addressRequirement == 'full') {
1482
+ $form.find('.address').addClass('full');
1483
+ }
1484
+ // == BUILD ACCEPTED CARDS DOM
1485
+ var $acceptedCards = $form.find('.accepted_cards');
1486
+
1487
+ if(options.acceptedCards) {
1488
+ var a = options.acceptedCards
1489
+ , l = a.length;
1490
+
1491
+ for(var i=0; i < l; ++i) {
1492
+ var cardId = a[i];
1493
+ var $card = $('<div class="card '+cardId+'">');
1494
+ var card = R.knownCards[cardId];
1495
+ if(card && card.name) {
1496
+ $card.text(card.name);
1497
+ }
1498
+ $acceptedCards.append($card);
1499
+ }
1500
+ }
1501
+
1502
+ // == SHOW/HIDE CARD TYPES
1503
+ $form.find('.card_number input').bind('change keyup', function() {
1504
+ var type = R.detectCardType( $(this).val() );
1505
+ if(type) {
1506
+ $acceptedCards.find('.card').each(function(){
1507
+ $(this).toggleClass('match', $(this).hasClass(type));
1508
+ $(this).toggleClass('no_match', !$(this).hasClass(type));
1509
+ });
1510
+ }
1511
+ else {
1512
+ $acceptedCards.find('.card').removeClass('match no_match');
1513
+ }
1514
+ });
1515
+ }
1516
+
1517
+
1518
+ function pullAccountFields($form, account, options, pull) {
1519
+ account.firstName = pull.field($form, '.contact_info .first_name', V(R.isNotEmpty));
1520
+ account.lastName = pull.field($form, '.contact_info .last_name', V(R.isNotEmpty));
1521
+ account.companyName = pull.field($form, '.contact_info .company_name');
1522
+ account.email = pull.field($form, '.email', V(R.isNotEmpty), V(R.isValidEmail));
1523
+ account.code = options.accountCode;
1524
+ }
1525
+
1526
+
1527
+ function pullBillingInfoFields($form, billingInfo, options, pull) {
1528
+ billingInfo.firstName = pull.field($form, '.billing_info .first_name', V(R.isNotEmpty));
1529
+ billingInfo.lastName = pull.field($form, '.billing_info .last_name', V(R.isNotEmpty));
1530
+ billingInfo.number = pull.field($form, '.card_number', V(R.isNotEmpty), V(R.isValidCC));
1531
+ billingInfo.cvv = pull.field($form, '.cvv', V(R.isNotEmpty), V(R.isValidCVV));
1532
+
1533
+ billingInfo.month = pull.field($form, '.month');
1534
+ billingInfo.year = pull.field($form, '.year');
1535
+
1536
+ billingInfo.phone = pull.field($form, '.phone');
1537
+ billingInfo.address1 = pull.field($form, '.address1', V(R.isNotEmpty));
1538
+ billingInfo.address2 = pull.field($form, '.address2');
1539
+ billingInfo.city = pull.field($form, '.city', V(R.isNotEmpty));
1540
+ billingInfo.state = pull.field($form, '.state', V(R.isNotEmptyState));
1541
+ billingInfo.zip = pull.field($form, '.zip', V(R.isNotEmpty));
1542
+ billingInfo.country = pull.field($form, '.country', V(R.isNotEmpty));
1543
+ }
1544
+
1545
+
1546
+ function pullPlanQuantity($form, plan, options, pull) {
1547
+ var qty = pull.field($form, '.plan .quantity', V(R.isValidQuantity));
1548
+ // An empty quantity field indicates 1
1549
+ plan.quantity = qty || 1;
1550
+ }
1551
+
1552
+
1553
+ function verifyTOSChecked($form, pull) {
1554
+ pull.field($form, '.accept_tos', V(R.isChecked));
1555
+ }
1556
+
1557
+
1558
+ R.buildBillingInfoUpdateForm = function(options) {
1559
+ var defaults = {
1560
+ addressRequirement: 'full'
1561
+ , distinguishContactFromBillingInfo: true
1562
+ };
1563
+
1564
+ options = $.extend(createObject(R.settings), defaults, options);
1565
+
1566
+ if(!options.accountCode) R.raiseError('accountCode missing');
1567
+ if(!options.signature) R.raiseError('signature missing');
1568
+
1569
+ var billingInfo = R.BillingInfo.create();
1570
+
1571
+ var $form = $(R.dom.update_billing_info_form);
1572
+ $form.find('.billing_info').html(R.dom.billing_info_fields);
1573
+
1574
+
1575
+ initCommonForm($form, options);
1576
+ initBillingInfoForm($form, options);
1577
+
1578
+
1579
+ $form.submit(function(e) {
1580
+ e.preventDefault();
1581
+
1582
+ clearServerErrors($form);
1583
+
1584
+ $form.find('.error').remove();
1585
+ $form.find('.invalid').removeClass('invalid');
1586
+
1587
+ validationGroup(function(puller) {
1588
+ pullBillingInfoFields($form, billingInfo, options, puller);
1589
+ }
1590
+ , function() {
1591
+ $form.addClass('submitting');
1592
+ $form.find('button.submit').attr('disabled', true).text('Please Wait');
1593
+
1594
+ billingInfo.save({
1595
+ signature: options.signature
1596
+ , distinguishContactFromBillingInfo: options.distinguishContactFromBillingInfo
1597
+ , accountCode: options.accountCode
1598
+ , success: function(response) {
1599
+ if(options.successHandler) {
1600
+ options.successHandler(R.getToken(response));
1601
+ }
1602
+
1603
+ if(options.successURL) {
1604
+ var url = options.successURL;
1605
+ R.postResult(url, response, options);
1606
+ }
1607
+ }
1608
+ , error: function(errors) {
1609
+ if(!options.onError || !options.onError(errors)) {
1610
+ displayServerErrors($form, errors);
1611
+ }
1612
+ }
1613
+ , complete: function() {
1614
+ $form.removeClass('submitting');
1615
+ $form.find('button.submit').removeAttr('disabled').text('Update');
1616
+ }
1617
+ });
1618
+ });
1619
+ });
1620
+
1621
+ if(options.beforeInject) {
1622
+ options.beforeInject($form.get(0));
1623
+ }
1624
+
1625
+ $(function() {
1626
+ var $container = $(options.target);
1627
+ $container.html($form);
1628
+
1629
+ if(options.afterInject) {
1630
+ options.afterInject($form.get(0));
1631
+ }
1632
+ });
1633
+
1634
+ };
1635
+
1636
+
1637
+ function initTOSCheck($form, options) {
1638
+
1639
+ if(options.termsOfServiceURL || options.privacyPolicyURL) {
1640
+ var $tos = $form.find('.accept_tos').html(R.dom.terms_of_service);
1641
+
1642
+ // If only one, remove 'and'
1643
+ if(!(options.termsOfServiceURL && options.privacyPolicyURL)) {
1644
+ $tos.find('span.and').remove();
1645
+ }
1646
+
1647
+ // set href or remove tos_link
1648
+ if(options.termsOfServiceURL) {
1649
+ $tos.find('a.tos_link').attr('href', options.termsOfServiceURL);
1650
+ }
1651
+ else {
1652
+ $tos.find('a.tos_link').remove();
1653
+ }
1654
+
1655
+ // set href or remove pp_link
1656
+ if(options.privacyPolicyURL) {
1657
+ $tos.find('a.pp_link').attr('href', options.privacyPolicyURL);
1658
+ }
1659
+ else {
1660
+ $tos.find('a.pp_link').remove();
1661
+ }
1662
+
1663
+ }
1664
+ else {
1665
+ $form.find('.accept_tos').remove();
1666
+ }
1667
+
1668
+ }
1669
+
1670
+ R.buildTransactionForm = function(options) {
1671
+ var defaults = {
1672
+ addressRequirement: 'full'
1673
+ , distinguishContactFromBillingInfo: true
1674
+ , collectContactInfo: true
1675
+ };
1676
+
1677
+ options = $.extend(createObject(R.settings), defaults, options);
1678
+
1679
+
1680
+ if(!options.collectContactInfo && !options.accountCode) {
1681
+ R.raiseError('collectContactInfo is false, but no accountCode provided');
1682
+ }
1683
+
1684
+ if(!options.signature) R.raiseError('signature missing');
1685
+
1686
+
1687
+ var billingInfo = R.BillingInfo.create()
1688
+ , account = R.Account.create()
1689
+ , transaction = R.Transaction.create();
1690
+
1691
+
1692
+ transaction.account = account;
1693
+ transaction.billingInfo = billingInfo;
1694
+ transaction.currency = options.currency;
1695
+ transaction.cost = new R.Cost(options.amountInCents);
1696
+
1697
+ var $form = $(R.dom.one_time_transaction_form);
1698
+ $form.find('.billing_info').html(R.dom.billing_info_fields);
1699
+
1700
+ if(options.collectContactInfo) {
1701
+ $form.find('.contact_info').html(R.dom.contact_info_fields);
1702
+ }
1703
+ else {
1704
+ $form.find('.contact_info').remove();
1705
+ }
1706
+
1707
+
1708
+ initCommonForm($form, options);
1709
+ initContactInfoForm($form, options);
1710
+ initBillingInfoForm($form, options);
1711
+ initTOSCheck($form, options);
1712
+
1713
+ $form.submit(function(e) {
1714
+ e.preventDefault();
1715
+
1716
+ clearServerErrors($form);
1717
+
1718
+ $form.find('.error').remove();
1719
+ $form.find('.invalid').removeClass('invalid');
1720
+
1721
+ validationGroup(function(puller) {
1722
+ pullAccountFields($form, account, options, puller);
1723
+ pullBillingInfoFields($form, billingInfo, options, puller);
1724
+ verifyTOSChecked($form, puller);
1725
+ }
1726
+ , function() {
1727
+ $form.addClass('submitting');
1728
+ $form.find('button.submit').attr('disabled', true).text('Please Wait');
1729
+
1730
+ transaction.save({
1731
+ signature: options.signature
1732
+ , accountCode: options.accountCode
1733
+ , success: function(response) {
1734
+ if(options.successHandler) {
1735
+ options.successHandler(R.getToken(response));
1736
+ }
1737
+
1738
+ if(options.successURL) {
1739
+ var url = options.successURL;
1740
+ R.postResult(url, response, options);
1741
+ }
1742
+ }
1743
+ , error: function(errors) {
1744
+ if(!options.onError || !options.onError(errors)) {
1745
+ displayServerErrors($form, errors);
1746
+ }
1747
+ }
1748
+ , complete: function() {
1749
+ $form.removeClass('submitting');
1750
+ $form.find('button.submit').removeAttr('disabled').text('Pay');
1751
+ }
1752
+ });
1753
+ });
1754
+ });
1755
+
1756
+ if(options.beforeInject) {
1757
+ options.beforeInject($form.get(0));
1758
+ }
1759
+
1760
+ $(function() {
1761
+ var $container = $(options.target);
1762
+ $container.html($form);
1763
+
1764
+ if(options.afterInject) {
1765
+ options.afterInject($form.get(0));
1766
+ }
1767
+ });
1768
+
1769
+ };
1770
+
1771
+
1772
+ R.buildSubscriptionForm = function(options) {
1773
+ var defaults = {
1774
+ enableAddOns: true
1775
+ , enableCoupons: true
1776
+ , addressRequirement: 'full'
1777
+ , distinguishContactFromBillingInfo: false
1778
+ };
1779
+
1780
+ options = $.extend(createObject(R.settings), defaults, options);
1781
+
1782
+ if(!options.signature) R.raiseError('signature missing');
1783
+
1784
+ var $form = $(R.dom.subscribe_form);
1785
+ $form.find('.contact_info').html(R.dom.contact_info_fields);
1786
+ $form.find('.billing_info').html(R.dom.billing_info_fields);
1787
+
1788
+
1789
+ if(options.planCode)
1790
+ R.Plan.get(options.planCode, options.currency, gotPlan);
1791
+ else if(options.plan) {
1792
+ // this should never be called
1793
+ // the api does not have it, nor does anywhere else in the program refer to it
1794
+ gotPlan(options.plan);
1795
+ }
1796
+
1797
+ initCommonForm($form, options);
1798
+ initContactInfoForm($form, options);
1799
+ initBillingInfoForm($form, options);
1800
+ initTOSCheck($form, options);
1801
+
1802
+ function gotPlan(plan) {
1803
+
1804
+ if(options.filterPlan)
1805
+ plan = options.filterPlan(plan) || plan;
1806
+
1807
+
1808
+ var subscription = plan.createSubscription(),
1809
+ account = R.Account.create(),
1810
+ billingInfo = R.BillingInfo.create();
1811
+
1812
+ subscription.account = account;
1813
+ subscription.billingInfo = billingInfo;
1814
+
1815
+ if(options.filterSubscription)
1816
+ subscription = options.filterSubscription(subscription) || subscription;
1817
+
1818
+ // == EDITABLE PLAN QUANTITY
1819
+ if(!plan.displayQuantity) {
1820
+ $form.find('.plan .quantity').remove();
1821
+ }
1822
+
1823
+ // == SETUP FEE
1824
+ if(plan.setupFee) {
1825
+ $form.find('.subscription').addClass('with_setup_fee');
1826
+ $form.find('.plan .setup_fee .cost').text('' + plan.setupFee);
1827
+ }
1828
+ else {
1829
+ $form.find('.plan .setup_fee').remove();
1830
+ }
1831
+
1832
+ // == FREE TRIAL
1833
+ if(plan.trial) {
1834
+ $form.find('.subscription').addClass('with_trial');
1835
+
1836
+ $form.find('.plan .free_trial').text('First ' + plan.trial + ' free');
1837
+ }
1838
+ else {
1839
+ $form.find('.plan .free_trial').remove();
1840
+ }
1841
+
1842
+
1843
+ // == UPDATE ALL UI TOTALS via subscription.calculateTotals() results
1844
+ function updateTotals() {
1845
+ var totals = subscription.calculateTotals();
1846
+
1847
+ $form.find('.plan .recurring_cost .cost').text('' + totals.plan);
1848
+ $form.find('.due_now .cost').text('' + totals.stages.now);
1849
+ $form.find('.coupon .discount').text('' + (totals.coupon || ''));
1850
+ $form.find('.vat .cost').text('' + (totals.vat || ''));
1851
+
1852
+ $form.find('.add_ons .add_on').each(function() {
1853
+ var addOn = $(this).data('add_on');
1854
+ if($(this).hasClass('selected')) {
1855
+ var cost = totals.addOns[addOn.code];
1856
+ $(this).find('.cost').text('+ '+cost);
1857
+ }
1858
+ else {
1859
+ $(this).find('.cost').text('+ '+addOn.cost);
1860
+ }
1861
+ });
1862
+ }
1863
+
1864
+ $form.find('.plan .quantity input').bind('change keyup', function() {
1865
+ subscription.plan.quantity = parseInt($(this).val(), 10) || 1;
1866
+ updateTotals();
1867
+ });
1868
+
1869
+ // == SUBSCRIPTION PLAN GENERAL
1870
+ $form.find('.plan .name').text(plan.name);
1871
+ $form.find('.plan .recurring_cost .cost').text(''+plan.cost);
1872
+ $form.find('.plan .recurring_cost .interval').text('every '+plan.interval);
1873
+
1874
+
1875
+ // == GENERATE ADD-ONS
1876
+ var $addOnsList = $form.find('.add_ons');
1877
+ if(options.enableAddOns) {
1878
+ var l = plan.addOns.length;
1879
+ if(l) {
1880
+ $addOnsList.removeClass('none').addClass('any');
1881
+ for(var i=0; i < l; ++i) {
1882
+ var addOn = plan.addOns[i];
1883
+
1884
+ var classAttr = 'add_on add_on_'+ addOn.code + (i % 2 ? ' even' : ' odd');
1885
+ if(i == 0) classAttr += ' first';
1886
+ if(i == l-1) classAttr += ' last';
1887
+
1888
+ var $addOn = $('<div class="'+classAttr+'">' +
1889
+ '<div class="name">'+addOn.name+'</div>' +
1890
+ '<div class="field quantity">' +
1891
+ '<div class="placeholder">Qty</div>' +
1892
+ '<input type="text">' +
1893
+ '</div>' +
1894
+ '<div class="cost"/>' +
1895
+ '</div>');
1896
+ if(!addOn.displayQuantity) {
1897
+ $addOn.find('.quantity').remove();
1898
+ }
1899
+ $addOn.data('add_on', addOn);
1900
+ $addOn.appendTo($addOnsList);
1901
+ }
1902
+
1903
+ // Quantity Change
1904
+ $addOnsList.delegate('.add_ons .quantity input', 'change keyup', function(e) {
1905
+ var $addOn = $(this).closest('.add_on');
1906
+ var addOn = $addOn.data('add_on');
1907
+ var newQty = parseInt($(this).val(),10) || 1;
1908
+ subscription.findAddOnByCode(addOn.code).quantity = newQty;
1909
+ updateTotals();
1910
+ });
1911
+
1912
+ $addOnsList.bind('selectstart', function(e) {
1913
+ if($(e.target).is('.add_on')) {
1914
+ e.preventDefault();
1915
+ }
1916
+ });
1917
+
1918
+ // Add-on click
1919
+ $addOnsList.delegate('.add_ons .add_on', 'click', function(e) {
1920
+ if($(e.target).closest('.quantity').length) return;
1921
+
1922
+ var selected = !$(this).hasClass('selected');
1923
+ $(this).toggleClass('selected', selected);
1924
+
1925
+ var addOn = $(this).data('add_on');
1926
+
1927
+ if(selected) {
1928
+ // add
1929
+ var sa = subscription.redeemAddOn(addOn);
1930
+ var $qty = $(this).find('.quantity input');
1931
+ sa.quantity = parseInt($qty.val(),10) || 1;
1932
+ $qty.focus();
1933
+ }
1934
+ else {
1935
+ // remove
1936
+ subscription.removeAddOn(addOn.code);
1937
+ }
1938
+
1939
+ updateTotals();
1940
+ });
1941
+ }
1942
+ }
1943
+ else {
1944
+ $addOnsList.remove();
1945
+ }
1946
+
1947
+ // == COUPON REDEEMER
1948
+ var $coupon = $form.find('.coupon');
1949
+ var lastCode = null;
1950
+
1951
+ function updateCoupon() {
1952
+
1953
+ var code = $coupon.find('input').val();
1954
+ if(code == lastCode) {
1955
+ return;
1956
+ }
1957
+
1958
+ lastCode = code;
1959
+
1960
+ if(!code) {
1961
+ $coupon.removeClass('invalid').removeClass('valid');
1962
+ $coupon.find('.description').text('');
1963
+ subscription.coupon = undefined;
1964
+ updateTotals();
1965
+ return;
1966
+ }
1967
+
1968
+ $coupon.addClass('checking');
1969
+ subscription.getCoupon(code, function(coupon) {
1970
+
1971
+ $coupon.removeClass('checking');
1972
+
1973
+ subscription.coupon = coupon;
1974
+ $coupon.removeClass('invalid').addClass('valid');
1975
+ $coupon.find('.description').text(coupon.description);
1976
+
1977
+ updateTotals();
1978
+ }, function() {
1979
+
1980
+ subscription.coupon = undefined;
1981
+
1982
+ $coupon.removeClass('checking');
1983
+ $coupon.removeClass('valid').addClass('invalid');
1984
+ $coupon.find('.description').text('Not Found');
1985
+
1986
+ updateTotals();
1987
+ });
1988
+ }
1989
+
1990
+ if(options.enableCoupons) {
1991
+ $coupon.find('input').bind('keyup change', function(e) {
1992
+ });
1993
+
1994
+ $coupon.find('input').keypress(function(e) {
1995
+ if(e.charCode == 13) {
1996
+ e.preventDefault();
1997
+ updateCoupon();
1998
+ }
1999
+ });
2000
+
2001
+
2002
+ $coupon.find('.check').click(function() {
2003
+ updateCoupon();
2004
+ });
2005
+
2006
+ $coupon.find('input').blur(function() { $coupon.find('.check').click(); });
2007
+ }
2008
+ else {
2009
+ $coupon.remove();
2010
+ }
2011
+
2012
+
2013
+ // == VAT
2014
+ var $vat = $form.find('.vat');
2015
+ var $vatNumber = $form.find('.vat_number');
2016
+ var $vatNumberInput = $vatNumber.find('input');
2017
+
2018
+ $vat.find('.title').text('VAT at ' + R.settings.VATPercent + '%');
2019
+ function showHideVAT() {
2020
+ var buyerCountry = $form.find('.country select').val();
2021
+ var vatNumberApplicable = R.isVATNumberApplicable(buyerCountry);
2022
+
2023
+ // VAT Number is applicable to collection in any EU country
2024
+ $vatNumber.toggleClass('applicable', vatNumberApplicable);
2025
+ $vatNumber.toggleClass('inapplicable', !vatNumberApplicable);
2026
+
2027
+ var vatNumber = $vatNumberInput.val();
2028
+
2029
+ // Only applicable to charge if isVATApplicable()
2030
+ var chargeApplicable = R.isVATChargeApplicable(buyerCountry, vatNumber);
2031
+ $vat.toggleClass('applicable', chargeApplicable);
2032
+ $vat.toggleClass('inapplicable', !chargeApplicable);
2033
+ }
2034
+ // showHideVAT();
2035
+ $form.find('.country select').change(function() {
2036
+ billingInfo.country = $(this).val();
2037
+ updateTotals();
2038
+ showHideVAT();
2039
+ }).change();
2040
+ $vatNumberInput.bind('keyup change', function() {
2041
+ billingInfo.vatNumber = $(this).val();
2042
+ updateTotals();
2043
+ showHideVAT();
2044
+ });
2045
+
2046
+ // SUBMIT HANDLER
2047
+ $form.submit(function(e) {
2048
+ e.preventDefault();
2049
+
2050
+ clearServerErrors($form);
2051
+
2052
+
2053
+ $form.find('.error').remove();
2054
+ $form.find('.invalid').removeClass('invalid');
2055
+
2056
+ validationGroup(function(puller) {
2057
+ pullPlanQuantity($form, subscription.plan, options, puller);
2058
+ pullAccountFields($form, account, options, puller);
2059
+ pullBillingInfoFields($form, billingInfo, options, puller);
2060
+ verifyTOSChecked($form, puller);
2061
+ }, function() {
2062
+
2063
+ $form.addClass('submitting');
2064
+ $form.find('button.submit').attr('disabled', true).text('Please Wait');
2065
+
2066
+ subscription.save({
2067
+
2068
+ signature: options.signature
2069
+ , success: function(response) {
2070
+ if(options.successHandler) {
2071
+ options.successHandler(R.getToken(response));
2072
+ }
2073
+ if(options.successURL) {
2074
+ var url = options.successURL;
2075
+ R.postResult(url, response, options);
2076
+ }
2077
+ }
2078
+ , error: function(errors) {
2079
+ if(!options.onError || !options.onError(errors)) {
2080
+ displayServerErrors($form, errors);
2081
+ }
2082
+ }
2083
+ , complete: function() {
2084
+ $form.removeClass('submitting');
2085
+ $form.find('button.submit').removeAttr('disabled').text('Subscribe');
2086
+ }
2087
+ });
2088
+ });
2089
+
2090
+ });
2091
+
2092
+ // FINALLY - UPDATE INITIAL TOTALS AND INJECT INTO DOM
2093
+ updateTotals();
2094
+
2095
+ if(options.beforeInject) {
2096
+ options.beforeInject($form.get(0));
2097
+ }
2098
+
2099
+ $(function() {
2100
+ var $container = $(options.target);
2101
+ $container.html($form);
2102
+
2103
+ if(options.afterInject) {
2104
+ options.afterInject($form.get(0));
2105
+ }
2106
+ });
2107
+
2108
+ }
2109
+
2110
+ };
2111
+
2112
+
2113
+
2114
+ //////////////////////////////////////////////////
2115
+ // Compiled from src/js/states.js
2116
+ //////////////////////////////////////////////////
2117
+
2118
+ R.states = {};
2119
+ R.states.US = {
2120
+ "-": "Select State"
2121
+ , "--": "------------"
2122
+ , "AK": "Alaska"
2123
+ , "AL": "Alabama"
2124
+ , "AP": "Armed Forces Pacific"
2125
+ , "AR": "Arkansas"
2126
+ , "AS": "American Samoa"
2127
+ , "AZ": "Arizona"
2128
+ , "CA": "California"
2129
+ , "CO": "Colorado"
2130
+ , "CT": "Connecticut"
2131
+ , "DC": "District of Columbia"
2132
+ , "DE": "Delaware"
2133
+ , "FL": "Florida"
2134
+ , "FM": "Federated States of Micronesia"
2135
+ , "GA": "Georgia"
2136
+ , "GU": "Guam"
2137
+ , "HI": "Hawaii"
2138
+ , "IA": "Iowa"
2139
+ , "ID": "Idaho"
2140
+ , "IL": "Illinois"
2141
+ , "IN": "Indiana"
2142
+ , "KS": "Kansas"
2143
+ , "KY": "Kentucky"
2144
+ , "LA": "Louisiana"
2145
+ , "MA": "Massachusetts"
2146
+ , "MD": "Maryland"
2147
+ , "ME": "Maine"
2148
+ , "MH": "Marshall Islands"
2149
+ , "MI": "Michigan"
2150
+ , "MN": "Minnesota"
2151
+ , "MO": "Missouri"
2152
+ , "MP": "Northern Mariana Islands"
2153
+ , "MS": "Mississippi"
2154
+ , "MT": "Montana"
2155
+ , "NC": "North Carolina"
2156
+ , "ND": "North Dakota"
2157
+ , "NE": "Nebraska"
2158
+ , "NH": "New Hampshire"
2159
+ , "NJ": "New Jersey"
2160
+ , "NM": "New Mexico"
2161
+ , "NV": "Nevada"
2162
+ , "NY": "New York"
2163
+ , "OH": "Ohio"
2164
+ , "OK": "Oklahoma"
2165
+ , "OR": "Oregon"
2166
+ , "PA": "Pennsylvania"
2167
+ , "PR": "Puerto Rico"
2168
+ , "PW": "Palau"
2169
+ , "RI": "Rhode Island"
2170
+ , "SC": "South Carolina"
2171
+ , "SD": "South Dakota"
2172
+ , "TN": "Tennessee"
2173
+ , "TX": "Texas"
2174
+ , "UT": "Utah"
2175
+ , "VA": "Virginia"
2176
+ , "VI": "Virgin Islands"
2177
+ , "VT": "Vermont"
2178
+ , "WA": "Washington"
2179
+ , "WV": "West Virginia"
2180
+ , "WI": "Wisconsin"
2181
+ , "WY": "Wyoming"
2182
+ };
2183
+
2184
+ R.states.CA = {
2185
+ "-": "Select State"
2186
+ , "--": "------------"
2187
+ , "AB": "Alberta"
2188
+ , "BC": "British Columbia"
2189
+ , "MB": "Manitoba"
2190
+ , "NB": "New Brunswick"
2191
+ , "NL": "Newfoundland"
2192
+ , "NS": "Nova Scotia"
2193
+ , "NU": "Nunavut"
2194
+ , "ON": "Ontario"
2195
+ , "PE": "Prince Edward Island"
2196
+ , "QC": "Quebec"
2197
+ , "SK": "Saskatchewan"
2198
+ , "NT": "Northwest Territories"
2199
+ , "YT": "Yukon Territory"
2200
+ , "AA": "Armed Forces Americas"
2201
+ , "AE": "Armed Forces Europe, Middle East, &amp; Canada"
2202
+ };
2203
+
2204
+
2205
+
2206
+
2207
+ //////////////////////////////////////////////////
2208
+ // Compiled from src/dom/contact_info_fields.jade
2209
+ //////////////////////////////////////////////////
2210
+
2211
+ R.dom['contact_info_fields'] = '<div class="title">Contact Info</div><div class="full_name"><div class="field first_name"><div class="placeholder">First Name </div><input type="text"/></div><div class="field last_name"><div class="placeholder">Last Name </div><input type="text"/></div></div><div class="field email"><div class="placeholder">Email </div><input type="text"/></div><div class="field phone"><div class="placeholder">Phone Number</div><input type="text"/></div><div class="field company_name"><div class="placeholder">Company/Organization Name</div><input type="text"/></div>';
2212
+
2213
+ //////////////////////////////////////////////////
2214
+ // Compiled from src/dom/billing_info_fields.jade
2215
+ //////////////////////////////////////////////////
2216
+
2217
+ R.dom['billing_info_fields'] = '<div class="title">Billing Info</div><div class="accepted_cards"></div><div class="credit_card"><div class="field first_name"><div class="placeholder">First Name </div><input type="text"/></div><div class="field last_name"><div class="placeholder">Last Name </div><input type="text"/></div><div class="card_cvv"><div class="field card_number"><div class="placeholder">Credit Card Number </div><input type="text"/></div><div class="field cvv"><div class="placeholder">CVV </div><input type="text"/></div></div><div class="field expires"><div class="title">Expires </div><div class="month"><select><option value="1">01 - January</option><option value="2">02 - February</option><option value="3">03 - March</option><option value="4">04 - April</option><option value="5">05 - May</option><option value="6">06 - June</option><option value="7">07 - July</option><option value="8">08 - August</option><option value="9">09 - September</option><option value="10">10 - October</option><option value="11">11 - November</option><option value="12">12 - December</option></select></div><div class="year"><select></select></div></div></div><div class="address"><div class="field address1"><div class="placeholder">Address</div><input type="text"/></div><div class="field address2"><div class="placeholder">Apt/Suite</div><input type="text"/></div><div class="field city"><div class="placeholder">City</div><input type="text"/></div><div class="state_zip"><div class="field state"><div class="placeholder">State/Province</div><input type="text"/></div><div class="field zip"><div class="placeholder">Zip/Postal</div><input type="text"/></div></div><div class="field country"><select><option value="-">Select Country</option><option value="-">--------------</option><option value="AF">Afghanistan</option><option value="AL">Albania</option><option value="DZ">Algeria</option><option value="AS">American Samoa</option><option value="AD">Andorra</option><option value="AO">Angola</option><option value="AI">Anguilla</option><option value="AQ">Antarctica</option><option value="AG">Antigua and Barbuda</option><option value="AR">Argentina</option><option value="AM">Armenia</option><option value="AW">Aruba</option><option value="AC">Ascension Island</option><option value="AU">Australia</option><option value="AT">Austria</option><option value="AZ">Azerbaijan</option><option value="BS">Bahamas</option><option value="BH">Bahrain</option><option value="BD">Bangladesh</option><option value="BB">Barbados</option><option value="BE">Belgium</option><option value="BZ">Belize</option><option value="BJ">Benin</option><option value="BM">Bermuda</option><option value="BT">Bhutan</option><option value="BO">Bolivia</option><option value="BA">Bosnia and Herzegovina</option><option value="BW">Botswana</option><option value="BV">Bouvet Island</option><option value="BR">Brazil</option><option value="BQ">British Antarctic Territory</option><option value="IO">British Indian Ocean Territory</option><option value="VG">British Virgin Islands</option><option value="BN">Brunei</option><option value="BG">Bulgaria</option><option value="BF">Burkina Faso</option><option value="BI">Burundi</option><option value="KH">Cambodia</option><option value="CM">Cameroon</option><option value="CA">Canada</option><option value="IC">Canary Islands</option><option value="CT">Canton and Enderbury Islands</option><option value="CV">Cape Verde</option><option value="KY">Cayman Islands</option><option value="CF">Central African Republic</option><option value="EA">Ceuta and Melilla</option><option value="TD">Chad</option><option value="CL">Chile</option><option value="CN">China</option><option value="CX">Christmas Island</option><option value="CP">Clipperton Island</option><option value="CC">Cocos [Keeling] Islands</option><option value="CO">Colombia</option><option value="KM">Comoros</option><option value="CD">Congo [DRC]</option><option value="CK">Cook Islands</option><option value="CR">Costa Rica</option><option value="HR">Croatia</option><option value="CU">Cuba</option><option value="CY">Cyprus</option><option value="CZ">Czech Republic</option><option value="DK">Denmark</option><option value="DG">Diego Garcia</option><option value="DJ">Djibouti</option><option value="DM">Dominica</option><option value="DO">Dominican Republic</option><option value="NQ">Dronning Maud Land</option><option value="DD">East Germany</option><option value="TL">East Timor</option><option value="EC">Ecuador</option><option value="EG">Egypt</option><option value="SV">El Salvador</option><option value="EE">Estonia</option><option value="ET">Ethiopia</option><option value="EU">European Union</option><option value="FK">Falkland Islands [Islas Malvinas]</option><option value="FO">Faroe Islands</option><option value="FJ">Fiji</option><option value="FI">Finland</option><option value="FR">France</option><option value="GF">French Guiana</option><option value="PF">French Polynesia</option><option value="TF">French Southern Territories</option><option value="FQ">French Southern and Antarctic Territories</option><option value="GA">Gabon</option><option value="GM">Gambia</option><option value="GE">Georgia</option><option value="DE">Germany</option><option value="GH">Ghana</option><option value="GI">Gibraltar</option><option value="GR">Greece</option><option value="GL">Greenland</option><option value="GD">Grenada</option><option value="GP">Guadeloupe</option><option value="GU">Guam</option><option value="GT">Guatemala</option><option value="GG">Guernsey</option><option value="GW">Guinea-Bissau</option><option value="GY">Guyana</option><option value="HT">Haiti</option><option value="HM">Heard Island and McDonald Islands</option><option value="HN">Honduras</option><option value="HK">Hong Kong</option><option value="HU">Hungary</option><option value="IS">Iceland</option><option value="IN">India</option><option value="ID">Indonesia</option><option value="IE">Ireland</option><option value="IM">Isle of Man</option><option value="IL">Israel</option><option value="IT">Italy</option><option value="JM">Jamaica</option><option value="JP">Japan</option><option value="JE">Jersey</option><option value="JT">Johnston Island</option><option value="JO">Jordan</option><option value="KZ">Kazakhstan</option><option value="KE">Kenya</option><option value="KI">Kiribati</option><option value="KW">Kuwait</option><option value="KG">Kyrgyzstan</option><option value="LA">Laos</option><option value="LV">Latvia</option><option value="LS">Lesotho</option><option value="LY">Libya</option><option value="LI">Liechtenstein</option><option value="LT">Lithuania</option><option value="LU">Luxembourg</option><option value="MO">Macau</option><option value="MK">Macedonia [FYROM]</option><option value="MG">Madagascar</option><option value="MW">Malawi</option><option value="MY">Malaysia</option><option value="MV">Maldives</option><option value="ML">Mali</option><option value="MT">Malta</option><option value="MH">Marshall Islands</option><option value="MQ">Martinique</option><option value="MR">Mauritania</option><option value="MU">Mauritius</option><option value="YT">Mayotte</option><option value="FX">Metropolitan France</option><option value="MX">Mexico</option><option value="FM">Micronesia</option><option value="MI">Midway Islands</option><option value="MD">Moldova</option><option value="MC">Monaco</option><option value="MN">Mongolia</option><option value="ME">Montenegro</option><option value="MS">Montserrat</option><option value="MA">Morocco</option><option value="MZ">Mozambique</option><option value="NA">Namibia</option><option value="NR">Nauru</option><option value="NP">Nepal</option><option value="NL">Netherlands</option><option value="AN">Netherlands Antilles</option><option value="NT">Neutral Zone</option><option value="NC">New Caledonia</option><option value="NZ">New Zealand</option><option value="NI">Nicaragua</option><option value="NE">Niger</option><option value="NG">Nigeria</option><option value="NU">Niue</option><option value="NF">Norfolk Island</option><option value="VD">North Vietnam</option><option value="MP">Northern Mariana Islands</option><option value="NO">Norway</option><option value="OM">Oman</option><option value="QO">Outlying Oceania</option><option value="PC">Pacific Islands Trust Territory</option><option value="PK">Pakistan</option><option value="PW">Palau</option><option value="PS">Palestinian Territories</option><option value="PA">Panama</option><option value="PZ">Panama Canal Zone</option><option value="PY">Paraguay</option><option value="YD">People\'s Democratic Republic of Yemen</option><option value="PE">Peru</option><option value="PH">Philippines</option><option value="PN">Pitcairn Islands</option><option value="PL">Poland</option><option value="PT">Portugal</option><option value="PR">Puerto Rico</option><option value="QA">Qatar</option><option value="RO">Romania</option><option value="RU">Russia</option><option value="RW">Rwanda</option><option value="RE">R\u00e9union</option><option value="BL">Saint Barth\u00e9lemy</option><option value="SH">Saint Helena</option><option value="KN">Saint Kitts and Nevis</option><option value="LC">Saint Lucia</option><option value="MF">Saint Martin</option><option value="PM">Saint Pierre and Miquelon</option><option value="VC">Saint Vincent and the Grenadines</option><option value="WS">Samoa</option><option value="SM">San Marino</option><option value="SA">Saudi Arabia</option><option value="SN">Senegal</option><option value="RS">Serbia</option><option value="CS">Serbia and Montenegro</option><option value="SC">Seychelles</option><option value="SL">Sierra Leone</option><option value="SG">Singapore</option><option value="SK">Slovakia</option><option value="SI">Slovenia</option><option value="SB">Solomon Islands</option><option value="ZA">South Africa</option><option value="GS">South Georgia and the South Sandwich Islands</option><option value="KR">South Korea</option><option value="ES">Spain</option><option value="LK">Sri Lanka</option><option value="SR">Suriname</option><option value="SJ">Svalbard and Jan Mayen</option><option value="SZ">Swaziland</option><option value="SE">Sweden</option><option value="CH">Switzerland</option><option value="ST">S\u00e3o Tom\u00e9 and Pr\u00edncipe</option><option value="TW">Taiwan</option><option value="TJ">Tajikistan</option><option value="TZ">Tanzania</option><option value="TH">Thailand</option><option value="TG">Togo</option><option value="TK">Tokelau</option><option value="TO">Tonga</option><option value="TT">Trinidad and Tobago</option><option value="TA">Tristan da Cunha</option><option value="TN">Tunisia</option><option value="TR">Turkey</option><option value="TM">Turkmenistan</option><option value="TC">Turks and Caicos Islands</option><option value="TV">Tuvalu</option><option value="UM">U.S. Minor Outlying Islands</option><option value="PU">U.S. Miscellaneous Pacific Islands</option><option value="VI">U.S. Virgin Islands</option><option value="UG">Uganda</option><option value="UA">Ukraine</option><option value="AE">United Arab Emirates</option><option value="GB">United Kingdom</option><option value="US">United States</option><option value="UY">Uruguay</option><option value="UZ">Uzbekistan</option><option value="VU">Vanuatu</option><option value="VA">Vatican City</option><option value="VE">Venezuela</option><option value="VN">Vietnam</option><option value="WK">Wake Island</option><option value="WF">Wallis and Futuna</option><option value="EH">Western Sahara</option><option value="YE">Yemen</option><option value="ZM">Zambia</option><option value="AX">\u00c5land Islands</option></select></div></div><div class="field vat_number"><div class="placeholder">VAT Number</div><input type="text"/></div>';
2218
+
2219
+ //////////////////////////////////////////////////
2220
+ // Compiled from src/dom/subscribe_form.jade
2221
+ //////////////////////////////////////////////////
2222
+
2223
+ R.dom['subscribe_form'] = '<form class="recurly subscribe"><!--[if lt IE 7]><div class="iefail"><div class="chromeframe"><p class="blast">Your browser is not supported by Recurly.js.</p><p><a href="http://browsehappy.com/">Upgrade to a different browser</a></p><p>or</p><p><a href="http://www.google.com/chromeframe/?redirect=true">install Google Chrome Frame</a></p><p>to use this site.</p></div></div><![endif]--><div class="subscription"><div class="plan"><div class="name"></div><div class="field quantity"><div class="placeholder">Qty</div><input type="text"/></div><div class="recurring_cost"><div class="cost"></div><div class="interval"></div></div><div class="free_trial"></div><div class="setup_fee"><div class="title">Setup Fee</div><div class="cost"></div></div></div><div class="add_ons none"></div><div class="coupon"><div class="coupon_code field"><div class="placeholder">Coupon Code</div><input type="text" class="coupon_code"/></div><div class="check"></div><div class="description"></div><div class="discount"></div></div><div class="vat"><div class="title">VAT</div><div class="cost"></div></div></div><div class="due_now"><div class="title">Order Total</div><div class="cost"></div></div><div class="server_errors none"></div><div class="contact_info"></div><div class="billing_info"></div><div class="accept_tos"></div><div class="footer"><button type="submit" class="submit">Subscribe</button></div></form>';
2224
+
2225
+ //////////////////////////////////////////////////
2226
+ // Compiled from src/dom/update_billing_info_form.jade
2227
+ //////////////////////////////////////////////////
2228
+
2229
+ R.dom['update_billing_info_form'] = '<form class="recurly update_billing_info"><div class="server_errors none"></div><div class="billing_info"></div><div class="footer"><button type="submit" class="submit">Update</button></div></form>';
2230
+
2231
+ //////////////////////////////////////////////////
2232
+ // Compiled from src/dom/one_time_transaction_form.jade
2233
+ //////////////////////////////////////////////////
2234
+
2235
+ R.dom['one_time_transaction_form'] = '<form class="recurly update_billing_info"><div class="server_errors none"></div><div class="contact_info"></div><div class="billing_info"></div><div class="accept_tos"></div><div class="footer"><button type="submit" class="submit">Pay</button></div></form>';
2236
+
2237
+ //////////////////////////////////////////////////
2238
+ // Compiled from src/dom/terms_of_service.jade
2239
+ //////////////////////////////////////////////////
2240
+
2241
+ R.dom['terms_of_service'] = '<input id="tos_check" type="checkbox"/><label id="accept_tos" for="tos_check">I accept the <a target="_blank" class="tos_link">Terms of Service</a><span class="and"> and </span><a target="_blank" class="pp_link">Privacy Policy</a></label>';
2242
+ window.Recurly = R;
2243
+ })(jQuery);