');
120 | $e.text(errors[i]);
121 | $serverErrors.append($e);
122 | }
123 | }
124 | }
125 |
126 |
127 | var preFillMap = {
128 | account: {
129 | firstName: '.contact_info > .full_name > .first_name > input'
130 | , lastName: '.contact_info > .full_name > .last_name > input'
131 | , email: '.contact_info > .email > input'
132 | , phone: '.contact_info > .phone > input'
133 | , companyName: '.contact_info > .company_name > input'
134 | }
135 | , billingInfo: {
136 | firstName: '.billing_info > .credit_card > .first_name > input'
137 | , lastName: '.billing_info > .credit_card > .last_name > input'
138 | , address1: '.billing_info > .address > .address1 > input'
139 | , address2: '.billing_info > .address > .address2 > input'
140 | , country: '.billing_info > .address > .country > select'
141 | , city: '.billing_info > .address > .city > input'
142 | , state: '.billing_info > .address > .state_zip > .state > input'
143 | , zip: '.billing_info > .address > .state_zip > .zip > input'
144 | , vatNumber: '.billing_info > .vat_number > input'
145 |
146 | , cardNumber: '.billing_info .card_number > input'
147 | , CVV: '.billing_info .cvv > input'
148 | }
149 | , subscription: {
150 | couponCode: '.subscription > .coupon > .coupon_code > input'
151 | }
152 | };
153 |
154 | function preFillValues($form, options, mapObject) {
155 |
156 | (function recurse(preFill,mapObject,keypath) {
157 |
158 | if(!preFill) return;
159 |
160 | for(var k in preFill) {
161 | if(preFill.hasOwnProperty(k) && mapObject.hasOwnProperty(k)) {
162 |
163 | var v = preFill[k];
164 | var selectorOrNested = mapObject[k];
165 | var lcuk = cc2lcu(k);
166 | var keypath2 = keypath ? (keypath+'.'+lcuk) : lcuk;
167 |
168 |
169 | // jquery selector
170 | if(typeof selectorOrNested == 'string') {
171 |
172 | var $input = $form.find(selectorOrNested);
173 | $input.val(v).change();
174 | }
175 | // nested mapping
176 | else if(typeof selectorOrNested == 'object') {
177 | recurse(v, selectorOrNested, keypath2);
178 | }
179 | }
180 | }
181 | })(options,mapObject);
182 | }
183 |
184 |
185 | function initCommonForm($form, options) {
186 |
187 | if(!options.collectPhone) {
188 | $form.find('.phone').remove();
189 | }
190 |
191 | if(!options.collectCompany) {
192 | $form.find('.company_name').remove();
193 | }
194 |
195 | $form.delegate('.placeholder', 'click', function() {
196 | var $label = $(this);
197 | var $li = $(this).parent();
198 | $li.find('input').focus();
199 | });
200 |
201 | $form.delegate('input', 'change keyup', function() {
202 | var $input = $(this);
203 | var $li = $(this).parent();
204 |
205 | if($input.val().length > 0) {
206 | $li.find('.placeholder').hide();
207 | }
208 | else {
209 | $li.find('.placeholder').show();
210 | }
211 | });
212 |
213 |
214 | $form.delegate('input', 'focus', function() {
215 | $(this).parent().addClass('focus');
216 | });
217 |
218 | $form.delegate('input', 'blur', function() {
219 | $(this).parent().removeClass('focus');
220 | });
221 |
222 | $form.delegate('input', 'keydown', function(e) {
223 | if(e.keyCode >= 48 && e.keyCode <= 90) {
224 | $(this).parent().find('.placeholder').hide();
225 | }
226 | });
227 |
228 | preFillValues($form, options, preFillMap);
229 | }
230 |
231 | function initContactInfoForm($form, options) {
232 |
233 | // == FIRSTNAME / LASTNAME REDUNDANCY
234 | if(options.distinguishContactFromBillingInfo) {
235 | var $contactFirstName = $form.find('.contact_info .first_name input');
236 | var $contactLastName = $form.find('.contact_info .last_name input');
237 | var prevFirstName = $contactFirstName.val();
238 | var prevLastName = $contactLastName.val();
239 | $form.find('.contact_info .first_name input').change(function() {
240 | var $billingFirstName = $form.find('.billing_info .first_name input');
241 | if($billingFirstName.val() == prevFirstName) {
242 | $billingFirstName.val( $(this).val() ).change();
243 | }
244 | prevFirstName = $contactFirstName.val();
245 | });
246 | $form.find('.contact_info .last_name input').change(function() {
247 | var $billingLastName = $form.find('.billing_info .last_name input');
248 | if($billingLastName.val() == prevLastName) {
249 | $billingLastName.val( $(this).val() ).change();
250 | }
251 | prevLastName = $contactLastName.val();
252 | });
253 |
254 | }
255 | else {
256 | $form.find('.billing_info .first_name, .billing_info .last_name').remove();
257 | }
258 |
259 | }
260 |
261 | function initBillingInfoForm($form, options) {
262 |
263 | // == SWAPPING OF STATE INPUT WITH SELECT
264 | var $countrySelect = $form.find('.country select');
265 | var $state = $form.find('.state');
266 | var $stateInput = $state.find('input');
267 | var $manualStateInput = $state.children();
268 | var $savedStateSelect = {};
269 | var knownStates = R.states;
270 | var prevCountry = $countrySelect.val();
271 |
272 | function matchKnownStateWithInput(country, stateStr) {
273 | var ref = knownStates[country];
274 | // Normalize stateStr
275 | stateStr = $.trim(stateStr.toUpperCase());
276 |
277 | // Is a state code
278 | if(ref.hasOwnProperty(stateStr)) {
279 | return stateStr;
280 | }
281 |
282 | // Search through state names to find the code
283 | for(var k in ref) {
284 | if(ref.hasOwnProperty(k)) {
285 | var v = ref[k];
286 | if(stateStr == v.toUpperCase()) {
287 | return k;
288 | }
289 | }
290 | }
291 |
292 | return false;
293 | }
294 |
295 | function swapStateSelectOrInput(country, state) {
296 | var inSelectMode = $state.hasClass('select_mode');
297 | if(country == 'US' || country == 'CA') {
298 | if(!inSelectMode || prevCountry != country) {
299 | var manualVal = $state.find('input').val();
300 |
301 | if(manualVal != undefined && manualVal != '') {
302 | state = matchKnownStateWithInput(country, manualVal);
303 |
304 | if(!state) return false;
305 | }
306 |
307 | // Change to select mode
308 | $state.addClass('select_mode');
309 | // Detatch manual-input children from field
310 | $state.children().detach();
311 | // Instantiate HTML DOM only now, and cache it
312 | $savedStateSelect[country] = $savedStateSelect[country] || jsonToSelect(knownStates[country]);
313 | // Insert select into field
314 | $state.append($savedStateSelect[country]);
315 | // Set known state, if provided
316 | if(state) $state.find('select').val(state);
317 | }
318 |
319 | }
320 | else if(inSelectMode) {
321 | // Restore original manual state input field
322 | $state.empty().append($manualStateInput).removeClass('select_mode');
323 | }
324 | }
325 |
326 | $stateInput.bind('change keyup', function() {
327 | swapStateSelectOrInput(prevCountry);
328 | });
329 |
330 | $countrySelect.change(function() {
331 | var country = $(this).val();
332 | swapStateSelectOrInput(country);
333 | prevCountry = country;
334 | });
335 |
336 | // == GEOIP
337 | function niceSet($jq, v) {
338 | var cur = $jq.val();
339 | if(!v || v == '') return false;
340 | if(cur && cur != '' && cur != '-') return false;
341 |
342 | return $jq.val(v);
343 | }
344 |
345 | if(options.enableGeoIP) {
346 | $.ajax({
347 | url: R.settings.baseURL+'location',
348 | dataType: "jsonp",
349 | jsonp: "callback",
350 | success: function(data) {
351 | if(data.country) {
352 | niceSet($countrySelect, data.country);
353 | swapStateSelectOrInput(data.country, data.state);
354 | }
355 | }
356 | });
357 | }
358 | else {
359 | // == DEFAULT BUYER TO SELLER COUNTRY
360 | if(R.settings.country) {
361 | var $countryOpt = $form.find('.country option[value='+R.settings.country+']');
362 | if($countryOpt.length) {
363 | $countryOpt.attr('selected', true).change();
364 | }
365 | }
366 | }
367 |
368 | var now = new Date();
369 | var year = now.getFullYear();
370 | var month = now.getMonth();
371 | var $yearSelect = $form.find('.year select');
372 | var $monthSelect = $form.find('.month select');
373 |
374 | // == GENERATE YEAR SELECT OPTIONS
375 | for(var i=year; i <= year+10; ++i) {
376 | var $yearOpt = $('
');
377 | $yearOpt.appendTo($yearSelect);
378 | }
379 | $yearSelect.val(year+1);
380 |
381 |
382 | // == DISABLE INVALID MONTHS, SELECT CURRENT
383 | function updateMonths() {
384 | if($yearSelect.val() == year) {
385 | var foundSelected = false; // If we've set a selection yet
386 |
387 | if($monthSelect.val() > month) {
388 | // We know the current selection is already valid
389 | foundSelected = true;
390 | }
391 |
392 | $monthSelect.find('option').each(function(){
393 | if($(this).val() <= month) {
394 | $(this).attr('disabled', true);
395 | }
396 | else {
397 | $(this).removeAttr('disabled');
398 |
399 | if(!foundSelected) {
400 | $(this).attr('selected', true);
401 | foundSelected = true;
402 | }
403 | }
404 | });
405 | }
406 | else {
407 | $monthSelect.find('option').removeAttr('disabled');
408 | }
409 | };
410 | updateMonths();
411 | $yearSelect.change(updateMonths);
412 |
413 |
414 | // == HIDE UNNECESSARY ADDRESS FIELDS
415 |
416 | if(options.addressRequirement == 'none') {
417 | $form.find('.address').remove();
418 | }
419 | else if(options.addressRequirement == 'zip') {
420 | $form.find('.address').addClass('only_zip');
421 | $form.find('.address1, .address2, .city, .state').remove();
422 |
423 | // Only remove country if no VAT support
424 | if(!R.settings.VATPercent) {
425 | $form.find('.country').remove();
426 | }
427 | }
428 | else if(options.addressRequirement == 'zipstreet') {
429 | $form.find('.address').addClass('only_zipstreet');
430 | $form.find('.city, .state').remove();
431 |
432 | // Only remove country if no VAT support
433 | if(!R.settings.VATPercent) {
434 | $form.find('.country').remove();
435 | }
436 | }
437 | else if(options.addressRequirement == 'full') {
438 | $form.find('.address').addClass('full');
439 | }
440 | // == BUILD ACCEPTED CARDS DOM
441 | var $acceptedCards = $form.find('.accepted_cards');
442 |
443 | if(options.acceptedCards) {
444 | var a = options.acceptedCards
445 | , l = a.length;
446 |
447 | for(var i=0; i < l; ++i) {
448 | var cardId = a[i];
449 | var $card = $('
');
450 | var card = R.knownCards[cardId];
451 | if(card && card.name) {
452 | $card.text(card.name);
453 | }
454 | $acceptedCards.append($card);
455 | }
456 | }
457 |
458 | // == SHOW/HIDE CARD TYPES
459 | $form.find('.card_number input').bind('change keyup', function() {
460 | var type = R.detectCardType( $(this).val() );
461 | if(type) {
462 | $acceptedCards.find('.card').each(function(){
463 | $(this).toggleClass('match', $(this).hasClass(type));
464 | $(this).toggleClass('no_match', !$(this).hasClass(type));
465 | });
466 | }
467 | else {
468 | $acceptedCards.find('.card').removeClass('match no_match');
469 | }
470 | });
471 | }
472 |
473 |
474 | function pullAccountFields($form, account, options, pull) {
475 | account.firstName = pull.field($form, '.contact_info .first_name', V(R.isNotEmpty));
476 | account.lastName = pull.field($form, '.contact_info .last_name', V(R.isNotEmpty));
477 | account.companyName = pull.field($form, '.contact_info .company_name');
478 | account.email = pull.field($form, '.email', V(R.isNotEmpty), V(R.isValidEmail));
479 | account.code = options.accountCode;
480 | }
481 |
482 |
483 | function pullBillingInfoFields($form, billingInfo, options, pull) {
484 | billingInfo.firstName = pull.field($form, '.billing_info .first_name', V(R.isNotEmpty));
485 | billingInfo.lastName = pull.field($form, '.billing_info .last_name', V(R.isNotEmpty));
486 | billingInfo.number = pull.field($form, '.card_number', V(R.isNotEmpty), V(R.isValidCC));
487 | billingInfo.cvv = pull.field($form, '.cvv', V(R.isNotEmpty), V(R.isValidCVV));
488 |
489 | billingInfo.month = pull.field($form, '.month');
490 | billingInfo.year = pull.field($form, '.year');
491 |
492 | billingInfo.phone = pull.field($form, '.phone');
493 | billingInfo.address1 = pull.field($form, '.address1', V(R.isNotEmpty));
494 | billingInfo.address2 = pull.field($form, '.address2');
495 | billingInfo.city = pull.field($form, '.city', V(R.isNotEmpty));
496 | billingInfo.state = pull.field($form, '.state', V(R.isNotEmptyState));
497 | billingInfo.zip = pull.field($form, '.zip', V(R.isNotEmpty));
498 | billingInfo.country = pull.field($form, '.country', V(R.isNotEmpty));
499 | }
500 |
501 |
502 | function pullPlanQuantity($form, plan, options, pull) {
503 | var qty = pull.field($form, '.plan .quantity', V(R.isValidQuantity));
504 | // An empty quantity field indicates 1
505 | plan.quantity = qty || 1;
506 | }
507 |
508 |
509 | function verifyTOSChecked($form, pull) {
510 | pull.field($form, '.accept_tos', V(R.isChecked));
511 | }
512 |
513 |
514 | R.buildBillingInfoUpdateForm = function(options) {
515 | var defaults = {
516 | addressRequirement: 'full'
517 | , distinguishContactFromBillingInfo: true
518 | };
519 |
520 | options = $.extend(createObject(R.settings), defaults, options);
521 |
522 | if(!options.accountCode) R.raiseError('accountCode missing');
523 | if(!options.signature) R.raiseError('signature missing');
524 |
525 | var billingInfo = R.BillingInfo.create();
526 |
527 | var $form = $(R.dom.update_billing_info_form);
528 | $form.find('.billing_info').html(R.dom.billing_info_fields);
529 |
530 |
531 | initCommonForm($form, options);
532 | initBillingInfoForm($form, options);
533 |
534 |
535 | $form.submit(function(e) {
536 | e.preventDefault();
537 |
538 | clearServerErrors($form);
539 |
540 | $form.find('.error').remove();
541 | $form.find('.invalid').removeClass('invalid');
542 |
543 | validationGroup(function(puller) {
544 | pullBillingInfoFields($form, billingInfo, options, puller);
545 | }
546 | , function() {
547 | $form.addClass('submitting');
548 | $form.find('button.submit').attr('disabled', true).text('Please Wait');
549 |
550 | billingInfo.save({
551 | signature: options.signature
552 | , distinguishContactFromBillingInfo: options.distinguishContactFromBillingInfo
553 | , accountCode: options.accountCode
554 | , success: function(response) {
555 | if(options.successHandler) {
556 | options.successHandler(R.getToken(response));
557 | }
558 |
559 | if(options.successURL) {
560 | var url = options.successURL;
561 | R.postResult(url, response, options);
562 | }
563 | }
564 | , error: function(errors) {
565 | if(!options.onError || !options.onError(errors)) {
566 | displayServerErrors($form, errors);
567 | }
568 | }
569 | , complete: function() {
570 | $form.removeClass('submitting');
571 | $form.find('button.submit').removeAttr('disabled').text('Update');
572 | }
573 | });
574 | });
575 | });
576 |
577 | if(options.beforeInject) {
578 | options.beforeInject($form.get(0));
579 | }
580 |
581 | $(function() {
582 | var $container = $(options.target);
583 | $container.html($form);
584 |
585 | if(options.afterInject) {
586 | options.afterInject($form.get(0));
587 | }
588 | });
589 |
590 | };
591 |
592 |
593 | function initTOSCheck($form, options) {
594 |
595 | if(options.termsOfServiceURL || options.privacyPolicyURL) {
596 | var $tos = $form.find('.accept_tos').html(R.dom.terms_of_service);
597 |
598 | // If only one, remove 'and'
599 | if(!(options.termsOfServiceURL && options.privacyPolicyURL)) {
600 | $tos.find('span.and').remove();
601 | }
602 |
603 | // set href or remove tos_link
604 | if(options.termsOfServiceURL) {
605 | $tos.find('a.tos_link').attr('href', options.termsOfServiceURL);
606 | }
607 | else {
608 | $tos.find('a.tos_link').remove();
609 | }
610 |
611 | // set href or remove pp_link
612 | if(options.privacyPolicyURL) {
613 | $tos.find('a.pp_link').attr('href', options.privacyPolicyURL);
614 | }
615 | else {
616 | $tos.find('a.pp_link').remove();
617 | }
618 |
619 | }
620 | else {
621 | $form.find('.accept_tos').remove();
622 | }
623 |
624 | }
625 |
626 | R.buildTransactionForm = function(options) {
627 | var defaults = {
628 | addressRequirement: 'full'
629 | , distinguishContactFromBillingInfo: true
630 | , collectContactInfo: true
631 | };
632 |
633 | options = $.extend(createObject(R.settings), defaults, options);
634 |
635 |
636 | if(!options.collectContactInfo && !options.accountCode) {
637 | R.raiseError('collectContactInfo is false, but no accountCode provided');
638 | }
639 |
640 | if(!options.signature) R.raiseError('signature missing');
641 |
642 |
643 | var billingInfo = R.BillingInfo.create()
644 | , account = R.Account.create()
645 | , transaction = R.Transaction.create();
646 |
647 |
648 | transaction.account = account;
649 | transaction.billingInfo = billingInfo;
650 | transaction.currency = options.currency;
651 | transaction.cost = new R.Cost(options.amountInCents);
652 |
653 | var $form = $(R.dom.one_time_transaction_form);
654 | $form.find('.billing_info').html(R.dom.billing_info_fields);
655 |
656 | if(options.collectContactInfo) {
657 | $form.find('.contact_info').html(R.dom.contact_info_fields);
658 | }
659 | else {
660 | $form.find('.contact_info').remove();
661 | }
662 |
663 |
664 | initCommonForm($form, options);
665 | initContactInfoForm($form, options);
666 | initBillingInfoForm($form, options);
667 | initTOSCheck($form, options);
668 |
669 | $form.submit(function(e) {
670 | e.preventDefault();
671 |
672 | clearServerErrors($form);
673 |
674 | $form.find('.error').remove();
675 | $form.find('.invalid').removeClass('invalid');
676 |
677 | validationGroup(function(puller) {
678 | pullAccountFields($form, account, options, puller);
679 | pullBillingInfoFields($form, billingInfo, options, puller);
680 | verifyTOSChecked($form, puller);
681 | }
682 | , function() {
683 | $form.addClass('submitting');
684 | $form.find('button.submit').attr('disabled', true).text('Please Wait');
685 |
686 | transaction.save({
687 | signature: options.signature
688 | , accountCode: options.accountCode
689 | , success: function(response) {
690 | if(options.successHandler) {
691 | options.successHandler(R.getToken(response));
692 | }
693 |
694 | if(options.successURL) {
695 | var url = options.successURL;
696 | R.postResult(url, response, options);
697 | }
698 | }
699 | , error: function(errors) {
700 | if(!options.onError || !options.onError(errors)) {
701 | displayServerErrors($form, errors);
702 | }
703 | }
704 | , complete: function() {
705 | $form.removeClass('submitting');
706 | $form.find('button.submit').removeAttr('disabled').text('Pay');
707 | }
708 | });
709 | });
710 | });
711 |
712 | if(options.beforeInject) {
713 | options.beforeInject($form.get(0));
714 | }
715 |
716 | $(function() {
717 | var $container = $(options.target);
718 | $container.html($form);
719 |
720 | if(options.afterInject) {
721 | options.afterInject($form.get(0));
722 | }
723 | });
724 |
725 | };
726 |
727 |
728 | R.buildSubscriptionForm = function(options) {
729 | var defaults = {
730 | enableAddOns: true
731 | , enableCoupons: true
732 | , addressRequirement: 'full'
733 | , distinguishContactFromBillingInfo: false
734 | };
735 |
736 | options = $.extend(createObject(R.settings), defaults, options);
737 |
738 | if(!options.signature) R.raiseError('signature missing');
739 |
740 | var $form = $(R.dom.subscribe_form);
741 | $form.find('.contact_info').html(R.dom.contact_info_fields);
742 | $form.find('.billing_info').html(R.dom.billing_info_fields);
743 |
744 |
745 | if(options.planCode)
746 | R.Plan.get(options.planCode, options.currency, gotPlan);
747 | else if(options.plan) {
748 | // this should never be called
749 | // the api does not have it, nor does anywhere else in the program refer to it
750 | gotPlan(options.plan);
751 | }
752 |
753 | initCommonForm($form, options);
754 | initContactInfoForm($form, options);
755 | initBillingInfoForm($form, options);
756 | initTOSCheck($form, options);
757 |
758 | function gotPlan(plan) {
759 |
760 | if(options.filterPlan)
761 | plan = options.filterPlan(plan) || plan;
762 |
763 |
764 | var subscription = plan.createSubscription(),
765 | account = R.Account.create(),
766 | billingInfo = R.BillingInfo.create();
767 |
768 | subscription.account = account;
769 | subscription.billingInfo = billingInfo;
770 |
771 | if(options.filterSubscription)
772 | subscription = options.filterSubscription(subscription) || subscription;
773 |
774 | // == EDITABLE PLAN QUANTITY
775 | if(!plan.displayQuantity) {
776 | $form.find('.plan .quantity').remove();
777 | }
778 |
779 | // == SETUP FEE
780 | if(plan.setupFee) {
781 | $form.find('.subscription').addClass('with_setup_fee');
782 | $form.find('.plan .setup_fee .cost').text('' + plan.setupFee);
783 | }
784 | else {
785 | $form.find('.plan .setup_fee').remove();
786 | }
787 |
788 | // == FREE TRIAL
789 | if(plan.trial) {
790 | $form.find('.subscription').addClass('with_trial');
791 |
792 | $form.find('.plan .free_trial').text('First ' + plan.trial + ' free');
793 | }
794 | else {
795 | $form.find('.plan .free_trial').remove();
796 | }
797 |
798 |
799 | // == UPDATE ALL UI TOTALS via subscription.calculateTotals() results
800 | function updateTotals() {
801 | var totals = subscription.calculateTotals();
802 |
803 | $form.find('.plan .recurring_cost .cost').text('' + totals.plan);
804 | $form.find('.due_now .cost').text('' + totals.stages.now);
805 | $form.find('.coupon .discount').text('' + (totals.coupon || ''));
806 | $form.find('.vat .cost').text('' + (totals.vat || ''));
807 |
808 | $form.find('.add_ons .add_on').each(function() {
809 | var addOn = $(this).data('add_on');
810 | if($(this).hasClass('selected')) {
811 | var cost = totals.addOns[addOn.code];
812 | $(this).find('.cost').text('+ '+cost);
813 | }
814 | else {
815 | $(this).find('.cost').text('+ '+addOn.cost);
816 | }
817 | });
818 | }
819 |
820 | $form.find('.plan .quantity input').bind('change keyup', function() {
821 | subscription.plan.quantity = parseInt($(this).val(), 10) || 1;
822 | updateTotals();
823 | });
824 |
825 | // == SUBSCRIPTION PLAN GENERAL
826 | $form.find('.plan .name').text(plan.name);
827 | $form.find('.plan .recurring_cost .cost').text(''+plan.cost);
828 | $form.find('.plan .recurring_cost .interval').text('every '+plan.interval);
829 |
830 |
831 | // == GENERATE ADD-ONS
832 | var $addOnsList = $form.find('.add_ons');
833 | if(options.enableAddOns) {
834 | var l = plan.addOns.length;
835 | if(l) {
836 | $addOnsList.removeClass('none').addClass('any');
837 | for(var i=0; i < l; ++i) {
838 | var addOn = plan.addOns[i];
839 |
840 | var classAttr = 'add_on add_on_'+ addOn.code + (i % 2 ? ' even' : ' odd');
841 | if(i == 0) classAttr += ' first';
842 | if(i == l-1) classAttr += ' last';
843 |
844 | var $addOn = $('
' +
845 | '
'+addOn.name+'
' +
846 | '
' +
847 | '
Qty
' +
848 | '
' +
849 | '
' +
850 | '
' +
851 | '
');
852 | if(!addOn.displayQuantity) {
853 | $addOn.find('.quantity').remove();
854 | }
855 | $addOn.data('add_on', addOn);
856 | $addOn.appendTo($addOnsList);
857 | }
858 |
859 | // Quantity Change
860 | $addOnsList.delegate('.add_ons .quantity input', 'change keyup', function(e) {
861 | var $addOn = $(this).closest('.add_on');
862 | var addOn = $addOn.data('add_on');
863 | var newQty = parseInt($(this).val(),10) || 1;
864 | subscription.findAddOnByCode(addOn.code).quantity = newQty;
865 | updateTotals();
866 | });
867 |
868 | $addOnsList.bind('selectstart', function(e) {
869 | if($(e.target).is('.add_on')) {
870 | e.preventDefault();
871 | }
872 | });
873 |
874 | // Add-on click
875 | $addOnsList.delegate('.add_ons .add_on', 'click', function(e) {
876 | if($(e.target).closest('.quantity').length) return;
877 |
878 | var selected = !$(this).hasClass('selected');
879 | $(this).toggleClass('selected', selected);
880 |
881 | var addOn = $(this).data('add_on');
882 |
883 | if(selected) {
884 | // add
885 | var sa = subscription.redeemAddOn(addOn);
886 | var $qty = $(this).find('.quantity input');
887 | sa.quantity = parseInt($qty.val(),10) || 1;
888 | $qty.focus();
889 | }
890 | else {
891 | // remove
892 | subscription.removeAddOn(addOn.code);
893 | }
894 |
895 | updateTotals();
896 | });
897 | }
898 | }
899 | else {
900 | $addOnsList.remove();
901 | }
902 |
903 | // == COUPON REDEEMER
904 | var $coupon = $form.find('.coupon');
905 | var lastCode = null;
906 |
907 | function updateCoupon() {
908 |
909 | var code = $coupon.find('input').val();
910 | if(code == lastCode) {
911 | return;
912 | }
913 |
914 | lastCode = code;
915 |
916 | if(!code) {
917 | $coupon.removeClass('invalid').removeClass('valid');
918 | $coupon.find('.description').text('');
919 | subscription.coupon = undefined;
920 | updateTotals();
921 | return;
922 | }
923 |
924 | $coupon.addClass('checking');
925 | subscription.getCoupon(code, function(coupon) {
926 |
927 | $coupon.removeClass('checking');
928 |
929 | subscription.coupon = coupon;
930 | $coupon.removeClass('invalid').addClass('valid');
931 | $coupon.find('.description').text(coupon.description);
932 |
933 | updateTotals();
934 | }, function() {
935 |
936 | subscription.coupon = undefined;
937 |
938 | $coupon.removeClass('checking');
939 | $coupon.removeClass('valid').addClass('invalid');
940 | $coupon.find('.description').text('Not Found');
941 |
942 | updateTotals();
943 | });
944 | }
945 |
946 | if(options.enableCoupons) {
947 | $coupon.find('input').bind('keyup change', function(e) {
948 | });
949 |
950 | $coupon.find('input').keypress(function(e) {
951 | if(e.charCode == 13) {
952 | e.preventDefault();
953 | updateCoupon();
954 | }
955 | });
956 |
957 |
958 | $coupon.find('.check').click(function() {
959 | updateCoupon();
960 | });
961 |
962 | $coupon.find('input').blur(function() { $coupon.find('.check').click(); });
963 | }
964 | else {
965 | $coupon.remove();
966 | }
967 |
968 |
969 | // == VAT
970 | var $vat = $form.find('.vat');
971 | var $vatNumber = $form.find('.vat_number');
972 | var $vatNumberInput = $vatNumber.find('input');
973 |
974 | $vat.find('.title').text('VAT at ' + R.settings.VATPercent + '%');
975 | function showHideVAT() {
976 | var buyerCountry = $form.find('.country select').val();
977 | var vatNumberApplicable = R.isVATNumberApplicable(buyerCountry);
978 |
979 | // VAT Number is applicable to collection in any EU country
980 | $vatNumber.toggleClass('applicable', vatNumberApplicable);
981 | $vatNumber.toggleClass('inapplicable', !vatNumberApplicable);
982 |
983 | var vatNumber = $vatNumberInput.val();
984 |
985 | // Only applicable to charge if isVATApplicable()
986 | var chargeApplicable = R.isVATChargeApplicable(buyerCountry, vatNumber);
987 | $vat.toggleClass('applicable', chargeApplicable);
988 | $vat.toggleClass('inapplicable', !chargeApplicable);
989 | }
990 | // showHideVAT();
991 | $form.find('.country select').change(function() {
992 | billingInfo.country = $(this).val();
993 | updateTotals();
994 | showHideVAT();
995 | }).change();
996 | $vatNumberInput.bind('keyup change', function() {
997 | billingInfo.vatNumber = $(this).val();
998 | updateTotals();
999 | showHideVAT();
1000 | });
1001 |
1002 | // SUBMIT HANDLER
1003 | $form.submit(function(e) {
1004 | e.preventDefault();
1005 |
1006 | clearServerErrors($form);
1007 |
1008 |
1009 | $form.find('.error').remove();
1010 | $form.find('.invalid').removeClass('invalid');
1011 |
1012 | validationGroup(function(puller) {
1013 | pullPlanQuantity($form, subscription.plan, options, puller);
1014 | pullAccountFields($form, account, options, puller);
1015 | pullBillingInfoFields($form, billingInfo, options, puller);
1016 | verifyTOSChecked($form, puller);
1017 | }, function() {
1018 |
1019 | $form.addClass('submitting');
1020 | $form.find('button.submit').attr('disabled', true).text('Please Wait');
1021 |
1022 | subscription.save({
1023 |
1024 | signature: options.signature
1025 | , success: function(response) {
1026 | if(options.successHandler) {
1027 | options.successHandler(R.getToken(response));
1028 | }
1029 | if(options.successURL) {
1030 | var url = options.successURL;
1031 | R.postResult(url, response, options);
1032 | }
1033 | }
1034 | , error: function(errors) {
1035 | if(!options.onError || !options.onError(errors)) {
1036 | displayServerErrors($form, errors);
1037 | }
1038 | }
1039 | , complete: function() {
1040 | $form.removeClass('submitting');
1041 | $form.find('button.submit').removeAttr('disabled').text('Subscribe');
1042 | }
1043 | });
1044 | });
1045 |
1046 | });
1047 |
1048 | // FINALLY - UPDATE INITIAL TOTALS AND INJECT INTO DOM
1049 | updateTotals();
1050 |
1051 | if(options.beforeInject) {
1052 | options.beforeInject($form.get(0));
1053 | }
1054 |
1055 | $(function() {
1056 | var $container = $(options.target);
1057 | $container.html($form);
1058 |
1059 | if(options.afterInject) {
1060 | options.afterInject($form.get(0));
1061 | }
1062 | });
1063 |
1064 | }
1065 |
1066 | };
1067 |
1068 |
--------------------------------------------------------------------------------