recurly-js-rails 0.0.2

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,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);