diff --git a/app/assets/javascripts/darkswarm/controllers/authorised_shops_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authorised_shops_controller.js.coffee new file mode 100644 index 0000000000..f100a0a7c3 --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/authorised_shops_controller.js.coffee @@ -0,0 +1,3 @@ +angular.module("Darkswarm").controller "AuthorisedShopsCtrl", ($scope, Customers, Shops) -> + $scope.customers = Customers.index() + $scope.shopsByID = Shops.byID diff --git a/app/assets/javascripts/darkswarm/services/customer.js.coffee b/app/assets/javascripts/darkswarm/services/customer.js.coffee new file mode 100644 index 0000000000..ac27945c54 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/customer.js.coffee @@ -0,0 +1,20 @@ +angular.module("Darkswarm").factory 'Customer', ($resource, RailsFlashLoader) -> + Customer = $resource('/api/customers/:id/:action.json', {}, { + 'index': + method: 'GET' + isArray: true + 'update': + method: 'PUT' + params: + id: '@id' + transformRequest: (data, headersGetter) -> + angular.toJson(customer: data) + }) + + Customer.prototype.update = -> + @$update().then (response) => + RailsFlashLoader.loadFlash({success: t('js.changes_saved')}) + , (response) => + RailsFlashLoader.loadFlash({error: response.data.error}) + + Customer diff --git a/app/assets/javascripts/darkswarm/services/customers.js.coffee b/app/assets/javascripts/darkswarm/services/customers.js.coffee new file mode 100644 index 0000000000..cf5c56563a --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/customers.js.coffee @@ -0,0 +1,13 @@ +angular.module("Darkswarm").factory 'Customers', (Customer) -> + new class Customers + all: [] + byID: {} + + index: (params={}) -> + Customer.index params, (data) => @load(data) + @all + + load: (customers) -> + for customer in customers + @all.push customer + @byID[customer.id] = customer diff --git a/app/assets/javascripts/darkswarm/services/shops.js.coffee b/app/assets/javascripts/darkswarm/services/shops.js.coffee new file mode 100644 index 0000000000..0af4152508 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/shops.js.coffee @@ -0,0 +1,13 @@ +angular.module("Darkswarm").factory 'Shops', ($injector) -> + new class Shops + all: [] + byID: {} + + constructor: -> + if $injector.has('shops') + @load($injector.get('shops')) + + load: (shops) -> + for shop in shops + @all.push shop + @byID[shop.id] = shop diff --git a/app/assets/stylesheets/darkswarm/account.css.scss b/app/assets/stylesheets/darkswarm/account.css.scss index c41cd2ef98..44d93b4cd1 100644 --- a/app/assets/stylesheets/darkswarm/account.css.scss +++ b/app/assets/stylesheets/darkswarm/account.css.scss @@ -28,6 +28,12 @@ margin-bottom: 0px; } } + + .authorised_shops{ + table { + width: 100%; + } + } } .orders { diff --git a/app/views/spree/users/_authorised_shops.html.haml b/app/views/spree/users/_authorised_shops.html.haml new file mode 100644 index 0000000000..692c953f12 --- /dev/null +++ b/app/views/spree/users/_authorised_shops.html.haml @@ -0,0 +1,13 @@ +%table + %tr + %th= t(:shop_title) + %th= t(:allow_charges?) + %tr.customer{ id: "customer{{ customer.id }}", ng: { repeat: "customer in customers" } } + %td.shop{ ng: { bind: 'shopsByID[customer.enterprise_id].name' } } + %td.allow_charges + %input{ type: 'checkbox', + name: 'allow_charges', + ng: { model: 'customer.allow_charges', + change: 'customer.update()', + "true-value" => "true", + "false-value" => "false" } } diff --git a/app/views/spree/users/_cards.html.haml b/app/views/spree/users/_cards.html.haml index 80069c282f..8929aac791 100644 --- a/app/views/spree/users/_cards.html.haml +++ b/app/views/spree/users/_cards.html.haml @@ -10,6 +10,13 @@ %button.button.primary{ ng: { click: 'showForm()', hide: 'CreditCard.visible' } } = t(:add_a_card) - .small-12.medium-6.columns.new_card{ ng: { show: 'CreditCard.visible', class: '{visible: CreditCard.visible}' } } - %h3= t(:add_a_new_card) - = render 'new_card_form' + .small-12.medium-6.columns + .new_card{ ng: { show: 'CreditCard.visible', class: '{visible: CreditCard.visible}' } } + %h3= t(:add_a_new_card) + = render 'new_card_form' + .authorised_shops{ ng: { controller: 'AuthorisedShopsCtrl', hide: 'CreditCard.visible' } } + %h3 + = t('.authorised_shops') + %button.button.secondary.tiny.help-btn.ng-scope{ :popover => t('.authorised_shops_popover'), "popover-placement" => 'right' } + %i.ofn-i_013-help + = render 'authorised_shops' diff --git a/config/locales/en.yml b/config/locales/en.yml index 35754e0bc2..597dd60c36 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2740,5 +2740,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using saved_cards: default?: Default? delete?: Delete? + cards: + authorised_shops: Authorised Shops + authorised_shops_popover: This is a list of shops which are permitted to charge your default credit card for OFN services (eg. subscriptions). Your card details will be kept secure and will not be shared with shop owners. localized_number: invalid_format: has an invalid format. Please enter a number. diff --git a/spec/features/consumer/account/cards_spec.rb b/spec/features/consumer/account/cards_spec.rb index 2f78511135..25c6410ade 100644 --- a/spec/features/consumer/account/cards_spec.rb +++ b/spec/features/consumer/account/cards_spec.rb @@ -4,6 +4,7 @@ feature "Credit Cards", js: true do include AuthenticationWorkflow describe "as a logged in user" do let(:user) { create(:user) } + let!(:customer) { create(:customer, user: user) } let!(:default_card) { create(:credit_card, user_id: user.id, gateway_customer_profile_id: 'cus_AZNMJ', is_default: true) } let!(:non_default_card) { create(:credit_card, user_id: user.id, gateway_customer_profile_id: 'cus_FDTG') } @@ -49,10 +50,10 @@ feature "Credit Cards", js: true do expect(page).to have_content I18n.t('js.default_card_updated') + expect(default_card.reload.is_default).to be false within(".card#card#{default_card.id}") do expect(find_field('default_card')).to_not be_checked end - expect(default_card.reload.is_default).to be false expect(non_default_card.reload.is_default).to be true # Shows the interface for adding a card @@ -67,6 +68,14 @@ feature "Credit Cards", js: true do expect(page).to have_content I18n.t(:card_has_been_removed, number: "x-#{default_card.last_digits}") expect(page).to_not have_selector ".card#card#{default_card.id}" + + # Allows authorisation of card use by shops + within "tr#customer#{customer.id}" do + expect(find_field('allow_charges')).to_not be_checked + find_field('allow_charges').click + end + expect(page).to have_content I18n.t('js.changes_saved') + expect(customer.reload.allow_charges).to be true end end end diff --git a/spec/javascripts/unit/darkswarm/services/customer_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/customer_spec.js.coffee new file mode 100644 index 0000000000..33a1d72cdc --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/customer_spec.js.coffee @@ -0,0 +1,39 @@ +describe 'Customer', -> + describe "update", -> + $httpBackend = null + customer = null + response = { id: 3, code: '1234' } + RailsFlashLoaderMock = jasmine.createSpyObj('RailsFlashLoader', ['loadFlash']) + + beforeEach -> + module 'Darkswarm' + module ($provide) -> + $provide.value 'RailsFlashLoader', RailsFlashLoaderMock + null + + inject (_$httpBackend_, Customer)-> + customer = new Customer(id: 3) + $httpBackend = _$httpBackend_ + + it "nests the params inside 'customer'", -> + $httpBackend + .expectPUT('/api/customers/3.json', { customer: { id: 3 } }) + .respond 200, response + customer.update() + $httpBackend.flush() + + describe "when the request succeeds", -> + it "shows a success flash", -> + $httpBackend.expectPUT('/api/customers/3.json').respond 200, response + customer.update() + $httpBackend.flush() + expect(RailsFlashLoaderMock.loadFlash) + .toHaveBeenCalledWith({success: jasmine.any(String)}) + + describe "when the request fails", -> + it "shows a error flash", -> + $httpBackend.expectPUT('/api/customers/3.json').respond 400, { error: 'Some error' } + customer.update() + $httpBackend.flush() + expect(RailsFlashLoaderMock.loadFlash) + .toHaveBeenCalledWith({error: 'Some error'}) diff --git a/spec/javascripts/unit/darkswarm/services/customers_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/customers_spec.js.coffee new file mode 100644 index 0000000000..9680b89341 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/customers_spec.js.coffee @@ -0,0 +1,24 @@ +describe 'Customers', -> + describe "index", -> + $httpBackend = null + Customers = null + customerList = ['somecustomer'] + + beforeEach -> + module 'Darkswarm' + module ($provide) -> + $provide.value 'RailsFlashLoader', null + null + + inject (_$httpBackend_, _Customers_)-> + Customers = _Customers_ + $httpBackend = _$httpBackend_ + + it "asks for customers and returns @all, promises to populate via @load", -> + spyOn(Customers,'load').and.callThrough() + $httpBackend.expectGET('/api/customers.json').respond 200, customerList + result = Customers.index() + $httpBackend.flush() + expect(Customers.load).toHaveBeenCalled() + expect(result).toEqual customerList + expect(Customers.all).toEqual customerList diff --git a/spec/javascripts/unit/darkswarm/services/shops_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/shops_spec.js.coffee new file mode 100644 index 0000000000..ddc9e14af5 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/shops_spec.js.coffee @@ -0,0 +1,27 @@ +describe 'Shops', -> + describe "initialisation", -> + Shops = null + shops = ['some shop'] + + beforeEach -> + module 'Darkswarm' + + describe "when the injector does not have a value for 'shops'", -> + beforeEach -> + inject (_Shops_) -> + Shops = _Shops_ + + it "does nothing, leaves @all empty", -> + expect(Shops.all).toEqual [] + + describe "when the injector has a value for 'shops'", -> + beforeEach -> + module ($provide) -> + $provide.value 'shops', shops + null + + inject (_Shops_) -> + Shops = _Shops_ + + it "loads injected shops array into @all", -> + expect(Shops.all).toEqual shops