diff --git a/ruby/lib/jam_ruby/models/shopping_cart.rb b/ruby/lib/jam_ruby/models/shopping_cart.rb index 24f4db02c..4faffae08 100644 --- a/ruby/lib/jam_ruby/models/shopping_cart.rb +++ b/ruby/lib/jam_ruby/models/shopping_cart.rb @@ -8,7 +8,7 @@ module JamRuby validates :cart_id, presence: true validates :cart_type, presence: true validates :cart_class_name, presence: true - validates :marked_for_redeem, :inclusion => {:in => [true, false]} + validates :marked_for_redeem, numericality: {only_integer: true} default_scope order('created_at DESC') diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb index 955f702fe..69b8d5a45 100644 --- a/ruby/lib/jam_ruby/recurly_client.rb +++ b/ruby/lib/jam_ruby/recurly_client.rb @@ -38,6 +38,8 @@ module JamRuby def get_account(current_user) current_user && current_user.recurly_code ? Recurly::Account.find(current_user.recurly_code) : nil + rescue Recurly::Error => x + raise RecurlyClientError, x.to_s end def update_account(current_user, billing_info=nil) diff --git a/web/app/assets/javascripts/checkout_order.js b/web/app/assets/javascripts/checkout_order.js index c232f4c07..5ca574c7e 100644 --- a/web/app/assets/javascripts/checkout_order.js +++ b/web/app/assets/javascripts/checkout_order.js @@ -221,7 +221,7 @@ else { $("#order_error").text(xhr.responseText).removeClass("hidden") } - $orderContent.find(".place-order").on('click', placeOrder) + $screen.find(".place-order").on('click', placeOrder).removeClass('disabled') } function moveToThanks(purchaseResponse) { diff --git a/web/app/assets/javascripts/checkout_payment.js b/web/app/assets/javascripts/checkout_payment.js index c492ddf3e..69f306b50 100644 --- a/web/app/assets/javascripts/checkout_payment.js +++ b/web/app/assets/javascripts/checkout_payment.js @@ -61,13 +61,22 @@ if(user) { user.done(populateAccountInfo).error(app.ajaxError); } + else { + $reuseExistingCardChk.iCheck('uncheck').attr('checked', false) + if(gon.global.one_free_jamtrack_per_user) { + $freeJamTrackPrompt.removeClass('hidden') + } + else { + $noFreeJamTrackPrompt.removeClass('hidden') + } + } }) } function populateAccountInfo(user) { userDetail = user; - $reuseExistingCardChk.iCheck(userDetail.reuse_card ? 'check' : 'uncheck').attr('checked', userDetail.reuse_card) + $reuseExistingCardChk.iCheck(userDetail.reuse_card && userDetail.has_recurly_account ? 'check' : 'uncheck').attr('checked', userDetail.reuse_card) // show appropriate prompt text based on whether user has a free jamtrack if(user.free_jamtrack) { @@ -151,7 +160,6 @@ var reuse_card_this_time = $reuseExistingCardChk.is(':checked'); var reuse_card_next_time = $paymentMethod.find('#save-card').is(':checked'); - // validation var billing_first_name = $billingInfo.find("#billing-first-name").val(); var billing_last_name = $billingInfo.find("#billing-last-name").val(); @@ -162,13 +170,14 @@ var billing_zip = $billingInfo.find("#billing-zip").val(); var billing_country = $billingInfo.find("#billing-country").val(); + var billingInfoValid = true; if (!billing_first_name) { $billingInfo.find('#divBillingFirstName .error-text').remove(); - $billingInfo.find('#divBillingFirstName').addClass("error"); + $billingInfo.find('#divBillingFirstName').addClass("error").addClass("transparent"); $billingInfo.find('#billing-first-name').after(""); logger.info("no billing first name"); - return false; + billingInfoValid = false; } else { $billingInfo.find('#divBillingFirstName').removeClass("error"); @@ -176,11 +185,11 @@ if (!billing_last_name) { $billingInfo.find('#divBillingLastName .error-text').remove(); - $billingInfo.find('#divBillingLastName').addClass("error"); + $billingInfo.find('#divBillingLastName').addClass("error").addClass("transparent"); $billingInfo.find('#billing-last-name').after(""); logger.info("no billing last name"); - return false; + billingInfoValid = false; } else { $billingInfo.find('#divBillingLastName').removeClass("error"); @@ -188,11 +197,11 @@ if (!billing_address1) { $billingInfo.find('#divBillingAddress1 .error-text').remove(); - $billingInfo.find('#divBillingAddress1').addClass("error"); + $billingInfo.find('#divBillingAddress1').addClass("error").addClass("transparent"); $billingInfo.find('#billing-address1').after(""); logger.info("no billing address line 1"); - return false; + billingInfoValid = false; } else { $billingInfo.find('#divBillingAddress1').removeClass("error"); @@ -200,11 +209,11 @@ if (!billing_zip) { $billingInfo.find('#divBillingZip .error-text').remove(); - $billingInfo.find('#divBillingZip').addClass("error"); - $billingInfo.find('#billing-zip').after(""); + $billingInfo.find('#divBillingZip').addClass("error").addClass("transparent"); + $billingInfo.find('#billing-zip').after(""); - logger.info("no billing address line 2"); - return false; + logger.info("no billing zip"); + billingInfoValid = false; } else { $billingInfo.find('#divBillingZip').removeClass("error"); @@ -212,11 +221,11 @@ if (!billing_state) { $billingInfo.find('#divBillingState .error-text').remove(); - $billingInfo.find('#divBillingState').addClass("error"); - $billingInfo.find('#billing-zip').after(""); + $billingInfo.find('#divBillingState').addClass("error").addClass("transparent"); + $billingInfo.find('#billing-state').after(""); - logger.info("no billing zip"); - return false; + logger.info("no billing state"); + billingInfoValid = false; } else { $billingInfo.find('#divBillingState').removeClass("error"); @@ -224,11 +233,11 @@ if (!billing_city) { $billingInfo.find('#divBillingCity .error-text').remove(); - $billingInfo.find('#divBillingCity').addClass("error"); + $billingInfo.find('#divBillingCity').addClass("error").addClass("transparent"); $billingInfo.find('#billing-city').after(""); logger.info("no billing city"); - return false; + billingInfoValid = false; } else { $billingInfo.find('#divBillingCity').removeClass("error"); @@ -236,11 +245,11 @@ if (!billing_country) { $billingInfo.find('#divBillingCountry .error-text').remove(); - $billingInfo.find('#divBillingCountry').addClass("error"); + $billingInfo.find('#divBillingCountry').addClass("error").addClass("transparent"); $billingInfo.find('#billing-country').after(""); logger.info("no billing country"); - return false; + billingInfoValid = false; } else { $billingInfo.find('#divBillingCountry').removeClass("error"); @@ -263,7 +272,7 @@ if (!shipping_first_name) { $shippingAddress.find('#divShippingFirstName .error-text').remove(); - $shippingAddress.find('#divShippingFirstName').addClass("error"); + $shippingAddress.find('#divShippingFirstName').addClass("error").addClass("transparent"); $shippingAddress.find('#shipping-first-name').after(""); logger.info("no address first name"); @@ -275,7 +284,7 @@ if (!shipping_last_name) { $shippingAddress.find('#divShippingLastName .error-text').remove(); - $shippingAddress.find('#divShippingLastName').addClass("error"); + $shippingAddress.find('#divShippingLastName').addClass("error").addClass("transparent"); $shippingAddress.find('#shipping-last-name').after(""); logger.info("no last name"); @@ -287,7 +296,7 @@ if (!shipping_address1) { $shippingAddress.find('#divShippingAddress1 .error-text').remove(); - $shippingAddress.find('#divShippingAddress1').addClass("error"); + $shippingAddress.find('#divShippingAddress1').addClass("error").addClass("transparent"); $shippingAddress.find('#shipping-address1').after(""); logger.info("no shipping address 1"); @@ -299,7 +308,7 @@ if (!shipping_zip) { $shippingAddress.find('#divShippingZip .error-text').remove(); - $shippingAddress.find('#divShippingZip').addClass("error"); + $shippingAddress.find('#divShippingZip').addClass("error").addClass("transparent"); $shippingAddress.find('#shipping-zip').after(""); logger.info("no shipping address 2"); @@ -311,7 +320,7 @@ if (!shipping_state) { $shippingAddress.find('#divShippingState .error-text').remove(); - $shippingAddress.find('#divShippingState').addClass("error"); + $shippingAddress.find('#divShippingState').addClass("error").addClass("transparent"); $shippingAddress.find('#shipping-zip').after(""); logger.info("no shipping state"); @@ -323,7 +332,7 @@ if (!shipping_city) { $shippingAddress.find('#divShippingCity .error-text').remove(); - $shippingAddress.find('#divShippingCity').addClass("error"); + $shippingAddress.find('#divShippingCity').addClass("error").addClass("transparent"); $shippingAddress.find('#shipping-city').after(""); logger.info("no shipping city"); @@ -335,7 +344,7 @@ if (!shipping_country) { $shippingAddress.find('#divShippingCountry .error-text').remove(); - $shippingAddress.find('#divShippingCountry').addClass("error"); + $shippingAddress.find('#divShippingCountry').addClass("error").addClass("transparent"); $shippingAddress.find('#shipping-country').after(""); logger.info("no shipping country"); @@ -356,7 +365,7 @@ /** if (!card_name) { $paymentMethod.find('#divCardName .error-text').remove(); - $paymentMethod.find('#divCardName').addClass("error"); + $paymentMethod.find('#divCardName').addClass("error").addClass("transparent"); $paymentMethod.find('#card-name').after(""); return false; } else { @@ -367,48 +376,54 @@ if(!reuse_card_this_time) { if (!card_number) { $paymentMethod.find('#divCardNumber .error-text').remove(); - $paymentMethod.find('#divCardNumber').addClass("error"); + $paymentMethod.find('#divCardNumber').addClass("error").addClass("transparent"); $paymentMethod.find('#card-number').after(""); logger.info("no card number"); - return false; + billingInfoValid = false; } else if (!$.payment.validateCardNumber(card_number)) { $paymentMethod.find('#divCardNumber .error-text').remove(); - $paymentMethod.find('#divCardNumber').addClass("error"); + $paymentMethod.find('#divCardNumber').addClass("error").addClass("transparent"); $paymentMethod.find('#card-number').after(""); logger.info("invalid card number"); - return false; + billingInfoValid = false; } else { $paymentMethod.find('#divCardNumber').removeClass("error"); } if (!$.payment.validateCardExpiry(card_month, card_year)) { $paymentMethod.find('#divCardExpiry .error-text').remove(); - $paymentMethod.find('#divCardExpiry').addClass("error"); + $paymentMethod.find('#divCardExpiry').addClass("error").addClass("transparent"); $paymentMethod.find('#card-expiry').after(""); logger.info("invalid card expiry"); - return false; + billingInfoValid = false; } else { $paymentMethod.find('#divCardExpiry').removeClass("error"); } if (!card_verify) { $paymentMethod.find('#divCardVerify .error-text').remove(); - $paymentMethod.find('#divCardVerify').addClass("error"); + $paymentMethod.find('#divCardVerify').addClass("error").addClass("transparent"); $paymentMethod.find('#card-verify').after(""); logger.info("no card verify"); - return false; + billingInfoValid = false; } else if (!$.payment.validateCardCVC(card_verify)) { $paymentMethod.find('#divCardVerify .error-text').remove(); - $paymentMethod.find('#divCardVerify').addClass("error"); + $paymentMethod.find('#divCardVerify').addClass("error").addClass("transparent"); $paymentMethod.find('#card-verify').after(""); logger.info("bad card CVC"); - return false; + billingInfoValid = false; } else { $paymentMethod.find('#divCardVerify').removeClass("error"); } } + + if(!billingInfoValid) { + logger.debug("billing info is invalid. returning"); + return false; + } + billing_info = {}; shipping_info = {}; billing_info.first_name = billing_first_name; @@ -480,34 +495,34 @@ $.each(xhr.responseJSON.errors, function(key, error) { if (key == 'number') { $paymentMethod.find('#divCardNumber .error-text').remove(); - $paymentMethod.find('#divCardNumber').addClass("error"); + $paymentMethod.find('#divCardNumber').addClass("error").addClass("transparent"); $paymentMethod.find('#card-number').after(""); } else if (key == 'verification_value') { $paymentMethod.find('#divCardVerify .error-text').remove(); - $paymentMethod.find('#divCardVerify').addClass("error"); + $paymentMethod.find('#divCardVerify').addClass("error").addClass("transparent"); $paymentMethod.find('#card-verify').after(""); } else if(key == 'email') { var $email = $accountSignup.find('input[name="email"]') var $field = $email.closest('.field') $field.find('.error-text').remove() - $field.addClass("error"); - $field.append(""); + $field.addClass("error").addClass("transparent"); + $email.after(""); } else if(key == 'password') { var $password = $accountSignup.find('input[name="password"]') var $field = $password.closest('.field') $field.find('.error-text').remove() - $field.addClass("error"); - $field.append(""); + $field.addClass("error").addClass("transparent"); + $password.after(""); } else if(key == 'terms_of_service') { var $terms = $accountSignup.find('input[name="terms-of-service"]') var $field = $terms.closest('.field') $field.find('.error-text').remove() - $field.addClass("error"); - $field.append(""); + $field.addClass("error").addClass("transparent"); + $accountSignup.find('.terms-of-service-label-holder').append(""); } }); } diff --git a/web/app/assets/stylesheets/client/checkout_payment.css.scss b/web/app/assets/stylesheets/client/checkout_payment.css.scss index 97c17c359..0f86dd0cf 100644 --- a/web/app/assets/stylesheets/client/checkout_payment.css.scss +++ b/web/app/assets/stylesheets/client/checkout_payment.css.scss @@ -16,6 +16,16 @@ line-height:125%; } + .field.error { + background-color: transparent !important; + padding: 0 !important; + border-width:0 !important; + + li { + list-style:none; + } + } + h2 { color:white; background-color:#4d4d4d; diff --git a/web/app/assets/stylesheets/client/jamkazam.css.scss b/web/app/assets/stylesheets/client/jamkazam.css.scss index 2aa254437..0269adfd2 100644 --- a/web/app/assets/stylesheets/client/jamkazam.css.scss +++ b/web/app/assets/stylesheets/client/jamkazam.css.scss @@ -392,6 +392,24 @@ textarea { padding:5px; border: solid 1px #900; + .error-text { + display:block; + font-size:11px; + color:#F00; + margin:10px 0 0; + } + + &.transparent { + background-color:transparent; + padding:0; + border-width:0px; + + .error-text { + margin:5px 0 0; + font-size:14px; + font-weight:bold; + } + } } .error input { @@ -403,12 +421,6 @@ textarea { display:none; } -.error .error-text { - display:block; - font-size:11px; - color:#F00; - margin:10px 0 0; -} .grey { color:#999; } diff --git a/web/spec/features/checkout_spec.rb b/web/spec/features/checkout_spec.rb index adff4c750..1099fc0ad 100644 --- a/web/spec/features/checkout_spec.rb +++ b/web/spec/features/checkout_spec.rb @@ -17,33 +17,63 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d JamTrack.delete_all JamTrackTrack.delete_all JamTrackLicensor.delete_all + User.delete_all stub_const("APP_CONFIG", web_config) end + def verify_nav(selected) + + 3.times do |i| + badge = i + 1 + + if i == selected + find('.badge-number', text:badge) + else + find('.badge-number.disabled', text:badge) + end + end + end + describe "Checkout Signin" do - it "allows user to log in" do + it "allows user to log in on the signin page" do visit '/client#/checkoutSignin' find('h3', text: 'ALREADY A MEMBER OF THE JAMKAZAM COMMUNITY?') + verify_nav(1) + # try a bogus user/pass first fill_in "email", with: user.email fill_in "password", with: 'wrong' + find('.signin-submit').trigger(:click) find('.login-error-msg', text: 'Invalid login') # try successfully fill_in "email", with: user.email fill_in "password", with: user.password + find('.signin-submit').trigger(:click) - + # this should take us to the payment screen + find('p.payment-prompt') + end + it "allows user to skip login and go to payment screen" do + visit '/client#/checkoutSignin' + find('h3', text: 'ALREADY A MEMBER OF THE JAMKAZAM COMMUNITY?') + verify_nav(1) + + # skip to payment without signing in + find('a.btnNext').trigger(:click) + + # this should take us to the payment screen + find('p.payment-prompt') end it "indicates already logged in" do @@ -51,6 +81,9 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d # verify that the signin page shows, but indicates to the user that they are already signed in find('h3', text: 'YOU ARE ALREADY LOGGED IN') + + verify_nav(1) + find('p.carry-on-prompt', text: 'You can move on to the next step of checkout.') # let them move on to the next step @@ -59,4 +92,146 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d end end + describe "Checkout Payment" do + it "allows anonymous to visit" do + + visit '/client#/checkoutPayment' + + find('p.payment-prompt.free-jamtrack') + + find('.jamkazam-account-signup') + + # try to submit, and see slew of errors + find('#payment-info-next').trigger(:click) + + find('#divBillingFirstName.error .error-text', text: 'First Name is required') + find('#divBillingLastName.error .error-text', text: 'Last Name is required') + find('#divBillingAddress1.error .error-text', text: 'Address is required') + find('#divBillingCity.error .error-text', text: 'City is required') + find('#divBillingState.error .error-text', text: 'State is required') + find('#divBillingZip.error .error-text', text: 'Zip Code is required') + find('#divCardNumber.error .error-text', text: 'Card Number is required') + find('#divCardVerify.error .error-text', text: 'Card Verification Value is required') + + # fill out all billing info, but not account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + + # try to submit, and see new account errors + find('#payment-info-next').trigger(:click) + + find('#divJamKazamEmail.error .error-text', text: "can't be blank,is invalid") + find('#divJamKazamPassword.error .error-text', text: "is too short (minimum is 6 characters)") + find('#divJamKazamTos.error .error-text', text: "must be accepted") + + # verify that all filled out fields have had errors removed + find('#divBillingFirstName').has_no_css?('.error') + find('#divBillingLastName').has_no_css?('.error') + find('#divBillingAddress1').has_no_css?('.error') + find('#divBillingCity').has_no_css?('.error') + find('#divBillingState').has_no_css?('.error') + find('#divBillingZip').has_no_css?('.error') + find('#divCardNumber').has_no_css?('.error') + find('#divCardVerify').has_no_css?('.error') + + # fill in user/email/tos + fill_in 'email', with: 'seth@jamkazam.com' + fill_in 'password', with: 'jam123' + find('#divJamKazamTos ins.iCheck-helper').trigger(:click) # accept TOS + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # find empty shopping cart prompt notice + find('p.empty-cart-prompt') + + user.reload + user.reuse_card.should be_true + end + + it "allows billing info submit for existing user" do + + fast_signin(user, '/client#/checkoutPayment') + + find('p.payment-prompt.free-jamtrack') + + expect(page).to_not have_selector('.jamkazam-account-signup') + + # try to submit, and see slew of errors + find('#payment-info-next').trigger(:click) + + find('#divBillingAddress1.error .error-text', text: 'Address is required') + find('#divBillingZip.error .error-text', text: 'Zip Code is required') + find('#divCardNumber.error .error-text', text: 'Card Number is required') + find('#divCardVerify.error .error-text', text: 'Card Verification Value is required') + + # fill out all billing info, but not account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # find empty shopping cart prompt notice + find('p.empty-cart-prompt') + + user.reload + user.reuse_card.should be_true + end + + it "allows user to specify don't save card" do + + fast_signin(user, '/client#/checkoutPayment') + + find('p.payment-prompt.free-jamtrack') + + expect(page).to_not have_selector('.jamkazam-account-signup') + + # fill out all billing info, but not account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + find('.save-card-checkbox ins.iCheck-helper').trigger(:click) # don't accept re-use card default + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # find empty shopping cart prompt notice + find('p.empty-cart-prompt') + + user.reload + user.reuse_card.should be_false + end + + it "payment shows saved card info correctly if user has billing info and reuse_card set to true" do + + user.reuse_card = true + user.recurly_code = user.id + user.has_redeemable_jamtrack = false + user.save! + + fast_signin(user, '/client#/checkoutPayment') + + find('p.payment-prompt.no-free-jamtrack') + + end + end + end