├── .gitignore ├── examples ├── confirmation.html ├── simple.css ├── index.html ├── one_time_transaction.html ├── update_billing_info.html ├── subscribe.html ├── gridsystem.html ├── gridsystem.css └── examples.css ├── images ├── check.png ├── dash.png ├── error.png ├── noise.gif ├── noise.png ├── pulp.jpeg ├── due_now.png ├── loading.gif ├── uncheck.png ├── grid_demo.png ├── hydranoise.jpeg ├── submitting.gif ├── coupon_check.png ├── coupon_valid.png ├── fragmentnoise.jpg ├── coupon_checking.gif ├── coupon_invalid.png └── credit_cards │ ├── amex.png │ ├── visa.png │ ├── discover.png │ └── mastercard.png ├── license.txt ├── changelog.md ├── README.md ├── test └── all.html ├── recurly.styl ├── recurly.css ├── recurly.min.js └── recurly.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /examples/confirmation.html: -------------------------------------------------------------------------------- 1 | 2 |

Success!

3 | -------------------------------------------------------------------------------- /images/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/check.png -------------------------------------------------------------------------------- /images/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/dash.png -------------------------------------------------------------------------------- /images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/error.png -------------------------------------------------------------------------------- /images/noise.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/noise.gif -------------------------------------------------------------------------------- /images/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/noise.png -------------------------------------------------------------------------------- /images/pulp.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/pulp.jpeg -------------------------------------------------------------------------------- /images/due_now.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/due_now.png -------------------------------------------------------------------------------- /images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/loading.gif -------------------------------------------------------------------------------- /images/uncheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/uncheck.png -------------------------------------------------------------------------------- /images/grid_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/grid_demo.png -------------------------------------------------------------------------------- /images/hydranoise.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/hydranoise.jpeg -------------------------------------------------------------------------------- /images/submitting.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/submitting.gif -------------------------------------------------------------------------------- /images/coupon_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/coupon_check.png -------------------------------------------------------------------------------- /images/coupon_valid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/coupon_valid.png -------------------------------------------------------------------------------- /images/fragmentnoise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/fragmentnoise.jpg -------------------------------------------------------------------------------- /images/coupon_checking.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/coupon_checking.gif -------------------------------------------------------------------------------- /images/coupon_invalid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/coupon_invalid.png -------------------------------------------------------------------------------- /images/credit_cards/amex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/credit_cards/amex.png -------------------------------------------------------------------------------- /images/credit_cards/visa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/credit_cards/visa.png -------------------------------------------------------------------------------- /images/credit_cards/discover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/credit_cards/discover.png -------------------------------------------------------------------------------- /images/credit_cards/mastercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/recurly-js/HEAD/images/credit_cards/mastercard.png -------------------------------------------------------------------------------- /examples/simple.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: "helvetica neue", helvetica, arial; 3 | margin: 0; 4 | padding: 0; 5 | background: #c0c1c3 url("../images/noise.png") no-repeat center top; 6 | } 7 | #recurly-subscribe { 8 | width: 500px; 9 | margin: 40px auto; 10 | background: #fff; 11 | margin: 20px auto; 12 | padding: 0px; 13 | border: 1px solid #999; 14 | border-radius: 8px; 15 | background: #fcfcfc url("../images/hydranoise.jpeg") repeat; 16 | box-shadow: 0px 1px 2px rgba(0,0,0,0.2), inset 0 1px 0 #fff; 17 | } 18 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | (MIT License) 2 | 3 | Copyright (C) 2011 by Recurly, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | #Recurly.js CHANGELOG 2 | 3 | ##Version 1.1.3 (October 31, 2011) 4 | 5 | - Made percent coupons discount only recurring amounts, not setup fee. 6 | 7 | ##Version 1.1.2 (September 9, 2011) 8 | 9 | - Fix issue with expiration dates, stop trying to use the browser Date object. 10 | 11 | ##Version 1.1.1 (August 30, 2011) 12 | 13 | - Added resultNamespace option 14 | 15 | - Minor UI improvement in year/month expiration select. 16 | 17 | ##Version 1.1.0 (August 29, 2011) 18 | 19 | - Added Company, and Phone fields 20 | with associated options collectCompany/collectPhone 21 | 22 | - When distinguishContactFromBilling == false on buildBillingInfoUpdateForm 23 | it will update the parent account first/last name. 24 | 25 | - Added preFill option for pre-populating fields with known values. 26 | 27 | - Added privacyPolicyURL to accompany termsOfServiceURL 28 | 29 | ##Version 1.0.4 (August 24, 2011) 30 | 31 | - Add VAT instead of subtracting it 32 | 33 | ##Version 1.0.3 (August 24, 2011) 34 | 35 | - Added termsOfService acceptance check 36 | 37 | ##Version 1.0.2 (August 24, 2011) 38 | 39 | - Add before/afterInject options to buildTransactionForm and buildBillingInfoUpdateForm 40 | 41 | ##Version 1.0.1 (August 24, 2011) 42 | 43 | - Fix a bug with VAT not applying when buyer and seller are in the same country 44 | 45 | ##Version 1.0.0 (August 23, 2011) 46 | 47 | - Initial public release 48 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Recurly.js Examples 6 | 7 | 8 | 9 | 10 |

Recurly.js

11 |

Examples

12 |
13 | 14 |
15 | Subscribe to Plan 16 | Update Billing Info 17 | One Time Transaction 18 | Grid System Design 19 |
20 | 21 |

All examples point to a company we created specifically for this demo, called recurlyjsdemo. Naturally you'll want to replace all occurances with your real company subdomain.

22 |

The demo company defines two plans: simpleplan, and complexplan, and 23 | a forever 30% off coupon, named test.

24 | 25 |

Update Billing Info, and One-time Transactions both require signatures, which must be generated server-side and rendered to an option. For demonstration purposes we pre‑computed the signatures and hardcoded them in.

26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/one_time_transaction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RecurlyJS One-time Transaction Example 6 | 7 | 8 | 9 | 10 | 11 | 33 | 34 | 35 | 36 |

One-time Transaction

37 |

Dollar Amount: $50

38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/update_billing_info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RecurlyJS Update Billing Info Example 6 | 7 | 8 | 9 | 10 | 11 | 33 | 34 | 35 | 36 |

Update Billing Info

37 |

Account Code: testaccount (exists)

38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/subscribe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RecurlyJS Subscribe Example 6 | 7 | 8 | 9 | 10 | 11 | 46 | 47 | 48 |

Subscribe to Plan

49 |

Plan Code: simpleplan (exists)

50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/gridsystem.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RecurlyJS Grid System Example 5 | 6 | 7 | 8 | 9 | 10 | 24 | 25 | 26 | 27 | 28 |
29 | 34 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 | 60 | 65 |
66 |
67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /examples/gridsystem.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 18px "helvetica neue", helvetica, arial 3 | } 4 | 5 | #container 6 | { 7 | margin: 0 auto; 8 | width: 980px; 9 | background: #fff; 10 | } 11 | 12 | #header 13 | { 14 | background: #ccc; 15 | padding: 20px; 16 | } 17 | 18 | #header h1 { margin: 0; } 19 | 20 | #navigation 21 | { 22 | float: left; 23 | width: 980px; 24 | background: #333; 25 | } 26 | 27 | #navigation ul 28 | { 29 | margin: 0; 30 | padding: 0; 31 | } 32 | 33 | #navigation ul li 34 | { 35 | list-style-type: none; 36 | display: inline; 37 | } 38 | 39 | #navigation li a 40 | { 41 | display: block; 42 | float: left; 43 | padding: 5px 10px; 44 | color: #fff; 45 | text-decoration: none; 46 | border-right: 1px solid #fff; 47 | } 48 | 49 | #navigation li a:hover { background: #383; } 50 | 51 | #content-container 52 | { 53 | float: left; 54 | width: 980px; 55 | background: #f0f0f0 url(../images/grid_demo.png) repeat 20px 0; 56 | } 57 | 58 | #content 59 | { 60 | clear: left; 61 | float: left; 62 | width: 520px; 63 | margin: 0 0 0 0px; 64 | display: inline; 65 | } 66 | 67 | #sidebar 68 | { 69 | float: right; 70 | font-weight: 100; 71 | letter-spacing: 1px; 72 | width: 400px; 73 | padding: 20px 0; 74 | margin: 0 0 0 0; 75 | display: inline; 76 | background: #333; 77 | color: white; 78 | } 79 | 80 | #sidebar p { margin: 20px; } 81 | 82 | #footer 83 | { 84 | clear: both; 85 | background: #ccc; 86 | text-align: right; 87 | padding: 20px; 88 | height: 1%; 89 | } 90 | 91 | 92 | #demo480 { 93 | background: skyblue; 94 | width: 480px; 95 | padding: 5px 0; 96 | } 97 | 98 | 99 | #demo960 { 100 | background: seagreen; 101 | width: 960px; 102 | padding: 5px 0; 103 | } 104 | 105 | 106 | #recurly-subscribe { 107 | padding 40px; 108 | height 100px; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /examples/examples.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: "helvetica neue", helvetica, arial; 3 | margin: 0; 4 | padding: 0; 5 | background: #c0c1c3 url("../images/noise.png") no-repeat center top; 6 | } 7 | h1 { 8 | width: 500px; 9 | margin: 30px auto; 10 | text-align: center; 11 | color: #fff; 12 | text-shadow: 0 1px 1px #111; 13 | } 14 | h2 { 15 | width: 500px; 16 | margin: -20px auto 0 auto; 17 | text-align: center; 18 | font-size: 18px; 19 | text-shadow: 0 1px 0 #fff; 20 | } 21 | p { 22 | margin: 20px; 23 | text-shadow: 0 1px 0 #eee; 24 | font-size: 16px; 25 | font-weight: 300; 26 | line-height: 26px; 27 | text-align: justify; 28 | } 29 | pre { 30 | margin: 20px; 31 | padding: 20px; 32 | background: #000; 33 | color: #fff; 34 | } 35 | code { 36 | font-family: menlo, monaco, "Lucida Console", monospace; 37 | padding: 2px 4px; 38 | font-size: 13px; 39 | background: rgba(55,100,150,0.30); 40 | color: #fff; 41 | letter-space: 1px; 42 | text-shadow: 0 1px 0 #467; 43 | border-radius: 4px; 44 | } 45 | a { 46 | color: inherit; 47 | font-weight: bold; 48 | } 49 | #index, 50 | #result { 51 | width: 480px; 52 | margin: 0 auto; 53 | } 54 | #examples { 55 | margin: 20px; 56 | font-weight: inherit; 57 | } 58 | #examples a { 59 | display: block; 60 | overflow: hidden; 61 | padding: 10px 15px; 62 | margin-bottom: 10px; 63 | border-radius: 18px; 64 | color: #fff; 65 | text-shadow: 0 1px 0 #000; 66 | font-weight: inherit; 67 | text-decoration: none; 68 | position: relative; 69 | background: rgba(0,10,0,0.40); 70 | box-shadow: 0 2px 1px rgba(0,0,0,0.10); 71 | } 72 | #examples a:hover { 73 | color: #fff; 74 | background: rgba(0,0,0,0.85); 75 | } 76 | #examples a:hover:after { 77 | content: '\25B6'; 78 | position: absolute; 79 | right: 15px; 80 | } 81 | #recurly-subscribe, 82 | #recurly-update-billing-info, 83 | #recurly-transaction { 84 | width: 500px; 85 | margin: 30px auto; 86 | padding: 0px; 87 | border: 1px solid #999; 88 | border-radius: 8px; 89 | background: #fcfcfc url("../images/pulp.jpeg") repeat; 90 | box-shadow: 0px 1px 2px rgba(0,0,0,0.20), inset 0 1px 0 #fff; 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Recurly.js 2 | 3 | Full Reference: http://docs.recurly.com/recurlyjs/reference/ 4 | 5 | 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. 6 | 7 | This library depends on jQuery 1.5.2+. A future version may be framework agnostic. 8 | 9 | ### Dynamic Total Calculation and Error Handling 10 | 11 | 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. 12 | 13 | ### PCI Compliance 14 | 15 | 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. 16 | 17 | ### Fully Customizable CSS 18 | 19 | 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. 20 | 21 | __Learn more:__ View the Recurly.js [intro video and examples](http://js.recurly.com) and [documentation](http://docs.recurly.com/recurlyjs/overview). 22 | 23 | 24 | # In the Project 25 | 26 | Recurly.js includes: 27 | 28 | * A Javscript library (_recurly.js_) for creating well-structured forms with validation and error handling 29 | * A stock stylesheet (_recurly.css_) 30 | * [stylus](https://github.com/LearnBoost/stylus) source for customizing the stylesheet (_recurly.styl_) 31 | * And examples for creating subscriptions, one time transactions, and updating billing information 32 | 33 | # Getting Started 34 | 35 | Accepting subscriptions is as simple as dropping in this Javascript: 36 | 37 | ```javascript 38 | Recurly.config({ 39 | subdomain: 'mycompany', 40 | }); 41 | 42 | Recurly.buildSubscriptionForm({ 43 | target: '#subscribe', // A jQuery selector for the container element to append the form to 44 | planCode: 'myplancode' // A plan you have created in recurly-app 45 | successURL: '/success' // Redirect on success URL 46 | }); 47 | ``` 48 | 49 | View our [documentation](http://docs.recurly.com/recurlyjs/overview) for more details. 50 | 51 | ## Additional Options 52 | ```javascript 53 | Recurly.config({ 54 | subdomain: 'mycompany' 55 | , currency: 'USD' // GBP | CAD | EUR, etc... 56 | , VATPercent: 10 // European Value Added Tax 57 | , country: 'GB' // Seller country, needed for VAT to work 58 | , locale: { 59 | // Currency formatting rules 60 | currency: { 61 | format: "%u%n" // Unit symbol and Number 62 | , separator: "." 63 | , delimiter: "," 64 | , precision: 2 65 | } 66 | // Error messages 67 | , errors: { 68 | emptyField: 'Forget something?' 69 | , invalidEmail: 'This doesn\'t look right.' 70 | , invalidCC: 'This doesn\'t look right.' 71 | , invalidCVV: 'This doesn\'t look right.' 72 | , invalidCoupon: 'Coupon not found' 73 | } 74 | } 75 | }); 76 | 77 | Recurly.buildSubscriptionForm({ 78 | target: '#subscribe' // A jQuery selector for the container element to append the form to 79 | , planCode: 'myplancode' // A plan you have created in recurly-app 80 | , afterSubscribe: function() {} // Callback after subscription success 81 | , addressRequirement: 'full' // Address fields to display (full | zipstreet | zip | none) 82 | , enableAddOns: true | false 83 | , enableCoupons: true | false 84 | , accountCode // Account code for the account created w/ subscription. Defaults to email address if not provided. 85 | }); 86 | ``` 87 | 88 | ## Customizing the style 89 | 90 | A stock stylesheet is provided that is coded in [stylus](/LearnBoost/stylus), a wonderful language that compiles to CSS. 91 | 92 | 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 npm install stylus, then use the stylus command-line to compile to CSS. There is also a Ruby gem for stylus, [ruby-stylus](https://github.com/lucasmazza/ruby-stylus). 93 | 94 | 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. 95 | 96 | The default stylesheet is designed around the grid system. You will notice the default grid variables at the top of _recurly.styl_. 97 | 98 | # Responding to subscription creates 99 | 100 | 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. 101 | 102 | Alternatively, you can pass in an option to buildSubscriptionForm, afterSubscribe, to handle subscription creates. 103 | 104 | # Additional Requirements 105 | 106 | 107 | 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. 108 | 109 | # Coming Soon 110 | 111 | * Multi-currency (Supporting more than one currency per merchant) 112 | * Multi-lingual support (English only today) 113 | -------------------------------------------------------------------------------- /test/all.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 501 | 502 | 503 | 504 |

recurly.js

505 |

506 |
507 |

