From c71a5ec0df7a766b7de80c046c778c75408d50ee Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 9 May 2018 11:39:56 +1000 Subject: [PATCH] Update subscription form to use new card validations for Stripe payment method --- .../controllers/details_controller.js.coffee | 52 ++++++++++--------- .../services/credit_card_resource.js.coffee | 5 -- .../services/customer_resource.js.coffee | 2 + .../admin/subscriptions/_details.html.haml | 11 ++-- config/locales/en.yml | 8 +-- spec/features/admin/subscriptions_spec.rb | 24 ++++----- 6 files changed, 50 insertions(+), 52 deletions(-) delete mode 100644 app/assets/javascripts/admin/subscriptions/services/credit_card_resource.js.coffee create mode 100644 app/assets/javascripts/admin/subscriptions/services/customer_resource.js.coffee diff --git a/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee b/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee index b3afc1c5d6..b4ace027dc 100644 --- a/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee +++ b/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee @@ -1,38 +1,42 @@ -angular.module("admin.subscriptions").controller "DetailsController", ($scope, $http, CreditCardResource, StatusMessage) -> +angular.module("admin.subscriptions").controller "DetailsController", ($scope, $http, CustomerResource, StatusMessage) -> $scope.cardRequired = false $scope.registerNextCallback 'details', -> $scope.subscription_form.$submitted = true - if $scope.subscription_details_form.$valid - $scope.subscription_form.$setPristine() - StatusMessage.clear() - $scope.setView('address') - else - StatusMessage.display 'failure', t('admin.subscriptions.details.invalid_error') + return unless $scope.validate() + $scope.subscription_form.$setPristine() + StatusMessage.clear() + $scope.setView('address') $scope.$watch "subscription.customer_id", (newValue, oldValue) -> return if !newValue? - $scope.loadAddresses(newValue) unless $scope.subscription.id? - $scope.loadCreditCards(newValue) + $scope.loadCustomer(newValue) unless $scope.subscription.id? $scope.$watch "subscription.payment_method_id", (newValue, oldValue) -> return if !newValue? paymentMethod = ($scope.paymentMethods.filter (pm) -> pm.id == newValue)[0] return unless paymentMethod? - if paymentMethod.type == "Spree::Gateway::StripeConnect" - $scope.cardRequired = true - else - $scope.cardRequired = false - $scope.subscription.credit_card_id = null + $scope.cardRequired = (paymentMethod.type == "Spree::Gateway::StripeConnect") + $scope.loadCustomer() if $scope.cardRequired && !$scope.customer - $scope.loadAddresses = (customer_id) -> - $http.get("/admin/customers/#{customer_id}/addresses") - .success (response) => - delete response.bill_address.id - delete response.ship_address.id - angular.extend($scope.subscription.bill_address, response.bill_address) - angular.extend($scope.subscription.ship_address, response.ship_address) - $scope.shipAddressFromBilling() unless response.ship_address.address1? + $scope.loadCustomer = -> + params = { id: $scope.subscription.customer_id } + $scope.customer = CustomerResource.get params, (response) -> + for address in ['bill_address','ship_address'] + return unless response[address] + delete response[address].id + return if $scope.subscription[address].address1? + angular.extend($scope.subscription[address], response[address]) + $scope.shipAddressFromBilling() unless response.ship_address?.address1? - $scope.loadCreditCards = (customer_id) -> - $scope.creditCards = CreditCardResource.index(customer_id: customer_id) + $scope.validate = -> + return true if $scope.subscription_details_form.$valid && $scope.creditCardOk() + StatusMessage.display 'failure', t('admin.subscriptions.details.invalid_error') + false + + $scope.creditCardOk = -> + return true unless $scope.cardRequired + return false unless $scope.customer + return false unless $scope.customer.allow_charges + return false unless $scope.customer.default_card_present + true diff --git a/app/assets/javascripts/admin/subscriptions/services/credit_card_resource.js.coffee b/app/assets/javascripts/admin/subscriptions/services/credit_card_resource.js.coffee deleted file mode 100644 index 0bb31cf3b0..0000000000 --- a/app/assets/javascripts/admin/subscriptions/services/credit_card_resource.js.coffee +++ /dev/null @@ -1,5 +0,0 @@ -angular.module("admin.subscriptions").factory 'CreditCardResource', ($resource) -> - resource = $resource '/admin/customers/:customer_id/cards.json', {}, - 'index': - method: 'GET' - isArray: true diff --git a/app/assets/javascripts/admin/subscriptions/services/customer_resource.js.coffee b/app/assets/javascripts/admin/subscriptions/services/customer_resource.js.coffee new file mode 100644 index 0000000000..56c999278e --- /dev/null +++ b/app/assets/javascripts/admin/subscriptions/services/customer_resource.js.coffee @@ -0,0 +1,2 @@ +angular.module("admin.subscriptions").factory 'CustomerResource', ($resource) -> + $resource '/admin/customers/:id.json' diff --git a/app/views/admin/subscriptions/_details.html.haml b/app/views/admin/subscriptions/_details.html.haml index 6fa57f41f4..7dedbf26f4 100644 --- a/app/views/admin/subscriptions/_details.html.haml +++ b/app/views/admin/subscriptions/_details.html.haml @@ -14,19 +14,16 @@ .error{ ng: { repeat: 'error in errors.schedule', show: 'subscription_details_form.schedule_id.$pristine'} } {{ error }} .row - .columns.alpha.field{ ng: { class: '{four: cardRequired, seven: !cardRequired}' } } + .seven.columns.alpha.field %label{ for: 'payment_method_id'} = t('admin.payment_method') %span.with-tip.icon-question-sign{ data: { powertip: "#{t('.allowed_payment_method_types_tip')}" } } %input.ofn-select2.fullwidth#payment_method_id{ name: 'payment_method_id', type: 'number', data: 'paymentMethods', required: true, placeholder: t('admin.choose'), ng: { model: 'subscription.payment_method_id' } } .error{ ng: { show: 'subscription_form.$submitted && subscription_details_form.payment_method_id.$error.required' } }= t(:error_required) .error{ ng: { repeat: 'error in errors.payment_method', show: 'subscription_details_form.payment_method_id.$pristine' } } {{ error }} - .three.columns.field{ ng: { show: 'cardRequired' } } - %label{ for: 'credit_card_id'}= t('.credit_card') - %input.ofn-select2.fullwidth#credit_card_id{ name: 'credit_card_id', type: 'number', data: 'creditCards', text: 'formatted', placeholder: t('admin.choose'), ng: { model: 'subscription.credit_card_id', required: "cardRequired" } } - .error{ ng: { show: 'creditCards.$promise && creditCards.$resolved && creditCards.length == 0' } }= t('.no_cards_available') - .error{ ng: { show: 'subscription_form.$submitted && subscription_details_form.credit_card_id.$error.required' } }= t(:error_required) - .error{ ng: { repeat: 'error in errors.credit_card', show: 'subscription_details_form.credit_card_id.$pristine' } } {{ error }} + .error{ ng: { show: 'cardRequired && customer.$promise && customer.$resolved && !customer.allow_charges' } }= t('.charges_not_allowed') + .error{ ng: { show: 'cardRequired && customer.$promise && customer.$resolved && customer.allow_charges && !customer.default_card_present' } }= t('.no_default_card') + .error{ ng: { repeat: 'error in errors.credit_card', show: 'subscription_details_form.payment_method_id.$pristine' } } {{ error }} .two.columns   .seven.columns.omega.field %label{ for: 'shipping_method_id'}= t('admin.shipping_method') diff --git a/config/locales/en.yml b/config/locales/en.yml index 8a34f8b9ff..87a6799143 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -100,8 +100,8 @@ en: shipping_method: not_available_to_shop: "is not available to %{shop}" credit_card: - not_available: "is not available" - blank: "is required" + charges_not_allowed: "charges are not allowed by this customer" + no_default_card: "not available for this customer" devise: confirmations: send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." @@ -1025,7 +1025,9 @@ en: invalid_error: Oops! Please fill in all of the required fields... allowed_payment_method_types_tip: Only Cash and Stripe payment methods may be used at the moment credit_card: Credit Card - no_cards_available: No cards available + charges_not_allowed: Charges are not allowed by this customer + no_default_card: Customer has no cards available to charge + card_ok: Customer has a card available to charge loading_flash: loading: LOADING SUBSCRIPTIONS review: diff --git a/spec/features/admin/subscriptions_spec.rb b/spec/features/admin/subscriptions_spec.rb index ad115cbb5a..556164386a 100644 --- a/spec/features/admin/subscriptions_spec.rb +++ b/spec/features/admin/subscriptions_spec.rb @@ -124,9 +124,7 @@ feature 'Subscriptions' do let(:address) { create(:address) } let!(:customer_user) { create(:user) } let!(:credit_card1) { create(:credit_card, user: customer_user, cc_type: 'visa', last_digits: 1111, month: 10, year: 2030) } - let!(:credit_card2) { create(:credit_card, user: customer_user, cc_type: 'master', last_digits: 9999, month: 2, year: 2044) } - let!(:credit_card3) { create(:credit_card, cc_type: 'visa', last_digits: 5555, month: 6, year: 2066) } - let!(:customer) { create(:customer, enterprise: shop, bill_address: address, user: customer_user) } + let!(:customer) { create(:customer, enterprise: shop, bill_address: address, user: customer_user, allow_charges: true) } let!(:product1) { create(:product, supplier: shop) } let!(:product2) { create(:product, supplier: shop) } let!(:variant1) { create(:variant, product: product1, unit_value: '100', price: 12.00, option_values: []) } @@ -149,21 +147,14 @@ feature 'Subscriptions' do select2_select payment_method.name, from: 'payment_method_id' select2_select shipping_method.name, from: 'shipping_method_id' - # Credit card - card1_option = "Visa x-1111 #{I18n.t(:card_expiry_abbreviation)}:10/2030" - card2_option = "Master x-9999 #{I18n.t(:card_expiry_abbreviation)}:02/2044" - card3_option = "Visa x-5555 #{I18n.t(:card_expiry_abbreviation)}:06/2066" - expect(page).to have_select2 'credit_card_id', with_options: [card1_option, card2_option], without_options: [card3_option] - - # No date or credit card filled out, so error returned + # No date, so error returned click_button('Next') - expect(page).to have_content 'can\'t be blank', count: 2 + expect(page).to have_content 'can\'t be blank', count: 1 expect(page).to have_content 'Oops! Please fill in all of the required fields...' find_field('begins_at').click within(".ui-datepicker-calendar") do find('.ui-datepicker-today').click end - select2_select card2_option, from: 'credit_card_id' click_button('Next') expect(page).to have_content 'BILLING ADDRESS' @@ -263,7 +254,6 @@ feature 'Subscriptions' do expect(subscription.shipping_method).to eq shipping_method expect(subscription.bill_address.firstname).to eq 'Freda' expect(subscription.ship_address.firstname).to eq 'Freda' - expect(subscription.credit_card_id).to eq credit_card2.id # Standing Line Items are created expect(subscription.subscription_line_items.count).to eq 1 @@ -287,6 +277,7 @@ feature 'Subscriptions' do let!(:variant3_oc) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } let!(:variant3_ex) { variant3_oc.exchanges.create(sender: shop, receiver: shop, variants: [variant3]) } let!(:payment_method) { create(:payment_method, distributors: [shop]) } + let!(:stripe_payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop], preferred_enterprise_id: shop.id) } let!(:shipping_method) { create(:shipping_method, distributors: [shop]) } let!(:subscription) { create(:subscription, @@ -306,6 +297,13 @@ feature 'Subscriptions' do click_button 'edit-details' expect(page).to have_selector '#s2id_customer_id.select2-container-disabled' expect(page).to have_selector '#s2id_schedule_id.select2-container-disabled' + + # Can't use a Stripe payment method because customer does not allow it + select2_select stripe_payment_method.name, from: 'payment_method_id' + expect(page).to have_content I18n.t('admin.subscriptions.details.charges_not_allowed') + click_button 'Save Changes' + expect(page).to have_content 'Credit card charges are not allowed by this customer' + select2_select payment_method.name, from: 'payment_method_id' click_button 'Review' # Existing products should be visible