Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

official public release

  • Loading branch information...
commit d82c3d9c8f3dc7b2a95ddbeba53505cb96553581 1 parent f96f00c
@em em authored
View
60 README.md
@@ -1,30 +1,34 @@
# Recurly.js
-Recurly.js makes it easy to provide the user experience available on our hosted payment pages, on your site, with complete control over the look and feel, outside of PCI scope.
+Recurly.js is an open-source Javascript library for creating great looking credit card forms to securely create subscriptions, one-time transactions, and update billing information using Recurly. The library is designed to create fully customizable order forms while minimizing your PCI compliance scope.
-*Currently depends on jQuery version 1.5.2 or higher.*
+### Dynamic Total Calculation and Error Handling
-# Why you should use it
-Don't reinvent the wheel. The fundamentals of paying for subscriptions doesn't change across implementations, there's got to be one approach that gets it right for everyone. We aim to be that solution. The one thing that does change, however, is design: your website has its own look, and we want you to keep it. This is why we created Recurly.js to handle all of the hard work, leaving you with the sole task of styling to fit your design. And with the help of [stylus](/LearnBoost/stylus), that couldn't be easier.
+The library performs client-side validation of cardholder data, immediate pricing calculations for add-ons and Value Added Tax (VAT), and coupon validation. The library handles transaction failures gracefully. Should a transaction be declined, the library automatically highlights the appropriate fields and displays proper error messages for your customers.
+### PCI Compliance
-# How it works
+Recurly.js simplifies PCI compliance for Recurly merchants. After performing client-side validation on the cardholder data, the library securely submits the order details directly to Recurly. Because the sensitive cardholder data is never transmitted to your web servers, your PCI compliance scope is dramatically reduced. This allows you to host the credit card order forms on your website without the headaches of PCI compliance.
-Recurly.js comes with:
+### Fully Customizable CSS
-1. A little script that:
- * Builds the DOM of a well-structured subscription form
- * Performs the same tricky client-side total calculations on our hosted pages. (quantities,addons,coupons,vat,etc..)
- * Does inline validation, and server-side validation.
- * Talks to Recurly (through a JSONP API), pulling down plan criteria, and creating subscriptions.
-
-2. A stock stylesheet that is also very similar to our hosted payment pages. It is coded in stylus, and comes with the css compilations.
- * Take this stylesheet and tweak it to your heart's content to match the look and feel of your design.
+Recurly.js is designed to be fully customized to fit within your website. To help get you started, this library includes a sample stylesheet that resembles Recurly's hosted payment pages. We use [stylus](https://github.com/LearnBoost/stylus) to create the CSS.
+__Learn more:__ View the Recurly.js [intro video and examples](http://js.recurly.com) and [documentation](http://docs.recurly.com/recurlyjs/overview).
+
+
+# In the Project
+
+Recurly.js includes:
+
+* A Javscript library (_recurly.js_) for creating well-structured forms with validation and error handling
+* A stock stylesheet (_recurly.css_)
+* [stylus](https://github.com/LearnBoost/stylus) source for customizing the stylesheet (_recurly.styl_)
+* And examples for creating subscriptions, one time transactions, and updating billing information
# Getting Started
-Accepting subscriptions is as simple as dropping in this js:
+Accepting subscriptions is as simple as dropping in this Javascript:
```javascript
Recurly.config({
@@ -35,10 +39,12 @@ Recurly.config({
Recurly.buildSubscriptionForm({
target: '#subscribe', // A jQuery selector for the container element to append the form to
planCode: 'myplancode' // A plan you have created in recurly-app
- successURL: '/success?account_code={account_code}' // Redirect on success URL
+ successURL: '/success' // Redirect on success URL
});
```
+View our [documentation](http://docs.recurly.com/recurlyjs/overview) for more details.
+
## Additional Options
```javascript
Recurly.config({
@@ -78,30 +84,28 @@ Recurly.buildSubscriptionForm({
```
## Customizing the style
+
A stock stylesheet is provided that is coded in [stylus](/LearnBoost/stylus), a wonderful language that compiles to CSS.
Stylus is officially implemented in node.js, but you don't need to have a node app to use it. You can install node and <code>npm install stylus</code>, then use the <code>stylus</code> command-line to compile to CSS. There is also a Ruby on Rails port of stylus, [stylus_rails](/lucasmazza/stylus_rails).
Alternatively, you could modify the compiled css and ignore the stylus source. But this is heavily discouraged. It's much easier to get accustom to stylus, than to attempt to work with the compiled CSS which has lost all of the original structure that stylus provides. Give it a try, it's worth it.
-The first thing you'll want to do is take a look at the variables defined at the top. You'll notice that the default stylesheets is all centered around defined grid system dimensions, making customization a breeze.
+The default stylesheet is designed around the grid system. You will notice the default grid variables at the top of _recurly.styl_.
# Responding to subscription creates
-Once the user subscribes through the recurly.js form on your site, you have to act accordingly with your respective business logic. (giving your users what it is they just paid for)
-The easiest way to do this is by simply passing a <code>successURL</code> option to buildSubscribeForm.
-When the user's credit card is processed successfully, recurly.js will redirect to successURL replacing <code>{account_code}</code> with the newly created account.
-
-All you have to do is have your server read the GET variable, pull down the account from Recurly with one of our client libraries, and act accordingly giving them what they paid for.
+Once the subscription is successfully started, Recurly.js will POST to `successURL`. The parameters are signed by Recurly for validation. Using the client library, you should validate the results and start the subscription. Alternatively, you may skip the validation and simply use the API to query the account's subscription status.
Alternatively, you can pass in an option to buildSubscribeForm, <code>afterSubscribe</code>, to handle subscription creates.
-# Caveats
-You will still need to use one of our existing server-side client libraries to pull down the account after it's been created, and act accordingly. But that's the easy part. The hard part in the past has been building out subscription UX and mediating errors.
+# Additional Requirements
+
+This library depends on jQuery 1.5.2+. A future version may be framework agnostic.
-It currently depends on jQuery 1.5.2+. Not a problem if you already use it. A future version of the library may be framework agnostic.
+You will need a Recurly client library in order to sign the protected fields for one-time transaction and billing info updates. Today, our [PHP](https://github.com/recurly/recurly-client-php) and [Ruby](https://github.com/recurly/recurly-client-ruby) clients have support for creating Recurly.js signatures. A client library is also necessary for performing other actions, such as retrieving account information, upgrading or downgrading a subscription, etc.
-# Soon To Come
+# Coming Soon
-* Multi-currency
-* Localization (english only right now)
+* Multi-currency (Supporting more than one currency per merchant)
+* Multi-lingual support (English only today)
View
9 examples/examples.css
@@ -26,6 +26,12 @@ p {
line-height: 26px;
text-align: justify;
}
+pre {
+ margin: 20px;
+ padding: 20px;
+ background: #000;
+ color: #fff;
+}
code {
font-family: menlo, monaco, "Lucida Console", monospace;
padding: 2px 4px;
@@ -40,7 +46,8 @@ a {
color: inherit;
font-weight: bold;
}
-#index {
+#index,
+#result {
width: 480px;
margin: 0 auto;
}
View
35 examples/gridsystem.html
@@ -9,29 +9,19 @@
<script src="lib/jquery-1.6.1.js"></script>
<script src="../recurly.js"></script>
<script>
- $(function() {
- Recurly.config({
- environment: 'sandbox'
- , subdomain: 'recurlyjsdemo-test'
- , currency: 'GBP'
- , country: 'GB'
- , VATPercent: 10
- });
-
- Recurly.buildSubscriptionForm({
- target: '#recurly-subscribe'
- , planCode: 'complexplan'
- , addressRequirement: 'full'
- , successURL: 'confirmation.html?account_code={account_code}'
- });
+ Recurly.config({
+ environment: 'sandbox'
+ , subdomain: 'recurlyjsdemo-test'
+ , currency: 'USD'
+ , country: 'US'
+ , VATPercent: 10
+ });
- $('#test').click(function() {
- $('.first_name input').val('joe').change();
- $('.last_name input').val('user').change();
- $('.email input').val('joeuser@example.com').change();
- $('.card_number input').val('4111-1111-1111-1111').change();
- $('.cvv input').val('123').change();
- });
+ Recurly.buildSubscriptionForm({
+ target: '#recurly-subscribe'
+ , planCode: 'simpleplan'
+ , addressRequirement: 'full'
+ , successURL: 'confirmation.html?{*}'
});
</script>
@@ -74,7 +64,6 @@
<div id="demo480">480px</div>
<div id="demo960">960px</div>
- <button id="test">test</button>
</div>
</div>
</div>
View
2  examples/index.html
@@ -22,7 +22,7 @@
<p>The demo company defines two plans: <code>simpleplan</code>, and <code>complexplan</code>, and
a forever 30% off coupon, named <code>test</code>.</p>
- <p>Update Billing Info, and One-time Transactions both require <a href="vv">parameter signatures</a>, which must be generated server-side and rendered to an option. For demonstration purposes we pre&#8209;computed the signatures and hardcoded them in.</p>
+ <p>Update Billing Info, and One-time Transactions both require <a href="http://docs.recurly.com/recurlyjs/signatures/">signatures</a>, which must be generated server-side and rendered to an option. For demonstration purposes we pre&#8209;computed the signatures and hardcoded them in.</p>
</div>
</body>
</html>
View
11 examples/one_time_transaction.html
@@ -12,23 +12,22 @@
Recurly.config({
environment: 'sandbox'
, subdomain: 'recurlyjsdemo-test'
- , baseURL: 'http://api-sandbox.lvh.me:3000/jsonp/emeryco-test/'
, currency: 'USD'
, country: 'US'
- , VATPercent: 10
});
Recurly.buildTransactionForm({
target: '#recurly-transaction'
- , accountCode: 'testaccount'
, amountInCents: 5000
- , successURL: 'confirmation.html?account_code={account_code}&signature={signature}'
- , signature: 'f37ecbd8556f936161d3b9a76cf9291ed46a3000-1312409907'
+ , successURL: 'confirmation.html?acc={account_code}'
+ , signature: '83634a130e340191abca714d7391da48ece7bfd2-0'
// Signature must be generated server-side with sign_transaction()
// Without it the request will be rejected.
+ //
+ // The example signature only works with recurlyjsdemo-test
//
// !!! IMPORTANT !!!
- // You must use verify_transaction() with the GET variables passed to successURL
+ // You must use verify_transaction() with the POST params passed to successURL
// http://docs.recurly.com/recurlyjs/signatures/
});
</script>
View
28 examples/subscribe.html
@@ -9,22 +9,20 @@
<script src="lib/jquery-1.6.1.js"></script>
<script src="../recurly.js"></script>
<script>
- $(function() {
- Recurly.config({
- environment: 'sandbox'
- , subdomain: 'recurlyjsdemo-test'
- , currency: 'GBP'
- , country: 'GB'
- , VATPercent: 10
- });
+ Recurly.config({
+ environment: 'sandbox'
+ , subdomain: 'recurlyjsdemo-test'
+ , currency: 'GBP'
+ , country: 'GB'
+ , VATPercent: 10
+ });
- Recurly.buildSubscriptionForm({
- target: '#recurly-subscribe'
- , planCode: 'simpleplan'
- , addressRequirement: 'zipstreet'
- , successURL: 'confirmation.html?account_code={account_code}'
- , distinguishContactFromBillingInfo: true
- });
+ Recurly.buildSubscriptionForm({
+ target: '#recurly-subscribe'
+ , planCode: 'simpleplan'
+ , addressRequirement: 'zipstreet'
+ , successURL: 'confirmation.html'
+ , distinguishContactFromBillingInfo: true
});
</script>
</head>
View
9 examples/update_billing_info.html
@@ -12,21 +12,22 @@
Recurly.config({
environment: 'sandbox'
, subdomain: 'recurlyjsdemo-test'
- /* , baseURL: 'http://api-sandbox.lvh.me:3000/jsonp/emeryco-test/' */
, currency: 'GBP'
, country: 'GB'
, VATPercent: 10
});
- Recurly.buildUpdateBillingInfoForm({
+ Recurly.buildBillingInfoUpdateForm({
target: '#recurly-update-billing-info'
, accountCode: 'testaccount'
- , successURL: 'confirmation.html?account_code={account_code}'
- , signature: '04e2f13f2db4f7cfc87d915eee94870fc3e15caa-1312409866'
+ , successURL: 'confirmation.html'
+ , signature: '1970639b53c29e9ed3f0a5c7574751caa1b30553-0'
// Signature must be generated server-side with a utility method provided in client libraries.
// e.g. Recurly::billing_info_signature(accountcode)
// It guarantees that you grant the end-user the right to update billing info for this account
// Without it the request will be rejected.
+ //
+ // The example signature only works for recurlyjsdemo-test
});
</script>
View
105 recurly.js
@@ -195,13 +195,13 @@ R.RecurringCost.FREE = new R.RecurringCost(0,null);
R.locale = {};
R.locale.errors = {
- emptyField: 'Forget something?'
+ emptyField: 'Required field'
, missingFullAddress: 'Please enter your full address.'
-, invalidEmail: 'This doesn\'t look right.'
-, invalidCC: 'This doesn\'t look right.'
-, invalidCVV: 'This doesn\'t look right.'
-, invalidCoupon: 'Coupon not found'
-, cardDeclined: 'Sorry, your card was declined.'
+, invalidEmail: 'Invalid'
+, invalidCC: 'Invalid'
+, invalidCVV: 'Invalid'
+, invalidCoupon: 'Invalid'
+, cardDeclined: 'Transaction declined'
};
R.locale.currencies = {};
@@ -428,21 +428,40 @@ R.flattenErrors = function(obj, attr) {
R.replaceVars = function(str, vars) {
- var all = [];
for(var k in vars) {
if(vars.hasOwnProperty(k)) {
- var v = vars[k];
-
+ var v = encodeURIComponent(vars[k]);
str = str.replace(new RegExp('\\{'+k+'\\}', 'g'), v);
- all.push(k + '=' + v);
}
}
- str = str.replace(/\{\*\}/g, all.join('&'));
-
return str;
};
+R.post = function(url, params, urlEncoded) {
+ var form = $('<form />').hide();
+ form.attr('action', url)
+ .attr('method', 'POST')
+ .attr('enctype', urlEncoded ? 'application/x-www-form-urlencoded' : 'multipart/form-data');
+
+ function addParam(name, value, parent) {
+ var fullname = (parent.length > 0 ? (parent + '[' + name + ']') : name);
+ if(typeof value === 'object') {
+ for(var i in value) {
+ if(value.hasOwnProperty(i)) {
+ addParam(i, value[i], fullname);
+ }
+ }
+ }
+ else $('<input type="hidden" />').attr({name: fullname, value: value}).appendTo(form);
+ };
+
+ addParam('', params, '');
+
+ $('body').append(form);
+ form.submit();
+};
+
@@ -879,7 +898,7 @@ R.Transaction = {
};
$.ajax({
- url: R.settings.baseURL+'accounts/'+options.accountCode+'/transactions/create'
+ url: R.settings.baseURL+'transactions/create'
, data: json
, dataType: 'jsonp'
, jsonp: 'callback'
@@ -1168,7 +1187,7 @@ function pullAccountFields($form, account, accountCode) {
account.firstName = getField($form, '.contact_info .first_name', V(R.isNotEmpty));
account.lastName = getField($form, '.contact_info .last_name', V(R.isNotEmpty));
account.email = getField($form, '.email', V(R.isNotEmpty), V(R.isValidEmail));
- account.code = accountCode || account.email;
+ account.code = accountCode;
}
@@ -1184,7 +1203,7 @@ function pullBillingInfoFields($form, billingInfo) {
billingInfo.expires = exp;
billingInfo.address1 = getField($form, '.address1', V(R.isNotEmpty));
- billingInfo.address2 = getField($form, '.address2', V(R.isNotEmpty));
+ billingInfo.address2 = getField($form, '.address2');
billingInfo.city = getField($form, '.city', V(R.isNotEmpty));
billingInfo.state = getField($form, '.state', V(R.isNotEmpty));
billingInfo.zip = getField($form, '.zip', V(R.isNotEmpty));
@@ -1193,7 +1212,7 @@ function pullBillingInfoFields($form, billingInfo) {
}
-R.buildUpdateBillingInfoForm = function(options) {
+R.buildBillingInfoUpdateForm = function(options) {
var defaults = {
addressRequirement: 'full'
};
@@ -1239,10 +1258,8 @@ R.buildUpdateBillingInfoForm = function(options) {
if(options.successURL) {
var url = options.successURL;
- url = R.replaceVars({
- account_code: options.accountCode
- });
- window.location = url;
+ // url = R.replaceVars(url, response);
+ R.post(url, response, true);
}
}
, error: function(errors) {
@@ -1268,18 +1285,27 @@ R.buildUpdateBillingInfoForm = function(options) {
R.buildTransactionForm = function(options) {
var defaults = {
addressRequirement: 'full'
+ , distinguishContactFromBillingInfo: true
+ , collectContactInfo: true
};
options = $.extend(createObject(R.settings), defaults, options);
+
- options.distinguishContactFromBillingInfo = true;
+ if(!options.collectContactInfo && !options.accountCode) {
+ R.raiseError('collectContactInfo is false, but no accountCode provided');
+ }
- if(!options.accountCode) R.raiseError('accountCode missing');
+
+ // if(!options.accountCode) R.raiseError('accountCode missing');
if(!options.signature) R.raiseError('signature missing');
var billingInfo = R.BillingInfo.create()
+ , account = R.Account.create()
, transaction = R.Transaction.create();
+
+ transaction.account = account;
transaction.billingInfo = billingInfo;
transaction.currency = options.currency;
transaction.cost = new R.Cost(options.amountInCents);
@@ -1287,6 +1313,12 @@ R.buildTransactionForm = function(options) {
var $form = $(R.oneTimeTransactionFormHTML);
$form.find('.billing_info').html(R.billingInfoFieldsHTML);
+ if(options.collectContactInfo) {
+ $form.find('.contact_info').html(R.contactInfoFieldsHTML);
+ }
+ else {
+ $form.find('.contact_info').remove();
+ }
initCommonForm($form, options);
initBillingInfoForm($form, options);
@@ -1300,6 +1332,7 @@ R.buildTransactionForm = function(options) {
$form.find('.invalid').removeClass('invalid');
handleUserErrors(function() {
+ pullAccountFields($form, account, options.accountCode);
pullBillingInfoFields($form, billingInfo);
$form.addClass('submitting');
@@ -1314,9 +1347,8 @@ R.buildTransactionForm = function(options) {
if(options.successURL) {
var url = options.successURL;
- url = url.replace(/\{account_code\}/g, options.accountCode);
- url = url.replace(/\{signature\}/g, options.signature);
- window.location = url;
+ // url = R.replaceVars(url, response);
+ R.post(url, response, true);
}
}
, error: function(errors) {
@@ -1350,13 +1382,9 @@ R.buildSubscriptionForm = function(options) {
options = $.extend(createObject(R.settings), defaults, options);
var $form = $(R.subscribeFormHTML);
+ $form.find('.contact_info').html(R.contactInfoFieldsHTML);
$form.find('.billing_info').html(R.billingInfoFieldsHTML);
-// // Insert clearfix hacks just for IE6
-// if($.browser.msie && $.browser.version == 6 && !options.disableClearFixHack) {
-// $form.find('div').append('<div class="clearfix">');
-// }
-
initCommonForm($form, options);
@@ -1614,6 +1642,7 @@ R.buildSubscriptionForm = function(options) {
clearServerErrors($form);
+
$form.find('.error').remove();
$form.find('.invalid').removeClass('invalid');
@@ -1631,8 +1660,8 @@ R.buildSubscriptionForm = function(options) {
if(options.successURL) {
var url = options.successURL;
- url = url.replace(/\{account_code\}/g, subscription.account.code);
- window.location = url;
+ // url = R.replaceVars(url, response);
+ R.post(url, response, true);
}
}
, error: function(errors) {
@@ -1674,6 +1703,14 @@ R.buildSubscriptionForm = function(options) {
//////////////////////////////////////////////////
+// Compiled from dom/contact_info_fields.jade
+//////////////////////////////////////////////////
+
+R.contactInfoFieldsHTML = '<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>';
+
+
+
+//////////////////////////////////////////////////
// Compiled from dom/billing_info_fields.jade
//////////////////////////////////////////////////
@@ -1685,7 +1722,7 @@ R.billingInfoFieldsHTML = '<div class="title">Billing Info</div><div class="acce
// Compiled from dom/subscribe_form.jade
//////////////////////////////////////////////////
-R.subscribeFormHTML = '<form class="recurly subscribe"><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 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><div class="billing_info"></div><div class="footer"><button type="submit" class="submit">Subscribe</button></div></form>';
+R.subscribeFormHTML = '<form class="recurly subscribe"><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="footer"><button type="submit" class="submit">Subscribe</button></div></form>';
@@ -1701,7 +1738,7 @@ R.updateBillingInfoFormHTML = '<form class="recurly update_billing_info"><div cl
// Compiled from dom/one_time_transaction_form.jade
//////////////////////////////////////////////////
-R.oneTimeTransactionFormHTML = '<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">Pay</button></div></form>';
+R.oneTimeTransactionFormHTML = '<form class="recurly update_billing_info"><div class="server_errors none"></div><div class="contact_info"></div><div class="billing_info"></div><div class="footer"><button type="submit" class="submit">Pay</button></div></form>';
window.Recurly = R;
View
1  recurly.min.js
@@ -0,0 +1 @@
+(function(h){function j(s){function r(){}r.prototype=s||this;return new r()}var i={};i.settings={};i.Error={toString:function(){return"RecurlyJS Error: "+this.message}};i.raiseError=function(r){var s=j(i.Error);s.message=r;throw s};i.config=function(r){h.extend(true,i.settings,r);if(!r.baseURL){switch(i.settings.environment){case"sandbox":i.settings.baseURL="https://api-sandbox.recurly.com/jsonp/";break;case"production":i.settings.baseURL="https://api-production.recurly.com/jsonp/";break;default:i.raiseError("environment not configured (sandbox or production)");break}var s=i.settings.subdomain||i.raiseError("company subdomain not configured");i.settings.baseURL+=s+"/"}};function q(s,r){if(s==1){return r.substr(0,r.length-1)}return""+s+" "+r}(i.Cost=function(r){this._cents=r||0}).prototype={toString:function(){return i.formatCurrency(this.dollars())},cents:function(r){if(r===undefined){return this._cents}return new Cost(r)},dollars:function(r){if(r===undefined){return this._cents/100}return new i.Cost(r*100)},mult:function(r){return new i.Cost(this._cents*r)},add:function(r){if(r.cents){r=r.cents()}return new i.Cost(this._cents+r)},sub:function(r){if(r.cents){r=r.cents()}return new i.Cost(this._cents-r)}};i.Cost.FREE=new i.Cost(0);(i.TimePeriod=function(s,r){this.length=s;this.unit=r}).prototype={toString:function(){return""+q(this.length,this.unit)},toDate:function(){var r=new Date();switch(this.unit){case"month":r.setMonth(r.getMonth()+this.length);break;case"day":r.setDay(r.getDay()+this.length);break}return r},clone:function(){return new i.TimePeriod(this.length,this.unit)}};(i.RecurringCost=function(s,r){this.cost=s;this.interval=r}).prototype={toString:function(){return""+this.cost+" every "+this.interval},clone:function(){return new i.TimePeriod(this.length,this.unit)}};i.RecurringCost.FREE=new i.RecurringCost(0,null);(i.RecurringCostStage=function(r,s){this.recurringCost=r;this.duration=s}).prototype={toString:function(){this.recurringCost.toString()+" for "+this.duration.toString()}};i.locale={};i.locale.errors={emptyField:"Required field",missingFullAddress:"Please enter your full address.",invalidEmail:"Invalid",invalidCC:"Invalid",invalidCVV:"Invalid",invalidCoupon:"Invalid",cardDeclined:"Transaction declined"};i.locale.currencies={};i.locale.currency={format:"%u%n",separator:".",delimiter:",",precision:2};function a(r,s){var u=i.locale.currencies[r]=j(i.locale.currency);for(var t in s){u[t]=s[t]}}a("USD",{symbol:"$"});a("AUD",{symbol:"$"});a("CAD",{symbol:"$"});a("EUR",{symbol:"\u20ac"});a("GBP",{symbol:"\u00a3"});a("CZK",{symbol:"\u004b"});a("DKK",{symbol:"\u6b72"});a("HUF",{symbol:"Ft"});a("JPY",{symbol:"\u00a5"});a("NOK",{symbol:"kr"});a("NZD",{symbol:"$"});a("PLN",{symbol:"\u007a"});a("SGD",{symbol:"$"});a("SEK",{symbol:"kr"});a("CHF",{symbol:"Fr"});a("ZAR",{symbol:"R"});i.settings.locale=i.locale;i.detectCardType=function(r){r=r.replace(/\D/g,"");var t=[{name:"visa",prefixes:[4]},{name:"mastercard",prefixes:[51,52,53,54,55]},{name:"american_express",prefixes:[34,37]},{name:"discover",prefixes:[6011,62,64,65]},{name:"diners_club",prefixes:[305,36,38]},{name:"carte_blanche",prefixes:[300,301,302,303,304,305]},{name:"jcb",prefixes:[35]},{name:"enroute",prefixes:[2014,2149]},{name:"solo",prefixes:[6334,6767]},{name:"switch",prefixes:[4903,4905,4911,4936,564182,633110,6333,6759]},{name:"maestro",prefixes:[5018,5020,5038,6304,6759,6761]},{name:"visa",prefixes:[417500,4917,4913,4508,4844]},{name:"laser",prefixes:[6304,6706,6771,6709]}];for(var u=0;u<t.length;u++){for(var s=0;s<t[u].prefixes.length;s++){if(new RegExp("^"+t[u].prefixes[s].toString()).test(r)){return t[u].name}}}};i.formatCurrency=function(u,y){if(u<0){u=-u;var t=true}else{var t=false}y=y||i.settings.currency||i.raiseError("currency not configured");var x=i.locale.currency;var s=i.locale.currencies[y];var w=u.toFixed(s.precision);if(x.separator!="."){w=w.replace(/\./g,x.separator)}function r(A){var z=new RegExp("(-?[0-9]+)([0-9]{3})");while(z.test(A)){A=A.replace(z,"$1"+x.delimiter+"$2")}return A}w=r(w);var v=x.format;v=v.replace(/%u/g,s.symbol);v=v.replace(/%n/g,w);w=v;if(t){w="-"+w}return w};var l=["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"];i.isVATApplicable=function(t){if(!i.settings.VATPercent){return false}if(!i.settings.country){i.raiseError("you must configure a country for VAT to work")}var s=i.settings.country;var r=h.inArray(t,l)!==-1;return r&&(s!=t)};i.flattenErrors=function(y,s){var r=[];var v=["base","account_id"];var s=s||"";if(typeof y=="string"||typeof y=="number"||typeof y=="boolean"){if(h.inArray(v,s)){return[y]}return[""+s+" "+y]}for(var u in y){if(y.hasOwnProperty(u)){s=(parseInt(u).toString()==u)?s:u;var x=i.flattenErrors(y[u],s);for(var w=0,t=x.length;w<t;++w){r.push(x[w])}}}return r};i.replaceVars=function(u,t){for(var s in t){if(t.hasOwnProperty(s)){var r=encodeURIComponent(t[s]);u=u.replace(new RegExp("\\{"+s+"\\}","g"),r)}}return u};i.post=function(r,v,s){var t=h("<form />").hide();t.attr("action",r).attr("method","POST").attr("enctype",s?"application/x-www-form-urlencoded":"multipart/form-data");function u(x,A,z){var w=(z.length>0?(z+"["+x+"]"):x);if(typeof A==="object"){for(var y in A){if(A.hasOwnProperty(y)){u(y,A[y],w)}}}else{h('<input type="hidden" />').attr({name:w,value:A}).appendTo(t)}}u("",v,"");h("body").append(t);t.submit()};(i.isValidCC=function(u){if(/[^0-9-]+/.test(u)){return false}var v=0,t=0,r=false;u=u.replace(/\D/g,"");for(var w=u.length-1;w>=0;w--){var s=u.charAt(w);var t=parseInt(s,10);if(r){if((t*=2)>9){t-=9}}v+=t;r=!r}return(v%10)==0}).defaultErrorKey="invalidCC";(i.isValidEmail=function(r){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(r)}).defaultErrorKey="invalidEmail";function f(r){return/^[0-9]+$/.test(r)}(i.isValidCVV=function(r){return(r.length==3||r.length==4)&&f(r)}).defaultErrorKey="invalidCVV";(i.isNotEmpty=function(r){return !!r}).defaultErrorKey="emptyField";i.Plan={create:j,fromJSON:function(u){var v=this.create();v.name=u.name;v.code=u.plan_code;v.cost=new i.Cost(u.unit_amount_in_cents);v.displayQuantity=u.display_quantity;v.interval=new i.TimePeriod(u.plan_interval_length,u.plan_interval_unit);if(u.trial_interval_length){v.trial=new i.TimePeriod(u.trial_interval_length,u.trial_interval_unit)}if(u.setup_fee_in_cents){v.setupFee=new i.Cost(u.setup_fee_in_cents)}v.addOns=[];if(u.add_ons){for(var s=u.add_ons.length,t=0;t<s;++t){var r=u.add_ons[t];v.addOns.push(i.AddOn.fromJSON(r))}}return v},get:function(r,s){h.ajax({url:i.settings.baseURL+"plans/"+r,dataType:"jsonp",jsonp:"callback",timeout:10000,success:function(t){var u=i.Plan.fromJSON(t);s(u)}})},createSubscription:function(){var r=j(i.Subscription);r.plan=j(this);r.plan.quantity=1;r.addOns=[];return r}};i.AddOn={fromJSON:function(s){var r=j(i.AddOn);r.name=s.name;r.code=s.add_on_code;r.cost=new i.Cost(s.default_unit_amount_in_cents);r.displayQuantity=s.display_quantity;return r},toJSON:function(){return{name:this.name,add_on_code:this.code,default_unit_amount_in_cents:this.default_unit_amount_in_cents}}};i.Account={create:j,toJSON:function(){return{first_name:this.firstName,last_name:this.lastName,account_code:this.code,email:this.email}}};i.BillingInfo={create:j,toJSON:function(){return{first_name:this.firstName,last_name:this.lastName,month:this.expires.getMonth()+1,year:this.expires.getFullYear(),number:this.number,verification_value:this.cvv,address1:this.address1,address2:this.address2,city:this.city,state:this.state,zip:this.zip,country:this.country}},save:function(r){var s={billing_info:this.toJSON(),signature:r.signature};h.ajax({url:i.settings.baseURL+"accounts/"+r.accountCode+"/billing_info/update",data:s,dataType:"jsonp",jsonp:"callback",timeout:60000,success:function(t){if(t.success&&r.success){r.success(t.success)}else{if(t.errors&&r.error){r.error(i.flattenErrors(t.errors))}}},error:function(){if(r.error){r.error(["Unknown error processing transaction. Please try again later."])}},complete:r.complete||h.noop})}};i.Subscription={create:j,plan:i.Plan,addOns:[],calculateTotals:function(){var u={stages:{}};u.plan=this.plan.cost.mult(this.plan.quantity);u.allAddOns=new i.Cost(0);u.addOns={};for(var s=this.addOns.length,t=0;t<s;++t){var r=this.addOns[t],x=r.cost.mult(r.quantity);u.addOns[r.code]=x;u.allAddOns=u.allAddOns.add(x)}u.stages.recurring=u.plan.add(u.allAddOns);u.stages.now=u.plan.add(u.allAddOns);if(this.plan.trial){u.stages.now=i.Cost.FREE}if(this.plan.setupFee){u.stages.now=u.stages.now.add(this.plan.setupFee)}if(this.coupon){var v=u.stages.now;var w=u.stages.now.discount(this.coupon);u.coupon=w.sub(v);u.stages.now=w}if(this.billingInfo&&i.isVATApplicable(this.billingInfo.country)&&!this.billingInfo.vatNumber){u.vat=u.stages.now.mult((i.settings.VATPercent/100));u.stages.now=u.stages.now.sub(u.vat)}return u},redeemAddOn:function(s){var r=s.createRedemption();this.addOns.push(r);return r},removeAddOn:function(u){for(var s=this.addOns,r=s.length,t=0;t<r;++t){if(s[t].code==u){return s.splice(t,1)}}},findAddOnByCode:function(t){for(var r=this.addOns.length,s=0;s<r;++s){if(this.addOns[s].code==t){return this.addOns[s]}}return false},toJSON:function(){var v={plan_code:this.plan.code,quantity:this.plan.quantity,coupon_code:this.coupon?this.coupon.code:undefined,add_ons:[]};for(var u=0,t=this.addOns.length,s=v.add_ons,r=this.addOns;u<t;++u){s.push({add_on_code:r[u].code,quantity:r[u].quantity})}return v},save:function(r){var s={subscription:this.toJSON(),account:this.account.toJSON(),billing_info:this.billingInfo.toJSON()};h.ajax({url:i.settings.baseURL+"subscribe",data:s,dataType:"jsonp",jsonp:"callback",timeout:60000,success:function(t){if(t.success&&r.success){r.success(t.success)}else{if(t.errors&&r.error){var u=t.errors.error_code;delete t.errors.error_code;r.error(i.flattenErrors(t.errors),u)}}},error:function(){if(r.error){r.error(["Unknown error processing transaction. Please try again later."])}},complete:r.complete})}};i.AddOn.createRedemption=function(t){var s=j(this);s.quantity=t||1;return s};i.Coupon={fromJSON:function(r){var s=j(i.Coupon);if(r.discount_in_cents){s.discountCost=new i.Cost(-r.discount_in_cents)}else{if(r.discount_percent){s.discountRatio=r.discount_percent/100}}s.description=r.description;return s},toJSON:function(){}};i.Cost.prototype.discount=function(r){if(r.discountCost){return this.add(r.discountCost)}var s=this.sub(this.mult(r.discountRatio));if(s.cents()<0){return i.Cost.FREE}return s};i.Subscription.getCoupon=function(t,r,s){if(!i.settings.baseURL){i.raiseError("Company subdomain not configured")}return h.ajax({url:i.settings.baseURL+"plans/"+this.plan.code+"/coupons/"+t,dataType:"jsonp",jsonp:"callback",timeout:10000,success:function(v){var u=i.Coupon.fromJSON(v);u.code=t;if(v.valid){r(u)}else{s()}},error:function(){s()}})};i.Transaction={toJSON:function(){return{currency:this.currency,amount_in_cents:this.cost.cents()}},create:j,save:function(r){var s={transaction:this.toJSON(),account:this.account?this.account.toJSON():undefined,billing_info:this.billingInfo.toJSON(),signature:r.signature};h.ajax({url:i.settings.baseURL+"transactions/create",data:s,dataType:"jsonp",jsonp:"callback",timeout:60000,success:function(t){if(t.success&&r.success){r.success(t.success)}else{if(t.errors&&r.error){r.error(i.flattenErrors(t.errors))}}},error:function(){if(r.error){r.error(["Unknown error processing transaction. Please try again later."])}},complete:r.complete||h.noop})}};i.UserError={};function k(r,s){var t=j(i.UserError);t.validation=r;t.element=s;throw t}function o(v){try{v()}catch(u){if(!u.validation){throw u}var w=u.element;var t=i.locale.errors[u.validation.errorKey];var s=u.validation.validator;var r=h('<div class="error">');r.text(t);r.insertAfter(w);w.addClass("invalid");w.bind("change keyup",function(){var x=w.val();if(s(x)){w.removeClass("invalid");r.remove();w.unbind()}});w.focus()}}function e(t,w,s){var y=t.find(w+" input");if(y.length==0){y=t.find(w+" select")}if(y.length==0){return undefined}var x=y.val();for(var u=2,r;r=arguments[u];++u){if(!r.validator(x)){k(r,y)}}return x}function b(s,r){return{validator:s,errorKey:r||s.defaultErrorKey}}function d(r){var s=r.find(".server_errors");s.removeClass("any").addClass("none");s.empty()}function m(s,w){var u=s.find(".server_errors");d(s);var r=w.length;if(r){u.removeClass("none").addClass("any");for(var v=0;v<r;++v){var t=h('<div class="error">');t.text(w[v]);u.append(t)}}}function p(r,s){r.delegate(".placeholder","click",function(){var t=h(this);var u=h(this).parent();u.find("input").focus()});r.delegate("input","change keyup",function(){var u=h(this);var t=h(this).parent();if(u.val().length>0){t.find(".placeholder").hide()}else{t.find(".placeholder").show()}});r.delegate("input","focus",function(){h(this).parent().addClass("focus")});r.delegate("input","blur",function(){h(this).parent().removeClass("focus")});r.delegate("input","keydown",function(t){if(t.keyCode>=48&&t.keyCode<=90){h(this).parent().find(".placeholder").hide()}})}function n(G,F){if(i.settings.country){var v=G.find(".country option[value="+i.settings.country+"]");if(v.length){v.attr("selected",true).change()}}if(F.distinguishContactFromBillingInfo){var E=G.find(".contact_info .first_name input");var x=G.find(".contact_info .last_name input");var z=E.val();var B=x.val();G.find(".contact_info .first_name input").change(function(){var H=G.find(".billing_info .first_name input");if(H.val()==z){H.val(h(this).val()).change()}z=E.val()});G.find(".contact_info .last_name input").change(function(){var H=G.find(".billing_info .last_name input");if(H.val()==B){H.val(h(this).val()).change()}B=x.val()})}else{G.find(".billing_info .first_name, .billing_info .last_name").remove()}var t=new Date();var D=t.getFullYear();var C=t.getMonth();var w=G.find(".year select");var r=G.find(".month select");for(var y=D;y<=D+10;++y){var s=h('<option name="'+y+'">'+y+"</option>");s.appendTo(w)}function A(){if(w.val()==D){r.find('option[value="'+C+'"]');var H=false;r.find("option").each(function(){if(h(this).val()<=C){h(this).attr("disabled",true)}else{h(this).removeAttr("disabled");if(!H){H=true;h(this).attr("selected",true)}}})}else{r.find("option").removeAttr("disabled")}}A();w.change(A);if(F.addressRequirement=="none"){G.find(".address").remove()}else{if(F.addressRequirement=="zip"){G.find(".address").addClass("only_zip");G.find(".address1, .address2, .city, .state").remove();if(!i.settings.VATPercent){G.find(".country").remove()}}else{if(F.addressRequirement=="zipstreet"){G.find(".address").addClass("only_zipstreet");G.find(".city, .state").remove();if(!i.settings.VATPercent){G.find(".country").remove()}}else{if(F.addressRequirement=="full"){G.find(".address").addClass("full")}}}}var u=G.find(".accepted_cards");G.find(".card_number input").bind("change keyup",function(){var H=i.detectCardType(h(this).val());if(H){u.find(".card").each(function(){h(this).toggleClass("match",h(this).hasClass(H));h(this).toggleClass("no_match",!h(this).hasClass(H))})}else{u.find(".card").removeClass("match no_match")}})}function g(r,t,s){t.firstName=e(r,".contact_info .first_name",b(i.isNotEmpty));t.lastName=e(r,".contact_info .last_name",b(i.isNotEmpty));t.email=e(r,".email",b(i.isNotEmpty),b(i.isValidEmail));t.code=s}function c(r,t){t.firstName=e(r,".billing_info .first_name",b(i.isNotEmpty));t.lastName=e(r,".billing_info .last_name",b(i.isNotEmpty));t.number=e(r,".card_number",b(i.isNotEmpty),b(i.isValidCC));t.cvv=e(r,".cvv",b(i.isNotEmpty),b(i.isValidCVV));var s=new Date(0);s.setMonth(e(r,".month")-1);s.setFullYear(e(r,".year"));t.expires=s;t.address1=e(r,".address1",b(i.isNotEmpty));t.address2=e(r,".address2");t.city=e(r,".city",b(i.isNotEmpty));t.state=e(r,".state",b(i.isNotEmpty));t.zip=e(r,".zip",b(i.isNotEmpty));t.country=e(r,".country",b(function(u){return u!="-"},"emptyField"))}i.buildBillingInfoUpdateForm=function(s){var t={addressRequirement:"full"};s=h.extend({},t,s);s.distinguishContactFromBillingInfo=true;if(!s.accountCode){i.raiseError("accountCode missing")}if(!s.signature){i.raiseError("signature missing")}var u=i.BillingInfo.create();var r=h(i.updateBillingInfoFormHTML);r.find(".billing_info").html(i.billingInfoFieldsHTML);p(r,s);n(r,s);r.submit(function(v){v.preventDefault();d(r);r.find(".error").remove();r.find(".invalid").removeClass("invalid");o(function(){c(r,u);r.addClass("submitting");r.find("button.submit").attr("disabled",true).text("Please Wait");u.save({signature:s.signature,accountCode:s.accountCode,success:function(w){if(s.afterUpdate){s.afterUpdate(w)}if(s.successURL){var x=s.successURL;i.post(x,w,true)}},error:function(w){if(!s.onError||!s.onError(w)){m(r,w)}},complete:function(){r.removeClass("submitting");r.find("button.submit").removeAttr("disabled").text("Update")}})})});h(function(){var v=h(s.target);v.html(r)})};i.buildTransactionForm=function(s){var u={addressRequirement:"full",distinguishContactFromBillingInfo:true,collectContactInfo:true};s=h.extend(j(i.settings),u,s);if(!s.collectContactInfo&&!s.accountCode){i.raiseError("collectContactInfo is false, but no accountCode provided")}if(!s.signature){i.raiseError("signature missing")}var w=i.BillingInfo.create(),t=i.Account.create(),v=i.Transaction.create();v.account=t;v.billingInfo=w;v.currency=s.currency;v.cost=new i.Cost(s.amountInCents);var r=h(i.oneTimeTransactionFormHTML);r.find(".billing_info").html(i.billingInfoFieldsHTML);if(s.collectContactInfo){r.find(".contact_info").html(i.contactInfoFieldsHTML)}else{r.find(".contact_info").remove()}p(r,s);n(r,s);r.submit(function(x){x.preventDefault();d(r);r.find(".error").remove();r.find(".invalid").removeClass("invalid");o(function(){g(r,t,s.accountCode);c(r,w);r.addClass("submitting");r.find("button.submit").attr("disabled",true).text("Please Wait");v.save({signature:s.signature,accountCode:s.accountCode,success:function(y){if(s.afterPay){s.afterPay(y)}if(s.successURL){var z=s.successURL;i.post(z,y,true)}},error:function(y){if(!s.onError||!s.onError(y)){m(r,y)}},complete:function(){r.removeClass("submitting");r.find("button.submit").removeAttr("disabled").text("Pay")}})})});h(function(){var x=h(s.target);x.html(r)})};i.buildSubscriptionForm=function(s){var u={enableAddOns:true,enableCoupons:true,addressRequirement:"full",distinguishContactFromBillingInfo:false};s=h.extend(j(i.settings),u,s);var r=h(i.subscribeFormHTML);r.find(".contact_info").html(i.contactInfoFieldsHTML);r.find(".billing_info").html(i.billingInfoFieldsHTML);p(r,s);if(s.planCode){i.Plan.get(s.planCode,t)}else{if(s.plan){t(s.plan)}}function t(v){if(s.filterPlan){v=s.filterPlan(v)||v}var K=v.createSubscription(),w=i.Account.create(),D=i.BillingInfo.create();K.account=w;K.billingInfo=D;if(s.filterSubscription){K=s.filterSubscription(K)||K}n(r,s,D);if(!v.displayQuantity){r.find(".plan .quantity").remove()}if(v.setupFee){r.find(".subscription").addClass("with_setup_fee");r.find(".plan .setup_fee .cost").text(""+v.setupFee)}else{r.find(".plan .setup_fee").remove()}if(v.trial){r.find(".subscription").addClass("with_trial");r.find(".plan .free_trial").text("First "+v.trial+" free")}else{r.find(".plan .free_trial").remove()}function B(){var N=K.calculateTotals();r.find(".plan .recurring_cost .cost").text(""+N.plan);r.find(".due_now .cost").text(""+N.stages.now);r.find(".coupon .discount").text(""+(N.coupon||""));r.find(".vat .cost").text(""+(N.vat||""));r.find(".add_ons .add_on").each(function(){var O=h(this).data("add_on");if(h(this).hasClass("selected")){var P=N.addOns[O.code];h(this).find(".cost").text("+ "+P)}else{h(this).find(".cost").text("+ "+O.cost)}})}r.find(".plan .quantity input").bind("change keyup",function(){K.plan.quantity=parseInt(h(this).val(),10)||1;B()});r.find(".plan .name").text(v.name);r.find(".plan .recurring_cost .cost").text(""+v.cost);r.find(".plan .recurring_cost .interval").text("every "+v.interval);var z=r.find(".add_ons");if(s.enableAddOns){var H=v.addOns.length;if(H){z.removeClass("none").addClass("any");for(var I=0;I<H;++I){var E=v.addOns[I];var J="add_on add_on_"+E.code+(I%2?" even":" odd");if(I==0){J+=" first"}if(I==H-1){J+=" last"}var y=h('<div class="'+J+'"><div class="name">'+E.name+'</div><div class="field quantity"><div class="placeholder">Qty</div><input type="text"></div><div class="cost"/></div>');if(!E.displayQuantity){y.find(".quantity").remove()}y.data("add_on",E);y.appendTo(z)}z.delegate(".add_ons .quantity input","change keyup",function(Q){var N=h(this).closest(".add_on");var P=N.data("add_on");var O=parseInt(h(this).val(),10)||1;K.findAddOnByCode(P.code).quantity=O;B()});z.bind("selectstart",function(N){if(h(N.target).is(".add_on")){N.preventDefault()}});z.delegate(".add_ons .add_on","click",function(R){if(h(R.target).closest(".quantity").length){return}var P=!h(this).hasClass("selected");h(this).toggleClass("selected",P);var Q=h(this).data("add_on");if(P){var N=K.redeemAddOn(Q);var O=h(this).find(".quantity input");N.quantity=parseInt(O.val(),10)||1;O.focus()}else{K.removeAddOn(Q.code)}B()})}}else{z.remove()}var C=r.find(".coupon");var G=null;function L(){var N=C.find("input").val();if(N==G){return}G=N;if(!N){C.removeClass("invalid").removeClass("valid");C.find(".description").text("");K.coupon=undefined;B();return}C.addClass("checking");K.getCoupon(N,function(O){C.removeClass("checking");K.coupon=O;C.removeClass("invalid").addClass("valid");C.find(".description").text(O.description);B()},function(){K.coupon=undefined;C.removeClass("checking");C.removeClass("valid").addClass("invalid");C.find(".description").text("Not Found");B()})}if(s.enableCoupons){C.find("input").bind("keyup change",function(N){});C.find("input").keypress(function(N){if(N.charCode==13){N.preventDefault();L()}});C.find(".check").click(function(){L()});C.find("input").blur(function(){C.find(".check").click()})}else{C.remove()}var x=r.find(".vat");var M=r.find(".vat_number");var F=M.find("input");x.find("label").text("VAT at "+i.settings.VATPercent+"%");function A(){var O=r.find(".country select").val();var N=i.isVATApplicable(O);var P=F.val();M.toggleClass("applicable",N);M.toggleClass("inapplicable",!N);N=N&&!P;x.toggleClass("applicable",N);x.toggleClass("inapplicable",!N)}A();r.find(".country select").change(function(){D.country=h(this).val();B();A()});F.bind("keyup change",function(){D.vatNumber=h(this).val();B();A()});r.submit(function(N){N.preventDefault();d(r);r.find(".error").remove();r.find(".invalid").removeClass("invalid");o(function(){g(r,w,s.accountCode);c(r,D);r.addClass("submitting");r.find("button.submit").attr("disabled",true).text("Please Wait");K.save({success:function(O){if(s.afterSubscribe){s.afterSubscribe(O)}if(s.successURL){var P=s.successURL;i.post(P,O,true)}},error:function(O){if(!s.onError||!s.onError(O)){m(r,O)}},complete:function(){r.removeClass("submitting");r.find("button.submit").removeAttr("disabled").text("Subscribe")}})})});B();if(s.beforeInject){s.beforeInject(r.get(0))}h(function(){var N=h(s.target);N.html(r);if(s.afterInject){s.afterInject(r.get(0))}})}};i.contactInfoFieldsHTML='<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>';i.billingInfoFieldsHTML='<div class="title">Billing Info</div><div class="accepted_cards"><div class="card american_express">American Express</div><div class="card discover">Discover</div><div class="card mastercard">MasterCard </div><div class="card visa">Visa</div></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><!-- :number, :maxlength => 22, :class => \'required creditcard\', :placeholder => \'Credit Card Number\'--></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><!-- = render :partial => \'months\' --></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"><!-- label 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="BY">Belarus</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="CG">Congo [Republic]</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="GQ">Equatorial Guinea</option><option value="ER">Eritrea</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="GN">Guinea</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="IR">Iran</option><option value="IQ">Iraq</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="CI">Ivory Coast</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="LB">Lebanon</option><option value="LS">Lesotho</option><option value="LR">Liberia</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="MM">Myanmar [Burma]</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="KP">North Korea</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="PG">Papua New Guinea</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="SO">Somalia</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="SD">Sudan</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="SY">Syria</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="SU">Union(of Soviet Socialist Republics</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="ZW">Zimbabwe</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>';i.subscribeFormHTML='<form class="recurly subscribe"><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="footer"><button type="submit" class="submit">Subscribe</button></div></form>';i.updateBillingInfoFormHTML='<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>';i.oneTimeTransactionFormHTML='<form class="recurly update_billing_info"><div class="server_errors none"></div><div class="contact_info"></div><div class="billing_info"></div><div class="footer"><button type="submit" class="submit">Pay</button></div></form>';window.Recurly=i}(jQuery));
View
10 test/all.html
@@ -5,7 +5,7 @@
<script src="http://code.jquery.com/jquery-latest.js"></script>
<link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen" />
<script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>
- <script type="text/javascript" src="../recurly.js"></script>
+ <script type="text/javascript" src="../recurly.min.js"></script>
<script>
$(document).ready(function(){
@@ -256,18 +256,18 @@
test('replaceVars', function() {
- var url = '/success?account={account_code}&plan={plan_code}&account={account_code}&{*}';
+ var url = '/success?account={account_code}&plan={plan_code}&account={account_code}';
var vars = {
account_code: 123
, plan_code: 'abc'
+ , add_on_codes: ['abc']
};
- var expect = '/success?account=123&plan=abc&account=123&account_code=123&plan_code=abc';
+ var expect = '/success?account=123&plan=abc&account=123';
var result = R.replaceVars(url, vars);
deepEqual(result, expect);
});
-
module('buidSubscribeForm');
test('without editable quantity', function() {
@@ -407,7 +407,7 @@
"account": {
"first_name": "joe",
"last_name": "user",
- "account_code": "joeuser@example.com",
+ "account_code": undefined,
"email": "joeuser@example.com"
},
"billing_info": {
Please sign in to comment.
Something went wrong with that request. Please try again.