508 |
    509 | 510 |
    511 | 512 | 513 | 514 | -------------------------------------------------------------------------------- /recurly.styl: -------------------------------------------------------------------------------- 1 | // Base grid system dimensions 2 | grid_width = 480px 3 | grid_columns = 12 4 | outer_margin = 20px 5 | column_space = 20px 6 | column_width = grid_width / grid_columns - column_space 7 | base_font_size = round(grid_width * 0.033333) 8 | vertical_space = 20px 9 | 10 | input_border = 1px 11 | input_padding = 5px 12 | input_height = base_font_size + 6px 13 | field_height = input_height + (input_padding + input_border)*2 14 | 15 | columns(n) 16 | (column_width * n + column_space * (-1+n)) 17 | 18 | // Some CSS3 property mixins 19 | user-select() 20 | -webkit-user-select arguments 21 | -khtml-user-select arguments 22 | -moz-user-select arguments 23 | -o-user-select arguments 24 | user-select arguments 25 | 26 | linear-gradient(start, stops...) 27 | prop = current-property[0] 28 | stops = unquote(join(', ', stops)) 29 | add-property(prop, '-webkit-linear-gradient(%s, %s)' % (start stops) ) 30 | add-property(prop, '-moz-linear-gradient(%s, %s)' % (start stops) ) 31 | add-property(prop, '-o-linear-gradient(%s, %s)' % (start stops) ) 32 | 'linear-gradient(%s, %s)' % (start stops) 33 | 34 | image-linear-gradient(image, start, stops...) 35 | prop = current-property[0] 36 | stops = unquote(join(', ', stops)) 37 | add-property(prop, '%s, -webkit-linear-gradient(%s, %s)' % (image start stops) ) 38 | add-property(prop, '%s, -moz-linear-gradient(%s, %s)' % (image start stops) ) 39 | add-property(prop, '%s, -o-linear-gradient(%s, %s)' % (image start stops) ) 40 | '%s linear-gradient(%s, %s)' % (image start stops) 41 | 42 | transition() 43 | -webkit-transition arguments 44 | -moz-transition arguments 45 | 46 | // For IE6/7 support we use this for all inputs, 47 | // in lieu of border-box sizing 100% 48 | field-width(v) 49 | width v 50 | 51 | // Fit the input by subtracting padding and border from box 52 | & input[type=text] 53 | width v - (input_border+input_padding)*2 54 | 55 | // Horizontally stack a field on the preceeding field 56 | hstack(cols) 57 | field-width columns(cols) 58 | add-property(margin-left, column_space) 59 | add-property(clear, none) 60 | 61 | // Subscription form 62 | .recurly 63 | display block 64 | position relative 65 | width columns(grid_columns) + (outer_margin*2) 66 | 67 | .cost, .discount 68 | font-size base_font_size 69 | text-align right 70 | 71 | .subscription 72 | border-radius 9px 9px 0 0 73 | text-shadow 0 1px 0 white 74 | padding-top 20px 75 | overflow hidden 76 | 77 | .plan 78 | color #333 79 | overflow hidden 80 | position relative 81 | zoom 1 // IE6 82 | 83 | .name 84 | float left 85 | font-size base_font_size*2 86 | min-width columns(6) - column_space 87 | padding-left outer_margin 88 | padding-right (column_space*2) 89 | 90 | .quantity.field 91 | clear none 92 | field-width columns(2) 93 | margin 4px 0 94 | 95 | &:before 96 | content "\d7" 97 | height 48px 98 | line-height 30px 99 | position absolute 100 | right 100% 101 | width column_space * 2 102 | font-size 20px 103 | text-align center 104 | vertical-align middle 105 | z-index 1337 106 | color #666 107 | 108 | .recurring_cost 109 | float right 110 | text-align right 111 | padding-right outer_margin 112 | 113 | .cost 114 | font-size base_font_size*2 115 | 116 | .interval 117 | font-size round(base_font_size*0.75) 118 | padding-bottom vertical_space 119 | 120 | .free_trial 121 | clear left 122 | float left 123 | font-size 13px 124 | height 22px 125 | margin 0 126 | position absolute 127 | top 35px 128 | left outer_margin 129 | font-style italic 130 | 131 | 132 | .setup_fee 133 | clear both 134 | background url(images/dash.png) repeat-x 1px top 135 | overflow hidden 136 | padding-top vertical_space 137 | 138 | .title 139 | float left 140 | padding-left outer_margin 141 | font-weight bold 142 | font-size base_font_size 143 | .cost 144 | float right 145 | padding-right outer_margin 146 | 147 | .vat 148 | height 24px 149 | padding column_space outer_margin 150 | display none 151 | background url(images/dash.png) repeat-x 1px top 152 | 153 | &.applicable 154 | display block 155 | 156 | .title 157 | font-size base_font_size 158 | font-weight normal 159 | float left 160 | 161 | .cost 162 | float right 163 | font-size 18px 164 | 165 | .add_ons 166 | clear both 167 | 168 | &.any 169 | margin vertical_space (outer_margin/2) 170 | 171 | .add_on 172 | background #ecedee 173 | background linear-gradient(top, #ecedee, #e5e6e7) 174 | margin 0 175 | height 43px 176 | line-height 42px 177 | vertical-align middle 178 | position relative 179 | clear both 180 | overflow hidden 181 | border-top 1px solid #ccc 182 | border-left 1px solid #ccc 183 | border-right 1px solid #ccc 184 | text-shadow 0 1px 0 white 185 | color #999 186 | font-weight 300 187 | font-size 16px 188 | zoom 1 // IE6 189 | cursor default 190 | 191 | &.first 192 | border-top-left-radius 10px 193 | border-top-right-radius 10px 194 | 195 | &.last 196 | border-bottom 1px solid #ccc 197 | border-bottom-left-radius 10px 198 | border-bottom-right-radius 10px 199 | 200 | .name 201 | font-size inherit 202 | font-weight inherit 203 | font-style italic 204 | color inherit 205 | width columns(6) - outer_margin 206 | margin-left (outer_margin/2) - 1 207 | margin-right column_space 208 | position absolute 209 | left 0 210 | top 0 211 | 212 | .quantity.field 213 | position absolute 214 | top 4px 215 | left columns(6) + (outer_margin/2) + column_space - 1px 216 | field-width columns(2) 217 | display none 218 | 219 | &:before 220 | content "\d7" 221 | height 48px 222 | line-height 30px 223 | position absolute 224 | right 100% 225 | width column_space * 2 226 | font-size 20px 227 | text-align center 228 | vertical-align middle 229 | z-index 1337 230 | color #666 231 | 232 | .cost 233 | font-size inherit 234 | line-height inherit 235 | vertical-align middle 236 | position absolute 237 | right (outer_margin/2) 238 | 239 | &:hover 240 | background linear-gradient(top, '#f0f0f0 0%', '#dfdfdf 50%', '#d5d5d5 50%', '#e0e0e0 100%') 241 | box-shadow inset 0 1px 0 #fff 242 | text-shadow none 243 | color #111 244 | 245 | &:active 246 | &.selected 247 | color #111 248 | background linear-gradient(top, #f0f0f0, #fff) 249 | width auto 250 | box-shadow inset 0 1px 4px 0 rgba(0,0,0,0.075) 251 | text-shadow none 252 | 253 | &.selected 254 | background #fff url(images/check.png) no-repeat (outer_margin/2) center 255 | 256 | .name 257 | padding-left 24px 258 | 259 | &:hover 260 | background #fcf5f0 url(images/uncheck.png) no-repeat (outer_margin/2) center 261 | 262 | &.selected .quantity 263 | display block 264 | 265 | .coupon 266 | clear both 267 | overflow hidden 268 | height field_height 269 | color #333 270 | padding column_space outer_margin 271 | position relative 272 | background url(images/dash.png) repeat-x 1px top 273 | 274 | .check 275 | width 26px 276 | height 26px 277 | float left 278 | border-radius 15px 15px 15px 15px 279 | background #70CCF8 280 | border 1px solid #0090C9 281 | margin 3px 0 1px 10px 282 | box-shadow inset 0 1px 0 0 rgba(255,255,255,.35), 0 1px 1px 0 rgba(0,0,0,0.1) 283 | background #43BEF9 url(images/coupon_check.png) no-repeat center center 284 | background image-linear-gradient(url(images/coupon_check.png) no-repeat center center, top, '#71CDFA 0%', '#43BEF9 50%', '#00B1F6 50%', '#71CEFB 100%') 285 | 286 | &:hover 287 | background image-linear-gradient(url(images/coupon_check.png) no-repeat center center, top, '#71CDFA 0%', '#43BEF9 50%', '#00B1F6 50%', '#71CEFB 100%') 288 | box-shadow inset 0 1px 0 0 rgba(255,255,255,.75), 0 1px 1px 0 rgba(0,0,0,0.1) 289 | 290 | &:active 291 | background image-linear-gradient(url(images/coupon_check.png) no-repeat center center, top, #f0f0f0, #fff) 292 | box-shadow inset 0 3px 3px 0 rgba(0,0,0,0.025) 293 | border 1px solid #999 294 | 295 | &.checking .check 296 | background #f0f0f0 url(images/coupon_checking.gif) no-repeat center center 297 | box-shadow inset 0 3px 3px 0 rgba(0,0,0,0.025) 298 | border 1px solid #999 299 | 300 | &.invalid .coupon_code 301 | border-color #a55 302 | background #fee 303 | color #311 304 | 305 | .coupon_code .error 306 | left columns(8) 307 | 308 | 309 | 310 | .description 311 | float left 312 | margin-left column_space 313 | height field_height 314 | line-height field_height 315 | vertical-align middle 316 | font-size base_font_size * 0.9 317 | 318 | .discount 319 | float right 320 | height field_height 321 | line-height field_height 322 | vertical-align middle 323 | 324 | .error 325 | padding input_padding 326 | line-height input_height 327 | vertical-align middle 328 | color black 329 | text-shadow 0 1px 0 #fec 330 | background #ffc 331 | border 1px solid #ba1 332 | box-shadow 3px 5px 5px 0 rgba(0,0,0,0.1) 333 | border-radius 5px 334 | font-size 13px 335 | 336 | 337 | .server_errors 338 | color #fff 339 | text-shadow 0 1px 0 black 340 | margin 0 outer_margin 341 | 342 | .error 343 | padding-left 26px 344 | background rgba(240,250,0,0.5) url(images/error.png) no-repeat 5px (input_padding + 4px) 345 | 346 | opacity 0 347 | &.any 348 | opacity 1.0 349 | transition opacity 0.5s linear 350 | margin column_space outer_margin 351 | margin-bottom 0 352 | 353 | .contact_info 354 | .billing_info 355 | .accept_tos 356 | position relative 357 | padding vertical_space outer_margin 358 | padding-bottom 0 359 | overflow hidden 360 | zoom 1 // IE6 361 | 362 | .title 363 | font-size base_font_size 364 | height base_font_size + 4px 365 | font-weight bold 366 | padding-bottom 20px 367 | color #404041 368 | text-shadow 0 1px 0 white 369 | 370 | .credit_card 371 | clear both 372 | 373 | .contact_info 374 | background url(images/dash.png) repeat-x 1px bottom 375 | 376 | .accept_tos 377 | background url(images/dash.png) repeat-x 1px top 378 | overflow visible 379 | 380 | input[type=checkbox] 381 | display inline 382 | line-height field_height 383 | vertical-align middle 384 | 385 | label 386 | margin 0 0 0 5px 387 | display inline 388 | line-height field_height 389 | vertical-align middle 390 | 391 | .field .error 392 | display block 393 | position static 394 | 395 | .field 396 | display inline // IE margin bug hack 397 | float left 398 | clear left 399 | field-width columns(8) // Default field is full width 400 | height field_height 401 | margin-bottom vertical_space 402 | position relative 403 | 404 | .error 405 | min-width columns(4) - (input_padding+1)*2 406 | white-space nowrap 407 | position absolute 408 | top 0 409 | left 100% 410 | margin-left column_space 411 | z-index 1337 412 | 413 | .placeholder 414 | position absolute 415 | top 0 416 | left 0 417 | right 0 418 | bottom 0 419 | padding-left input_padding + 4px 420 | font-size base_font_size 421 | font-weight normal 422 | line-height field_height 423 | vertical-align middle 424 | color #999 425 | cursor text 426 | overflow hidden 427 | white-space nowrap 428 | user-select none 429 | font-weight 300 430 | 431 | &.focus .placeholder 432 | color #ccc 433 | 434 | &.invalid .placeholder 435 | color #a77 436 | 437 | // Special field sizes 438 | &.coupon_code 439 | field-width columns(4) 440 | 441 | &.first_name 442 | clear left 443 | field-width columns(4) 444 | 445 | .error 446 | left columns(8) 447 | 448 | &.last_name 449 | hstack(4) 450 | 451 | &.card_number 452 | field-width columns(6) 453 | 454 | .error 455 | left columns(8) 456 | 457 | &.cvv 458 | hstack(2) 459 | 460 | &.expires 461 | field-width columns(8) 462 | 463 | .title 464 | float left 465 | font-size round(base_font_size*0.8) 466 | line-height 24px 467 | vertical-align middle 468 | width columns(2) - 1px 469 | 470 | .month 471 | float left 472 | field-width columns(3) + column_width 473 | margin-left 0 474 | 475 | .year 476 | float left 477 | margin-left 1px 478 | field-width columns(2) 479 | 480 | 481 | &.state 482 | field-width columns(5) 483 | .error 484 | left columns(8) 485 | 486 | &.zip 487 | hstack(3) 488 | 489 | &.vat_number 490 | field-width columns(4) 491 | display none 492 | &.applicable 493 | display block 494 | 495 | // Special limited address cases for zip 496 | .only_zipstreet .zip.field 497 | .only_zip .zip.field 498 | margin-left 0 499 | clear left 500 | 501 | .accepted_cards 502 | position absolute 503 | top vertical_space 504 | right (grid_width - columns(8)) 505 | width 160px 506 | 507 | .card 508 | background-position right top 509 | background-repeat no-repeat 510 | text-indent -3000px 511 | width 40px 512 | height 30px 513 | margin 0 514 | padding 0 515 | float left 516 | 517 | &.mastercard 518 | background-image url(images/credit_cards/mastercard.png) 519 | &.american_express 520 | background-image url(images/credit_cards/amex.png) 521 | &.visa 522 | background-image url(images/credit_cards/visa.png) 523 | &.discover 524 | background-image url(images/credit_cards/discover.png) 525 | 526 | &.match 527 | // nothing 528 | 529 | &.no_match 530 | opacity 0.5 531 | 532 | 533 | // General 534 | input[type=text], 535 | select 536 | vertical-align middle 537 | color #000000 538 | 539 | &.invalid 540 | border-color #a55 541 | background #fee 542 | color #311 543 | 544 | input[type=text] 545 | display block 546 | background white 547 | border input_border solid #a0a0a5 548 | box-shadow inset 0 2px 3px rgba(0,0,0,0.1) 549 | font-size base_font_size 550 | font-family inherit 551 | padding input_padding 552 | height input_height 553 | 554 | input[type=checkbox] 555 | color red 556 | 557 | select 558 | color inherit 559 | font-family inherit 560 | width 100% 561 | 562 | > option 563 | color inherit 564 | 565 | // DUE NOW BAR 566 | .due_now 567 | background url(images/due_now.png) no-repeat top left 568 | clear both 569 | color #2a3a3c 570 | height 70px 571 | line-height 67px 572 | vertical-align middle 573 | // overflow hidden 574 | padding 0 outer_margin + 5px 575 | width columns(12) 576 | position relative 577 | left -5px 578 | text-shadow 0 1px 0 rgba(255,255,255,0.5) 579 | 580 | .title 581 | float left 582 | font-size 29px 583 | position relative 584 | 585 | .cost 586 | color #fff 587 | float right 588 | font-size 33px 589 | font-weight bold 590 | letter-spacing 1px 591 | margin 0 592 | position relative 593 | text-shadow 0px 1px 1px rgba(0,0,0,0.9) 594 | 595 | .footer 596 | border-radius 0px 0px 9px 9px 597 | margin 0px 598 | padding outer_margin 599 | 600 | &.submitting .footer 601 | background url(images/submitting.gif) no-repeat columns(5) 28px 602 | 603 | button.submit 604 | height 46px 605 | max-width 600px 606 | font-size 18px 607 | font-weight 700 608 | color #302106 609 | text-align center 610 | margin-left 0px 611 | border 1px solid #767674 612 | background #e7a500 613 | border-radius 10px 614 | outline none 615 | box-shadow inset rgba(255, 255, 255, 0.7) 0px 1px 0px, rgba(0,0,0, 0.5) 0px 1px 3px 616 | background-image -webkit-gradient(linear, 0% 20%, 0% 100%, from(#FECD00), to(#CE7B00)) 617 | background -moz-linear-gradient(top, #fecd00, #ce7b00) 618 | text-shadow rgba(255,255,255, .5) 0 1px 0 619 | padding 10px 20px 620 | 621 | &:hover 622 | color #451 623 | 624 | &:active 625 | top 2px 626 | color #302106 627 | text-shadow rgba(255,255,255, .5694) 0 -1px 0 628 | outline none 629 | background-image -webkit-gradient(linear, 0% 0%, 0% 100%, from(#CE7B00), to(#FECD00)) 630 | background -moz-linear-gradient(top, #ce7b00, #fecd00) 631 | box-shadow rgba(255, 255, 255, 0.69) 0px -1px 0px inset, rgba(0,0,0, 0.26) 0px 2px 3px 632 | 633 | &[disabled] 634 | position relative 635 | height 46px 636 | max-width 600px 637 | padding 0 10px 638 | font-weight 700 639 | color #555 640 | text-shadow rgba(255,255,255, .5694) 0 1px 0 641 | text-align center 642 | 643 | opacity .75 644 | border 1px solid #767674 645 | background #e7a500 646 | 647 | -moz-border-radius 10px 648 | -webkit-border-radius 10px 649 | border-radius 10px 650 | -webkit-user-select none 651 | -moz-user-select -moz-none 652 | outline none 653 | background-image -webkit-gradient(linear, 0% 0%, 0% 100%, from(#DBD9D2), to(#999)) 654 | background -moz-linear-gradient(top, #DBD9D2, #999999) 655 | -webkit-background-clip padding-box 656 | -webkit-box-shadow rgba(255, 255, 255, 0.69) 0px 1px 0px inset, rgba(0,0,0, 0.26) 0px 2px 3px 657 | box-shadow rgba(255, 255, 255, 0.699219) 0px 1px 0px inset, rgba(0,0,0, 0.269219) 0px 2px 3px 658 | 659 | -------------------------------------------------------------------------------- /recurly.css: -------------------------------------------------------------------------------- 1 | .recurly { 2 | display: block; 3 | position: relative; 4 | width: 500px; 5 | } 6 | .recurly .cost, 7 | .recurly .discount { 8 | font-size: 16px; 9 | text-align: right; 10 | } 11 | .recurly .subscription { 12 | border-radius: 9px 9px 0 0; 13 | text-shadow: 0 1px 0 #fff; 14 | padding-top: 20px; 15 | overflow: hidden; 16 | } 17 | .recurly .plan { 18 | color: #333; 19 | overflow: hidden; 20 | position: relative; 21 | zoom: 1; 22 | } 23 | .recurly .plan .name { 24 | float: left; 25 | font-size: 32px; 26 | min-width: 200px; 27 | padding-left: 20px; 28 | padding-right: 40px; 29 | } 30 | .recurly .plan .quantity.field { 31 | clear: none; 32 | width: 60px; 33 | margin: 4px 0; 34 | } 35 | .recurly .plan .quantity.field input[type=text] { 36 | width: 48px; 37 | } 38 | .recurly .plan .quantity.field:before { 39 | content: "\d7"; 40 | height: 48px; 41 | line-height: 30px; 42 | position: absolute; 43 | right: 100%; 44 | width: 40px; 45 | font-size: 20px; 46 | text-align: center; 47 | vertical-align: middle; 48 | z-index: 1337; 49 | color: #666; 50 | } 51 | .recurly .plan .recurring_cost { 52 | float: right; 53 | text-align: right; 54 | padding-right: 20px; 55 | } 56 | .recurly .plan .recurring_cost .cost { 57 | font-size: 32px; 58 | } 59 | .recurly .plan .recurring_cost .interval { 60 | font-size: 12px; 61 | padding-bottom: 20px; 62 | } 63 | .recurly .free_trial { 64 | clear: left; 65 | float: left; 66 | font-size: 13px; 67 | height: 22px; 68 | margin: 0; 69 | position: absolute; 70 | top: 35px; 71 | left: 20px; 72 | font-style: italic; 73 | } 74 | .recurly .setup_fee { 75 | clear: both; 76 | background: url("images/dash.png") repeat-x 1px top; 77 | overflow: hidden; 78 | padding-top: 20px; 79 | } 80 | .recurly .setup_fee .title { 81 | float: left; 82 | padding-left: 20px; 83 | font-weight: bold; 84 | font-size: 16px; 85 | } 86 | .recurly .setup_fee .cost { 87 | float: right; 88 | padding-right: 20px; 89 | } 90 | .recurly .vat { 91 | height: 24px; 92 | padding: 20px 20px; 93 | display: none; 94 | background: url("images/dash.png") repeat-x 1px top; 95 | } 96 | .recurly .vat.applicable { 97 | display: block; 98 | } 99 | .recurly .vat .title { 100 | font-size: 16px; 101 | font-weight: normal; 102 | float: left; 103 | } 104 | .recurly .vat .cost { 105 | float: right; 106 | font-size: 18px; 107 | } 108 | .recurly .add_ons { 109 | clear: both; 110 | } 111 | .recurly .add_ons.any { 112 | margin: 20px 10px; 113 | } 114 | .recurly .add_ons .add_on { 115 | background: #ecedee; 116 | background: -webkit-linear-gradient(top, #ecedee, #e5e6e7); 117 | background: -moz-linear-gradient(top, #ecedee, #e5e6e7); 118 | background: -o-linear-gradient(top, #ecedee, #e5e6e7); 119 | background: linear-gradient(top, #ecedee, #e5e6e7); 120 | margin: 0; 121 | height: 43px; 122 | line-height: 42px; 123 | vertical-align: middle; 124 | position: relative; 125 | clear: both; 126 | overflow: hidden; 127 | border-top: 1px solid #ccc; 128 | border-left: 1px solid #ccc; 129 | border-right: 1px solid #ccc; 130 | text-shadow: 0 1px 0 #fff; 131 | color: #999; 132 | font-weight: 300; 133 | font-size: 16px; 134 | zoom: 1; 135 | cursor: default; 136 | } 137 | .recurly .add_ons .add_on.first { 138 | border-top-left-radius: 10px; 139 | border-top-right-radius: 10px; 140 | } 141 | .recurly .add_ons .add_on.last { 142 | border-bottom: 1px solid #ccc; 143 | border-bottom-left-radius: 10px; 144 | border-bottom-right-radius: 10px; 145 | } 146 | .recurly .add_ons .add_on .name { 147 | font-size: inherit; 148 | font-weight: inherit; 149 | font-style: italic; 150 | color: inherit; 151 | width: 200px; 152 | margin-left: 9px; 153 | margin-right: 20px; 154 | position: absolute; 155 | left: 0; 156 | top: 0; 157 | } 158 | .recurly .add_ons .add_on .quantity.field { 159 | position: absolute; 160 | top: 4px; 161 | left: 249px; 162 | width: 60px; 163 | display: none; 164 | } 165 | .recurly .add_ons .add_on .quantity.field input[type=text] { 166 | width: 48px; 167 | } 168 | .recurly .add_ons .add_on .quantity.field:before { 169 | content: "\d7"; 170 | height: 48px; 171 | line-height: 30px; 172 | position: absolute; 173 | right: 100%; 174 | width: 40px; 175 | font-size: 20px; 176 | text-align: center; 177 | vertical-align: middle; 178 | z-index: 1337; 179 | color: #666; 180 | } 181 | .recurly .add_ons .add_on .cost { 182 | font-size: inherit; 183 | line-height: inherit; 184 | vertical-align: middle; 185 | position: absolute; 186 | right: 10px; 187 | } 188 | .recurly .add_ons .add_on:hover { 189 | background: -webkit-linear-gradient(top, #f0f0f0 0%, #dfdfdf 50%, #d5d5d5 50%, #e0e0e0 100%); 190 | background: -moz-linear-gradient(top, #f0f0f0 0%, #dfdfdf 50%, #d5d5d5 50%, #e0e0e0 100%); 191 | background: -o-linear-gradient(top, #f0f0f0 0%, #dfdfdf 50%, #d5d5d5 50%, #e0e0e0 100%); 192 | background: linear-gradient(top, #f0f0f0 0%, #dfdfdf 50%, #d5d5d5 50%, #e0e0e0 100%); 193 | box-shadow: inset 0 1px 0 #fff; 194 | text-shadow: none; 195 | color: #111; 196 | } 197 | .recurly .add_ons .add_on:active, 198 | .recurly .add_ons .add_on.selected { 199 | color: #111; 200 | background: -webkit-linear-gradient(top, #f0f0f0, #fff); 201 | background: -moz-linear-gradient(top, #f0f0f0, #fff); 202 | background: -o-linear-gradient(top, #f0f0f0, #fff); 203 | background: linear-gradient(top, #f0f0f0, #fff); 204 | width: auto; 205 | box-shadow: inset 0 1px 4px 0 rgba(0,0,0,0.07); 206 | text-shadow: none; 207 | } 208 | .recurly .add_ons .add_on.selected { 209 | background: #fff url("images/check.png") no-repeat 10px center; 210 | } 211 | .recurly .add_ons .add_on.selected .name { 212 | padding-left: 24px; 213 | } 214 | .recurly .add_ons .add_on.selected:hover { 215 | background: #fcf5f0 url("images/uncheck.png") no-repeat 10px center; 216 | } 217 | .recurly .add_ons .add_on.selected .quantity { 218 | display: block; 219 | } 220 | .recurly .coupon { 221 | clear: both; 222 | overflow: hidden; 223 | height: 34px; 224 | color: #333; 225 | padding: 20px 20px; 226 | position: relative; 227 | background: url("images/dash.png") repeat-x 1px top; 228 | } 229 | .recurly .coupon .check { 230 | width: 26px; 231 | height: 26px; 232 | float: left; 233 | border-radius: 15px 15px 15px 15px; 234 | background: #70ccf8; 235 | border: 1px solid #0090c9; 236 | margin: 3px 0 1px 10px; 237 | box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.35), 0 1px 1px 0 rgba(0,0,0,0.10); 238 | background: #43bef9 url("images/coupon_check.png") no-repeat center center; 239 | background: url("images/coupon_check.png") no-repeat center center, -webkit-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); 240 | background: url("images/coupon_check.png") no-repeat center center, -moz-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); 241 | background: url("images/coupon_check.png") no-repeat center center, -o-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); 242 | background: url("images/coupon_check.png") no-repeat center center linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); 243 | } 244 | .recurly .coupon .check:hover { 245 | background: url("images/coupon_check.png") no-repeat center center, -webkit-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); 246 | background: url("images/coupon_check.png") no-repeat center center, -moz-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); 247 | background: url("images/coupon_check.png") no-repeat center center, -o-linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); 248 | background: url("images/coupon_check.png") no-repeat center center linear-gradient(top, #71CDFA 0%, #43BEF9 50%, #00B1F6 50%, #71CEFB 100%); 249 | box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.75), 0 1px 1px 0 rgba(0,0,0,0.10); 250 | } 251 | .recurly .coupon .check:active { 252 | background: url("images/coupon_check.png") no-repeat center center, -webkit-linear-gradient(top, #f0f0f0, #fff); 253 | background: url("images/coupon_check.png") no-repeat center center, -moz-linear-gradient(top, #f0f0f0, #fff); 254 | background: url("images/coupon_check.png") no-repeat center center, -o-linear-gradient(top, #f0f0f0, #fff); 255 | background: url("images/coupon_check.png") no-repeat center center linear-gradient(top, #f0f0f0, #fff); 256 | box-shadow: inset 0 3px 3px 0 rgba(0,0,0,0.03); 257 | border: 1px solid #999; 258 | } 259 | .recurly .coupon.checking .check { 260 | background: #f0f0f0 url("images/coupon_checking.gif") no-repeat center center; 261 | box-shadow: inset 0 3px 3px 0 rgba(0,0,0,0.03); 262 | border: 1px solid #999; 263 | } 264 | .recurly .coupon.invalid .coupon_code { 265 | border-color: #a55; 266 | background: #fee; 267 | color: #311; 268 | } 269 | .recurly .coupon .coupon_code .error { 270 | left: 300px; 271 | } 272 | .recurly .coupon .description { 273 | float: left; 274 | margin-left: 20px; 275 | height: 34px; 276 | line-height: 34px; 277 | vertical-align: middle; 278 | font-size: 14.4px; 279 | } 280 | .recurly .coupon .discount { 281 | float: right; 282 | height: 34px; 283 | line-height: 34px; 284 | vertical-align: middle; 285 | } 286 | .recurly .error { 287 | padding: 5px; 288 | line-height: 22px; 289 | vertical-align: middle; 290 | color: #000; 291 | text-shadow: 0 1px 0 #fec; 292 | background: #ffc; 293 | border: 1px solid #ba1; 294 | box-shadow: 3px 5px 5px 0 rgba(0,0,0,0.10); 295 | border-radius: 5px; 296 | font-size: 13px; 297 | } 298 | .recurly .server_errors { 299 | color: #fff; 300 | text-shadow: 0 1px 0 #000; 301 | margin: 0 20px; 302 | opacity: 0; 303 | } 304 | .recurly .server_errors .error { 305 | padding-left: 26px; 306 | background: rgba(240,250,0,0.50) url("images/error.png") no-repeat 5px 9px; 307 | } 308 | .recurly .server_errors.any { 309 | opacity: 1; 310 | -webkit-transition: opacity 0.5s linear; 311 | -moz-transition: opacity 0.5s linear; 312 | margin: 20px 20px; 313 | margin-bottom: 0; 314 | } 315 | .recurly .contact_info, 316 | .recurly .billing_info, 317 | .recurly .accept_tos { 318 | position: relative; 319 | padding: 20px 20px; 320 | padding-bottom: 0; 321 | overflow: hidden; 322 | zoom: 1; 323 | } 324 | .recurly .contact_info .title, 325 | .recurly .billing_info .title, 326 | .recurly .accept_tos .title { 327 | font-size: 16px; 328 | height: 20px; 329 | font-weight: bold; 330 | padding-bottom: 20px; 331 | color: #404041; 332 | text-shadow: 0 1px 0 #fff; 333 | } 334 | .recurly .contact_info .credit_card, 335 | .recurly .billing_info .credit_card, 336 | .recurly .accept_tos .credit_card { 337 | clear: both; 338 | } 339 | .recurly .contact_info { 340 | background: url("images/dash.png") repeat-x 1px bottom; 341 | } 342 | .recurly .accept_tos { 343 | background: url("images/dash.png") repeat-x 1px top; 344 | overflow: visible; 345 | } 346 | .recurly .accept_tos input[type=checkbox] { 347 | display: inline; 348 | line-height: 34px; 349 | vertical-align: middle; 350 | } 351 | .recurly .accept_tos label { 352 | margin: 0 0 0 5px; 353 | display: inline; 354 | line-height: 34px; 355 | vertical-align: middle; 356 | } 357 | .recurly .accept_tos .field .error { 358 | display: block; 359 | position: static; 360 | } 361 | .recurly .field { 362 | display: inline; 363 | float: left; 364 | clear: left; 365 | width: 300px; 366 | height: 34px; 367 | margin-bottom: 20px; 368 | position: relative; 369 | } 370 | .recurly .field input[type=text] { 371 | width: 288px; 372 | } 373 | .recurly .field .error { 374 | min-width: 128px; 375 | white-space: nowrap; 376 | position: absolute; 377 | top: 0; 378 | left: 100%; 379 | margin-left: 20px; 380 | z-index: 1337; 381 | } 382 | .recurly .field .placeholder { 383 | position: absolute; 384 | top: 0; 385 | left: 0; 386 | right: 0; 387 | bottom: 0; 388 | padding-left: 9px; 389 | font-size: 16px; 390 | font-weight: normal; 391 | line-height: 34px; 392 | vertical-align: middle; 393 | color: #999; 394 | cursor: text; 395 | overflow: hidden; 396 | white-space: nowrap; 397 | -webkit-user-select: none; 398 | -khtml-user-select: none; 399 | -moz-user-select: none; 400 | -o-user-select: none; 401 | user-select: none; 402 | font-weight: 300; 403 | } 404 | .recurly .field.focus .placeholder { 405 | color: #ccc; 406 | } 407 | .recurly .field.invalid .placeholder { 408 | color: #a77; 409 | } 410 | .recurly .field.coupon_code { 411 | width: 140px; 412 | } 413 | .recurly .field.coupon_code input[type=text] { 414 | width: 128px; 415 | } 416 | .recurly .field.first_name { 417 | clear: left; 418 | width: 140px; 419 | } 420 | .recurly .field.first_name input[type=text] { 421 | width: 128px; 422 | } 423 | .recurly .field.first_name .error { 424 | left: 300px; 425 | } 426 | .recurly .field.last_name { 427 | width: 140px; 428 | margin-left: 20px; 429 | margin-left: 20px; 430 | clear: none; 431 | clear: none; 432 | } 433 | .recurly .field.last_name input[type=text] { 434 | width: 128px; 435 | } 436 | .recurly .field.card_number { 437 | width: 220px; 438 | } 439 | .recurly .field.card_number input[type=text] { 440 | width: 208px; 441 | } 442 | .recurly .field.card_number .error { 443 | left: 300px; 444 | } 445 | .recurly .field.cvv { 446 | width: 60px; 447 | margin-left: 20px; 448 | margin-left: 20px; 449 | clear: none; 450 | clear: none; 451 | } 452 | .recurly .field.cvv input[type=text] { 453 | width: 48px; 454 | } 455 | .recurly .field.expires { 456 | width: 300px; 457 | } 458 | .recurly .field.expires input[type=text] { 459 | width: 288px; 460 | } 461 | .recurly .field.expires .title { 462 | float: left; 463 | font-size: 13px; 464 | line-height: 24px; 465 | vertical-align: middle; 466 | width: 59px; 467 | } 468 | .recurly .field.expires .month { 469 | float: left; 470 | width: 120px; 471 | margin-left: 0; 472 | } 473 | .recurly .field.expires .month input[type=text] { 474 | width: 108px; 475 | } 476 | .recurly .field.expires .year { 477 | float: left; 478 | margin-left: 1px; 479 | width: 60px; 480 | } 481 | .recurly .field.expires .year input[type=text] { 482 | width: 48px; 483 | } 484 | .recurly .field.state { 485 | width: 180px; 486 | } 487 | .recurly .field.state input[type=text] { 488 | width: 168px; 489 | } 490 | .recurly .field.state .error { 491 | left: 300px; 492 | } 493 | .recurly .field.zip { 494 | width: 100px; 495 | margin-left: 20px; 496 | margin-left: 20px; 497 | clear: none; 498 | clear: none; 499 | } 500 | .recurly .field.zip input[type=text] { 501 | width: 88px; 502 | } 503 | .recurly .field.vat_number { 504 | width: 140px; 505 | display: none; 506 | } 507 | .recurly .field.vat_number input[type=text] { 508 | width: 128px; 509 | } 510 | .recurly .field.vat_number.applicable { 511 | display: block; 512 | } 513 | .recurly .only_zipstreet .zip.field, 514 | .recurly .only_zip .zip.field { 515 | margin-left: 0; 516 | clear: left; 517 | } 518 | .recurly .accepted_cards { 519 | position: absolute; 520 | top: 20px; 521 | right: 180px; 522 | width: 160px; 523 | } 524 | .recurly .card { 525 | background-position: right top; 526 | background-repeat: no-repeat; 527 | text-indent: -3000px; 528 | width: 40px; 529 | height: 30px; 530 | margin: 0; 531 | padding: 0; 532 | float: left; 533 | } 534 | .recurly .card.mastercard { 535 | background-image: url("images/credit_cards/mastercard.png"); 536 | } 537 | .recurly .card.american_express { 538 | background-image: url("images/credit_cards/amex.png"); 539 | } 540 | .recurly .card.visa { 541 | background-image: url("images/credit_cards/visa.png"); 542 | } 543 | .recurly .card.discover { 544 | background-image: url("images/credit_cards/discover.png"); 545 | } 546 | .recurly .card.no_match { 547 | opacity: 0.5; 548 | } 549 | .recurly input[type=text], 550 | .recurly select { 551 | vertical-align: middle; 552 | color: #000; 553 | } 554 | .recurly input[type=text].invalid, 555 | .recurly select.invalid { 556 | border-color: #a55; 557 | background: #fee; 558 | color: #311; 559 | } 560 | .recurly input[type=text] { 561 | display: block; 562 | background: #fff; 563 | border: 1px solid #a0a0a5; 564 | box-shadow: inset 0 2px 3px rgba(0,0,0,0.10); 565 | font-size: 16px; 566 | font-family: inherit; 567 | padding: 5px; 568 | height: 22px; 569 | } 570 | .recurly input[type=checkbox] { 571 | color: #f00; 572 | } 573 | .recurly select { 574 | color: inherit; 575 | font-family: inherit; 576 | width: 100%; 577 | } 578 | .recurly select > option { 579 | color: inherit; 580 | } 581 | .recurly .due_now { 582 | background: url("images/due_now.png") no-repeat top left; 583 | clear: both; 584 | color: #2a3a3c; 585 | height: 70px; 586 | line-height: 67px; 587 | vertical-align: middle; 588 | padding: 0 25px; 589 | width: 460px; 590 | position: relative; 591 | left: -5px; 592 | text-shadow: 0 1px 0 rgba(255,255,255,0.50); 593 | } 594 | .recurly .due_now .title { 595 | float: left; 596 | font-size: 29px; 597 | position: relative; 598 | } 599 | .recurly .due_now .cost { 600 | color: #fff; 601 | float: right; 602 | font-size: 33px; 603 | font-weight: bold; 604 | letter-spacing: 1px; 605 | margin: 0; 606 | position: relative; 607 | text-shadow: 0px 1px 1px rgba(0,0,0,0.90); 608 | } 609 | .recurly .footer { 610 | border-radius: 0px 0px 9px 9px; 611 | margin: 0px; 612 | padding: 20px; 613 | } 614 | .recurly.submitting .footer { 615 | background: url("images/submitting.gif") no-repeat 180px 28px; 616 | } 617 | .recurly button.submit { 618 | height: 46px; 619 | max-width: 600px; 620 | font-size: 18px; 621 | font-weight: 700; 622 | color: #302106; 623 | text-align: center; 624 | margin-left: 0px; 625 | border: 1px solid #767674; 626 | background: #e7a500; 627 | border-radius: 10px; 628 | outline: none; 629 | box-shadow: inset rgba(255,255,255,0.70) 0px 1px 0px, rgba(0,0,0,0.50) 0px 1px 3px; 630 | background-image: -webkit-gradient(linear, 0% 20%, 0% 100%, from(#fecd00), to(#ce7b00)); 631 | background: -moz-linear-gradient(top, #fecd00, #ce7b00); 632 | text-shadow: rgba(255,255,255,0.50) 0 1px 0; 633 | padding: 10px 20px; 634 | } 635 | .recurly button.submit:hover { 636 | color: #451; 637 | } 638 | .recurly button.submit:active { 639 | top: 2px; 640 | color: #302106; 641 | text-shadow: rgba(255,255,255,0.57) 0 -1px 0; 642 | outline: none; 643 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ce7b00), to(#fecd00)); 644 | background: -moz-linear-gradient(top, #ce7b00, #fecd00); 645 | box-shadow: rgba(255,255,255,0.69) 0px -1px 0px inset, rgba(0,0,0,0.26) 0px 2px 3px; 646 | } 647 | .recurly button.submit[disabled] { 648 | position: relative; 649 | height: 46px; 650 | max-width: 600px; 651 | padding: 0 10px; 652 | font-weight: 700; 653 | color: #555; 654 | text-shadow: rgba(255,255,255,0.57) 0 1px 0; 655 | text-align: center; 656 | opacity: 0.75; 657 | border: 1px solid #767674; 658 | background: #e7a500; 659 | -moz-border-radius: 10px; 660 | -webkit-border-radius: 10px; 661 | border-radius: 10px; 662 | -webkit-user-select: none; 663 | -moz-user-select: -moz-none; 664 | outline: none; 665 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#dbd9d2), to(#999)); 666 | background: -moz-linear-gradient(top, #dbd9d2, #999); 667 | -webkit-background-clip: padding-box; 668 | -webkit-box-shadow: rgba(255,255,255,0.69) 0px 1px 0px inset, rgba(0,0,0,0.26) 0px 2px 3px; 669 | box-shadow: rgba(255,255,255,0.70) 0px 1px 0px inset, rgba(0,0,0,0.27) 0px 2px 3px; 670 | } 671 | -------------------------------------------------------------------------------- /recurly.min.js: -------------------------------------------------------------------------------- 1 | (function(f){function s(x){function w(){}w.prototype=x||this;return new w()}var h={};h.settings={};h.Error={toString:function(){return"RecurlyJS Error: "+this.message}};h.raiseError=function(w){var x=s(h.Error);x.message=w;throw x};h.config=function(w){f.extend(true,h.settings,w);if(!w.baseURL){h.settings.baseURL="https://api.recurly.com/jsonp/";var x=h.settings.subdomain||h.raiseError("company subdomain not configured");h.settings.baseURL+=x+"/"}};function r(x,w){if(x==1){return w.substr(0,w.length-1)}return""+x+" "+w}(h.Cost=function(w){this._cents=w||0}).prototype={toString:function(){return h.formatCurrency(this.dollars())},cents:function(w){if(w===undefined){return this._cents}return new Cost(w)},dollars:function(w){if(w===undefined){return this._cents/100}return new h.Cost(w*100)},mult:function(w){return new h.Cost(this._cents*w)},add:function(w){if(w.cents){w=w.cents()}return new h.Cost(this._cents+w)},sub:function(w){if(w.cents){w=w.cents()}return new h.Cost(this._cents-w)}};h.Cost.FREE=new h.Cost(0);(h.TimePeriod=function(x,w){this.length=x;this.unit=w}).prototype={toString:function(){return""+r(this.length,this.unit)},toDate:function(){var w=new Date();switch(this.unit){case"month":w.setMonth(w.getMonth()+this.length);break;case"day":w.setDay(w.getDay()+this.length);break}return w},clone:function(){return new h.TimePeriod(this.length,this.unit)}};(h.RecurringCost=function(x,w){this.cost=x;this.interval=w}).prototype={toString:function(){return""+this.cost+" every "+this.interval},clone:function(){return new h.TimePeriod(this.length,this.unit)}};h.RecurringCost.FREE=new h.RecurringCost(0,null);(h.RecurringCostStage=function(w,x){this.recurringCost=w;this.duration=x}).prototype={toString:function(){this.recurringCost.toString()+" for "+this.duration.toString()}};h.locale={};h.locale.errors={emptyField:"Required field",missingFullAddress:"Please enter your full address.",invalidEmail:"Invalid",invalidCC:"Invalid",invalidCVV:"Invalid",invalidCoupon:"Invalid",cardDeclined:"Transaction declined",acceptTOS:"Please accept the Terms of Service."};h.locale.currencies={};h.locale.currency={format:"%u%n",separator:".",delimiter:",",precision:2};function p(w,x){var z=h.locale.currencies[w]=s(h.locale.currency);for(var y in x){z[y]=x[y]}}p("USD",{symbol:"$"});p("AUD",{symbol:"$"});p("CAD",{symbol:"$"});p("EUR",{symbol:"\u20ac"});p("GBP",{symbol:"\u00a3"});p("CZK",{symbol:"\u004b"});p("DKK",{symbol:"\u006b\u0072"});p("HUF",{symbol:"Ft"});p("JPY",{symbol:"\u00a5"});p("NOK",{symbol:"kr"});p("NZD",{symbol:"$"});p("PLN",{symbol:"\u007a"});p("SGD",{symbol:"$"});p("SEK",{symbol:"kr"});p("CHF",{symbol:"Fr"});p("ZAR",{symbol:"R"});h.settings.locale=h.locale;h.detectCardType=function(w){w=w.replace(/\D/g,"");var y=[{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 z=0;z").hide();z.attr("action",x).attr("method","POST").attr("enctype","application/x-www-form-urlencoded");function A(E,H,G){var D=(G.length>0?(G+"["+E+"]"):E);if(typeof H==="object"){for(var F in H){if(H.hasOwnProperty(F)){A(F,H[F],D)}}}else{f('').attr({name:D,value:H}).appendTo(z)}}A("",B,"");f("body").append(z);z.submit()};(h.isValidCC=function(C){var x=C.val();if(/[^0-9-]+/.test(x)){return false}var A=0,z=0,w=false;x=x.replace(/\D/g,"");for(var B=x.length-1;B>=0;B--){var y=x.charAt(B);var z=parseInt(y,10);if(w){if((z*=2)>9){z-=9}}A+=z;w=!w}return(A%10)==0}).defaultErrorKey="invalidCC";(h.isValidEmail=function(x){var w=x.val();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(w)}).defaultErrorKey="invalidEmail";function b(w){return/^[0-9]+$/.test(w)}(h.isValidCVV=function(x){var w=x.val();return(w.length==3||w.length==4)&&b(w)}).defaultErrorKey="invalidCVV";(h.isNotEmpty=function(x){var w=x.val();return !!w}).defaultErrorKey="emptyField";(h.isChecked=function(w){return w.is(":checked")}).defaultErrorKey="acceptTOS";h.Plan={create:s,fromJSON:function(z){var A=this.create();A.name=z.name;A.code=z.plan_code;A.cost=new h.Cost(z.unit_amount_in_cents);A.displayQuantity=z.display_quantity;A.interval=new h.TimePeriod(z.plan_interval_length,z.plan_interval_unit);if(z.trial_interval_length){A.trial=new h.TimePeriod(z.trial_interval_length,z.trial_interval_unit)}if(z.setup_fee_in_cents){A.setupFee=new h.Cost(z.setup_fee_in_cents)}A.addOns=[];if(z.add_ons){for(var x=z.add_ons.length,y=0;y');w.text(y);w.appendTo(B.parent());B.addClass("invalid");B.bind("change keyup",function(){if(x(B)){B.removeClass("invalid");w.remove();B.unbind()}});B.focus()}}function o(y,A,x){var C=y.find(A+" input");if(C.length==0){C=y.find(A+" select")}if(C.length==0){return undefined}var B=C.val();for(var z=2,w;w=arguments[z];++z){if(!w.validator(C)){q(w,C)}}return B}function c(x,w){return{validator:x,errorKey:w||x.defaultErrorKey}}function a(w){var x=w.find(".server_errors");x.removeClass("any").addClass("none");x.empty()}function m(x,B){var z=x.find(".server_errors");a(x);var w=B.length;if(w){z.removeClass("none").addClass("any");for(var A=0;A');y.text(B[A]);z.append(y)}}}var j={contactInfo:{firstName:".contact_info > .full_name > .first_name > input",lastName:".contact_info > .full_name > .last_name > input",email:".contact_info > .email > input",phone:".contact_info > .phone > input",companyName:".contact_info > .company_name > input"},billingInfo:{firstName:".billing_info > .credit_card > .first_name > input",lastName:".billing_info > .credit_card > .last_name > input",address1:".billing_info > .address > .address1 > input",address2:".billing_info > .address > .address2 > input",country:".billing_info > .address > .country > select",city:".billing_info > .address > .city > input",state:".billing_info > .address > .state_zip > .state > input",zip:".billing_info > .address > .state_zip > .zip > input",vatNumber:".billing_info > .vat_number > input"}};function n(z,w,B){if(!w){return}for(var y in w){if(w.hasOwnProperty(y)&&B.hasOwnProperty(y)){var x=w[y];var A=B[y];if(typeof A=="string"){z.find(A).val(x).change()}else{if(typeof A=="object"){n(z,x,A)}}}}}function k(w,x){if(!x.collectPhone){w.find(".phone").remove()}if(!x.collectCompany){w.find(".company_name").remove()}w.delegate(".placeholder","click",function(){var y=f(this);var z=f(this).parent();z.find("input").focus()});w.delegate("input","change keyup",function(){var z=f(this);var y=f(this).parent();if(z.val().length>0){y.find(".placeholder").hide()}else{y.find(".placeholder").show()}});w.delegate("input","focus",function(){f(this).parent().addClass("focus")});w.delegate("input","blur",function(){f(this).parent().removeClass("focus")});w.delegate("input","keydown",function(y){if(y.keyCode>=48&&y.keyCode<=90){f(this).parent().find(".placeholder").hide()}});n(w,x.preFill,j)}function g(y,z){if(z.distinguishContactFromBillingInfo){var x=y.find(".contact_info .first_name input");var A=y.find(".contact_info .last_name input");var B=x.val();var w=A.val();y.find(".contact_info .first_name input").change(function(){var C=y.find(".billing_info .first_name input");if(C.val()==B){C.val(f(this).val()).change()}B=x.val()});y.find(".contact_info .last_name input").change(function(){var C=y.find(".billing_info .last_name input");if(C.val()==w){C.val(f(this).val()).change()}w=A.val()})}else{y.find(".billing_info .first_name, .billing_info .last_name").remove()}}function e(H,G){if(h.settings.country){var A=H.find(".country option[value="+h.settings.country+"]");if(A.length){A.attr("selected",true).change()}}var y=new Date();var F=y.getFullYear();var E=y.getMonth();var B=H.find(".year select");var w=H.find(".month select");for(var C=F;C<=F+10;++C){var x=f('");x.appendTo(B)}B.val(F+1);function D(){if(B.val()==F){w.find('option[value="'+E+'"]');var I=false;w.find("option").each(function(){if(f(this).val()<=E){f(this).attr("disabled",true)}else{f(this).removeAttr("disabled");if(!I){I=true;f(this).attr("selected",true)}}})}else{w.find("option").removeAttr("disabled")}}D();B.change(D);if(G.addressRequirement=="none"){H.find(".address").remove()}else{if(G.addressRequirement=="zip"){H.find(".address").addClass("only_zip");H.find(".address1, .address2, .city, .state").remove();if(!h.settings.VATPercent){H.find(".country").remove()}}else{if(G.addressRequirement=="zipstreet"){H.find(".address").addClass("only_zipstreet");H.find(".city, .state").remove();if(!h.settings.VATPercent){H.find(".country").remove()}}else{if(G.addressRequirement=="full"){H.find(".address").addClass("full")}}}}var z=H.find(".accepted_cards");H.find(".card_number input").bind("change keyup",function(){var I=h.detectCardType(f(this).val());if(I){z.find(".card").each(function(){f(this).toggleClass("match",f(this).hasClass(I));f(this).toggleClass("no_match",!f(this).hasClass(I))})}else{z.find(".card").removeClass("match no_match")}})}function i(w,y,x){y.firstName=o(w,".contact_info .first_name",c(h.isNotEmpty));y.lastName=o(w,".contact_info .last_name",c(h.isNotEmpty));y.companyName=o(w,".contact_info .company_name");y.email=o(w,".email",c(h.isNotEmpty),c(h.isValidEmail));y.code=x.accountCode}function d(w,y,x){y.firstName=o(w,".billing_info .first_name",c(h.isNotEmpty));y.lastName=o(w,".billing_info .last_name",c(h.isNotEmpty));y.number=o(w,".card_number",c(h.isNotEmpty),c(h.isValidCC));y.cvv=o(w,".cvv",c(h.isNotEmpty),c(h.isValidCVV));y.month=o(w,".month");y.year=o(w,".year");y.phone=o(w,".phone");y.address1=o(w,".address1",c(h.isNotEmpty));y.address2=o(w,".address2");y.city=o(w,".city",c(h.isNotEmpty));y.state=o(w,".state",c(h.isNotEmpty));y.zip=o(w,".zip",c(h.isNotEmpty));y.country=o(w,".country",c(function(z){return z.val()!="-"},"emptyField"))}function t(w){o(w,".accept_tos",c(h.isChecked))}h.buildBillingInfoUpdateForm=function(x){var y={addressRequirement:"full",distinguishContactFromBillingInfo:true};x=f.extend(s(h.settings),y,x);if(!x.accountCode){h.raiseError("accountCode missing")}if(!x.signature){h.raiseError("signature missing")}var z=h.BillingInfo.create();var w=f(h.updateBillingInfoFormHTML);w.find(".billing_info").html(h.billingInfoFieldsHTML);k(w,x);e(w,x);w.submit(function(A){A.preventDefault();a(w);w.find(".error").remove();w.find(".invalid").removeClass("invalid");u(function(){d(w,z,x);w.addClass("submitting");w.find("button.submit").attr("disabled",true).text("Please Wait");z.save({signature:x.signature,distinguishContactFromBillingInfo:x.distinguishContactFromBillingInfo,accountCode:x.accountCode,success:function(B){if(x.afterUpdate){x.afterUpdate(B)}if(x.successURL){var C=x.successURL;h.post(C,B,x)}},error:function(B){if(!x.onError||!x.onError(B)){m(w,B)}},complete:function(){w.removeClass("submitting");w.find("button.submit").removeAttr("disabled").text("Update")}})})});if(x.beforeInject){x.beforeInject(w.get(0))}f(function(){var A=f(x.target);A.html(w);if(x.afterInject){x.afterInject(w.get(0))}})};function v(w,x){if(x.termsOfServiceURL||x.privacyPolicyURL){var y=w.find(".accept_tos").html(h.termsOfServiceHTML);if(!(x.termsOfServiceURL&&x.privacyPolicyURL)){y.find("span.and").remove()}if(x.termsOfServiceURL){y.find("a.tos_link").attr("href",x.termsOfServiceURL)}else{y.find("a.tos_link").remove()}if(x.privacyPolicyURL){y.find("a.pp_link").attr("href",x.privacyPolicyURL)}else{y.find("a.pp_link").remove()}}else{w.find(".accept_tos").remove()}}h.buildTransactionForm=function(x){var z={addressRequirement:"full",distinguishContactFromBillingInfo:true,collectContactInfo:true};x=f.extend(s(h.settings),z,x);if(!x.collectContactInfo&&!x.accountCode){h.raiseError("collectContactInfo is false, but no accountCode provided")}if(!x.signature){h.raiseError("signature missing")}var B=h.BillingInfo.create(),y=h.Account.create(),A=h.Transaction.create();A.account=y;A.billingInfo=B;A.currency=x.currency;A.cost=new h.Cost(x.amountInCents);var w=f(h.oneTimeTransactionFormHTML);w.find(".billing_info").html(h.billingInfoFieldsHTML);if(x.collectContactInfo){w.find(".contact_info").html(h.contactInfoFieldsHTML)}else{w.find(".contact_info").remove()}k(w,x);g(w,x);e(w,x);v(w,x);w.submit(function(C){C.preventDefault();a(w);w.find(".error").remove();w.find(".invalid").removeClass("invalid");u(function(){i(w,y,x);d(w,B,x);t(w);w.addClass("submitting");w.find("button.submit").attr("disabled",true).text("Please Wait");A.save({signature:x.signature,accountCode:x.accountCode,success:function(D){if(x.afterPay){x.afterPay(D)}if(x.successURL){var E=x.successURL;h.post(E,D,x)}},error:function(D){if(!x.onError||!x.onError(D)){m(w,D)}},complete:function(){w.removeClass("submitting");w.find("button.submit").removeAttr("disabled").text("Pay")}})})});if(x.beforeInject){x.beforeInject(w.get(0))}f(function(){var C=f(x.target);C.html(w);if(x.afterInject){x.afterInject(w.get(0))}})};h.buildSubscriptionForm=function(x){var z={enableAddOns:true,enableCoupons:true,addressRequirement:"full",distinguishContactFromBillingInfo:false};x=f.extend(s(h.settings),z,x);var w=f(h.subscribeFormHTML);w.find(".contact_info").html(h.contactInfoFieldsHTML);w.find(".billing_info").html(h.billingInfoFieldsHTML);k(w,x);g(w,x);e(w,x);v(w,x);if(x.planCode){h.Plan.get(x.planCode,y)}else{if(x.plan){y(x.plan)}}function y(A){if(x.filterPlan){A=x.filterPlan(A)||A}var P=A.createSubscription(),B=h.Account.create(),I=h.BillingInfo.create();P.account=B;P.billingInfo=I;if(x.filterSubscription){P=x.filterSubscription(P)||P}if(!A.displayQuantity){w.find(".plan .quantity").remove()}if(A.setupFee){w.find(".subscription").addClass("with_setup_fee");w.find(".plan .setup_fee .cost").text(""+A.setupFee)}else{w.find(".plan .setup_fee").remove()}if(A.trial){w.find(".subscription").addClass("with_trial");w.find(".plan .free_trial").text("First "+A.trial+" free")}else{w.find(".plan .free_trial").remove()}function G(){var S=P.calculateTotals();w.find(".plan .recurring_cost .cost").text(""+S.plan);w.find(".due_now .cost").text(""+S.stages.now);w.find(".coupon .discount").text(""+(S.coupon||""));w.find(".vat .cost").text(""+(S.vat||""));w.find(".add_ons .add_on").each(function(){var T=f(this).data("add_on");if(f(this).hasClass("selected")){var U=S.addOns[T.code];f(this).find(".cost").text("+ "+U)}else{f(this).find(".cost").text("+ "+T.cost)}})}w.find(".plan .quantity input").bind("change keyup",function(){P.plan.quantity=parseInt(f(this).val(),10)||1;G()});w.find(".plan .name").text(A.name);w.find(".plan .recurring_cost .cost").text(""+A.cost);w.find(".plan .recurring_cost .interval").text("every "+A.interval);var E=w.find(".add_ons");if(x.enableAddOns){var M=A.addOns.length;if(M){E.removeClass("none").addClass("any");for(var N=0;N
    '+J.name+'
    Qty
    ');if(!J.displayQuantity){D.find(".quantity").remove()}D.data("add_on",J);D.appendTo(E)}E.delegate(".add_ons .quantity input","change keyup",function(V){var S=f(this).closest(".add_on");var U=S.data("add_on");var T=parseInt(f(this).val(),10)||1;P.findAddOnByCode(U.code).quantity=T;G()});E.bind("selectstart",function(S){if(f(S.target).is(".add_on")){S.preventDefault()}});E.delegate(".add_ons .add_on","click",function(W){if(f(W.target).closest(".quantity").length){return}var U=!f(this).hasClass("selected");f(this).toggleClass("selected",U);var V=f(this).data("add_on");if(U){var S=P.redeemAddOn(V);var T=f(this).find(".quantity input");S.quantity=parseInt(T.val(),10)||1;T.focus()}else{P.removeAddOn(V.code)}G()})}}else{E.remove()}var H=w.find(".coupon");var L=null;function Q(){var S=H.find("input").val();if(S==L){return}L=S;if(!S){H.removeClass("invalid").removeClass("valid");H.find(".description").text("");P.coupon=undefined;G();return}H.addClass("checking");P.getCoupon(S,function(T){H.removeClass("checking");P.coupon=T;H.removeClass("invalid").addClass("valid");H.find(".description").text(T.description);G()},function(){P.coupon=undefined;H.removeClass("checking");H.removeClass("valid").addClass("invalid");H.find(".description").text("Not Found");G()})}if(x.enableCoupons){H.find("input").bind("keyup change",function(S){});H.find("input").keypress(function(S){if(S.charCode==13){S.preventDefault();Q()}});H.find(".check").click(function(){Q()});H.find("input").blur(function(){H.find(".check").click()})}else{H.remove()}var C=w.find(".vat");var R=w.find(".vat_number");var K=R.find("input");C.find(".title").text("VAT at "+h.settings.VATPercent+"%");function F(){var T=w.find(".country select").val();var V=h.isVATNumberApplicable(T);R.toggleClass("applicable",V);R.toggleClass("inapplicable",!V);var U=K.val();var S=h.isVATChargeApplicable(T,U);C.toggleClass("applicable",S);C.toggleClass("inapplicable",!S)}w.find(".country select").change(function(){I.country=f(this).val();G();F()}).change();K.bind("keyup change",function(){I.vatNumber=f(this).val();G();F()});w.submit(function(S){S.preventDefault();a(w);w.find(".error").remove();w.find(".invalid").removeClass("invalid");u(function(){i(w,B,x);d(w,I,x);t(w);w.addClass("submitting");w.find("button.submit").attr("disabled",true).text("Please Wait");P.save({success:function(T){if(x.afterSubscribe){x.afterSubscribe(T)}if(x.successURL){var U=x.successURL;h.post(U,T,x)}},error:function(T){if(!x.onError||!x.onError(T)){m(w,T)}},complete:function(){w.removeClass("submitting");w.find("button.submit").removeAttr("disabled").text("Subscribe")}})})});G();if(x.beforeInject){x.beforeInject(w.get(0))}f(function(){var S=f(x.target);S.html(w);if(x.afterInject){x.afterInject(w.get(0))}})}};h.contactInfoFieldsHTML='
    Contact Info
    First Name
    Last Name
    Phone Number
    Company/Organization Name
    ';h.billingInfoFieldsHTML='
    Billing Info
    American Express
    Discover
    MasterCard
    Visa
    First Name
    Last Name
    Credit Card Number
    CVV
    Expires
    Address
    Apt/Suite
    City
    State/Province
    Zip/Postal
    VAT Number
    ';h.subscribeFormHTML='';h.updateBillingInfoFormHTML='
    ';h.oneTimeTransactionFormHTML='
    ';h.termsOfServiceHTML='';window.Recurly=h}(jQuery)); -------------------------------------------------------------------------------- /recurly.js: -------------------------------------------------------------------------------- 1 | // Recurly.js - v1.1.4 2 | // 3 | // Communicates with Recurly 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 | // Author: Emery Denuccio 8 | // 9 | // (MIT License) 10 | // 11 | // Copyright (C) 2011 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 | 33 | (function($){ 34 | 35 | "use strict"; 36 | 37 | 38 | 39 | ////////////////////////////////////////////////// 40 | // Compiled from js/recurly.core.js 41 | ////////////////////////////////////////////////// 42 | 43 | // Non-intrusive Object.create 44 | function createObject(o) { 45 | function F() {} 46 | F.prototype = o || this; 47 | return new F(); 48 | }; 49 | 50 | var R = {}; 51 | R.settings = {}; 52 | 53 | R.Error = { 54 | toString: function() { 55 | return 'RecurlyJS Error: ' + this.message; 56 | } 57 | }; 58 | 59 | R.raiseError = function(message) { 60 | var e = createObject(R.Error); 61 | e.message = message; 62 | throw e; 63 | }; 64 | 65 | 66 | R.config = function(settings) { 67 | $.extend(true, R.settings, settings); 68 | 69 | if(!settings.baseURL) { 70 | R.settings.baseURL = 'https://api.recurly.com/jsonp/'; 71 | var subdomain = R.settings.subdomain || R.raiseError('company subdomain not configured'); 72 | R.settings.baseURL += subdomain + '/'; 73 | } 74 | }; 75 | 76 | 77 | function pluralize(count, term) { 78 | if(count == 1) { 79 | return term.substr(0,term.length-1); 80 | } 81 | 82 | return '' + count + ' ' + term; 83 | } 84 | 85 | // Immutable currency-amount object 86 | // This will eventually handle multi-currency 87 | // where it will store a list of costs per currency 88 | // and accessors will return the appropriate one 89 | // based on the current currency 90 | // 91 | 92 | (R.Cost = function(cents) { 93 | this._cents = cents || 0; 94 | }).prototype = { 95 | toString: function() { 96 | return R.formatCurrency(this.dollars()); 97 | } 98 | , cents: function(val) { 99 | if(val === undefined) 100 | return this._cents; 101 | 102 | return new Cost(val); 103 | } 104 | , dollars: function(val) { 105 | if(val === undefined) 106 | return this._cents/100; 107 | 108 | return new R.Cost(val*100); 109 | } 110 | , mult: function(n) { 111 | return new R.Cost(this._cents * n); 112 | } 113 | , add: function(n) { 114 | if(n.cents) n = n.cents(); 115 | return new R.Cost(this._cents + n); 116 | } 117 | , sub: function(n) { 118 | if(n.cents) n = n.cents(); 119 | return new R.Cost(this._cents - n); 120 | } 121 | }; 122 | 123 | R.Cost.FREE = new R.Cost(0); 124 | 125 | (R.TimePeriod = function(length,unit) { 126 | this.length = length; 127 | this.unit = unit; 128 | }).prototype = { 129 | toString: function() { 130 | return '' + pluralize(this.length, this.unit); 131 | } 132 | , toDate: function() { 133 | var d = new Date(); 134 | switch(this.unit) { 135 | case 'month': 136 | d.setMonth( d.getMonth() + this.length ); 137 | break; 138 | case 'day': 139 | d.setDay( d.getDay() + this.length ); 140 | break; 141 | } 142 | return d; 143 | } 144 | , clone: function() { 145 | return new R.TimePeriod(this.length,this.unit); 146 | } 147 | }; 148 | 149 | (R.RecurringCost = function(cost,interval) { 150 | this.cost = cost; 151 | this.interval = interval; 152 | }).prototype = { 153 | toString: function() { 154 | return '' + this.cost + ' every ' + this.interval; 155 | } 156 | , clone: function() { 157 | return new R.TimePeriod(this.length,this.unit); 158 | } 159 | }; 160 | 161 | R.RecurringCost.FREE = new R.RecurringCost(0,null); 162 | 163 | (R.RecurringCostStage = function(recurringCost, duration) { 164 | this.recurringCost = recurringCost; 165 | this.duration = duration; 166 | }).prototype = { 167 | toString: function() { 168 | this.recurringCost.toString() + ' for ' + this.duration.toString(); 169 | } 170 | }; 171 | 172 | 173 | 174 | 175 | 176 | 177 | ////////////////////////////////////////////////// 178 | // Compiled from js/recurly.locale.js 179 | ////////////////////////////////////////////////// 180 | 181 | R.locale = {}; 182 | 183 | R.locale.errors = { 184 | emptyField: 'Required field' 185 | , missingFullAddress: 'Please enter your full address.' 186 | , invalidEmail: 'Invalid' 187 | , invalidCC: 'Invalid' 188 | , invalidCVV: 'Invalid' 189 | , invalidCoupon: 'Invalid' 190 | , cardDeclined: 'Transaction declined' 191 | , acceptTOS: 'Please accept the Terms of Service.' 192 | }; 193 | 194 | R.locale.currencies = {}; 195 | 196 | R.locale.currency = { 197 | format: "%u%n" 198 | , separator: "." 199 | , delimiter: "," 200 | , precision: 2 201 | }; 202 | 203 | function C(key, def) { 204 | var c = R.locale.currencies[key] = createObject(R.locale.currency); 205 | for(var p in def) { 206 | c[p] = def[p]; 207 | } 208 | }; 209 | 210 | C('USD', { 211 | symbol: '$' 212 | }); 213 | 214 | C('AUD', { 215 | symbol: '$' 216 | }); 217 | 218 | C('CAD', { 219 | symbol: '$' 220 | }); 221 | 222 | C('EUR', { 223 | symbol: '\u20ac' 224 | }); 225 | 226 | C('GBP', { 227 | symbol: '\u00a3' 228 | }); 229 | 230 | C('CZK', { 231 | symbol: '\u004b' 232 | }); 233 | 234 | C('DKK', { 235 | symbol: '\u006b\u0072' 236 | }); 237 | 238 | C('HUF', { 239 | symbol: 'Ft' 240 | }); 241 | 242 | C('JPY', { 243 | symbol: '\u00a5' 244 | }); 245 | 246 | C('NOK', { 247 | symbol: 'kr' 248 | }); 249 | 250 | C('NZD', { 251 | symbol: '$' 252 | }); 253 | 254 | C('PLN', { 255 | symbol: '\u007a' 256 | }); 257 | 258 | C('SGD', { 259 | symbol: '$' 260 | }); 261 | 262 | C('SEK', { 263 | symbol: 'kr' 264 | }); 265 | 266 | C('CHF', { 267 | symbol: 'Fr' 268 | }); 269 | 270 | C('ZAR', { 271 | symbol: 'R' 272 | }); 273 | 274 | 275 | 276 | R.settings.locale = R.locale; 277 | 278 | 279 | 280 | 281 | ////////////////////////////////////////////////// 282 | // Compiled from js/recurly.util.js 283 | ////////////////////////////////////////////////// 284 | 285 | 286 | 287 | // Credit card type functions 288 | R.detectCardType = function(cardnumber) { 289 | 290 | cardnumber = cardnumber.replace(/\D/g, ''); 291 | 292 | var cards = [ 293 | { name: 'visa', prefixes: [4] }, 294 | { name: 'mastercard', prefixes: [51, 52, 53, 54, 55] }, 295 | { name: 'american_express', prefixes: [34, 37] }, 296 | { name: 'discover', prefixes: [6011, 62, 64, 65] }, 297 | { name: 'diners_club', prefixes: [305, 36, 38] }, 298 | { name: 'carte_blanche', prefixes: [300, 301, 302, 303, 304, 305] }, 299 | { name: 'jcb', prefixes: [35] }, 300 | { name: 'enroute', prefixes: [2014, 2149] }, 301 | { name: 'solo', prefixes: [6334, 6767] }, 302 | { name: 'switch', prefixes: [4903, 4905, 4911, 4936, 564182, 633110, 6333, 6759] }, 303 | { name: 'maestro', prefixes: [5018, 5020, 5038, 6304, 6759, 6761] }, 304 | { name: 'visa', prefixes: [417500, 4917, 4913, 4508, 4844] }, // visa electron 305 | { name: 'laser', prefixes: [6304, 6706, 6771, 6709] } 306 | ]; 307 | 308 | for (var c = 0; c < cards.length; c++) { 309 | for (var p = 0; p < cards[c].prefixes.length; p++) { 310 | if (new RegExp('^' + cards[c].prefixes[p].toString()).test(cardnumber)) 311 | return cards[c].name; 312 | } 313 | } 314 | 315 | }; 316 | 317 | // Formats currency amount in the current denomination or one provided 318 | // based on R.locale.currencies rules 319 | R.formatCurrency = function(num,denomination) { 320 | 321 | if(num < 0) { 322 | num = -num; 323 | var negative = true; 324 | } 325 | else { 326 | var negative = false; 327 | } 328 | 329 | denomination = denomination || R.settings.currency || 330 | R.raiseError('currency not configured'); 331 | 332 | var langspec = R.locale.currency; 333 | var currencyspec = R.locale.currencies[denomination]; 334 | 335 | // Format to precision 336 | var str = num.toFixed(currencyspec.precision); 337 | 338 | // Replace default period with format separator 339 | if(langspec.separator != '.') { 340 | str = str.replace(/\./g, langspec.separator); 341 | } 342 | 343 | function insertDelimiters(str) { 344 | var sRegExp = new RegExp('(-?[0-9]+)([0-9]{3})'); 345 | while(sRegExp.test(str)) { 346 | str = str.replace(sRegExp, '$1'+langspec.delimiter+'$2'); 347 | } 348 | return str; 349 | } 350 | 351 | // Apply thousands delimiter 352 | str = insertDelimiters(str); 353 | 354 | // Format unit/number order 355 | var format = langspec.format; 356 | format = format.replace(/%u/g, currencyspec.symbol); 357 | format = format.replace(/%n/g, str); 358 | str = format; 359 | 360 | if(negative) { 361 | str = '-' + str; 362 | } 363 | 364 | return str; 365 | }; 366 | 367 | 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"]; 368 | R.isCountryInEU = function(country) { 369 | return $.inArray(country, euCountries) !== -1; 370 | } 371 | 372 | R.isVATNumberApplicable = function(buyerCountry, sellerCountry) { 373 | if(!R.settings.VATPercent) return false; 374 | 375 | if(!R.settings.country) { 376 | R.raiseError('you must configure a country for VAT to work'); 377 | } 378 | 379 | if(!R.isCountryInEU(R.settings.country)) { 380 | R.raiseError('you cannot charge VAT outside of the EU'); 381 | } 382 | 383 | // Outside of EU don't even show the number 384 | if(!R.isCountryInEU(buyerCountry)) { 385 | return false; 386 | } 387 | 388 | return true; 389 | } 390 | 391 | R.isVATChargeApplicable = function(buyerCountry, vatNumber) { 392 | // We made it so the VAT Number is collectable in any case 393 | // where it could be charged, so this is logically sound: 394 | if(!R.isVATNumberApplicable(buyerCountry)) return false; 395 | 396 | var sellerCountry = R.settings.country; 397 | 398 | // 1) Outside EU never pays 399 | // 2) Same country in EU always pays 400 | // 3) Different countries in EU, pay only without vatNumber 401 | return (sellerCountry == buyerCountry || !vatNumber); 402 | }; 403 | 404 | R.flattenErrors = function(obj, attr) { 405 | var arr = []; 406 | 407 | var baseErrorKeys = ['base','account_id']; 408 | 409 | var attr = attr || ''; 410 | 411 | if( typeof obj == 'string' 412 | || typeof obj == 'number' 413 | || typeof obj == 'boolean') { 414 | 415 | if($.inArray(baseErrorKeys, attr)) { 416 | return [obj]; 417 | } 418 | 419 | return ['' + attr + ' ' + obj]; 420 | } 421 | 422 | for(var k in obj) { 423 | // console.log(k); 424 | if(obj.hasOwnProperty(k)) { 425 | // Inherit parent attribute names when property key 426 | // is a numeric string; how we deal with arrays 427 | attr = (parseInt(k).toString() == k) ? attr : k; 428 | var children = R.flattenErrors(obj[k], attr); 429 | for(var i=0, l=children.length; i < l; ++i) { 430 | arr.push(children[i]); 431 | } 432 | } 433 | } 434 | 435 | return arr; 436 | }; 437 | 438 | 439 | R.replaceVars = function(str, vars) { 440 | for(var k in vars) { 441 | if(vars.hasOwnProperty(k)) { 442 | var v = encodeURIComponent(vars[k]); 443 | str = str.replace(new RegExp('\\{'+k+'\\}', 'g'), v); 444 | } 445 | } 446 | 447 | return str; 448 | }; 449 | 450 | R.post = function(url, params, options) { 451 | 452 | var resultNamespace = options.resultNamespace || 'recurly_result'; 453 | 454 | var newParams = {}; 455 | newParams[resultNamespace] = params; 456 | params = newParams; 457 | 458 | var form = $('
    ').hide(); 459 | form.attr('action', url) 460 | .attr('method', 'POST') 461 | .attr('enctype', 'application/x-www-form-urlencoded'); 462 | 463 | function addParam(name, value, parent) { 464 | var fullname = (parent.length > 0 ? (parent + '[' + name + ']') : name); 465 | if(typeof value === 'object') { 466 | for(var i in value) { 467 | if(value.hasOwnProperty(i)) { 468 | addParam(i, value[i], fullname); 469 | } 470 | } 471 | } 472 | else $('').attr({name: fullname, value: value}).appendTo(form); 473 | }; 474 | 475 | addParam('', params, ''); 476 | 477 | $('body').append(form); 478 | form.submit(); 479 | }; 480 | 481 | 482 | 483 | 484 | ////////////////////////////////////////////////// 485 | // Compiled from js/recurly.validators.js 486 | ////////////////////////////////////////////////// 487 | 488 | 489 | (R.isValidCC = function($input) { 490 | var v = $input.val(); 491 | // accept only digits and dashes 492 | if (/[^0-9-]+/.test(v)) 493 | return false; 494 | 495 | var nCheck = 0, 496 | nDigit = 0, 497 | bEven = false; 498 | 499 | v = v.replace(/\D/g, ""); 500 | 501 | for (var n = v.length - 1; n >= 0; n--) { 502 | var cDigit = v.charAt(n); 503 | var nDigit = parseInt(cDigit, 10); 504 | if (bEven) { 505 | if ((nDigit *= 2) > 9) 506 | nDigit -= 9; 507 | } 508 | nCheck += nDigit; 509 | bEven = !bEven; 510 | } 511 | 512 | return (nCheck % 10) == 0; 513 | }).defaultErrorKey = 'invalidCC'; 514 | 515 | (R.isValidEmail = function($input) { 516 | var v = $input.val(); 517 | 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); 518 | }).defaultErrorKey = 'invalidEmail'; 519 | 520 | function wholeNumber(val) { 521 | return /^[0-9]+$/.test(val); 522 | } 523 | 524 | (R.isValidCVV = function($input) { 525 | var v = $input.val(); 526 | return (v.length == 3 || v.length == 4) && wholeNumber(v); 527 | }).defaultErrorKey = 'invalidCVV'; 528 | 529 | (R.isNotEmpty = function($input) { 530 | var v = $input.val(); 531 | return !!v; 532 | }).defaultErrorKey = 'emptyField'; 533 | 534 | (R.isChecked = function($input) { 535 | return $input.is(':checked'); 536 | }).defaultErrorKey = 'acceptTOS'; 537 | 538 | 539 | 540 | 541 | 542 | ////////////////////////////////////////////////// 543 | // Compiled from js/recurly.plan.js 544 | ////////////////////////////////////////////////// 545 | 546 | R.Plan = { 547 | create: createObject 548 | , fromJSON: function(json) { 549 | var p = this.create(); 550 | 551 | p.name = json.name; 552 | p.code = json.plan_code; 553 | p.cost = new R.Cost(json.unit_amount_in_cents); 554 | 555 | p.displayQuantity = json.display_quantity; 556 | 557 | p.interval = new R.TimePeriod( 558 | json.plan_interval_length, 559 | json.plan_interval_unit 560 | ); 561 | 562 | if(json.trial_interval_length) { 563 | p.trial = new R.TimePeriod( 564 | json.trial_interval_length, 565 | json.trial_interval_unit 566 | ); 567 | } 568 | 569 | if(json.setup_fee_in_cents) { 570 | p.setupFee = new R.Cost(json.setup_fee_in_cents); 571 | } 572 | 573 | p.addOns = []; 574 | if(json.add_ons) { 575 | for(var l=json.add_ons.length, i=0; i < l; ++i) { 576 | var a = json.add_ons[i]; 577 | p.addOns.push(R.AddOn.fromJSON(a)); 578 | } 579 | } 580 | 581 | return p; 582 | } 583 | , get: function(plan_code, callback) { 584 | $.ajax({ 585 | url: R.settings.baseURL+'plans/'+plan_code, 586 | // data: params, 587 | dataType: "jsonp", 588 | jsonp: "callback", 589 | timeout: 10000, 590 | success: function(data) { 591 | var plan = R.Plan.fromJSON(data); 592 | callback(plan); 593 | } 594 | }); 595 | } 596 | , createSubscription: function() { 597 | var s = createObject(R.Subscription); 598 | s.plan = createObject(this); 599 | s.plan.quantity = 1; 600 | s.addOns = []; 601 | return s; 602 | } 603 | }; 604 | 605 | 606 | R.AddOn = { 607 | fromJSON: function(json) { 608 | var a = createObject(R.AddOn); 609 | a.name = json.name; 610 | a.code = json.add_on_code; 611 | a.cost = new R.Cost(json.default_unit_amount_in_cents); 612 | a.displayQuantity = json.display_quantity; 613 | return a; 614 | } 615 | 616 | , toJSON: function() { 617 | return { 618 | name: this.name 619 | , add_on_code: this.code 620 | , default_unit_amount_in_cents: this.default_unit_amount_in_cents 621 | }; 622 | } 623 | }; 624 | 625 | 626 | 627 | 628 | ////////////////////////////////////////////////// 629 | // Compiled from js/recurly.account.js 630 | ////////////////////////////////////////////////// 631 | 632 | R.Account = { 633 | create: createObject 634 | , toJSON: function() { 635 | return { 636 | first_name: this.firstName 637 | , last_name: this.lastName 638 | , company_name: this.companyName 639 | , account_code: this.code 640 | , email: this.email 641 | }; 642 | } 643 | }; 644 | 645 | 646 | 647 | 648 | 649 | ////////////////////////////////////////////////// 650 | // Compiled from js/recurly.billing_info.js 651 | ////////////////////////////////////////////////// 652 | 653 | R.BillingInfo = { 654 | create: createObject 655 | , toJSON: function() { 656 | return { 657 | first_name: this.firstName 658 | , last_name: this.lastName 659 | , month: this.month 660 | , year: this.year 661 | , number: this.number 662 | , verification_value: this.cvv 663 | , address1: this.address1 664 | , address2: this.address2 665 | , city: this.city 666 | , state: this.state 667 | , zip: this.zip 668 | , country: this.country 669 | , phone: this.phone 670 | }; 671 | } 672 | , save: function(options) { 673 | var json = { 674 | billing_info: this.toJSON() 675 | , signature: options.signature 676 | }; 677 | 678 | // Save first/last name on the account 679 | // if not distinguished 680 | if(!options.distinguishContactFromBillingInfo) { 681 | json.account = { 682 | account_code: options.accountCode 683 | , first_name: this.firstName 684 | , last_name: this.lastName 685 | }; 686 | } 687 | 688 | $.ajax({ 689 | url: R.settings.baseURL+'accounts/'+options.accountCode+'/billing_info/update' 690 | , data: json 691 | , dataType: 'jsonp' 692 | , jsonp: 'callback' 693 | , timeout: 60000 694 | , success: function(data) { 695 | if(data.success && options.success) { 696 | options.success(data.success); 697 | } 698 | else if(data.errors && options.error) { 699 | options.error( R.flattenErrors(data.errors) ); 700 | } 701 | } 702 | , error: function() { 703 | if(options.error) { 704 | options.error(['Unknown error processing transaction. Please try again later.']); 705 | } 706 | } 707 | , complete: options.complete || $.noop 708 | }); 709 | } 710 | }; 711 | 712 | 713 | 714 | 715 | 716 | ////////////////////////////////////////////////// 717 | // Compiled from js/recurly.subscription.js 718 | ////////////////////////////////////////////////// 719 | 720 | // Base Subscription prototype 721 | R.Subscription = { 722 | create: createObject 723 | , plan: R.Plan 724 | , addOns: [] 725 | 726 | , calculateTotals: function() { 727 | var totals = { 728 | stages: {} 729 | }; 730 | 731 | // PLAN 732 | totals.plan = this.plan.cost.mult(this.plan.quantity); 733 | 734 | // ADD-ONS 735 | totals.allAddOns = new R.Cost(0); 736 | totals.addOns = {}; 737 | for(var l=this.addOns.length, i=0; i < l; ++i) { 738 | var a = this.addOns[i], 739 | c = a.cost.mult(a.quantity); 740 | totals.addOns[a.code] = c; 741 | totals.allAddOns = totals.allAddOns.add(c); 742 | } 743 | 744 | totals.stages.recurring = totals.plan.add(totals.allAddOns); 745 | 746 | totals.stages.now = totals.plan.add(totals.allAddOns); 747 | 748 | // FREE TRIAL 749 | if(this.plan.trial) { 750 | totals.stages.now = R.Cost.FREE; 751 | } 752 | 753 | // COUPON 754 | if(this.coupon) { 755 | var beforeDiscount = totals.stages.now; 756 | var afterDiscount = totals.stages.now.discount(this.coupon); 757 | totals.coupon = afterDiscount.sub(beforeDiscount); 758 | totals.stages.now = afterDiscount; 759 | } 760 | 761 | // SETUP FEE 762 | if(this.plan.setupFee) { 763 | totals.stages.now = totals.stages.now.add(this.plan.setupFee); 764 | } 765 | 766 | // VAT 767 | if(this.billingInfo && R.isVATChargeApplicable(this.billingInfo.country,this.billingInfo.vatNumber)) { 768 | totals.vat = totals.stages.now.mult( (R.settings.VATPercent/100) ); 769 | totals.stages.now = totals.stages.now.add(totals.vat); 770 | } 771 | 772 | return totals; 773 | } 774 | , redeemAddOn: function(addOn) { 775 | var redemption = addOn.createRedemption(); 776 | this.addOns.push(redemption); 777 | return redemption; 778 | } 779 | 780 | , removeAddOn: function(code) { 781 | for(var a=this.addOns, l=a.length, i=0; i < l; ++i) { 782 | if(a[i].code == code) { 783 | return a.splice(i,1); 784 | } 785 | } 786 | } 787 | 788 | , findAddOnByCode: function(code) { 789 | for(var l=this.addOns.length, i=0; i < l; ++i) { 790 | if(this.addOns[i].code == code) { 791 | return this.addOns[i]; 792 | } 793 | } 794 | return false; 795 | } 796 | 797 | , toJSON: function() { 798 | var json = { 799 | plan_code: this.plan.code 800 | , quantity: this.plan.quantity 801 | , coupon_code: this.coupon ? this.coupon.code : undefined 802 | , add_ons: [] 803 | }; 804 | 805 | for(var i=0, l=this.addOns.length, a=json.add_ons, b=this.addOns; i < l; ++i) { 806 | a.push({ 807 | add_on_code: b[i].code 808 | , quantity: b[i].quantity 809 | }); 810 | } 811 | 812 | return json; 813 | } 814 | 815 | , save: function(options) { 816 | var json = { 817 | subscription: this.toJSON() 818 | , account: this.account.toJSON() 819 | , billing_info: this.billingInfo.toJSON() 820 | }; 821 | 822 | $.ajax({ 823 | url: R.settings.baseURL+'subscribe', 824 | data: json, 825 | dataType: "jsonp", 826 | jsonp: "callback", 827 | timeout: 60000, 828 | success: function(data) { 829 | if(data.success && options.success) { 830 | options.success(data.success); 831 | } 832 | else if(data.errors && options.error) { 833 | var errorCode = data.errors.error_code; 834 | delete data.errors.error_code; 835 | options.error( R.flattenErrors(data.errors), errorCode ); 836 | } 837 | }, 838 | error: function() { 839 | if(options.error) { 840 | options.error(['Unknown error processing transaction. Please try again later.']); 841 | } 842 | }, 843 | complete: options.complete 844 | }); 845 | 846 | } 847 | }; 848 | 849 | R.AddOn.createRedemption = function(qty) { 850 | var r = createObject(this); 851 | r.quantity = qty || 1; 852 | return r; 853 | }; 854 | 855 | R.Coupon = { 856 | fromJSON: function(json) { 857 | var c = createObject(R.Coupon); 858 | 859 | if(json.discount_in_cents) 860 | c.discountCost = new R.Cost(-json.discount_in_cents); 861 | else if(json.discount_percent) 862 | c.discountRatio = json.discount_percent/100; 863 | 864 | c.description = json.description; 865 | 866 | return c; 867 | } 868 | 869 | , toJSON: function() { 870 | } 871 | }; 872 | 873 | R.Cost.prototype.discount = function(coupon){ 874 | if(coupon.discountCost) 875 | return this.add(coupon.discountCost); 876 | 877 | var ret = this.sub( this.mult(coupon.discountRatio) ); 878 | if(ret.cents() < 0) { 879 | return R.Cost.FREE; 880 | } 881 | 882 | return ret; 883 | }; 884 | 885 | R.Subscription.getCoupon = function(couponCode, successCallback, errorCallback) { 886 | 887 | if(!R.settings.baseURL) { R.raiseError('Company subdomain not configured'); } 888 | 889 | return $.ajax({ 890 | url: R.settings.baseURL+'plans/'+this.plan.code+'/coupons/'+couponCode, 891 | // data: params, 892 | dataType: "jsonp", 893 | jsonp: "callback", 894 | timeout: 10000, 895 | success: function(data) { 896 | if(data.valid) { 897 | var coupon = R.Coupon.fromJSON(data); 898 | coupon.code = couponCode; 899 | successCallback(coupon); 900 | } 901 | else { 902 | errorCallback(); 903 | } 904 | }, 905 | error: function() { 906 | errorCallback(); 907 | } 908 | }); 909 | }; 910 | 911 | 912 | 913 | 914 | 915 | ////////////////////////////////////////////////// 916 | // Compiled from js/recurly.transaction.js 917 | ////////////////////////////////////////////////// 918 | 919 | 920 | R.Transaction = { 921 | toJSON: function() { 922 | return { 923 | currency: this.currency 924 | , amount_in_cents: this.cost.cents() 925 | }; 926 | } 927 | , create: createObject 928 | , save: function(options) { 929 | var json = { 930 | transaction: this.toJSON() 931 | , account: this.account ? this.account.toJSON() : undefined 932 | , billing_info: this.billingInfo.toJSON() 933 | , signature: options.signature 934 | }; 935 | 936 | $.ajax({ 937 | url: R.settings.baseURL+'transactions/create' 938 | , data: json 939 | , dataType: 'jsonp' 940 | , jsonp: 'callback' 941 | , timeout: 60000 942 | , success: function(data) { 943 | if(data.success && options.success) { 944 | options.success(data.success); 945 | } 946 | else if(data.errors && options.error) { 947 | options.error( R.flattenErrors(data.errors) ); 948 | } 949 | } 950 | , error: function() { 951 | if(options.error) { 952 | options.error(['Unknown error processing transaction. Please try again later.']); 953 | } 954 | } 955 | , complete: options.complete || $.noop 956 | }); 957 | } 958 | }; 959 | 960 | 961 | 962 | 963 | 964 | 965 | ////////////////////////////////////////////////// 966 | // Compiled from js/recurly.ui.js 967 | ////////////////////////////////////////////////// 968 | 969 | R.UserError = {}; 970 | 971 | function raiseUserError(validation, elem) { 972 | var e = createObject(R.UserError); 973 | e.validation = validation; 974 | e.element = elem; 975 | throw e; 976 | } 977 | 978 | function handleUserErrors(block) { 979 | try { 980 | block(); 981 | } 982 | catch(e) { 983 | if(!e.validation) 984 | throw e; 985 | 986 | var $input = e.element; 987 | var message = R.locale.errors[e.validation.errorKey]; 988 | var validator = e.validation.validator; 989 | 990 | var $e = $('
    '); 991 | $e.text(message); 992 | $e.appendTo($input.parent()); 993 | // $e.insertAfter($input); 994 | 995 | $input.addClass('invalid'); 996 | $input.bind('change keyup', function() { 997 | 998 | if(validator($input)) { 999 | $input.removeClass('invalid'); 1000 | $e.remove(); 1001 | $input.unbind(); 1002 | } 1003 | }); 1004 | 1005 | $input.focus(); 1006 | } 1007 | } 1008 | 1009 | function getField($form, fieldSel, validation) { 1010 | // Try text input 1011 | var $input = $form.find(fieldSel + ' input'); 1012 | 1013 | // Try as select 1014 | if($input.length == 0) { 1015 | $input = $form.find(fieldSel + ' select'); 1016 | } 1017 | 1018 | // Treat nonexistence as removed deliberately 1019 | if($input.length == 0) return undefined; 1020 | 1021 | var val = $input.val(); 1022 | 1023 | for(var i=2,v; v=arguments[i]; ++i) { 1024 | 1025 | if(!v.validator($input)) { 1026 | raiseUserError(v, $input); 1027 | } 1028 | } 1029 | 1030 | return val; 1031 | } 1032 | 1033 | // Make a 'validation' from validator / errorKey 1034 | function V(v,k) { 1035 | return { 1036 | validator: v, 1037 | errorKey: k || v.defaultErrorKey 1038 | }; 1039 | } 1040 | 1041 | 1042 | // == SERVER ERROR UI METHODS 1043 | 1044 | function clearServerErrors($form) { 1045 | var $serverErrors = $form.find('.server_errors'); 1046 | $serverErrors.removeClass('any').addClass('none'); 1047 | $serverErrors.empty(); 1048 | } 1049 | 1050 | function displayServerErrors($form, errors) { 1051 | var $serverErrors = $form.find('.server_errors'); 1052 | clearServerErrors($form); 1053 | 1054 | var l = errors.length; 1055 | if(l) { 1056 | $serverErrors.removeClass('none').addClass('any'); 1057 | for(var i=0; i < l; ++i) { 1058 | var $e = $('
    '); 1059 | $e.text(errors[i]); 1060 | $serverErrors.append($e); 1061 | } 1062 | } 1063 | } 1064 | 1065 | 1066 | var preFillMap = { 1067 | contactInfo: { 1068 | firstName: '.contact_info > .full_name > .first_name > input' 1069 | , lastName: '.contact_info > .full_name > .last_name > input' 1070 | , email: '.contact_info > .email > input' 1071 | , phone: '.contact_info > .phone > input' 1072 | , companyName: '.contact_info > .company_name > input' 1073 | } 1074 | , billingInfo: { 1075 | firstName: '.billing_info > .credit_card > .first_name > input' 1076 | , lastName: '.billing_info > .credit_card > .last_name > input' 1077 | , address1: '.billing_info > .address > .address1 > input' 1078 | , address2: '.billing_info > .address > .address2 > input' 1079 | , country: '.billing_info > .address > .country > select' 1080 | , city: '.billing_info > .address > .city > input' 1081 | , state: '.billing_info > .address > .state_zip > .state > input' 1082 | , zip: '.billing_info > .address > .state_zip > .zip > input' 1083 | , vatNumber: '.billing_info > .vat_number > input' 1084 | } 1085 | }; 1086 | 1087 | function preFillValues($form, preFill, mapObject) { 1088 | 1089 | if(!preFill) return; 1090 | 1091 | for(var k in preFill) { 1092 | if(preFill.hasOwnProperty(k) && mapObject.hasOwnProperty(k)) { 1093 | 1094 | var v = preFill[k]; 1095 | var selectorOrNested = mapObject[k]; 1096 | 1097 | // jquery selector 1098 | if(typeof selectorOrNested == 'string') { 1099 | $form.find(selectorOrNested).val(v).change(); 1100 | } 1101 | // nested mapping 1102 | else if(typeof selectorOrNested == 'object') { 1103 | preFillValues($form, v, selectorOrNested); 1104 | } 1105 | } 1106 | } 1107 | } 1108 | 1109 | 1110 | function initCommonForm($form, options) { 1111 | 1112 | if(!options.collectPhone) { 1113 | $form.find('.phone').remove(); 1114 | } 1115 | 1116 | if(!options.collectCompany) { 1117 | $form.find('.company_name').remove(); 1118 | } 1119 | 1120 | $form.delegate('.placeholder', 'click', function() { 1121 | var $label = $(this); 1122 | var $li = $(this).parent(); 1123 | $li.find('input').focus(); 1124 | }); 1125 | 1126 | $form.delegate('input', 'change keyup', function() { 1127 | var $input = $(this); 1128 | var $li = $(this).parent(); 1129 | 1130 | if($input.val().length > 0) { 1131 | $li.find('.placeholder').hide(); 1132 | } 1133 | else { 1134 | $li.find('.placeholder').show(); 1135 | } 1136 | }); 1137 | 1138 | 1139 | $form.delegate('input', 'focus', function() { 1140 | $(this).parent().addClass('focus'); 1141 | }); 1142 | 1143 | $form.delegate('input', 'blur', function() { 1144 | $(this).parent().removeClass('focus'); 1145 | }); 1146 | 1147 | // Touch of perfection 1148 | $form.delegate('input', 'keydown', function(e) { 1149 | if(e.keyCode >= 48 && e.keyCode <= 90) { 1150 | $(this).parent().find('.placeholder').hide(); 1151 | } 1152 | }); 1153 | 1154 | preFillValues($form, options.preFill, preFillMap); 1155 | } 1156 | 1157 | function initContactInfoForm($form, options) { 1158 | 1159 | // == FIRSTNAME / LASTNAME REDUNDANCY 1160 | if(options.distinguishContactFromBillingInfo) { 1161 | var $contactFirstName = $form.find('.contact_info .first_name input'); 1162 | var $contactLastName = $form.find('.contact_info .last_name input'); 1163 | var prevFirstName = $contactFirstName.val(); 1164 | var prevLastName = $contactLastName.val(); 1165 | $form.find('.contact_info .first_name input').change(function() { 1166 | var $billingFirstName = $form.find('.billing_info .first_name input'); 1167 | if($billingFirstName.val() == prevFirstName) { 1168 | $billingFirstName.val( $(this).val() ).change(); 1169 | } 1170 | prevFirstName = $contactFirstName.val(); 1171 | }); 1172 | $form.find('.contact_info .last_name input').change(function() { 1173 | var $billingLastName = $form.find('.billing_info .last_name input'); 1174 | if($billingLastName.val() == prevLastName) { 1175 | $billingLastName.val( $(this).val() ).change(); 1176 | } 1177 | prevLastName = $contactLastName.val(); 1178 | }); 1179 | 1180 | } 1181 | else { 1182 | $form.find('.billing_info .first_name, .billing_info .last_name').remove(); 1183 | } 1184 | 1185 | } 1186 | 1187 | function initBillingInfoForm($form, options) { 1188 | 1189 | // == DEFAULT BUYER TO SELLER COUNTRY 1190 | if(R.settings.country) { 1191 | var $countryOpt = $form.find('.country option[value='+R.settings.country+']'); 1192 | if($countryOpt.length) { 1193 | $countryOpt.attr('selected', true).change(); 1194 | } 1195 | } 1196 | 1197 | var now = new Date(); 1198 | var year = now.getFullYear(); 1199 | var month = now.getMonth(); 1200 | var $yearSelect = $form.find('.year select'); 1201 | var $monthSelect = $form.find('.month select'); 1202 | 1203 | // == GENERATE YEAR SELECT OPTIONS 1204 | for(var i=year; i <= year+10; ++i) { 1205 | var $yearOpt = $(''); 1206 | $yearOpt.appendTo($yearSelect); 1207 | } 1208 | $yearSelect.val(year+1); 1209 | 1210 | 1211 | // == DISABLE INVALID MONTHS, SELECT CURRENT 1212 | function updateMonths() { 1213 | if($yearSelect.val() == year) { 1214 | $monthSelect.find('option[value="'+month+'"]') 1215 | var foundSelected = false; 1216 | $monthSelect.find('option').each(function(){ 1217 | if($(this).val() <= month) { 1218 | $(this).attr('disabled', true); 1219 | } 1220 | else { 1221 | $(this).removeAttr('disabled'); 1222 | if(!foundSelected) { 1223 | foundSelected = true; 1224 | $(this).attr('selected', true); 1225 | } 1226 | } 1227 | }); 1228 | } 1229 | else { 1230 | $monthSelect.find('option').removeAttr('disabled'); 1231 | } 1232 | }; 1233 | updateMonths(); 1234 | $yearSelect.change(updateMonths); 1235 | 1236 | 1237 | // == HIDE UNNECESSARY ADDRESS FIELDS 1238 | 1239 | if(options.addressRequirement == 'none') { 1240 | $form.find('.address').remove(); 1241 | } 1242 | else if(options.addressRequirement == 'zip') { 1243 | $form.find('.address').addClass('only_zip'); 1244 | $form.find('.address1, .address2, .city, .state').remove(); 1245 | 1246 | // Only remove country if no VAT support 1247 | if(!R.settings.VATPercent) { 1248 | $form.find('.country').remove(); 1249 | } 1250 | } 1251 | else if(options.addressRequirement == 'zipstreet') { 1252 | $form.find('.address').addClass('only_zipstreet'); 1253 | $form.find('.city, .state').remove(); 1254 | 1255 | // Only remove country if no VAT support 1256 | if(!R.settings.VATPercent) { 1257 | $form.find('.country').remove(); 1258 | } 1259 | } 1260 | else if(options.addressRequirement == 'full') { 1261 | $form.find('.address').addClass('full'); 1262 | } 1263 | 1264 | // == SHOW/HIDE CARD TYPES 1265 | var $acceptedCards = $form.find('.accepted_cards'); 1266 | $form.find('.card_number input').bind('change keyup', function() { 1267 | var type = R.detectCardType( $(this).val() ); 1268 | if(type) { 1269 | $acceptedCards.find('.card').each(function(){ 1270 | $(this).toggleClass('match', $(this).hasClass(type)); 1271 | $(this).toggleClass('no_match', !$(this).hasClass(type)); 1272 | }); 1273 | } 1274 | else { 1275 | $acceptedCards.find('.card').removeClass('match no_match'); 1276 | } 1277 | }); 1278 | 1279 | } 1280 | 1281 | 1282 | function pullAccountFields($form, account, options) { 1283 | account.firstName = getField($form, '.contact_info .first_name', V(R.isNotEmpty)); 1284 | account.lastName = getField($form, '.contact_info .last_name', V(R.isNotEmpty)); 1285 | account.companyName = getField($form, '.contact_info .company_name'); 1286 | account.email = getField($form, '.email', V(R.isNotEmpty), V(R.isValidEmail)); 1287 | account.code = options.accountCode; 1288 | } 1289 | 1290 | 1291 | function pullBillingInfoFields($form, billingInfo, options) { 1292 | billingInfo.firstName = getField($form, '.billing_info .first_name', V(R.isNotEmpty)); 1293 | billingInfo.lastName = getField($form, '.billing_info .last_name', V(R.isNotEmpty)); 1294 | billingInfo.number = getField($form, '.card_number', V(R.isNotEmpty), V(R.isValidCC)); 1295 | billingInfo.cvv = getField($form, '.cvv', V(R.isNotEmpty), V(R.isValidCVV)); 1296 | 1297 | billingInfo.month = getField($form, '.month'); 1298 | billingInfo.year = getField($form, '.year'); 1299 | 1300 | billingInfo.phone = getField($form, '.phone'); 1301 | billingInfo.address1 = getField($form, '.address1', V(R.isNotEmpty)); 1302 | billingInfo.address2 = getField($form, '.address2'); 1303 | billingInfo.city = getField($form, '.city', V(R.isNotEmpty)); 1304 | billingInfo.state = getField($form, '.state', V(R.isNotEmpty)); 1305 | billingInfo.zip = getField($form, '.zip', V(R.isNotEmpty)); 1306 | billingInfo.country = getField($form, '.country', 1307 | V(function(v) {return v.val() != '-';}, 'emptyField')); 1308 | } 1309 | 1310 | 1311 | function verifyTOSChecked($form) { 1312 | getField($form, '.accept_tos', V(R.isChecked)); 1313 | } 1314 | 1315 | 1316 | R.buildBillingInfoUpdateForm = function(options) { 1317 | var defaults = { 1318 | addressRequirement: 'full' 1319 | , distinguishContactFromBillingInfo: true 1320 | }; 1321 | 1322 | options = $.extend(createObject(R.settings), defaults, options); 1323 | 1324 | if(!options.accountCode) R.raiseError('accountCode missing'); 1325 | if(!options.signature) R.raiseError('signature missing'); 1326 | 1327 | var billingInfo = R.BillingInfo.create(); 1328 | 1329 | var $form = $(R.updateBillingInfoFormHTML); 1330 | $form.find('.billing_info').html(R.billingInfoFieldsHTML); 1331 | 1332 | 1333 | initCommonForm($form, options); 1334 | initBillingInfoForm($form, options); 1335 | 1336 | 1337 | $form.submit(function(e) { 1338 | e.preventDefault(); 1339 | 1340 | clearServerErrors($form); 1341 | 1342 | $form.find('.error').remove(); 1343 | $form.find('.invalid').removeClass('invalid'); 1344 | 1345 | handleUserErrors(function() { 1346 | pullBillingInfoFields($form, billingInfo, options); 1347 | 1348 | $form.addClass('submitting'); 1349 | $form.find('button.submit').attr('disabled', true).text('Please Wait'); 1350 | 1351 | billingInfo.save({ 1352 | signature: options.signature 1353 | , distinguishContactFromBillingInfo: options.distinguishContactFromBillingInfo 1354 | , accountCode: options.accountCode 1355 | , success: function(response) { 1356 | if(options.afterUpdate) 1357 | options.afterUpdate(response); 1358 | 1359 | if(options.successURL) { 1360 | var url = options.successURL; 1361 | // url = R.replaceVars(url, response); 1362 | R.post(url, response, options); 1363 | } 1364 | } 1365 | , error: function(errors) { 1366 | if(!options.onError || !options.onError(errors)) { 1367 | displayServerErrors($form, errors); 1368 | } 1369 | } 1370 | , complete: function() { 1371 | $form.removeClass('submitting'); 1372 | $form.find('button.submit').removeAttr('disabled').text('Update'); 1373 | } 1374 | }); 1375 | }); 1376 | }); 1377 | 1378 | if(options.beforeInject) { 1379 | options.beforeInject($form.get(0)); 1380 | } 1381 | 1382 | $(function() { 1383 | var $container = $(options.target); 1384 | $container.html($form); 1385 | 1386 | if(options.afterInject) { 1387 | options.afterInject($form.get(0)); 1388 | } 1389 | }); 1390 | 1391 | }; 1392 | 1393 | 1394 | function initTOSCheck($form, options) { 1395 | 1396 | if(options.termsOfServiceURL || options.privacyPolicyURL) { 1397 | var $tos = $form.find('.accept_tos').html(R.termsOfServiceHTML); 1398 | 1399 | // If only one, remove 'and' 1400 | if(!(options.termsOfServiceURL && options.privacyPolicyURL)) { 1401 | $tos.find('span.and').remove(); 1402 | } 1403 | 1404 | // set href or remove tos_link 1405 | if(options.termsOfServiceURL) { 1406 | $tos.find('a.tos_link').attr('href', options.termsOfServiceURL); 1407 | } 1408 | else { 1409 | $tos.find('a.tos_link').remove(); 1410 | } 1411 | 1412 | // set href or remove pp_link 1413 | if(options.privacyPolicyURL) { 1414 | $tos.find('a.pp_link').attr('href', options.privacyPolicyURL); 1415 | } 1416 | else { 1417 | $tos.find('a.pp_link').remove(); 1418 | } 1419 | 1420 | } 1421 | else { 1422 | $form.find('.accept_tos').remove(); 1423 | } 1424 | 1425 | } 1426 | 1427 | R.buildTransactionForm = function(options) { 1428 | var defaults = { 1429 | addressRequirement: 'full' 1430 | , distinguishContactFromBillingInfo: true 1431 | , collectContactInfo: true 1432 | }; 1433 | 1434 | options = $.extend(createObject(R.settings), defaults, options); 1435 | 1436 | 1437 | if(!options.collectContactInfo && !options.accountCode) { 1438 | R.raiseError('collectContactInfo is false, but no accountCode provided'); 1439 | } 1440 | 1441 | 1442 | // if(!options.accountCode) R.raiseError('accountCode missing'); 1443 | if(!options.signature) R.raiseError('signature missing'); 1444 | 1445 | 1446 | var billingInfo = R.BillingInfo.create() 1447 | , account = R.Account.create() 1448 | , transaction = R.Transaction.create(); 1449 | 1450 | 1451 | transaction.account = account; 1452 | transaction.billingInfo = billingInfo; 1453 | transaction.currency = options.currency; 1454 | transaction.cost = new R.Cost(options.amountInCents); 1455 | 1456 | var $form = $(R.oneTimeTransactionFormHTML); 1457 | $form.find('.billing_info').html(R.billingInfoFieldsHTML); 1458 | 1459 | if(options.collectContactInfo) { 1460 | $form.find('.contact_info').html(R.contactInfoFieldsHTML); 1461 | } 1462 | else { 1463 | $form.find('.contact_info').remove(); 1464 | } 1465 | 1466 | 1467 | initCommonForm($form, options); 1468 | initContactInfoForm($form, options); 1469 | initBillingInfoForm($form, options); 1470 | initTOSCheck($form, options); 1471 | 1472 | $form.submit(function(e) { 1473 | e.preventDefault(); 1474 | 1475 | clearServerErrors($form); 1476 | 1477 | $form.find('.error').remove(); 1478 | $form.find('.invalid').removeClass('invalid'); 1479 | 1480 | handleUserErrors(function() { 1481 | pullAccountFields($form, account, options); 1482 | pullBillingInfoFields($form, billingInfo, options); 1483 | verifyTOSChecked($form); 1484 | 1485 | $form.addClass('submitting'); 1486 | $form.find('button.submit').attr('disabled', true).text('Please Wait'); 1487 | 1488 | transaction.save({ 1489 | signature: options.signature 1490 | , accountCode: options.accountCode 1491 | , success: function(response) { 1492 | if(options.afterPay) 1493 | options.afterPay(response); 1494 | 1495 | if(options.successURL) { 1496 | var url = options.successURL; 1497 | // url = R.replaceVars(url, response); 1498 | R.post(url, response, options); 1499 | } 1500 | } 1501 | , error: function(errors) { 1502 | if(!options.onError || !options.onError(errors)) { 1503 | displayServerErrors($form, errors); 1504 | } 1505 | } 1506 | , complete: function() { 1507 | $form.removeClass('submitting'); 1508 | $form.find('button.submit').removeAttr('disabled').text('Pay'); 1509 | } 1510 | }); 1511 | }); 1512 | }); 1513 | 1514 | if(options.beforeInject) { 1515 | options.beforeInject($form.get(0)); 1516 | } 1517 | 1518 | $(function() { 1519 | var $container = $(options.target); 1520 | $container.html($form); 1521 | 1522 | if(options.afterInject) { 1523 | options.afterInject($form.get(0)); 1524 | } 1525 | }); 1526 | 1527 | }; 1528 | 1529 | 1530 | R.buildSubscriptionForm = function(options) { 1531 | var defaults = { 1532 | enableAddOns: true 1533 | , enableCoupons: true 1534 | , addressRequirement: 'full' 1535 | , distinguishContactFromBillingInfo: false 1536 | }; 1537 | 1538 | options = $.extend(createObject(R.settings), defaults, options); 1539 | 1540 | var $form = $(R.subscribeFormHTML); 1541 | $form.find('.contact_info').html(R.contactInfoFieldsHTML); 1542 | $form.find('.billing_info').html(R.billingInfoFieldsHTML); 1543 | 1544 | 1545 | initCommonForm($form, options); 1546 | initContactInfoForm($form, options); 1547 | initBillingInfoForm($form, options); 1548 | initTOSCheck($form, options); 1549 | 1550 | if(options.planCode) 1551 | R.Plan.get(options.planCode, gotPlan); 1552 | else if(options.plan) 1553 | gotPlan(options.plan); 1554 | 1555 | function gotPlan(plan) { 1556 | 1557 | if(options.filterPlan) 1558 | plan = options.filterPlan(plan) || plan; 1559 | 1560 | 1561 | var subscription = plan.createSubscription(), 1562 | account = R.Account.create(), 1563 | billingInfo = R.BillingInfo.create(); 1564 | 1565 | subscription.account = account; 1566 | subscription.billingInfo = billingInfo; 1567 | 1568 | if(options.filterSubscription) 1569 | subscription = options.filterSubscription(subscription) || subscription; 1570 | 1571 | // == EDITABLE PLAN QUANTITY 1572 | if(!plan.displayQuantity) { 1573 | $form.find('.plan .quantity').remove(); 1574 | } 1575 | 1576 | // == SETUP FEE 1577 | if(plan.setupFee) { 1578 | $form.find('.subscription').addClass('with_setup_fee'); 1579 | $form.find('.plan .setup_fee .cost').text('' + plan.setupFee); 1580 | } 1581 | else { 1582 | $form.find('.plan .setup_fee').remove(); 1583 | } 1584 | 1585 | // == FREE TRIAL 1586 | if(plan.trial) { 1587 | $form.find('.subscription').addClass('with_trial'); 1588 | 1589 | $form.find('.plan .free_trial').text('First ' + plan.trial + ' free'); 1590 | } 1591 | else { 1592 | $form.find('.plan .free_trial').remove(); 1593 | } 1594 | 1595 | 1596 | // == UPDATE ALL UI TOTALS via subscription.calculateTotals() results 1597 | function updateTotals() { 1598 | var totals = subscription.calculateTotals(); 1599 | 1600 | $form.find('.plan .recurring_cost .cost').text('' + totals.plan); 1601 | $form.find('.due_now .cost').text('' + totals.stages.now); 1602 | $form.find('.coupon .discount').text('' + (totals.coupon || '')); 1603 | $form.find('.vat .cost').text('' + (totals.vat || '')); 1604 | 1605 | $form.find('.add_ons .add_on').each(function() { 1606 | var addOn = $(this).data('add_on'); 1607 | if($(this).hasClass('selected')) { 1608 | var cost = totals.addOns[addOn.code]; 1609 | $(this).find('.cost').text('+ '+cost); 1610 | } 1611 | else { 1612 | $(this).find('.cost').text('+ '+addOn.cost); 1613 | } 1614 | }); 1615 | } 1616 | 1617 | $form.find('.plan .quantity input').bind('change keyup', function() { 1618 | subscription.plan.quantity = parseInt($(this).val(), 10) || 1; 1619 | updateTotals(); 1620 | }); 1621 | 1622 | // == SUBSCRIPTION PLAN GENERAL 1623 | $form.find('.plan .name').text(plan.name); 1624 | $form.find('.plan .recurring_cost .cost').text(''+plan.cost); 1625 | $form.find('.plan .recurring_cost .interval').text('every '+plan.interval); 1626 | 1627 | 1628 | // == GENERATE ADD-ONS 1629 | var $addOnsList = $form.find('.add_ons'); 1630 | if(options.enableAddOns) { 1631 | var l = plan.addOns.length; 1632 | if(l) { 1633 | $addOnsList.removeClass('none').addClass('any'); 1634 | for(var i=0; i < l; ++i) { 1635 | var addOn = plan.addOns[i]; 1636 | 1637 | var classAttr = 'add_on add_on_'+ addOn.code + (i % 2 ? ' even' : ' odd'); 1638 | if(i == 0) classAttr += ' first'; 1639 | if(i == l-1) classAttr += ' last'; 1640 | 1641 | var $addOn = $('
    ' + 1642 | '
    '+addOn.name+'
    ' + 1643 | '
    ' + 1644 | '
    Qty
    ' + 1645 | '' + 1646 | '
    ' + 1647 | '
    ' + 1648 | '
    '); 1649 | if(!addOn.displayQuantity) { 1650 | $addOn.find('.quantity').remove(); 1651 | } 1652 | $addOn.data('add_on', addOn); 1653 | $addOn.appendTo($addOnsList); 1654 | } 1655 | 1656 | // Quantity Change 1657 | $addOnsList.delegate('.add_ons .quantity input', 'change keyup', function(e) { 1658 | var $addOn = $(this).closest('.add_on'); 1659 | var addOn = $addOn.data('add_on'); 1660 | var newQty = parseInt($(this).val(),10) || 1; 1661 | subscription.findAddOnByCode(addOn.code).quantity = newQty; 1662 | updateTotals(); 1663 | }); 1664 | 1665 | $addOnsList.bind('selectstart', function(e) { 1666 | if($(e.target).is('.add_on')) { 1667 | e.preventDefault(); 1668 | } 1669 | }); 1670 | 1671 | // Add-on click 1672 | $addOnsList.delegate('.add_ons .add_on', 'click', function(e) { 1673 | if($(e.target).closest('.quantity').length) return; 1674 | 1675 | var selected = !$(this).hasClass('selected'); 1676 | $(this).toggleClass('selected', selected); 1677 | 1678 | var addOn = $(this).data('add_on'); 1679 | 1680 | if(selected) { 1681 | // add 1682 | var sa = subscription.redeemAddOn(addOn); 1683 | var $qty = $(this).find('.quantity input'); 1684 | sa.quantity = parseInt($qty.val(),10) || 1; 1685 | $qty.focus(); 1686 | } 1687 | else { 1688 | // remove 1689 | subscription.removeAddOn(addOn.code); 1690 | } 1691 | 1692 | updateTotals(); 1693 | }); 1694 | } 1695 | } 1696 | else { 1697 | $addOnsList.remove(); 1698 | } 1699 | 1700 | // == COUPON REDEEMER 1701 | var $coupon = $form.find('.coupon'); 1702 | var lastCode = null; 1703 | 1704 | function updateCoupon() { 1705 | 1706 | var code = $coupon.find('input').val(); 1707 | if(code == lastCode) { 1708 | return; 1709 | } 1710 | 1711 | lastCode = code; 1712 | 1713 | if(!code) { 1714 | $coupon.removeClass('invalid').removeClass('valid'); 1715 | $coupon.find('.description').text(''); 1716 | subscription.coupon = undefined; 1717 | updateTotals(); 1718 | return; 1719 | } 1720 | 1721 | $coupon.addClass('checking'); 1722 | subscription.getCoupon(code, function(coupon) { 1723 | 1724 | $coupon.removeClass('checking'); 1725 | 1726 | subscription.coupon = coupon; 1727 | $coupon.removeClass('invalid').addClass('valid'); 1728 | $coupon.find('.description').text(coupon.description); 1729 | 1730 | updateTotals(); 1731 | }, function() { 1732 | 1733 | subscription.coupon = undefined; 1734 | 1735 | $coupon.removeClass('checking'); 1736 | $coupon.removeClass('valid').addClass('invalid'); 1737 | $coupon.find('.description').text('Not Found'); 1738 | 1739 | updateTotals(); 1740 | }); 1741 | } 1742 | 1743 | if(options.enableCoupons) { 1744 | $coupon.find('input').bind('keyup change', function(e) { 1745 | }); 1746 | 1747 | $coupon.find('input').keypress(function(e) { 1748 | if(e.charCode == 13) { 1749 | e.preventDefault(); 1750 | updateCoupon(); 1751 | } 1752 | }); 1753 | 1754 | 1755 | $coupon.find('.check').click(function() { 1756 | updateCoupon(); 1757 | }); 1758 | 1759 | $coupon.find('input').blur(function() { $coupon.find('.check').click(); }); 1760 | } 1761 | else { 1762 | $coupon.remove(); 1763 | } 1764 | 1765 | 1766 | // == VAT 1767 | var $vat = $form.find('.vat'); 1768 | var $vatNumber = $form.find('.vat_number'); 1769 | var $vatNumberInput = $vatNumber.find('input'); 1770 | 1771 | $vat.find('.title').text('VAT at ' + R.settings.VATPercent + '%'); 1772 | function showHideVAT() { 1773 | var buyerCountry = $form.find('.country select').val(); 1774 | var vatNumberApplicable = R.isVATNumberApplicable(buyerCountry); 1775 | 1776 | // VAT Number is applicable to collection in any EU country 1777 | $vatNumber.toggleClass('applicable', vatNumberApplicable); 1778 | $vatNumber.toggleClass('inapplicable', !vatNumberApplicable); 1779 | 1780 | var vatNumber = $vatNumberInput.val(); 1781 | 1782 | // Only applicable to charge if isVATApplicable() 1783 | var chargeApplicable = R.isVATChargeApplicable(buyerCountry, vatNumber); 1784 | $vat.toggleClass('applicable', chargeApplicable); 1785 | $vat.toggleClass('inapplicable', !chargeApplicable); 1786 | } 1787 | // showHideVAT(); 1788 | $form.find('.country select').change(function() { 1789 | billingInfo.country = $(this).val(); 1790 | updateTotals(); 1791 | showHideVAT(); 1792 | }).change(); 1793 | $vatNumberInput.bind('keyup change', function() { 1794 | billingInfo.vatNumber = $(this).val(); 1795 | updateTotals(); 1796 | showHideVAT(); 1797 | }); 1798 | 1799 | // SUBMIT HANDLER 1800 | $form.submit(function(e) { 1801 | e.preventDefault(); 1802 | 1803 | clearServerErrors($form); 1804 | 1805 | 1806 | $form.find('.error').remove(); 1807 | $form.find('.invalid').removeClass('invalid'); 1808 | 1809 | handleUserErrors(function() { 1810 | pullAccountFields($form, account, options); 1811 | pullBillingInfoFields($form, billingInfo, options); 1812 | verifyTOSChecked($form); 1813 | 1814 | $form.addClass('submitting'); 1815 | $form.find('button.submit').attr('disabled', true).text('Please Wait'); 1816 | 1817 | subscription.save({ 1818 | success: function(response) { 1819 | if(options.afterSubscribe) 1820 | options.afterSubscribe(response); 1821 | 1822 | if(options.successURL) { 1823 | var url = options.successURL; 1824 | // url = R.replaceVars(url, response); 1825 | R.post(url, response, options); 1826 | } 1827 | } 1828 | , error: function(errors) { 1829 | if(!options.onError || !options.onError(errors)) { 1830 | displayServerErrors($form, errors); 1831 | } 1832 | } 1833 | , complete: function() { 1834 | $form.removeClass('submitting'); 1835 | $form.find('button.submit').removeAttr('disabled').text('Subscribe'); 1836 | } 1837 | }); 1838 | }); 1839 | 1840 | }); 1841 | 1842 | // FINALLY - UPDATE INITIAL TOTALS AND INJECT INTO DOM 1843 | updateTotals(); 1844 | 1845 | if(options.beforeInject) { 1846 | options.beforeInject($form.get(0)); 1847 | } 1848 | 1849 | $(function() { 1850 | var $container = $(options.target); 1851 | $container.html($form); 1852 | 1853 | if(options.afterInject) { 1854 | options.afterInject($form.get(0)); 1855 | } 1856 | }); 1857 | 1858 | } 1859 | 1860 | }; 1861 | 1862 | 1863 | 1864 | 1865 | 1866 | ////////////////////////////////////////////////// 1867 | // Compiled from dom/contact_info_fields.jade 1868 | ////////////////////////////////////////////////// 1869 | 1870 | R.contactInfoFieldsHTML = '
    Contact Info
    First Name
    Last Name
    Phone Number
    Company/Organization Name
    '; 1871 | 1872 | 1873 | 1874 | ////////////////////////////////////////////////// 1875 | // Compiled from dom/billing_info_fields.jade 1876 | ////////////////////////////////////////////////// 1877 | 1878 | R.billingInfoFieldsHTML = '
    Billing Info
    American Express
    Discover
    MasterCard
    Visa
    First Name
    Last Name
    Credit Card Number
    CVV
    Expires
    Address
    Apt/Suite
    City
    State/Province
    Zip/Postal
    VAT Number
    '; 1879 | 1880 | 1881 | 1882 | ////////////////////////////////////////////////// 1883 | // Compiled from dom/subscribe_form.jade 1884 | ////////////////////////////////////////////////// 1885 | 1886 | R.subscribeFormHTML = '
    Qty
    Setup Fee
    Coupon Code
    VAT
    Order Total
    '; 1887 | 1888 | 1889 | 1890 | ////////////////////////////////////////////////// 1891 | // Compiled from dom/update_billing_info_form.jade 1892 | ////////////////////////////////////////////////// 1893 | 1894 | R.updateBillingInfoFormHTML = '
    '; 1895 | 1896 | 1897 | 1898 | ////////////////////////////////////////////////// 1899 | // Compiled from dom/one_time_transaction_form.jade 1900 | ////////////////////////////////////////////////// 1901 | 1902 | R.oneTimeTransactionFormHTML = '
    '; 1903 | 1904 | 1905 | 1906 | ////////////////////////////////////////////////// 1907 | // Compiled from dom/terms_of_service.jade 1908 | ////////////////////////////////////////////////// 1909 | 1910 | R.termsOfServiceHTML = ''; 1911 | 1912 | window.Recurly = R; 1913 | 1914 | }(jQuery)); 1915 | 1916 | --------------------------------------------------------------------------------