diff --git a/app/views/spree/admin/payments/source_forms/_gateway.html.erb b/app/views/spree/admin/payments/source_forms/_gateway.html.erb
new file mode 100644
index 0000000000..8cf4b914cc
--- /dev/null
+++ b/app/views/spree/admin/payments/source_forms/_gateway.html.erb
@@ -0,0 +1,58 @@
+
diff --git a/app/views/spree/admin/payments/source_forms/_paypal.html.haml b/app/views/spree/admin/payments/source_forms/_paypal.html.haml
new file mode 100644
index 0000000000..e26db66846
--- /dev/null
+++ b/app/views/spree/admin/payments/source_forms/_paypal.html.haml
@@ -0,0 +1,8 @@
+-# We can remove this file as soon as we have a version of better_spree_paypal_express that includes:
+-# https://github.com/spree-contrib/better_spree_paypal_express/commit/4360a1fb82d30d7601bc6a98e7b74819f0b377f0
+
+-# The selectors in app/assets/javascripts/spree/backend/paypal_express.js don't work with the version
+-# of the views we are using, so the warning below wasn't displaying without this override.
+
+#paypal-warning
+ %strong= t('no_payment_via_admin_backend', :scope => 'paypal')
diff --git a/app/views/spree/admin/payments/source_forms/_stripe.html.haml b/app/views/spree/admin/payments/source_forms/_stripe.html.haml
new file mode 100644
index 0000000000..24bde74a31
--- /dev/null
+++ b/app/views/spree/admin/payments/source_forms/_stripe.html.haml
@@ -0,0 +1,4 @@
+-# = render "spree/admin/payments/source_forms/gateway", payment_method: payment_method
+
+%strong
+ = t('.no_payment_via_admin_backend')
diff --git a/app/views/spree/admin/payments/source_views/_stripe.html.haml b/app/views/spree/admin/payments/source_views/_stripe.html.haml
new file mode 100644
index 0000000000..64af07bd3b
--- /dev/null
+++ b/app/views/spree/admin/payments/source_views/_stripe.html.haml
@@ -0,0 +1 @@
+= render "spree/admin/payments/source_views/gateway", payment: payment
diff --git a/app/views/spree/admin/products/_display_as.html.haml b/app/views/spree/admin/products/_display_as.html.haml
index 33beec2b86..04af12bdce 100644
--- a/app/views/spree/admin/products/_display_as.html.haml
+++ b/app/views/spree/admin/products/_display_as.html.haml
@@ -1,4 +1,4 @@
.three.columns.omega{ "ng-if" => "product.variant_unit_with_scale != 'items'" }
= f.field_container :display_as do
- = f.label :product_display_as, t(:display_as)
- %input#product_display_as.fullwidth{name: "product[display_as]", placeholder: "{{ placeholder_text }}", type: "text"}
\ No newline at end of file
+ = f.label :product_display_as, t('.display_as')
+ %input#product_display_as.fullwidth{name: "product[display_as]", placeholder: "{{ placeholder_text }}", type: "text"}
diff --git a/app/views/spree/admin/products/_group_buy_form.html.haml b/app/views/spree/admin/products/_group_buy_form.html.haml
index 8f1de2a884..6ccf23a024 100644
--- a/app/views/spree/admin/products/_group_buy_form.html.haml
+++ b/app/views/spree/admin/products/_group_buy_form.html.haml
@@ -1,14 +1,14 @@
= f.field_container :group_buy do
- = f.label :group_buy, 'Group buy?'
+ = f.label :group_buy, t('.group_buy')
%br
.alpha.two.columns
- = f.radio_button :group_buy, '1', :checked => f.object.group_buy
- = f.label :group_buy_1, 'Yes'
+ = f.label :group_buy_1, t(:yes)
+ = f.radio_button :group_buy, '1', checked: f.object.group_buy
.alpha.two.columns
- = f.radio_button :group_buy, '0', :checked => !f.object.group_buy
- = f.label :group_buy_0, 'No'
-%br.clear
-= f.field_container :group_buy_unit_size do
- = f.label :group_buy_unit_size, "Bulk unit size"
- %br
- = f.text_field :group_buy_unit_size
+ = f.label :group_buy_0, t(:no)
+ = f.radio_button :group_buy, '0', checked: !f.object.group_buy
+ %br.clear
+ = f.field_container :group_buy_unit_size do
+ = f.label :group_buy_unit_size, t('.bulk_unit_size')
+ %br
+ = f.text_field :group_buy_unit_size
diff --git a/app/views/spree/admin/products/_primary_taxon_form.html.haml b/app/views/spree/admin/products/_primary_taxon_form.html.haml
index 8ffe5887a7..51da47f0f4 100644
--- a/app/views/spree/admin/products/_primary_taxon_form.html.haml
+++ b/app/views/spree/admin/products/_primary_taxon_form.html.haml
@@ -1,5 +1,5 @@
= f.field_container :primary_taxon_id do
- = f.label :primary_taxon_id, t(:product_category)
+ = f.label :primary_taxon_id, t('.product_category')
%span.required *
%br
= f.collection_select(:primary_taxon_id, Spree::Taxon.all, :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"})
diff --git a/app/views/spree/admin/products/_seo_form.html.haml b/app/views/spree/admin/products/_seo_form.html.haml
new file mode 100644
index 0000000000..bbc5bdb4f0
--- /dev/null
+++ b/app/views/spree/admin/products/_seo_form.html.haml
@@ -0,0 +1,15 @@
+.row{"data-hook" => "admin_product_meta_form"}
+ .alpha.eleven.columns
+ = f.field_container :meta_description do
+ = f.label :meta_keywords, t(:meta_keywords)
+ %br/
+ = f.text_field :meta_keywords, :class => 'fullwidth', :rows => 6
+ = f.field_container :meta_description do
+ = f.label :meta_description, t(:meta_description)
+ %br/
+ = f.text_field :meta_description, :class => 'fullwidth', :rows => 6
+ .alpha.eleven.columns
+ = f.field_container :notes do
+ = f.label :notes, t(:notes)
+ = f.text_area :notes, { :class => 'fullwidth', rows: 5 }
+ = f.error_message_on :notes
\ No newline at end of file
diff --git a/app/views/spree/admin/products/_tax_category_form.html.haml b/app/views/spree/admin/products/_tax_category_form.html.haml
index 3d529e6562..250ba8c832 100644
--- a/app/views/spree/admin/products/_tax_category_form.html.haml
+++ b/app/views/spree/admin/products/_tax_category_form.html.haml
@@ -2,5 +2,5 @@
= f.label :tax_category_id, t(:tax_category)
%span.required *
%br
- = f.collection_select(:tax_category_id, Spree::TaxCategory.all, :id, :name, {:include_blank => Spree::Config.products_require_tax_category ? false : 'None'}, {:class => "select2 fullwidth"})
+ = f.collection_select(:tax_category_id, Spree::TaxCategory.all, :id, :name, {:include_blank => Spree::Config.products_require_tax_category ? false : t(:none)}, {:class => "select2 fullwidth"})
= f.error_message_on :tax_category_id
diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml
index 9a8f7ad86a..5bd4418ac2 100644
--- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml
@@ -19,7 +19,7 @@
%thead
%tr{ ng: { controller: "ColumnsCtrl" } }
%th.left-actions
- %a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red' }
+ %a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red; cursor: pointer' }
= t(:expand_all)
%th.producer{ 'ng-show' => 'columns.producer.visible' }=t('admin.producer')
%th.sku{ 'ng-show' => 'columns.sku.visible' }=t('admin.sku')
diff --git a/app/views/spree/admin/products/group_buy_options.html.haml b/app/views/spree/admin/products/group_buy_options.html.haml
new file mode 100644
index 0000000000..9261ddbfc6
--- /dev/null
+++ b/app/views/spree/admin/products/group_buy_options.html.haml
@@ -0,0 +1,8 @@
+= render :partial => 'spree/admin/shared/product_sub_menu'
+= render :partial => 'spree/admin/shared/product_tabs', :locals => { :current => 'Group Buy Options' }
+= render :partial => 'spree/shared/error_messages', :locals => { :target => @product }
+
+= form_for [:admin, @product], :method => :put, :html => { :multipart => true } do |f|
+ %fieldset.no-border-top
+ = render :partial => 'group_buy_form', :locals => { :f => f }
+ = render :partial => 'spree/admin/shared/edit_resource_links'
diff --git a/app/views/spree/admin/products/new.js.erb b/app/views/spree/admin/products/new.js.erb
new file mode 100644
index 0000000000..0a3711620d
--- /dev/null
+++ b/app/views/spree/admin/products/new.js.erb
@@ -0,0 +1,12 @@
+<%# This chunk is just a copy of Spree's core/app/views/spree/admin/products/new.js.erb %>
+$("#new_product").html('<%= escape_javascript(render :template => "spree/admin/products/new", :formats => [:html], :handlers => [:erb]) %>');
+handle_date_picker_fields();
+<% unless Rails.env.test? %>
+ $('.select2').select2();
+<% end %>
+$("#table-filter").hide();
+$("#admin_new_product").parent().hide();
+
+<%# We need to replace the page's title as well. We're navigating to a new page
+ although through ajax %>
+$('#content-header .page-title').html('<%= t('.title') %>');
diff --git a/app/views/spree/admin/products/seo.html.haml b/app/views/spree/admin/products/seo.html.haml
new file mode 100644
index 0000000000..ddcd00402f
--- /dev/null
+++ b/app/views/spree/admin/products/seo.html.haml
@@ -0,0 +1,8 @@
+= render :partial => 'spree/admin/shared/product_sub_menu'
+= render :partial => 'spree/admin/shared/product_tabs', :locals => { :current => 'SEO' }
+= render :partial => 'spree/shared/error_messages', :locals => { :target => @product }
+
+= form_for [:admin, @product], :method => :put, :html => { :multipart => true } do |f|
+ %fieldset.no-border-top
+ = render :partial => 'seo_form', :locals => { :f => f }
+ = render :partial => 'spree/admin/shared/edit_resource_links'
diff --git a/app/views/spree/admin/shared/_routes.html.erb b/app/views/spree/admin/shared/_routes.html.erb
new file mode 100644
index 0000000000..27957a3466
--- /dev/null
+++ b/app/views/spree/admin/shared/_routes.html.erb
@@ -0,0 +1,10 @@
+
diff --git a/app/views/spree/checkout/payment/_gateway.html.haml b/app/views/spree/checkout/payment/_gateway.html.haml
index 33dcbda8fc..0bcf038cb9 100644
--- a/app/views/spree/checkout/payment/_gateway.html.haml
+++ b/app/views/spree/checkout/payment/_gateway.html.haml
@@ -2,17 +2,18 @@
.small-6.columns
%label
= t :first_name
- %input{type: :text, disabled: true, "ng-value" => "order.bill_address.firstname"}
+ -# Changing name not permitted by default (in checkout) - can be enabled by setting an allow_name_change variable in $scope
+ %input{type: :text, "ng-disabled" => "!allow_name_change", "ng-value" => "order.bill_address.firstname"}
.small-6.columns
%label
= t :last_name
- %input{type: :text, disabled: true, "ng-value" => "order.bill_address.lastname"}
+ %input{type: :text, "ng-disabled" => "!allow_name_change", "ng-value" => "order.bill_address.lastname"}
.small-6.columns
- = validated_input t(:card_number), "secrets.card_number", required: true, maxlength: 19, autocomplete: "off"
+ = validated_input t(:card_number), "secrets.card_number", maxlength: 19, autocomplete: "off"
.small-6.columns
- = validated_input t(:card_securitycode), "secrets.card_verification_value", required: true
+ = validated_input t(:card_securitycode), "secrets.card_verification_value"
.row
.small-12.columns
@@ -21,6 +22,6 @@
.row
.small-6.columns
- %select{"ng-model" => "secrets.card_month", "ng-options" => "currMonth.value as currMonth.key for currMonth in months", name: "secrets.card_month", required: true}
+ %select{"ng-model" => "secrets.card_month", "ng-options" => "currMonth.value as currMonth.key for currMonth in months", name: "secrets.card_month"}
.small-6.columns
- %select{"ng-model" => "secrets.card_year", "ng-options" => "year for year in years", name: "secrets.card_year", required: true}
+ %select{"ng-model" => "secrets.card_year", "ng-options" => "year for year in years", name: "secrets.card_year"}
diff --git a/app/views/spree/checkout/payment/_stripe.html.haml b/app/views/spree/checkout/payment/_stripe.html.haml
new file mode 100644
index 0000000000..3c1bc0c35e
--- /dev/null
+++ b/app/views/spree/checkout/payment/_stripe.html.haml
@@ -0,0 +1,17 @@
+.row{ "ng-show" => "savedCreditCards != null && savedCreditCards.length > 0" }
+ .small-12.columns
+ %h6= t('.used_saved_card')
+ %select{ name: "selected_card", required: false, ng: { model: "secrets.selected_card", options: "card.id as card.formatted for card in savedCreditCards" } }
+ %option{ value: "" }= "{{ secrets.selected_card ? '#{t('.enter_new_card')}' : '#{t('.choose_one')}' }}"
+
+ %h6{ ng: { if: '!secrets.selected_card' } }
+ = t('.or_enter_new_card')
+
+%div{ ng: { if: '!secrets.selected_card' } }
+ %stripe-elements
+
+ - if spree_current_user
+ .row
+ .small-12.columns.text-right
+ = check_box_tag 'secrets.save_requested_by_customer', '1', false, 'ng-model' => 'secrets.save_requested_by_customer'
+ = label_tag 'secrets.save_requested_by_customer', t('.remember_this_card')
diff --git a/app/views/spree/users/_cards.html.haml b/app/views/spree/users/_cards.html.haml
new file mode 100644
index 0000000000..80069c282f
--- /dev/null
+++ b/app/views/spree/users/_cards.html.haml
@@ -0,0 +1,15 @@
+%script{ type: "text/ng-template", id: "account/cards.html" }
+ .credit_cards{"ng-controller" => "CreditCardsCtrl"}
+ .row
+ .small-12.medium-6.columns
+ %h3= t(:saved_cards)
+ .saved_cards{ ng: { show: 'savedCreditCards.length > 0' } }
+ = render 'saved_cards'
+ .no_cards{ ng: { hide: 'savedCreditCards.length > 0' } }
+ = t(:you_have_no_saved_cards)
+ %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'
diff --git a/app/views/spree/users/_fat.html.haml b/app/views/spree/users/_fat.html.haml
index 4ce051e5b8..1a4c74ed3e 100644
--- a/app/views/spree/users/_fat.html.haml
+++ b/app/views/spree/users/_fat.html.haml
@@ -9,16 +9,7 @@
%th.order5.text-right= t :value
%th.order6.text-right.show-for-large-up= t :outstanding_balance
%th.order7.text-right= t :running_balance
- %tbody.transaction-group{"ng-repeat" => "order in distributor.distributed_orders", "ng-class-odd"=>"'odd'", "ng-class-even"=>"'even'"}
- %tr.order-row
- %td.order1
- %a{"ng-href" => "{{::order.path}}", "ng-bind" => "::('order' | t )+ ' ' + order.number"}
- %td.order2{"ng-bind" => "::order.completed_at"}
- %td.order3.show-for-large-up{"ng-bind" => "::'spree.payment_states.' + order.payment_state | t | capitalize"}
- %td.order4.show-for-large-up{"ng-bind" => "::'spree.shipment_states.' + order.shipment_state | t | capitalize"}
- %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","ng-bind" => "::order.total | localizeCurrency"}
- %td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "ng-bind" => "::order.outstanding_balance | localizeCurrency"}
- %td.order7.text-right{"ng-class" => "{'credit' : order.running_balance < 0, 'debit' : order.running_balance > 0, 'paid' : order.running_balance == 0}", "ng-bind" => "::order.running_balance | localizeCurrency"}
+ %tbody.transaction-group{"ng-repeat" => "order in shop.orders", "ng-class-odd"=>"'odd'", "ng-class-even"=>"'even'"}
%tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"}
%td.order1{"ng-bind" => "::payment.payment_method"}
%td.order2{"ng-bind" => "::payment.updated_at"}
@@ -29,3 +20,12 @@
%td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","ng-bind" => "::payment.amount | localizeCurrency"}
%td.order6.show-for-large-up
%td.order7
+ %tr.order-row
+ %td.order1
+ %a{"ng-href" => "{{::order.path}}", "ng-bind" => "::('order' | t )+ ' ' + order.number"}
+ %td.order2{"ng-bind" => "::order.completed_at"}
+ %td.order3.show-for-large-up{"ng-bind" => "::'spree.payment_states.' + order.payment_state | t | capitalize"}
+ %td.order4.show-for-large-up{"ng-bind" => "::'spree.shipment_states.' + order.shipment_state | t | capitalize"}
+ %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","ng-bind" => "::order.total | localizeCurrency"}
+ %td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "ng-bind" => "::order.outstanding_balance | localizeCurrency"}
+ %td.order7.text-right{"ng-class" => "{'credit' : order.runningBalance < 0, 'debit' : order.runningBalance > 0, 'paid' : order.runningBalance == 0}", "ng-bind" => "::order.runningBalance | localizeCurrency"}
diff --git a/app/views/spree/users/_form.html.haml b/app/views/spree/users/_form.html.haml
new file mode 100644
index 0000000000..61f6962abb
--- /dev/null
+++ b/app/views/spree/users/_form.html.haml
@@ -0,0 +1,6 @@
+= render :partial => 'spree/shared/error_messages', :locals => { :target => @user }
+%h3= t('.account_settings')
+= form_for Spree::User.new, :as => @user, :url => spree.user_path(@user), :method => :put do |f|
+ = render :partial => 'spree/shared/user_form', :locals => { :f => f }
+ %p
+ = f.submit t(:update), :class => 'button primary'
diff --git a/app/views/spree/users/_new_card_form.html.haml b/app/views/spree/users/_new_card_form.html.haml
new file mode 100644
index 0000000000..3d17afbf6a
--- /dev/null
+++ b/app/views/spree/users/_new_card_form.html.haml
@@ -0,0 +1,35 @@
+%form{ novalidate: true, name: 'new_card_form', "ng-submit" => "storeCard()" }
+ .row
+ .small-6.columns
+ %label
+ = t(:first_name)
+ -# Changing name not permitted by default (in checkout) - can be enabled by setting an allow_name_change variable in $scope
+ %input#first_name{ type: :text,
+ name: 'first_name',
+ required: true,
+ ng: { model: "secrets.first_name",
+ disabled: "!allow_name_change",
+ value: "order.bill_address.firstname"} }
+ %small.error{ ng: { show: 'new_card_form.$submitted && new_card_form.first_name.$error.required' } }= t(:error_required)
+
+ .small-6.columns
+ %label
+ = t(:last_name)
+ %input#last_name{type: :text,
+ name: "last_name",
+ required: true,
+ ng: { model: "secrets.last_name",
+ disabled: "!allow_name_change",
+ value: "order.bill_address.lastname" } }
+ %small.error{ ng: { show: 'new_card_form.$submitted && new_card_form.last_name.$error.required' } }= t(:error_required)
+
+ .row
+ .small-12.columns
+ %label
+ = t(:card_details)
+ %stripe-elements
+ .row
+ .small-4.columns
+ %p
+ %button.button.primary{type: :submit}
+ = t(:add_card)
diff --git a/app/views/spree/users/_open_orders.html.haml b/app/views/spree/users/_open_orders.html.haml
index 5f78738e34..ed5e43b9b5 100644
--- a/app/views/spree/users/_open_orders.html.haml
+++ b/app/views/spree/users/_open_orders.html.haml
@@ -9,12 +9,12 @@
%th.order5.text-right= t('.total')
%th.order6.text-right.show-for-large-up= t('.edit')
%th.order7.text-right= t('.cancel')
- %tbody.transaction-group{"ng-repeat" => "order in Orders.changeable_orders", "ng-class-odd"=>"'odd'", "ng-class-even"=>"'even'"}
+ %tbody.transaction-group{"ng-repeat" => "order in Orders.changeable", "ng-class-odd"=>"'odd'", "ng-class-even"=>"'even'"}
%tr.order-row
%td.order1
%a{"ng-href" => "{{::order.path}}", "ng-bind" => "::order.number"}
- %td.order2{"ng-bind" => "::order.shop_name"}
- %td.order3.show-for-large-up{"ng-bind" => "order.changes_allowed_until"}
+ %td.order2{"ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
+ %td.order3.show-for-large-up{"ng-bind" => "::order.changes_allowed_until"}
%td.order4.show-for-large-up{"ng-bind" => "::order.item_count"}
%td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","ng-bind" => "::order.total | localizeCurrency"}
%td.order6.text-right.show-for-large-up.brick
diff --git a/app/views/spree/users/_orders.html.haml b/app/views/spree/users/_orders.html.haml
new file mode 100644
index 0000000000..a8c34b22f1
--- /dev/null
+++ b/app/views/spree/users/_orders.html.haml
@@ -0,0 +1,10 @@
+%script{ type: "text/ng-template", id: "account/orders.html" }
+ .orders{"ng-controller" => "OrdersCtrl", "ng-cloak" => true}
+ .my-open-orders{ ng: { show: 'Orders.changeable.length > 0' } }
+ %h3= t('.open_orders')
+ = render 'open_orders'
+
+ .past-orders{ ng: { show: 'pastOrders.length > 0' } }
+ %h3= t('.past_orders')
+ = render 'past_orders'
+ .message{"ng-if" => "Orders.all.length == 0", "ng-bind" => "::'you_have_no_orders_yet' | t"}
diff --git a/app/views/spree/users/_past_orders.html.haml b/app/views/spree/users/_past_orders.html.haml
new file mode 100644
index 0000000000..2abf5a273e
--- /dev/null
+++ b/app/views/spree/users/_past_orders.html.haml
@@ -0,0 +1,22 @@
+.row
+ .small-12.columns
+ %table
+ %tr
+ %th.order1= t('.order')
+ %th.order2= t('.shop')
+ %th.order3.show-for-large-up= t('.completed_at')
+ %th.order4.show-for-large-up= t('.items')
+ %th.order5.text-right= t('.total')
+ %th.order6.text-right.show-for-large-up= t('.paid?')
+ %th.order7.text-right= t('.view')
+ %tbody.transaction-group{"ng-repeat" => "order in Orders.all | filter:{changes_allowed:false} as pastOrders", "ng-class-odd"=>"'odd'", "ng-class-even"=>"'even'"}
+ %tr.order-row
+ %td.order1
+ %a{"ng-href" => "{{::order.path}}", "ng-bind" => "::order.number"}
+ %td.order2{"ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
+ %td.order3.show-for-large-up{"ng-bind" => "::order.completed_at"}
+ %td.order4.show-for-large-up{"ng-bind" => "::order.item_count"}
+ %td.order5.text-right{"ng-class" => "{'debit': order.payment_state != 'paid', 'credit': order.payment_state == 'paid'}","ng-bind" => "::order.total | localizeCurrency"}
+ %td.order6.text-right.show-for-large-up{"ng-class" => "{'debit': order.payment_state != 'paid', 'credit': order.payment_state == 'paid'}", "ng-bind" => "::(order.payment_state == 'paid' ? 'say_yes' : 'say_no') | t"}
+ %td.order7.text-right
+ %a{"ng-href" => "{{::order.path}}" }= t('.view')
diff --git a/app/views/spree/users/_saved_cards.html.haml b/app/views/spree/users/_saved_cards.html.haml
new file mode 100644
index 0000000000..d8c710607b
--- /dev/null
+++ b/app/views/spree/users/_saved_cards.html.haml
@@ -0,0 +1,13 @@
+%table
+ %tr
+ %th= t(:card_type)
+ %th= t(:card_number)
+ %th= t(:card_expiry_date)
+ %th= t(:delete?)
+ %tr.card{ id: "card{{ card.id }}", ng: { repeat: "card in savedCreditCards" } }
+ %td.brand{ ng: { bind: '::card.brand' } }
+ %td.number{ ng: { bind: '::card.number' } }
+ %td.expiry{ ng: { bind: '::card.expiry' } }
+ %td.actions
+ %a{"rel" => "nofollow", "data-method" => "delete", "ng-href" => "{{card.delete_link}}" }
+ = t(:delete)
diff --git a/app/views/spree/users/_settings.html.haml b/app/views/spree/users/_settings.html.haml
new file mode 100644
index 0000000000..6107172190
--- /dev/null
+++ b/app/views/spree/users/_settings.html.haml
@@ -0,0 +1,2 @@
+%script{ type: "text/ng-template", id: "account/settings.html" }
+ = render 'form'
diff --git a/app/views/spree/users/_skinny.html.haml b/app/views/spree/users/_skinny.html.haml
index 70ba39d335..43bb039c46 100644
--- a/app/views/spree/users/_skinny.html.haml
+++ b/app/views/spree/users/_skinny.html.haml
@@ -1,10 +1,10 @@
.row.active_table_row.skinny-head.margin-top{"ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open()}"}
.columns.small-2
- %img.margin-top.account-logo{"logo-fallback" => true, "ng-src" => "{{distributor.logo}}"}
+ %img.margin-top.account-logo{"logo-fallback" => true, "ng-src" => "{{shop.logo}}"}
.columns.small-5
- %h3.margin-top{"ng-bind" => "::distributor.name"}
+ %h3.margin-top{"ng-bind" => "::shop.name"}
.columns.small-4.text-right
- %h3.margin-top.distributor-balance{"ng-bind" => "::distributor.balance | formatBalance", "ng-class" => "{'credit' : distributor.balance < 0, 'debit' : distributor.balance > 0, 'paid' : distributor.balance == 0}" }
+ %h3.margin-top.distributor-balance{"ng-bind" => "::shop.balance | formatBalance", "ng-class" => "{'credit' : shop.balance < 0, 'debit' : shop.balance > 0, 'paid' : shop.balance == 0}" }
.columns.small-1.text-right
%h3.margin-top
%i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
diff --git a/app/views/spree/users/_transactions.html.haml b/app/views/spree/users/_transactions.html.haml
new file mode 100644
index 0000000000..c46fbd38d7
--- /dev/null
+++ b/app/views/spree/users/_transactions.html.haml
@@ -0,0 +1,11 @@
+%script{ type: "text/ng-template", id: "account/transactions.html" }
+ .active_table.orders{"ng-controller" => "OrdersCtrl", "ng-cloak" => true}
+ %h3.my-orders= t(:transaction_history)
+ %distributor.active_table_node.row.animate-repeat{"ng-if" => "Orders.shops.length > 0", "ng-repeat" => "shop in Orders.shops",
+ "ng-controller" => "ShopNodeCtrl",
+ "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !shop.active}",
+ id: "{{shop.hash}}"}
+ .small-12.columns
+ = render partial: "spree/users/skinny"
+ = render partial: "spree/users/fat"
+ .message{"ng-if" => "Orders.shops.length == 0", "ng-bind" => "::'you_have_no_orders_yet' | t"}
diff --git a/app/views/spree/users/edit.html.haml b/app/views/spree/users/edit.html.haml
index 9815b5f34d..29e600582c 100644
--- a/app/views/spree/users/edit.html.haml
+++ b/app/views/spree/users/edit.html.haml
@@ -1,8 +1,3 @@
.darkswarm
.row
- = render :partial => 'spree/shared/error_messages', :locals => { :target => @user }
- %h1= t(:editing_user)
- = form_for Spree::User.new, :as => @user, :url => spree.user_path(@user), :method => :put do |f|
- = render :partial => 'spree/shared/user_form', :locals => { :f => f }
- %p
- = f.submit t(:update), :class => 'button primary'
+ = render 'form'
diff --git a/app/views/spree/users/show.html.haml b/app/views/spree/users/show.html.haml
index 8272cc9eeb..23de4fd744 100644
--- a/app/views/spree/users/show.html.haml
+++ b/app/views/spree/users/show.html.haml
@@ -1,5 +1,11 @@
.darkswarm
- = inject_orders_by_distributor
+ = inject_orders
+ = inject_shops
+ = inject_saved_credit_cards
+
+ - if Stripe.publishable_key
+ :javascript
+ angular.module('Darkswarm').value("stripeObject", Stripe("#{Stripe.publishable_key}"))
.row.pad-top
.small-12.columns.pad-top
@@ -7,22 +13,22 @@
= accurate_title
%span.account-summary{"data-hook" => "account_summary"}
= @user.email
- (#{link_to t(:edit), spree.edit_account_path})
- .orders{"ng-controller" => "OrdersCtrl", "ng-cloak" => true}
- .my-open-orders{ ng: { show: 'Orders.changeable_orders.length > 0' } }
- %h3= t(:open_orders)
- = render 'open_orders'
+ = render 'orders'
+ = render 'cards'
+ = render 'transactions'
+ = render 'settings'
- .active_table
- %h3.my-orders= t(:transaction_history)
- %distributor.active_table_node.row.animate-repeat{"ng-if" => "Orders.orders_by_distributor.length > 0", "ng-repeat" => "(key, distributor) in Orders.orders_by_distributor",
- "ng-controller" => "DistributorNodeCtrl",
- "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !distributor.active}",
- id: "{{distributor.hash}}"}
- .small-12.columns
- = render partial: "spree/users/skinny"
- = render partial: "spree/users/fat"
- .message{"ng-if" => "Orders.orders_by_distributor.length == 0", "ng-bind" => "::'you_have_no_orders_yet' | t"}
+ .row.tabset-ctrl#account-tabs{ style: 'margin-bottom: 100px', navigate: 'true', selected: 'orders', prefix: 'account' }
+ .small.12.medium-3.columns.tab{ name: "orders" }
+ %a{ href: 'javascript:void(0)' }=t('.tabs.orders')
+ - if Spree::Config.stripe_connect_enabled && Stripe.publishable_key
+ .small.12.medium-3.columns.tab{ name: "cards" }
+ %a{ href: 'javascript:void(0)' }=t('.tabs.cards')
+ .small.12.medium-3.columns.tab{ name: "transactions" }
+ %a{ href: 'javascript:void(0)' }=t('.tabs.transactions')
+ .small.12.medium-3.columns.tab{ name: "settings" }
+ %a{ href: 'javascript:void(0)' }=t('.tabs.settings')
+ .small-12.columns.tab-view
= render partial: "shared/footer"
diff --git a/config/application.rb b/config/application.rb
index cc402859da..e58c7e8115 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -24,27 +24,54 @@ module Openfoodnetwork
end
end
- # Register Spree calculators
- initializer "spree.register.calculators" do |app|
- app.config.spree.calculators.shipping_methods << OpenFoodNetwork::Calculator::Weight
+ # Settings dependent on locale
+ #
+ # We need to set this config before the promo environment gets loaded and
+ # after the spree environment gets loaded...
+ # This is because Spree uses `Spree::Config` while evaluating classes :scream:
+ #
+ # https://github.com/spree/spree/blob/2-0-stable/core/app/models/spree/calculator/per_item.rb#L6
+ #
+ # TODO: move back to spree initializer once we upgrade to a more recent version
+ # of Spree
+ initializer 'ofn.spree_locale_settings', before: 'spree.promo.environment' do |app|
+ Spree::Config['checkout_zone'] = ENV['CHECKOUT_ZONE']
+ Spree::Config['currency'] = ENV['CURRENCY']
+ if Spree::Country.table_exists?
+ country = Spree::Country.find_by_iso(ENV['DEFAULT_COUNTRY_CODE'])
+ Spree::Config['default_country_id'] = country.id if country.present?
+ else
+ Spree::Config['default_country_id'] = 12 # Australia
+ end
+ end
- app.config.spree.calculators.enterprise_fees = [Calculator::FlatPercentPerItem,
- Spree::Calculator::FlatRate,
- Spree::Calculator::FlexiRate,
- Spree::Calculator::PerItem,
- Spree::Calculator::PriceSack,
- OpenFoodNetwork::Calculator::Weight]
- app.config.spree.calculators.payment_methods = [Spree::Calculator::FlatPercentItemTotal,
- Spree::Calculator::FlatRate,
- Spree::Calculator::FlexiRate,
- Spree::Calculator::PerItem,
- Spree::Calculator::PriceSack]
+ # Register Spree calculators
+ initializer 'spree.register.calculators' do |app|
+ app.config.spree.calculators.shipping_methods << OpenFoodNetwork::Calculator::Weight
+ app.config.spree.calculators.add_class('enterprise_fees')
+ config.spree.calculators.enterprise_fees = [
+ Calculator::FlatPercentPerItem,
+ Spree::Calculator::FlatRate,
+ Spree::Calculator::FlexiRate,
+ Spree::Calculator::PerItem,
+ Spree::Calculator::PriceSack,
+ OpenFoodNetwork::Calculator::Weight
+ ]
+ app.config.spree.calculators.add_class('payment_methods')
+ config.spree.calculators.payment_methods = [
+ Spree::Calculator::FlatPercentItemTotal,
+ Spree::Calculator::FlatRate,
+ Spree::Calculator::FlexiRate,
+ Spree::Calculator::PerItem,
+ Spree::Calculator::PriceSack
+ ]
end
# Register Spree payment methods
initializer "spree.gateway.payment_methods", :after => "spree.register.payment_methods" do |app|
app.config.spree.payment_methods << Spree::Gateway::Migs
app.config.spree.payment_methods << Spree::Gateway::Pin
+ app.config.spree.payment_methods << Spree::Gateway::StripeConnect
end
# Settings in config/environments/* take precedence over those specified here.
diff --git a/config/application.yml.example b/config/application.yml.example
index e39f23fb59..310e248726 100644
--- a/config/application.yml.example
+++ b/config/application.yml.example
@@ -26,3 +26,12 @@ CURRENCY: AUD
# see: https://developers.google.com/maps/documentation/javascript/get-api-key
# GOOGLE_MAPS_API_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+# Stripe Connect details for instance account
+# Find these under 'API keys' and 'Connect' in your Stripe account dashboard -> Account Settings
+# Under 'Connect', the Redirect URI should be set to https://YOUR_SERVER_URL/stripe/callbacks (e.g. https://openfoodnetwork.org.uk/stripe/callbacks)
+# Under 'Webhooks', you should set up a Connect endpoint pointing to https://YOUR_SERVER_URL/stripe/webhooks e.g. (https://openfoodnetwork.org.uk/stripe/webhooks)
+# STRIPE_INSTANCE_SECRET_KEY: "sk_test_xxxxxx" # This can be a test key or a live key
+# STRIPE_INSTANCE_PUBLISHABLE_KEY: "pk_test_xxxx" # This can be a test key or a live key
+# STRIPE_CLIENT_ID: "ca_xxxx" # This can be a development ID or a production ID
+# STRIPE_ENDPOINT_SECRET: "whsec_xxxx"
diff --git a/config/environments/test.rb b/config/environments/test.rb
index f0ef05acaa..3a33fd53d7 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -45,7 +45,6 @@ Openfoodnetwork::Application.configure do
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
- config.action_mailer.default_url_options = { :host => "test.host" }
# To block requests before running the database cleaner
require 'open_food_network/rack_request_blocker'
@@ -55,3 +54,4 @@ end
# Allows us to use _url helpers in Rspec
Rails.application.routes.default_url_options[:host] = 'test.host'
+Spree::Core::Engine.routes.default_url_options[:host] = 'test.host'
diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb
index f439e779cf..dc41aa9258 100644
--- a/config/initializers/spree.rb
+++ b/config/initializers/spree.rb
@@ -6,27 +6,18 @@
# In order to initialize a setting do:
# config.setting_name = 'new value'
-
require 'spree/product_filters'
require 'spree/core/calculated_adjustments_decorator'
require "#{Rails.root}/app/models/spree/payment_method_decorator"
require "#{Rails.root}/app/models/spree/gateway_decorator"
+Spree::Api::Config[:requires_authentication] = true
+
Spree.config do |config|
config.shipping_instructions = true
config.address_requires_state = true
- # Settings dependent on locale
- config.checkout_zone = ENV["CHECKOUT_ZONE"]
- config.currency = ENV['CURRENCY']
- if Spree::Country.table_exists?
- country = Spree::Country.find_by_iso(ENV['DEFAULT_COUNTRY_CODE'])
- config.default_country_id = country.id if country.present?
- else
- config.default_country_id = 12 # Australia
- end
-
# -- spree_paypal_express
# Auto-capture payments. Without this option, payments must be manually captured in the paypal interface.
config.auto_capture = true
@@ -39,17 +30,6 @@ end
module OpenFoodNetwork
end
-# Add calculators category for enterprise fees
-module Spree
- module Core
- class Environment
- class Calculators
- attr_accessor :enterprise_fees, :payment_methods
- end
- end
- end
-end
-
# Forcing spree to always allow SSL connections
# Since we are using config.force_ssl = true
# Without this we get a redirect loop: see https://groups.google.com/forum/#!topic/spree-user/NwpqGxJ4klk
diff --git a/config/initializers/spree_auth_devise.rb b/config/initializers/spree_auth_devise.rb
new file mode 100644
index 0000000000..a59badf636
--- /dev/null
+++ b/config/initializers/spree_auth_devise.rb
@@ -0,0 +1,16 @@
+# `spree_auth_devise` gem decorators get loaded in a `to_prepare` callback
+# referring to Spree classes that have not been loaded yet
+#
+# When this initializer is loaded we're sure that those Spree classes have been
+# loaded and we load again the `spree_auth_devise` decorators to effectively
+# apply them.
+#
+# Give a look at `if defined?(Spree::Admin::BaseController)` in the following file
+# to get an example:
+# https://github.com/openfoodfoundation/spree_auth_devise/blob/spree-upgrade-intermediate/app/controllers/spree/admin/admin_controller_decorator.rb#L1
+#
+# TODO: remove this hack once we get to Spree 3.0
+gem_dir = Gem::Specification.find_by_name("spree_auth_devise").gem_dir
+Dir.glob(File.join(gem_dir, 'app/**/*_decorator*.rb')) do |c|
+ load c
+end
diff --git a/config/initializers/stripe.rb b/config/initializers/stripe.rb
new file mode 100644
index 0000000000..8237b0dcab
--- /dev/null
+++ b/config/initializers/stripe.rb
@@ -0,0 +1,14 @@
+# Add some additional properties, to allow us to access these
+# properties from the object, rather than calling from ENV directly.
+# This is mostly useful for stubbing when testing, but also feels
+# a bit cleaner than accessing keys in different ways.
+module Stripe
+ class << self
+ attr_accessor :publishable_key, :endpoint_secret
+ end
+end
+
+Stripe.api_key = ENV['STRIPE_INSTANCE_SECRET_KEY']
+Stripe.publishable_key = ENV['STRIPE_INSTANCE_PUBLISHABLE_KEY']
+Stripe.client_id = ENV['STRIPE_CLIENT_ID']
+Stripe.endpoint_secret = ENV['STRIPE_ENDPOINT_SECRET']
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 8ce260fb77..b759c83bdc 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -161,7 +161,14 @@ en:
powered_by: Powered by
blocked_cookies_alert: "Your browser may be blocking cookies needed to use this shopfront. Click below to allow cookies and reload the page."
allow_cookies: "Allow Cookies"
+ none: None
+ notes: Notes
+ error: Error
+ processing_payment: Processing payment...
+
+ actions:
+ create_and_add_another: "Create and Add Another"
admin:
# Common properties / models
date: Date
@@ -375,6 +382,8 @@ en:
index:
capture: "Capture"
ship: "Ship"
+ invoice_email_sent: 'Invoice email has been sent'
+ order_email_resent: 'Order email has been resent'
bulk_management:
tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required."
shared: "Shared Resource?"
@@ -514,6 +523,18 @@ en:
close_date: Close Date
social:
twitter_placeholder: eg. @the_prof
+ stripe_connect:
+ connect_with_stripe: "Connect with Stripe"
+ stripe_connect_intro: "To accept payments using credit card, you will need to connect your stripe account to the Open Food Network. Use the button to the right to get started."
+ stripe_account_connected: "Stripe account connected."
+ disconnect: "Disconnect account"
+ confirm_modal:
+ title: Connect with Stripe
+ part1: Stripe is a payment processing service that allows shops on the OFN to accept credit card payments from customers.
+ part2: To use this feature, you must connect your Stripe account to the OFN. Clicking 'I Agree' below will redirect to you the Stripe website where you can connect an existing Stripe account, or create a new one if you don't already have one.
+ part3: This will allow the Open Food Network to accept credit card payments from customers on your behalf. Please note that you will need to maintain your own Stripe account, pay the fees Stripe charges and handle any chargebacks and customer service yourself.
+ i_agree: I Agree
+ cancel: Cancel
tag_rules:
default_rules:
by_default: By Default
@@ -597,12 +618,17 @@ en:
advanced_settings: Advanced Settings
update_and_close: Update and Close
choose_products_from: 'Choose Products From:'
- pickup_time_tip: When orders from this OC will be ready for the customer
- pickup_instructions_tip: These instructions are shown to customers after they complete an order
exchange_form:
+ pickup_time_tip: When orders from this OC will be ready for the customer
pickup_instructions_placeholder: "Pick-up instructions"
+ pickup_instructions_tip: These instructions are shown to customers after they complete an order
pickup_time_placeholder: "Ready for (ie. Date / Time)"
receival_instructions_placeholder: "Receival instructions"
+ add_fee: 'Add fee'
+ selected: 'selected'
+ add_exchange_form:
+ add_supplier: 'Add supplier'
+ add_distributor: 'Add distributor'
advanced_settings:
title: Advanced Settings
choose_product_tip: You can opt to restrict all available products (both incoming and outgoing), to only those in %{inventory}'s inventory.
@@ -642,9 +668,11 @@ en:
producer_properties:
index:
title: Producer Properties
+
shared:
user_guide_link:
user_guide: User Guide
+
invoice_settings:
edit:
title: Invoice Settings
@@ -715,6 +743,34 @@ en:
description: Invoices for import into Xero
packing:
name: Packing Reports
+
+ stripe_connect_settings:
+ edit:
+ title: "Stripe Connect"
+ settings: "Settings"
+ stripe_beta_warning: "Warning: The Stripe Connect integration is currently in beta and should not be enabled in production yet"
+ stripe_connect_enabled: Enable shops to accept payments using Stripe Connect?
+ no_api_key_msg: No Stripe account exists for this enterprise.
+ configuration_explanation_html: For detailed instructions on configuring the Stripe Connect integration, please consult this guide.
+ status: Status
+ ok: Ok
+ instance_secret_key: Instance Secret Key
+ account_id: Account ID
+ business_name: Business Name
+ charges_enabled: Charges Enabled
+ charges_enabled_warning: "Warning: Charges are not enabled for your account"
+ auth_fail_error: The API key you provided is invalid
+ empty_api_key_error: No Stripe API key has been provided. To set your API key, please follow the instructions at
+
+ # Admin controllers
+ controllers:
+ enterprises:
+ stripe_connect_cancelled: "Connection to Stripe has been cancelled"
+ stripe_connect_success: "Stripe account connected successfully"
+ stripe_connect_fail: Sorry, the connection of your Stripe account failed
+ stripe_connect_settings:
+ resource: Stripe Connect configuration
+
# Frontend views
#
# These keys are referenced relatively like `t('.message')` in
@@ -746,6 +802,11 @@ en:
require_login_html: "Please %{login} if you have an account already. Otherwise, %{register} to become a customer."
require_customer_html: "Please %{contact} %{enterprise} to become a customer."
+
+ # Front-end controller translations
+ card_could_not_be_saved: card could not be saved
+ spree_gateway_error_flash_for_checkout: "There was a problem with your payment information: %{error}"
+
# Printable Invoice Columns
invoice_billing_address: "Billing address:"
invoice_column_tax: "GST"
@@ -844,6 +905,18 @@ en:
card_number: Card Number
card_securitycode: "Security Code"
card_expiry_date: Expiry Date
+ card_masked_digit: "X"
+ card_expiry_abbreviation: "Exp"
+ new_credit_card: "New credit card"
+ my_credit_cards: My credit cards
+ add_new_credit_card: Add new credit card
+ saved_cards: Saved cards
+ add_a_card: Add a Card
+ add_card: Add Card
+ you_have_no_saved_cards: You haven't saved any cards yet
+ saving_credit_card: Saving credit card...
+ card_has_been_removed: "Your card has been removed (number: %{number})"
+ card_could_not_be_removed: Sorry, the card could not be removed
ie_warning_headline: "Your browser is out of date :-("
ie_warning_text: "For the best Open Food Network experience, we strongly recommend upgrading your browser:"
@@ -936,6 +1009,7 @@ en:
checkout_shipping_price: Shipping
checkout_total_price: Total
checkout_back_to_cart: "Back to Cart"
+ cost_currency: "Cost Currency"
order_paid: PAID
order_not_paid: NOT PAID
@@ -1311,10 +1385,67 @@ See the %{link} to find out more about %{sitename}'s features and to start using
reset_password: "Reset password"
registration_greeting: "Greetings!"
who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?"
+ enterprise:
+ registration:
+ modal:
+ steps:
+ details:
+ title: 'Details'
+ headline: "Let's Get Started"
+ enterprise: "Woot! First we need to know a little bit about your enterprise:"
+ producer: "Woot! First we need to know a little bit about your farm:"
+ enterprise_name_field: "Enterprise Name:"
+ producer_name_field: "Farm Name:"
+ producer_name_field_placeholder: "e.g. Charlie's Awesome Farm"
+ producer_name_field_error: "Please choose a unique name for your enterprise"
+ address1_field: "Address line 1:"
+ address1_field_placeholder: "e.g. 123 Cranberry Drive"
+ address1_field_error: "Please enter an address"
+ address2_field: "Address line 2:"
+ suburb_field: "Suburb:"
+ suburb_field_placeholder: "e.g. Northcote"
+ suburb_field_error: "Please enter a suburb"
+ postcode_field: "Postcode:"
+ postcode_field_placeholder: "e.g. 3070"
+ postcode_field_error: "Postcode required"
+ state_field: "State:"
+ state_field_error: "State required"
+ country_field: "Country:"
+ country_field_error: "Please select a country"
+ contact:
+ title: 'Contact'
+ contact_field: 'Primary Contact'
+ contact_field_placeholder: 'Contact Name'
+ contact_field_required: "You need to enter a primary contact."
+ email_field: 'Email address'
+ email_field_placeholder: 'eg. charlie@thefarm.com'
+ phone_field: 'Phone number'
+ phone_field_placeholder: 'eg. (03) 1234 5678'
+ type:
+ title: 'Type'
+ headline: "Last step to add %{enterprise}!"
+ question: "Are you a producer?"
+ yes_producer: "Yes, I'm a producer"
+ no_producer: "No, I'm not a producer"
+ producer_field_error: "Please choose one. Are you are producer?"
+ yes_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it."
+ no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other."
+
+ about:
+ title: 'About'
+ images:
+ title: 'Images'
+ social:
+ title: 'Social'
+ # TODO: Remove these once the enterprise.registration.modal keys are translated
enterprise_contact: "Primary Contact"
+ enterprise_contact_placeholder: "Contact Name"
enterprise_contact_required: "You need to enter a primary contact."
enterprise_email_address: "Email address"
+ enterprise_email_placeholder: "eg. charlie@thefarm.com"
enterprise_phone: "Phone number"
+ enterprise_phone_placeholder: "eg. (03) 1234 5678"
+ # END
back: "Back"
continue: "Continue"
limit_reached_headline: "Oh no!"
@@ -1385,6 +1516,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using
registration_finished_activate_instruction_html: "We've sent a confirmation email to %{email} if it hasn't been activated before.
Please follow the instructions there to make your enterprise visible on the Open Food Network."
registration_finished_action: "Open Food Network home"
+ registration_contact_name: 'Contact Name'
+
+ # TODO: Remove these once the enterprise.registration.modal keys are translated
registration_type_headline: "Last step to add %{enterprise}!"
registration_type_question: "Are you a producer?"
registration_type_producer: "Yes, I'm a producer"
@@ -1392,9 +1526,13 @@ Please follow the instructions there to make your enterprise visible on the Open
registration_type_error: "Please choose one. Are you are producer?"
registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it."
registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other."
+ # END
create_profile: "Create Profile"
registration_images_headline: "Thanks!"
registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)"
+
+
+ # TODO: Remove these once the enterprise.registration.modal keys are translated
registration_detail_headline: "Let's Get Started"
registration_detail_enterprise: "Woot! First we need to know a little bit about your enterprise:"
registration_detail_producer: "Woot! First we need to know a little bit about your farm:"
@@ -1416,6 +1554,7 @@ Please follow the instructions there to make your enterprise visible on the Open
registration_detail_state_error: "State required"
registration_detail_country: "Country:"
registration_detail_country_error: "Please select a country"
+ # END
shipping_method_destroy_error: "That shipping method cannot be deleted as it is referenced by an order: %{number}."
accounts_and_billing_task_already_running_error: "A task is already running, please wait until it has finished"
accounts_and_billing_start_task_notice: "Task Queued"
@@ -1484,6 +1623,9 @@ Please follow the instructions there to make your enterprise visible on the Open
create: "Create"
search: "Search"
supplier: "Supplier"
+ product_name: "Product Name"
+ product_description: "Product Description"
+ units: "Unit Size"
coordinator: "Coordinator"
distributor: "Distributor"
enterprise_fees: "Enterprise Fees"
@@ -1498,9 +1640,11 @@ Please follow the instructions there to make your enterprise visible on the Open
new_order_cycles: "New Order Cycles"
new_order_cycle: "New Order Cycle"
select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle"
+ notify_producers: 'Notify producers'
edit_order_cycle: "Edit Order Cycle"
roles: "Roles"
update: "Update"
+ delete: Delete
add_producer_property: "Add producer property"
in_progress: "In Progress"
started_at: "Started at"
@@ -1545,6 +1689,8 @@ Please follow the instructions there to make your enterprise visible on the Open
spree_admin_unit_description: Unit Description
spree_admin_variant_unit: Variant unit
spree_admin_variant_unit_scale: Variant unit scale
+ spree_admin_supplier: Supplier
+ spree_admin_product_category: Product Category
spree_admin_variant_unit_name: Variant unit name
change_package: "Change Package"
spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under"
@@ -2014,6 +2160,10 @@ Please follow the instructions there to make your enterprise visible on the Open
start_free_profile: "Start with a free profile, and expand when you're ready!"
spree:
+ # TODO: remove `email` key once we get to Spree 2.0
+ email: Email
+ # TODO: remove 'account_updated' key once we get to Spree 2.0
+ account_updated: "Account updated!"
admin:
orders:
invoice:
@@ -2036,7 +2186,28 @@ Please follow the instructions there to make your enterprise visible on the Open
one: "You have one active order cycle."
other: "You have %{count} active order cycles."
manage_order_cycles: "MANAGE ORDER CYCLES"
+ payment_methods:
+ stripe_connect:
+ enterprise_select_placeholder: Choose...
+ loading_account_information_msg: Loading account information from stripe, please wait...
+ stripe_disabled_msg: Stripe payments have been disabled by the system administrator.
+ request_failed_msg: Sorry. Something went wrong when trying to verify account details with Stripe...
+ account_missing_msg: No Stripe account exists for this enterprise.
+ connect_one: Connect One
+ access_revoked_msg: Access to this Stripe account has been revoked, please reconnect your account.
+ status: Status
+ connected: Connected
+ account_id: Account ID
+ business_name: Business Name
+ charges_enabled: Charges Enabled
+ payments:
+ source_forms:
+ stripe:
+ no_payment_via_admin_backend: Creating Stripe-based payments from the admin backend is not possible at this time
products:
+ new:
+ title: 'New Product'
+ unit_name_placeholder: 'eg. bunches'
bulk_edit:
header:
title: Bulk Edit Products
@@ -2044,15 +2215,34 @@ Please follow the instructions there to make your enterprise visible on the Open
title: LOADING PRODUCTS
no_products: "No products yet. Why don't you add some?"
no_results: "Sorry, no results match"
+ product_name: Product Name
+ primary_taxon_form:
+ product_category: Product Category
+ group_buy_form:
+ group_buy: "Group Buy?"
+ bulk_unit_size: Bulk unit size
+ display_as:
+ display_as: Display As
reports:
bulk_coop:
bulk_coop_supplier_report: 'Bulk Co-op - Totals by Supplier'
bulk_coop_allocation: 'Bulk Co-op - Allocation'
bulk_coop_packing_sheets: 'Bulk Co-op - Packing Sheets'
bulk_coop_customer_payments: 'Bulk Co-op - Customer Payments'
+ shared:
+ configuration_menu:
+ stripe_connect: Stripe Connect
variants:
autocomplete:
producer_name: Producer
+ checkout:
+ payment:
+ stripe:
+ choose_one: Choose one
+ enter_new_card: Enter details for a new card
+ used_saved_card: "Use a saved card:"
+ or_enter_new_card: "Or, enter details for a new card:"
+ remember_this_card: Remember this card?
date_picker:
format: ! '%Y-%m-%d'
js_format: 'yy-mm-dd'
@@ -2125,8 +2315,18 @@ Please follow the instructions there to make your enterprise visible on the Open
weight: Weight (per kg)
zipcode: Postcode
users:
+ form:
+ account_settings: Account Settings
show:
+ tabs:
+ orders: Orders
+ cards: Credit Cards
+ transactions: Transactions
+ settings: Account Settings
+ orders:
open_orders: Open Orders
+ past_orders: Past Orders
+ transactions:
transaction_history: Transaction History
open_orders:
order: Order
@@ -2138,3 +2338,11 @@ Please follow the instructions there to make your enterprise visible on the Open
cancel: Cancel
closed: Closed
until: Until
+ past_orders:
+ order: Order
+ shop: Shop
+ completed_at: Completed At
+ items: Items
+ total: Total
+ paid?: Paid?
+ view: View
diff --git a/config/routes.rb b/config/routes.rb
index 52b23758ab..ca8114ddb8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -53,6 +53,15 @@ Openfoodnetwork::Application.routes.draw do
end
end
+ namespace :stripe do
+ resources :callbacks, only: [:index]
+ resources :webhooks, only: [:create]
+ end
+
+ namespace :admin do
+ resources :bulk_line_items
+ end
+
get '/checkout', :to => 'checkout#edit' , :as => :checkout
put '/checkout', :to => 'checkout#update' , :as => :update_checkout
get '/checkout/paypal_payment/:order_id', to: 'checkout#paypal_payment', as: :paypal_payment
@@ -160,6 +169,13 @@ Openfoodnetwork::Application.routes.draw do
end
resource :invoice_settings, only: [:edit, :update]
+
+ resource :stripe_connect_settings, only: [:edit, :update]
+
+ resources :stripe_accounts, only: [:destroy] do
+ get :connect, on: :collection
+ get :status, on: :collection
+ end
end
namespace :api do
@@ -223,6 +239,9 @@ Spree::Core::Engine.routes.prepend do
match '/admin/reports/xero_invoices' => 'admin/reports#xero_invoices', :as => "xero_invoices_admin_reports", :via => [:get, :post]
match '/admin', :to => 'admin/overview#index', :as => :admin
match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get
+ put 'credit_cards/new_from_token', to: 'credit_cards#new_from_token'
+
+ resources :credit_cards
namespace :api, :defaults => { :format => 'json' } do
@@ -237,6 +256,7 @@ Spree::Core::Engine.routes.prepend do
get :overridable
end
delete :soft_delete
+ post :clone
resources :variants do
delete :soft_delete
@@ -246,6 +266,7 @@ Spree::Core::Engine.routes.prepend do
resources :orders do
get :managed, on: :collection
end
+
end
namespace :admin do
@@ -254,6 +275,8 @@ Spree::Core::Engine.routes.prepend do
resources :products do
get :product_distributions, on: :member
+ get :group_buy_options, on: :member
+ get :seo, on: :member
post :bulk_update, :on => :collection, :as => :bulk_update
end
@@ -264,8 +287,6 @@ Spree::Core::Engine.routes.prepend do
get :print_ticket, on: :member
get :managed, on: :collection
end
-
- resources :line_items, only: [:index], format: :json
end
resources :orders do
diff --git a/db/migrate/20160828115018_create_stripe_accounts.rb b/db/migrate/20160828115018_create_stripe_accounts.rb
new file mode 100644
index 0000000000..56ab206540
--- /dev/null
+++ b/db/migrate/20160828115018_create_stripe_accounts.rb
@@ -0,0 +1,12 @@
+class CreateStripeAccounts < ActiveRecord::Migration
+ def change
+ create_table :stripe_accounts do |t|
+ t.string :stripe_user_id
+ t.string :stripe_publishable_key
+ t.timestamps
+ t.belongs_to :enterprise
+ end
+
+ add_index :stripe_accounts, :enterprise_id, unique: true
+ end
+end
diff --git a/db/migrate/20160916024535_add_state_to_spree_adjustments.spree.rb b/db/migrate/20160916024535_add_state_to_spree_adjustments.spree.rb
new file mode 100644
index 0000000000..a4224678df
--- /dev/null
+++ b/db/migrate/20160916024535_add_state_to_spree_adjustments.spree.rb
@@ -0,0 +1,7 @@
+# This migration comes from spree (originally 20121213162028)
+class AddStateToSpreeAdjustments < ActiveRecord::Migration
+ def change
+ add_column :spree_adjustments, :state, :string
+ remove_column :spree_adjustments, :locked
+ end
+end
diff --git a/db/migrate/20170225203658_add_user_id_to_spree_credit_cards.rb b/db/migrate/20170225203658_add_user_id_to_spree_credit_cards.rb
new file mode 100644
index 0000000000..29e50e0c1a
--- /dev/null
+++ b/db/migrate/20170225203658_add_user_id_to_spree_credit_cards.rb
@@ -0,0 +1,8 @@
+class AddUserIdToSpreeCreditCards < ActiveRecord::Migration
+ def change
+ unless Spree::CreditCard.column_names.include? "user_id"
+ add_column :spree_credit_cards, :user_id, :integer
+ add_index :spree_credit_cards, :user_id
+ end
+ end
+end
diff --git a/db/migrate/20170304151129_add_payment_method_to_spree_credit_cards.rb b/db/migrate/20170304151129_add_payment_method_to_spree_credit_cards.rb
new file mode 100644
index 0000000000..76c1cab805
--- /dev/null
+++ b/db/migrate/20170304151129_add_payment_method_to_spree_credit_cards.rb
@@ -0,0 +1,8 @@
+class AddPaymentMethodToSpreeCreditCards < ActiveRecord::Migration
+ def change
+ unless Spree::CreditCard.column_names.include? "payment_method_id"
+ add_column :spree_credit_cards, :payment_method_id, :integer
+ add_index :spree_credit_cards, :payment_method_id
+ end
+ end
+end
diff --git a/db/migrate/20170413074528_rename_payment_methods.spree_paypal_express.rb b/db/migrate/20170413074528_rename_payment_methods.spree_paypal_express.rb
new file mode 100644
index 0000000000..2c426824d8
--- /dev/null
+++ b/db/migrate/20170413074528_rename_payment_methods.spree_paypal_express.rb
@@ -0,0 +1,14 @@
+# This migration comes from spree_paypal_express (originally 20140117051315)
+class RenamePaymentMethods < ActiveRecord::Migration
+ def up
+ execute <<-SQL
+ update spree_payment_methods set type = 'Spree::Gateway::PayPalExpress' WHERE type = 'Spree::BillingIntegration::PaypalExpress'
+ SQL
+ end
+
+ def down
+ execute <<-SQL
+ update spree_payment_methods set type = 'Spree::BillingIntegration::PaypalExpress' WHERE type = 'Spree::Gateway::PayPalExpress'
+ SQL
+ end
+end
diff --git a/db/migrate/20170413083148_add_tracking_url_to_spree_shipping_methods.spree.rb b/db/migrate/20170413083148_add_tracking_url_to_spree_shipping_methods.spree.rb
new file mode 100644
index 0000000000..28a9807184
--- /dev/null
+++ b/db/migrate/20170413083148_add_tracking_url_to_spree_shipping_methods.spree.rb
@@ -0,0 +1,6 @@
+# This migration comes from spree (originally 20130301205200)
+class AddTrackingUrlToSpreeShippingMethods < ActiveRecord::Migration
+ def change
+ add_column :spree_shipping_methods, :tracking_url, :string
+ end
+end
diff --git a/db/migrate/20170512115519_add_locale_to_spree_users.rb b/db/migrate/20170512115519_add_locale_to_spree_users.rb
new file mode 100644
index 0000000000..f694bff1f7
--- /dev/null
+++ b/db/migrate/20170512115519_add_locale_to_spree_users.rb
@@ -0,0 +1,5 @@
+class AddLocaleToSpreeUsers < ActiveRecord::Migration
+ def change
+ add_column :spree_users, :locale, :string, limit: 5
+ end
+end
diff --git a/db/migrate/20170921065259_update_adjustment_states.spree.rb b/db/migrate/20170921065259_update_adjustment_states.spree.rb
new file mode 100644
index 0000000000..9040709751
--- /dev/null
+++ b/db/migrate/20170921065259_update_adjustment_states.spree.rb
@@ -0,0 +1,17 @@
+# This migration comes from spree (originally 20130417120035)
+class UpdateAdjustmentStates < ActiveRecord::Migration
+ def up
+ Spree::Order.complete.find_each do |order|
+ order.adjustments.update_all(:state => 'closed')
+ end
+
+ Spree::Shipment.shipped.includes(:adjustment).find_each do |shipment|
+ shipment.adjustment.update_column(:state, 'finalized') if shipment.adjustment
+ end
+
+ Spree::Adjustment.where(:state => nil).update_all(:state => 'open')
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 75c43b1d72..061c97e920 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20161215230219) do
+ActiveRecord::Schema.define(:version => 20170921065259) do
create_table "account_invoices", :force => true do |t|
t.integer "user_id", :null => false
@@ -401,12 +401,12 @@ ActiveRecord::Schema.define(:version => 20161215230219) do
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.boolean "mandatory"
- t.boolean "locked"
t.integer "originator_id"
t.string "originator_type"
t.boolean "eligible", :default => true
t.string "adjustable_type"
t.decimal "included_tax", :precision => 10, :scale => 2, :default => 0.0, :null => false
+ t.string "state"
end
add_index "spree_adjustments", ["adjustable_id"], :name => "index_adjustments_on_order_id"
@@ -469,8 +469,13 @@ ActiveRecord::Schema.define(:version => 20161215230219) do
t.datetime "updated_at", :null => false
t.string "gateway_customer_profile_id"
t.string "gateway_payment_profile_id"
+ t.integer "user_id"
+ t.integer "payment_method_id"
end
+ add_index "spree_credit_cards", ["payment_method_id"], :name => "index_spree_credit_cards_on_payment_method_id"
+ add_index "spree_credit_cards", ["user_id"], :name => "index_spree_credit_cards_on_user_id"
+
create_table "spree_gateways", :force => true do |t|
t.string "type"
t.string "name"
@@ -870,6 +875,7 @@ ActiveRecord::Schema.define(:version => 20161215230219) do
t.datetime "deleted_at"
t.boolean "require_ship_address", :default => true
t.text "description"
+ t.string "tracking_url"
end
create_table "spree_skrill_transactions", :force => true do |t|
@@ -997,6 +1003,7 @@ ActiveRecord::Schema.define(:version => 20161215230219) do
t.datetime "reset_password_sent_at"
t.string "api_key", :limit => 40
t.integer "enterprise_limit", :default => 1, :null => false
+ t.string "locale", :limit => 5
end
add_index "spree_users", ["email"], :name => "email_idx_unique", :unique => true
@@ -1042,6 +1049,16 @@ ActiveRecord::Schema.define(:version => 20161215230219) do
t.integer "zone_members_count", :default => 0
end
+ create_table "stripe_accounts", :force => true do |t|
+ t.string "stripe_user_id"
+ t.string "stripe_publishable_key"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "enterprise_id"
+ end
+
+ add_index "stripe_accounts", ["enterprise_id"], :name => "index_stripe_accounts_on_enterprise_id", :unique => true
+
create_table "suburbs", :force => true do |t|
t.string "name"
t.string "postcode"
diff --git a/knapsack_rspec_report.json b/knapsack_rspec_report.json
index 5d71fa12e2..8b7e7a0084 100644
--- a/knapsack_rspec_report.json
+++ b/knapsack_rspec_report.json
@@ -1,213 +1,221 @@
{
- "spec/controllers/admin/accounts_and_billing_settings_controller_spec.rb": 4.729289770126343,
- "spec/controllers/admin/business_model_configuration_controller_spec.rb": 0.3204472064971924,
- "spec/controllers/admin/column_preferences_controller_spec.rb": 0.21457862854003906,
- "spec/controllers/admin/customers_controller_spec.rb": 1.2570579051971436,
- "spec/controllers/admin/enterprises_controller_spec.rb": 6.453823804855347,
- "spec/controllers/admin/inventory_items_controller_spec.rb": 3.3494999408721924,
- "spec/controllers/admin/order_cycles_controller_spec.rb": 3.2418012619018555,
- "spec/controllers/admin/tag_rules_controller_spec.rb": 0.278639554977417,
- "spec/controllers/admin/variant_overrides_controller_spec.rb": 4.334253549575806,
- "spec/controllers/api/enterprises_controller_spec.rb": 0.4233386516571045,
- "spec/controllers/api/order_cycles_controller_spec.rb": 1.9799659252166748,
- "spec/controllers/api/statuses_controller_spec.rb": 0.0580599308013916,
- "spec/controllers/base_controller_spec.rb": 0.025392770767211914,
- "spec/controllers/cart_controller_spec.rb": 1.188997507095337,
- "spec/controllers/checkout_controller_spec.rb": 1.7111992835998535,
- "spec/controllers/enterprise_confirmations_controller_spec.rb": 1.057147741317749,
- "spec/controllers/enterprises_controller_spec.rb": 3.9115149974823,
- "spec/controllers/groups_controller_spec.rb": 0.4256296157836914,
- "spec/controllers/registration_controller_spec.rb": 0.1444110870361328,
- "spec/controllers/shop_controller_spec.rb": 2.8928422927856445,
- "spec/controllers/shops_controller_spec.rb": 0.23369908332824707,
- "spec/controllers/spree/admin/adjustments_controller_spec.rb": 1.0890753269195557,
- "spec/controllers/spree/admin/base_controller_spec.rb": 0.1624138355255127,
- "spec/controllers/spree/admin/line_items_controller_spec.rb": 16.208045721054077,
- "spec/controllers/spree/admin/orders_controller_spec.rb": 15.907819509506226,
- "spec/controllers/spree/admin/overview_controller_spec.rb": 0.8662476539611816,
- "spec/controllers/spree/admin/payment_methods_controller_spec.rb": 0.4896993637084961,
- "spec/controllers/spree/admin/products_controller_spec.rb": 2.074070930480957,
- "spec/controllers/spree/admin/reports_controller_spec.rb": 55.07741975784302,
- "spec/controllers/spree/admin/search_controller_spec.rb": 0.5158224105834961,
- "spec/controllers/spree/admin/variants_controller_spec.rb": 1.990790605545044,
- "spec/controllers/spree/api/line_items_controller_spec.rb": 0.3319206237792969,
- "spec/controllers/spree/api/products_controller_spec.rb": 6.679011106491089,
- "spec/controllers/spree/api/variants_controller_spec.rb": 3.7782905101776123,
- "spec/controllers/spree/checkout_controller_spec.rb": 0.9790353775024414,
- "spec/controllers/spree/orders_controller_spec.rb": 4.158925294876099,
- "spec/controllers/spree/paypal_controller_spec.rb": 0.020407676696777344,
- "spec/controllers/spree/store_controller_spec.rb": 0.03319215774536133,
- "spec/controllers/spree/user_sessions_controller_spec.rb": 0.07898402214050293,
- "spec/controllers/user_passwords_controller_spec.rb": 0.717381477355957,
- "spec/controllers/user_registrations_controller_spec.rb": 0.2160170078277588,
- "spec/features/admin/account_spec.rb": 0.2934560775756836,
- "spec/features/admin/accounts_and_billing_settings_spec.rb": 15.889720678329468,
- "spec/features/admin/adjustments_spec.rb": 5.175323009490967,
- "spec/features/admin/authentication_spec.rb": 18.345781087875366,
- "spec/features/admin/bulk_order_management_spec.rb": 111.50722217559814,
- "spec/features/admin/bulk_product_update_spec.rb": 63.564337730407715,
- "spec/features/admin/business_model_configuration_spec.rb": 2.1073272228240967,
- "spec/features/admin/caching_spec.rb": 0.8505651950836182,
- "spec/features/admin/content_spec.rb": 1.1712932586669922,
- "spec/features/admin/customers_spec.rb": 71.91736245155334,
- "spec/features/admin/enterprise_fees_spec.rb": 15.482876300811768,
- "spec/features/admin/enterprise_groups_spec.rb": 8.615704774856567,
- "spec/features/admin/enterprise_relationships_spec.rb": 11.908889293670654,
- "spec/features/admin/enterprise_roles_spec.rb": 5.027954578399658,
- "spec/features/admin/enterprise_user_spec.rb": 2.158304214477539,
- "spec/features/admin/enterprises/index_spec.rb": 5.7792346477508545,
- "spec/features/admin/enterprises_spec.rb": 39.01360893249512,
- "spec/features/admin/external_services_spec.rb": 0.41648149490356445,
- "spec/features/admin/image_settings_spec.rb": 0.4291551113128662,
- "spec/features/admin/order_cycles_spec.rb": 66.84531092643738,
- "spec/features/admin/orders_spec.rb": 50.86089587211609,
- "spec/features/admin/overview_spec.rb": 4.884965896606445,
- "spec/features/admin/payment_method_spec.rb": 14.099174499511719,
- "spec/features/admin/products_spec.rb": 17.05465793609619,
- "spec/features/admin/reports_spec.rb": 142.85665917396545,
- "spec/features/admin/shipping_methods_spec.rb": 6.785600900650024,
- "spec/features/admin/tag_rules_spec.rb": 21.80374526977539,
- "spec/features/admin/tax_settings_spec.rb": 0.5856199264526367,
- "spec/features/admin/variant_overrides_spec.rb": 54.87969517707825,
- "spec/features/admin/variants_spec.rb": 4.425906658172607,
- "spec/features/consumer/account_spec.rb": 14.138294458389282,
- "spec/features/consumer/authentication_spec.rb": 15.800535440444946,
- "spec/features/consumer/external_services_spec.rb": 0.47263646125793457,
- "spec/features/consumer/groups_spec.rb": 2.167065143585205,
- "spec/features/consumer/producers_spec.rb": 8.219613790512085,
- "spec/features/consumer/registration_spec.rb": 3.2899246215820312,
- "spec/features/consumer/shopping/cart_spec.rb": 7.931907653808594,
- "spec/features/consumer/shopping/checkout_auth_spec.rb": 10.027384042739868,
- "spec/features/consumer/shopping/checkout_spec.rb": 60.93560552597046,
- "spec/features/consumer/shopping/shopping_spec.rb": 64.26991128921509,
- "spec/features/consumer/shopping/variant_overrides_spec.rb": 71.18549585342407,
- "spec/features/consumer/shops_spec.rb": 12.581299543380737,
- "spec/helpers/admin/business_model_configuration_helper_spec.rb": 0.4121088981628418,
- "spec/helpers/checkout_helper_spec.rb": 0.017447471618652344,
- "spec/helpers/enterprises_helper_spec.rb": 3.16050124168396,
- "spec/helpers/groups_helper_spec.rb": 0.008687734603881836,
- "spec/helpers/html_helper_spec.rb": 0.12075495719909668,
- "spec/helpers/injection_helper_spec.rb": 13.099636316299438,
- "spec/helpers/navigation_helper_spec.rb": 0.037546634674072266,
- "spec/helpers/order_cycles_helper_spec.rb": 0.5602025985717773,
- "spec/helpers/products_helper_spec.rb": 0.009445667266845703,
- "spec/helpers/shared_helper_spec.rb": 0.021656036376953125,
- "spec/helpers/shop_helper_spec.rb": 0.06465697288513184,
- "spec/jobs/confirm_order_job_spec.rb": 0.04894542694091797,
- "spec/jobs/confirm_signup_job_spec.rb": 0.15027284622192383,
- "spec/jobs/finalize_account_invoices_spec.rb": 4.740641832351685,
- "spec/jobs/heartbeat_job_spec.rb": 0.020777225494384766,
- "spec/jobs/order_cycle_notification_job_spec.rb": 2.3326029777526855,
- "spec/jobs/products_cache_integrity_checker_job_spec.rb": 2.176734685897827,
- "spec/jobs/refresh_products_cache_job_spec.rb": 0.14060688018798828,
- "spec/jobs/update_account_invoices_spec.rb": 18.77696418762207,
- "spec/jobs/update_billable_periods_spec.rb": 5.19831395149231,
- "spec/jobs/welcome_enterprise_job_spec.rb": 0.05716228485107422,
- "spec/lib/open_food_network/bulk_coop_report_spec.rb": 5.522400140762329,
- "spec/lib/open_food_network/cached_products_renderer_spec.rb": 0.0779104232788086,
- "spec/lib/open_food_network/customers_report_spec.rb": 2.789498805999756,
- "spec/lib/open_food_network/distribution_change_validator_spec.rb": 0.12454366683959961,
- "spec/lib/open_food_network/enterprise_fee_applicator_spec.rb": 1.0582823753356934,
- "spec/lib/open_food_network/enterprise_fee_calculator_spec.rb": 9.390950918197632,
- "spec/lib/open_food_network/enterprise_injection_data_spec.rb": 0.30153727531433105,
- "spec/lib/open_food_network/enterprise_issue_validator_spec.rb": 0.09757637977600098,
- "spec/lib/open_food_network/feature_toggle_spec.rb": 0.011552810668945312,
- "spec/lib/open_food_network/group_buy_report_spec.rb": 4.40512228012085,
- "spec/lib/open_food_network/last_used_address_spec.rb": 0.4271695613861084,
- "spec/lib/open_food_network/lettuce_share_report_spec.rb": 2.5144362449645996,
- "spec/lib/open_food_network/option_value_namer_spec.rb": 0.47345566749572754,
- "spec/lib/open_food_network/order_and_distributor_report_spec.rb": 1.1131298542022705,
- "spec/lib/open_food_network/order_cycle_form_applicator_spec.rb": 7.482408761978149,
- "spec/lib/open_food_network/order_cycle_management_report_spec.rb": 2.9279346466064453,
- "spec/lib/open_food_network/order_cycle_permissions_spec.rb": 27.034855365753174,
- "spec/lib/open_food_network/order_grouper_spec.rb": 0.0347137451171875,
- "spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb": 6.190460443496704,
- "spec/lib/open_food_network/packing_report_spec.rb": 5.795913934707642,
- "spec/lib/open_food_network/permissions_spec.rb": 9.981389284133911,
- "spec/lib/open_food_network/products_and_inventory_report_spec.rb": 4.536849737167358,
- "spec/lib/open_food_network/products_cache_refreshment_spec.rb": 0.3400561809539795,
- "spec/lib/open_food_network/products_cache_spec.rb": 13.676804304122925,
- "spec/lib/open_food_network/products_renderer_spec.rb": 5.883910894393921,
- "spec/lib/open_food_network/property_merge_spec.rb": 0.28261804580688477,
- "spec/lib/open_food_network/referer_parser_spec.rb": 0.016553640365600586,
- "spec/lib/open_food_network/reports/report_spec.rb": 0.027279138565063477,
- "spec/lib/open_food_network/reports/row_spec.rb": 0.004057884216308594,
- "spec/lib/open_food_network/reports/rule_spec.rb": 0.018927812576293945,
- "spec/lib/open_food_network/sales_tax_report_spec.rb": 0.1267712116241455,
- "spec/lib/open_food_network/scope_variant_to_hub_spec.rb": 5.346986532211304,
- "spec/lib/open_food_network/tag_rule_applicator_spec.rb": 2.972744941711426,
- "spec/lib/open_food_network/user_balance_calculator_spec.rb": 6.290127754211426,
- "spec/lib/open_food_network/users_and_enterprises_report_spec.rb": 0.435042142868042,
- "spec/lib/open_food_network/xero_invoices_report_spec.rb": 1.327892780303955,
- "spec/lib/spree/product_filters_spec.rb": 0.14480209350585938,
- "spec/mailers/enterprise_mailer_spec.rb": 1.2255680561065674,
- "spec/mailers/order_mailer_spec.rb": 1.9922146797180176,
- "spec/mailers/producer_mailer_spec.rb": 28.504019021987915,
- "spec/mailers/user_mailer_spec.rb": 0.06116366386413574,
- "spec/models/adjustment_metadata_spec.rb": 0.22940421104431152,
- "spec/models/billable_period_spec.rb": 5.919523477554321,
- "spec/models/calculator/weight_spec.rb": 0.011056900024414062,
- "spec/models/cart_spec.rb": 4.7867491245269775,
- "spec/models/column_preference_spec.rb": 0.12476158142089844,
- "spec/models/content_configuration_spec.rb": 0.0069255828857421875,
- "spec/models/coordinator_fee_spec.rb": 0.1413099765777588,
- "spec/models/customer_spec.rb": 0.9213364124298096,
- "spec/models/enterprise_caching_spec.rb": 0.8015868663787842,
- "spec/models/enterprise_fee_spec.rb": 3.8326468467712402,
- "spec/models/enterprise_group_spec.rb": 0.35931992530822754,
- "spec/models/enterprise_relationship_spec.rb": 7.324019908905029,
- "spec/models/enterprise_spec.rb": 20.723163843154907,
- "spec/models/exchange_fee_spec.rb": 0.24502134323120117,
- "spec/models/exchange_spec.rb": 15.133025646209717,
- "spec/models/inventory_item_spec.rb": 0.2637319564819336,
- "spec/models/model_set_spec.rb": 0.2381300926208496,
- "spec/models/order_cycle_spec.rb": 19.834176540374756,
- "spec/models/producer_property_spec.rb": 0.12196111679077148,
- "spec/models/product_distribution_spec.rb": 2.8120880126953125,
- "spec/models/spree/ability_spec.rb": 16.657139778137207,
- "spec/models/spree/addresses_spec.rb": 0.06702327728271484,
- "spec/models/spree/adjustment_spec.rb": 13.998104333877563,
- "spec/models/spree/classification_spec.rb": 0.7607810497283936,
- "spec/models/spree/image_spec.rb": 2.1546812057495117,
- "spec/models/spree/line_item_spec.rb": 18.319732189178467,
- "spec/models/spree/option_type_spec.rb": 0.38923072814941406,
- "spec/models/spree/option_value_spec.rb": 0.4280354976654053,
- "spec/models/spree/order_populator_spec.rb": 1.4095511436462402,
- "spec/models/spree/order_spec.rb": 9.809221029281616,
- "spec/models/spree/payment_method_spec.rb": 0.5280671119689941,
- "spec/models/spree/payment_spec.rb": 2.4764130115509033,
- "spec/models/spree/preference_spec.rb": 0.059625864028930664,
- "spec/models/spree/preferences/file_configuration_spec.rb": 0.038376808166503906,
- "spec/models/spree/price_spec.rb": 0.5022625923156738,
- "spec/models/spree/product_property_spec.rb": 0.3601999282836914,
- "spec/models/spree/product_spec.rb": 16.564993381500244,
- "spec/models/spree/property_spec.rb": 1.6884117126464844,
- "spec/models/spree/shipping_method_spec.rb": 2.2080821990966797,
- "spec/models/spree/tax_rate_spec.rb": 0.37114739418029785,
- "spec/models/spree/taxon_spec.rb": 1.0655884742736816,
- "spec/models/spree/user_spec.rb": 16.094335317611694,
- "spec/models/spree/variant_spec.rb": 14.885905027389526,
- "spec/models/tag_rule/filter_order_cycles_spec.rb": 0.21634840965270996,
- "spec/models/tag_rule/filter_payment_methods_spec.rb": 0.4332749843597412,
- "spec/models/tag_rule/filter_products_spec.rb": 0.21471834182739258,
- "spec/models/tag_rule/filter_shipping_methods_spec.rb": 0.3680458068847656,
- "spec/models/tag_rule_spec.rb": 0.05348682403564453,
- "spec/models/variant_override_spec.rb": 5.598196029663086,
- "spec/performance/injection_helper_spec.rb": 4.83400297164917,
- "spec/performance/orders_controller_spec.rb": 0.028135061264038086,
- "spec/performance/shop_controller_spec.rb": 14.35703420639038,
- "spec/requests/large_request_spec.rb": 0.024456262588500977,
- "spec/requests/shop_spec.rb": 1.0987565517425537,
- "spec/serializers/admin/customer_serializer_spec.rb": 0.0909874439239502,
- "spec/serializers/admin/enterprise_serializer_spec.rb": 0.06178736686706543,
- "spec/serializers/admin/exchange_serializer_spec.rb": 2.586963653564453,
- "spec/serializers/admin/for_order_cycle/enterprise_serializer_spec.rb": 0.8507771492004395,
- "spec/serializers/admin/for_order_cycle/supplied_product_serializer_spec.rb": 0.6380510330200195,
- "spec/serializers/admin/index_enterprise_serializer_spec.rb": 0.19609999656677246,
- "spec/serializers/admin/variant_override_serializer_spec.rb": 0.27136850357055664,
- "spec/serializers/enterprise_serializer_spec.rb": 0.22696876525878906,
- "spec/serializers/order_serializer_spec.rb": 1.3858006000518799,
- "spec/serializers/orders_by_distributor_serializer_spec.rb": 3.6581554412841797,
- "spec/serializers/spree/product_serializer_spec.rb": 0.17654776573181152,
- "spec/serializers/spree/variant_serializer_spec.rb": 0.2116224765777588
+ "spec/controllers/admin/accounts_and_billing_settings_controller_spec.rb": 8.00712776184082,
+ "spec/controllers/admin/business_model_configuration_controller_spec.rb": 0.6013796329498291,
+ "spec/controllers/admin/column_preferences_controller_spec.rb": 0.6397809982299805,
+ "spec/controllers/admin/customers_controller_spec.rb": 1.770902156829834,
+ "spec/controllers/admin/enterprises_controller_spec.rb": 12.199191331863403,
+ "spec/controllers/admin/inventory_items_controller_spec.rb": 6.397810935974121,
+ "spec/controllers/admin/order_cycles_controller_spec.rb": 5.024861812591553,
+ "spec/controllers/admin/tag_rules_controller_spec.rb": 0.5270967483520508,
+ "spec/controllers/admin/variant_overrides_controller_spec.rb": 7.399488925933838,
+ "spec/controllers/api/enterprises_controller_spec.rb": 0.42368054389953613,
+ "spec/controllers/api/order_cycles_controller_spec.rb": 3.215219259262085,
+ "spec/controllers/api/statuses_controller_spec.rb": 0.06884050369262695,
+ "spec/controllers/base_controller_spec.rb": 0.03565478324890137,
+ "spec/controllers/cart_controller_spec.rb": 1.8274929523468018,
+ "spec/controllers/checkout_controller_spec.rb": 2.7706351280212402,
+ "spec/controllers/enterprise_confirmations_controller_spec.rb": 1.4932606220245361,
+ "spec/controllers/enterprises_controller_spec.rb": 6.069838762283325,
+ "spec/controllers/groups_controller_spec.rb": 0.6966016292572021,
+ "spec/controllers/registration_controller_spec.rb": 0.16758990287780762,
+ "spec/controllers/shop_controller_spec.rb": 5.656877517700195,
+ "spec/controllers/shops_controller_spec.rb": 0.9992175102233887,
+ "spec/controllers/spree/admin/adjustments_controller_spec.rb": 1.6936447620391846,
+ "spec/controllers/spree/admin/base_controller_spec.rb": 0.27201366424560547,
+ "spec/controllers/spree/admin/line_items_controller_spec.rb": 32.69303798675537,
+ "spec/controllers/spree/admin/orders_controller_spec.rb": 23.98875856399536,
+ "spec/controllers/spree/admin/overview_controller_spec.rb": 1.6584975719451904,
+ "spec/controllers/spree/admin/payment_methods_controller_spec.rb": 0.6059751510620117,
+ "spec/controllers/spree/admin/products_controller_spec.rb": 2.822031021118164,
+ "spec/controllers/spree/admin/reports_controller_spec.rb": 97.03786563873291,
+ "spec/controllers/spree/admin/search_controller_spec.rb": 2.2991843223571777,
+ "spec/controllers/spree/admin/variants_controller_spec.rb": 2.7615997791290283,
+ "spec/controllers/spree/api/line_items_controller_spec.rb": 0.519737720489502,
+ "spec/controllers/spree/api/products_controller_spec.rb": 15.634222030639648,
+ "spec/controllers/spree/api/variants_controller_spec.rb": 6.291322231292725,
+ "spec/controllers/spree/checkout_controller_spec.rb": 0.7929987907409668,
+ "spec/controllers/spree/orders_controller_spec.rb": 7.134824752807617,
+ "spec/controllers/spree/paypal_controller_spec.rb": 0.03331351280212402,
+ "spec/controllers/spree/store_controller_spec.rb": 0.05157065391540527,
+ "spec/controllers/spree/user_sessions_controller_spec.rb": 0.1207573413848877,
+ "spec/controllers/user_passwords_controller_spec.rb": 0.5991458892822266,
+ "spec/controllers/user_registrations_controller_spec.rb": 0.378939151763916,
+ "spec/features/admin/account_spec.rb": 0.9386508464813232,
+ "spec/features/admin/accounts_and_billing_settings_spec.rb": 21.184889793395996,
+ "spec/features/admin/adjustments_spec.rb": 8.66775107383728,
+ "spec/features/admin/authentication_spec.rb": 25.82377004623413,
+ "spec/features/admin/bulk_order_management_spec.rb": 164.24249410629272,
+ "spec/features/admin/bulk_product_update_spec.rb": 90.47183227539062,
+ "spec/features/admin/business_model_configuration_spec.rb": 2.6363422870635986,
+ "spec/features/admin/caching_spec.rb": 1.2790143489837646,
+ "spec/features/admin/content_spec.rb": 1.8612418174743652,
+ "spec/features/admin/customers_spec.rb": 56.48403525352478,
+ "spec/features/admin/enterprise_fees_spec.rb": 20.162436962127686,
+ "spec/features/admin/enterprise_groups_spec.rb": 11.802961111068726,
+ "spec/features/admin/enterprise_relationships_spec.rb": 15.264024496078491,
+ "spec/features/admin/enterprise_roles_spec.rb": 6.748473167419434,
+ "spec/features/admin/enterprise_user_spec.rb": 3.581881523132324,
+ "spec/features/admin/enterprises/index_spec.rb": 8.482917070388794,
+ "spec/features/admin/enterprises_spec.rb": 49.892003297805786,
+ "spec/features/admin/external_services_spec.rb": 0.6888332366943359,
+ "spec/features/admin/image_settings_spec.rb": 0.7543714046478271,
+ "spec/features/admin/order_cycles_spec.rb": 96.08795166015625,
+ "spec/features/admin/orders_spec.rb": 68.66088509559631,
+ "spec/features/admin/overview_spec.rb": 5.806450366973877,
+ "spec/features/admin/payment_method_spec.rb": 16.470131158828735,
+ "spec/features/admin/product_import_spec.rb": 35.73773694038391,
+ "spec/features/admin/products_spec.rb": 22.878377199172974,
+ "spec/features/admin/reports_spec.rb": 156.537535905838,
+ "spec/features/admin/shipping_methods_spec.rb": 9.275840759277344,
+ "spec/features/admin/tag_rules_spec.rb": 28.019567728042603,
+ "spec/features/admin/tax_settings_spec.rb": 0.8582248687744141,
+ "spec/features/admin/variant_overrides_spec.rb": 68.96011137962341,
+ "spec/features/admin/variants_spec.rb": 7.299575567245483,
+ "spec/features/consumer/account_spec.rb": 17.58187508583069,
+ "spec/features/consumer/authentication_spec.rb": 16.81424307823181,
+ "spec/features/consumer/external_services_spec.rb": 0.6560425758361816,
+ "spec/features/consumer/groups_spec.rb": 8.783919095993042,
+ "spec/features/consumer/producers_spec.rb": 13.732691049575806,
+ "spec/features/consumer/registration_spec.rb": 39.105244636535645,
+ "spec/features/consumer/shopping/cart_spec.rb": 10.158715724945068,
+ "spec/features/consumer/shopping/checkout_auth_spec.rb": 11.223945617675781,
+ "spec/features/consumer/shopping/checkout_spec.rb": 90.94879722595215,
+ "spec/features/consumer/shopping/shopping_spec.rb": 78.30096817016602,
+ "spec/features/consumer/shopping/variant_overrides_spec.rb": 83.33234310150146,
+ "spec/features/consumer/shops_spec.rb": 25.136919498443604,
+ "spec/features/consumer/sitemap_spec.rb": 0.12806296348571777,
+ "spec/helpers/admin/business_model_configuration_helper_spec.rb": 2.4084229469299316,
+ "spec/helpers/checkout_helper_spec.rb": 0.11321282386779785,
+ "spec/helpers/enterprises_helper_spec.rb": 4.312507390975952,
+ "spec/helpers/groups_helper_spec.rb": 0.012323379516601562,
+ "spec/helpers/html_helper_spec.rb": 0.06277966499328613,
+ "spec/helpers/injection_helper_spec.rb": 18.53943371772766,
+ "spec/helpers/navigation_helper_spec.rb": 0.06260061264038086,
+ "spec/helpers/order_cycles_helper_spec.rb": 0.7225909233093262,
+ "spec/helpers/products_helper_spec.rb": 0.010187149047851562,
+ "spec/helpers/shared_helper_spec.rb": 0.027238845825195312,
+ "spec/helpers/shop_helper_spec.rb": 0.08263945579528809,
+ "spec/jobs/confirm_order_job_spec.rb": 0.054292917251586914,
+ "spec/jobs/confirm_signup_job_spec.rb": 0.025557994842529297,
+ "spec/jobs/finalize_account_invoices_spec.rb": 6.124559640884399,
+ "spec/jobs/heartbeat_job_spec.rb": 0.025922060012817383,
+ "spec/jobs/order_cycle_notification_job_spec.rb": 3.4907965660095215,
+ "spec/jobs/products_cache_integrity_checker_job_spec.rb": 0.7591326236724854,
+ "spec/jobs/refresh_products_cache_job_spec.rb": 0.14359831809997559,
+ "spec/jobs/update_account_invoices_spec.rb": 25.14212441444397,
+ "spec/jobs/update_billable_periods_spec.rb": 7.548252820968628,
+ "spec/jobs/welcome_enterprise_job_spec.rb": 0.07267236709594727,
+ "spec/lib/open_food_network/bulk_coop_report_spec.rb": 6.935410737991333,
+ "spec/lib/open_food_network/cached_products_renderer_spec.rb": 0.1050572395324707,
+ "spec/lib/open_food_network/customers_report_spec.rb": 3.6295740604400635,
+ "spec/lib/open_food_network/distribution_change_validator_spec.rb": 0.16306090354919434,
+ "spec/lib/open_food_network/enterprise_fee_applicator_spec.rb": 1.435694932937622,
+ "spec/lib/open_food_network/enterprise_fee_calculator_spec.rb": 13.101191759109497,
+ "spec/lib/open_food_network/enterprise_injection_data_spec.rb": 0.5890538692474365,
+ "spec/lib/open_food_network/enterprise_issue_validator_spec.rb": 0.12314796447753906,
+ "spec/lib/open_food_network/feature_toggle_spec.rb": 0.01638960838317871,
+ "spec/lib/open_food_network/group_buy_report_spec.rb": 7.850870132446289,
+ "spec/lib/open_food_network/last_used_address_spec.rb": 0.03868246078491211,
+ "spec/lib/open_food_network/lettuce_share_report_spec.rb": 4.304181098937988,
+ "spec/lib/open_food_network/option_value_namer_spec.rb": 0.09165167808532715,
+ "spec/lib/open_food_network/order_and_distributor_report_spec.rb": 1.9102568626403809,
+ "spec/lib/open_food_network/order_cycle_form_applicator_spec.rb": 10.645771026611328,
+ "spec/lib/open_food_network/order_cycle_management_report_spec.rb": 3.786757469177246,
+ "spec/lib/open_food_network/order_cycle_permissions_spec.rb": 37.860092639923096,
+ "spec/lib/open_food_network/order_grouper_spec.rb": 0.046637773513793945,
+ "spec/lib/open_food_network/orders_and_fulfillments_report_spec.rb": 8.23448920249939,
+ "spec/lib/open_food_network/packing_report_spec.rb": 7.166008949279785,
+ "spec/lib/open_food_network/permissions_spec.rb": 14.218589782714844,
+ "spec/lib/open_food_network/products_and_inventory_report_spec.rb": 6.037408828735352,
+ "spec/lib/open_food_network/products_cache_refreshment_spec.rb": 0.43891096115112305,
+ "spec/lib/open_food_network/products_cache_spec.rb": 20.597163438796997,
+ "spec/lib/open_food_network/products_renderer_spec.rb": 8.173654317855835,
+ "spec/lib/open_food_network/property_merge_spec.rb": 0.4074442386627197,
+ "spec/lib/open_food_network/referer_parser_spec.rb": 0.0202939510345459,
+ "spec/lib/open_food_network/reports/report_spec.rb": 0.03491067886352539,
+ "spec/lib/open_food_network/reports/row_spec.rb": 0.00494694709777832,
+ "spec/lib/open_food_network/reports/rule_spec.rb": 0.02185201644897461,
+ "spec/lib/open_food_network/sales_tax_report_spec.rb": 0.2910194396972656,
+ "spec/lib/open_food_network/scope_variant_to_hub_spec.rb": 7.502454519271851,
+ "spec/lib/open_food_network/tag_rule_applicator_spec.rb": 3.9437036514282227,
+ "spec/lib/open_food_network/user_balance_calculator_spec.rb": 10.744928121566772,
+ "spec/lib/open_food_network/users_and_enterprises_report_spec.rb": 0.5333633422851562,
+ "spec/lib/open_food_network/xero_invoices_report_spec.rb": 1.6665534973144531,
+ "spec/lib/spree/product_filters_spec.rb": 0.17748093605041504,
+ "spec/mailers/enterprise_mailer_spec.rb": 0.44661855697631836,
+ "spec/mailers/order_mailer_spec.rb": 2.8625006675720215,
+ "spec/mailers/producer_mailer_spec.rb": 38.360108375549316,
+ "spec/mailers/user_mailer_spec.rb": 0.07233500480651855,
+ "spec/models/adjustment_metadata_spec.rb": 0.2955667972564697,
+ "spec/models/billable_period_spec.rb": 7.409304141998291,
+ "spec/models/calculator/flat_percent_per_item_spec.rb": 0.011715412139892578,
+ "spec/models/calculator/weight_spec.rb": 0.013759613037109375,
+ "spec/models/cart_spec.rb": 7.489097833633423,
+ "spec/models/column_preference_spec.rb": 0.12457847595214844,
+ "spec/models/content_configuration_spec.rb": 0.005526542663574219,
+ "spec/models/coordinator_fee_spec.rb": 0.1765611171722412,
+ "spec/models/customer_spec.rb": 0.7244162559509277,
+ "spec/models/enterprise_caching_spec.rb": 5.130070209503174,
+ "spec/models/enterprise_fee_spec.rb": 5.398890018463135,
+ "spec/models/enterprise_group_spec.rb": 0.4309041500091553,
+ "spec/models/enterprise_relationship_spec.rb": 11.179081439971924,
+ "spec/models/enterprise_spec.rb": 28.993456602096558,
+ "spec/models/exchange_fee_spec.rb": 0.31769680976867676,
+ "spec/models/exchange_spec.rb": 23.51863121986389,
+ "spec/models/inventory_item_spec.rb": 0.35733699798583984,
+ "spec/models/model_set_spec.rb": 1.2280616760253906,
+ "spec/models/order_cycle_spec.rb": 28.214235305786133,
+ "spec/models/producer_property_spec.rb": 3.207193613052368,
+ "spec/models/product_distribution_spec.rb": 4.137674570083618,
+ "spec/models/product_importer_spec.rb": 0.1319897174835205,
+ "spec/models/spree/ability_spec.rb": 23.034581184387207,
+ "spec/models/spree/addresses_spec.rb": 0.08480358123779297,
+ "spec/models/spree/adjustment_spec.rb": 19.734310150146484,
+ "spec/models/spree/calculator/flat_percent_item_total_spec.rb": 0.011153221130371094,
+ "spec/models/spree/calculator/flexi_rate_spec.rb": 0.016460657119750977,
+ "spec/models/spree/calculator/per_item_spec.rb": 0.006463527679443359,
+ "spec/models/spree/calculator/price_sack_spec.rb": 0.006432294845581055,
+ "spec/models/spree/classification_spec.rb": 0.8814013004302979,
+ "spec/models/spree/image_spec.rb": 3.7731142044067383,
+ "spec/models/spree/line_item_spec.rb": 24.783292293548584,
+ "spec/models/spree/option_type_spec.rb": 1.5399932861328125,
+ "spec/models/spree/option_value_spec.rb": 0.6168241500854492,
+ "spec/models/spree/order_populator_spec.rb": 1.8702630996704102,
+ "spec/models/spree/order_spec.rb": 13.248756647109985,
+ "spec/models/spree/payment_method_spec.rb": 0.5306015014648438,
+ "spec/models/spree/payment_spec.rb": 2.695707082748413,
+ "spec/models/spree/preference_spec.rb": 0.07149314880371094,
+ "spec/models/spree/preferences/file_configuration_spec.rb": 0.04694247245788574,
+ "spec/models/spree/price_spec.rb": 0.9263966083526611,
+ "spec/models/spree/product_property_spec.rb": 0.5712625980377197,
+ "spec/models/spree/product_spec.rb": 24.63173007965088,
+ "spec/models/spree/property_spec.rb": 9.745300054550171,
+ "spec/models/spree/shipping_method_spec.rb": 2.9844515323638916,
+ "spec/models/spree/tax_rate_spec.rb": 0.4417886734008789,
+ "spec/models/spree/taxon_spec.rb": 2.0731396675109863,
+ "spec/models/spree/user_spec.rb": 22.796449184417725,
+ "spec/models/spree/variant_spec.rb": 22.682554483413696,
+ "spec/models/tag_rule/filter_order_cycles_spec.rb": 0.2527964115142822,
+ "spec/models/tag_rule/filter_payment_methods_spec.rb": 0.36441707611083984,
+ "spec/models/tag_rule/filter_products_spec.rb": 0.249955415725708,
+ "spec/models/tag_rule/filter_shipping_methods_spec.rb": 1.038332462310791,
+ "spec/models/tag_rule_spec.rb": 0.06748843193054199,
+ "spec/models/variant_override_spec.rb": 7.998929977416992,
+ "spec/performance/injection_helper_spec.rb": 10.3869309425354,
+ "spec/performance/orders_controller_spec.rb": 0.029515743255615234,
+ "spec/performance/shop_controller_spec.rb": 20.927804470062256,
+ "spec/requests/large_request_spec.rb": 0.026319265365600586,
+ "spec/requests/shop_spec.rb": 1.9282338619232178,
+ "spec/serializers/admin/customer_serializer_spec.rb": 0.25053882598876953,
+ "spec/serializers/admin/enterprise_serializer_spec.rb": 0.07662534713745117,
+ "spec/serializers/admin/exchange_serializer_spec.rb": 4.449889421463013,
+ "spec/serializers/admin/for_order_cycle/enterprise_serializer_spec.rb": 1.3775944709777832,
+ "spec/serializers/admin/for_order_cycle/supplied_product_serializer_spec.rb": 0.6928744316101074,
+ "spec/serializers/admin/index_enterprise_serializer_spec.rb": 0.22203683853149414,
+ "spec/serializers/admin/variant_override_serializer_spec.rb": 0.3394124507904053,
+ "spec/serializers/enterprise_serializer_spec.rb": 0.8813695907592773,
+ "spec/serializers/order_serializer_spec.rb": 1.798943281173706,
+ "spec/serializers/orders_by_distributor_serializer_spec.rb": 5.19302225112915,
+ "spec/serializers/spree/product_serializer_spec.rb": 0.24654531478881836,
+ "spec/serializers/spree/variant_serializer_spec.rb": 0.8831884860992432
}
\ No newline at end of file
diff --git a/lib/open_food_network/available_payment_method_filter.rb b/lib/open_food_network/available_payment_method_filter.rb
new file mode 100644
index 0000000000..379beb22b5
--- /dev/null
+++ b/lib/open_food_network/available_payment_method_filter.rb
@@ -0,0 +1,23 @@
+module OpenFoodNetwork
+ class AvailablePaymentMethodFilter
+ def filter!(payment_methods)
+ if stripe_enabled?
+ payment_methods.reject!{ |p| p.type.ends_with?("StripeConnect") && stripe_configuration_incomplete?(p) }
+ else
+ payment_methods.reject!{ |p| p.type.ends_with?("StripeConnect") }
+ end
+ end
+
+ private
+
+ def stripe_enabled?
+ Spree::Config.stripe_connect_enabled && Stripe.publishable_key
+ end
+
+ def stripe_configuration_incomplete?(payment_method)
+ return true if payment_method.preferred_enterprise_id.zero?
+
+ payment_method.stripe_account_id.blank?
+ end
+ end
+end
diff --git a/lib/open_food_network/column_preference_defaults.rb b/lib/open_food_network/column_preference_defaults.rb
index 41eef63c09..6972fa3785 100644
--- a/lib/open_food_network/column_preference_defaults.rb
+++ b/lib/open_food_network/column_preference_defaults.rb
@@ -16,7 +16,7 @@ module OpenFoodNetwork
sku: { name: I18n.t("admin.sku"), visible: false },
price: { name: I18n.t("admin.price"), visible: true },
on_hand: { name: I18n.t("admin.on_hand"), visible: true },
- on_demand: { name: I18n.t("admin.on_demand?"), visible: false },
+ on_demand: { name: I18n.t("admin.on_demand?"), visible: true },
reset: { name: I18n.t("#{node}.enable_reset?"), visible: false },
inheritance: { name: I18n.t("#{node}.inherit?"), visible: false },
tags: { name: I18n.t("admin.tags"), visible: false },
@@ -64,7 +64,7 @@ module OpenFoodNetwork
unit: { name: I18n.t("#{node}.unit"), visible: true },
price: { name: I18n.t("admin.price"), visible: true },
on_hand: { name: I18n.t("admin.on_hand"), visible: true },
- on_demand: { name: I18n.t("admin.on_demand"), visible: false },
+ on_demand: { name: I18n.t("admin.on_demand"), visible: true },
category: { name: I18n.t("#{node}.category"), visible: false },
tax_category: { name: I18n.t("#{node}.tax_category"), visible: false },
inherits_properties: { name: I18n.t("#{node}.inherits_properties?"), visible: false },
diff --git a/lib/open_food_network/enterprise_fee_applicator.rb b/lib/open_food_network/enterprise_fee_applicator.rb
index e6d52e1749..e213204047 100644
--- a/lib/open_food_network/enterprise_fee_applicator.rb
+++ b/lib/open_food_network/enterprise_fee_applicator.rb
@@ -1,7 +1,7 @@
module OpenFoodNetwork
class EnterpriseFeeApplicator < Struct.new(:enterprise_fee, :variant, :role)
def create_line_item_adjustment(line_item)
- a = enterprise_fee.create_locked_adjustment(line_item_adjustment_label, line_item.order, line_item, true)
+ a = enterprise_fee.create_adjustment(line_item_adjustment_label, line_item.order, line_item, true)
AdjustmentMetadata.create! adjustment: a, enterprise: enterprise_fee.enterprise, fee_name: enterprise_fee.name, fee_type: enterprise_fee.fee_type, enterprise_role: role
@@ -9,7 +9,7 @@ module OpenFoodNetwork
end
def create_order_adjustment(order)
- a = enterprise_fee.create_locked_adjustment(order_adjustment_label, order, order, true)
+ a = enterprise_fee.create_adjustment(order_adjustment_label, order, order, true)
AdjustmentMetadata.create! adjustment: a, enterprise: enterprise_fee.enterprise, fee_name: enterprise_fee.name, fee_type: enterprise_fee.fee_type, enterprise_role: role
diff --git a/lib/open_food_network/enterprise_issue_validator.rb b/lib/open_food_network/enterprise_issue_validator.rb
index f755a6faf1..eccc5a4a68 100644
--- a/lib/open_food_network/enterprise_issue_validator.rb
+++ b/lib/open_food_network/enterprise_issue_validator.rb
@@ -1,7 +1,7 @@
module OpenFoodNetwork
class EnterpriseIssueValidator
include Rails.application.routes.url_helpers
- include Spree::Core::UrlHelpers
+ include Spree::TestingSupport::UrlHelpers
def initialize(enterprise)
@enterprise = enterprise
diff --git a/lib/open_food_network/users_and_enterprises_report.rb b/lib/open_food_network/users_and_enterprises_report.rb
index fd580393be..58cb9688c8 100644
--- a/lib/open_food_network/users_and_enterprises_report.rb
+++ b/lib/open_food_network/users_and_enterprises_report.rb
@@ -30,7 +30,7 @@ module OpenFoodNetwork
uae["sells"],
uae["visible"],
to_local_datetime(uae["confirmed_at"])
- ]
+ ]
end
end
diff --git a/lib/spree/core/calculated_adjustments_decorator.rb b/lib/spree/core/calculated_adjustments_decorator.rb
index 6a42eb68c5..592bf59854 100644
--- a/lib/spree/core/calculated_adjustments_decorator.rb
+++ b/lib/spree/core/calculated_adjustments_decorator.rb
@@ -1,13 +1,15 @@
module Spree
module Core
module CalculatedAdjustments
- module ClassMethods
- def calculated_adjustments_with_explicit_class_name
- calculated_adjustments_without_explicit_class_name
- # Class name is mis-inferred outside of Spree namespace
- has_one :calculator, as: :calculable, dependent: :destroy, class_name: 'Spree::Calculator'
+ class << self
+ def included_with_explicit_class_name(klass)
+ included_without_explicit_class_name(klass)
+
+ klass.class_eval do
+ has_one :calculator, as: :calculable, dependent: :destroy, class_name: 'Spree::Calculator'
+ end
end
- alias_method_chain :calculated_adjustments, :explicit_class_name
+ alias_method_chain :included, :explicit_class_name
end
end
end
diff --git a/lib/stripe/account_connector.rb b/lib/stripe/account_connector.rb
new file mode 100644
index 0000000000..9d0c8d3ddb
--- /dev/null
+++ b/lib/stripe/account_connector.rb
@@ -0,0 +1,60 @@
+# Encapsulation of logic used to handle the response from Stripe following an
+# attempt to connect an account to the instance using the OAuth Connection Flow
+# https://stripe.com/docs/connect/standard-accounts#oauth-flow
+
+module Stripe
+ class AccountConnector
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def create_account
+ return false if connection_cancelled_by_user?
+
+ raise StripeError, params["error_description"] unless params["code"]
+ raise CanCan::AccessDenied unless state.keys.include? "enterprise_id"
+
+ # Local authorisation issue, so request disconnection from Stripe
+ deauthorize unless user_has_permission_to_connect?
+
+ StripeAccount.create(
+ stripe_user_id: token.stripe_user_id,
+ stripe_publishable_key: token.stripe_publishable_key,
+ enterprise: enterprise
+ )
+ end
+
+ def connection_cancelled_by_user?
+ params[:action] == "connect_callback" && params[:error] == "access_denied"
+ end
+
+ def enterprise
+ @enterprise ||= Enterprise.find_by_permalink(state["enterprise_id"])
+ end
+
+ private
+
+ def state
+ # Returns the original payload
+ key = Openfoodnetwork::Application.config.secret_token
+ JWT.decode(params["state"], key, true, algorithm: 'HS256')[0]
+ end
+
+ def token
+ # Request an access token based on the code provided
+ @token ||= OAuth.token(grant_type: 'authorization_code', code: params["code"])
+ end
+
+ def deauthorize
+ OAuth.deauthorize(stripe_user_id: token.stripe_user_id)
+ raise CanCan::AccessDenied
+ end
+
+ def user_has_permission_to_connect?
+ user.enterprises.include?(enterprise) || user.admin?
+ end
+ end
+end
diff --git a/lib/stripe/profile_storer.rb b/lib/stripe/profile_storer.rb
new file mode 100644
index 0000000000..9b1579228d
--- /dev/null
+++ b/lib/stripe/profile_storer.rb
@@ -0,0 +1,63 @@
+# Encapsulation of logic used to convert a token generated by Stripe Elements
+# into a Stripe Customer + Card which can then be charged at a later point in time
+# Stores the generated customer & card ids against the local instance of Spree::CreditCard
+
+module Stripe
+ class ProfileStorer
+ def initialize(payment, provider)
+ @payment = payment
+ @provider = provider
+ end
+
+ def create_customer_from_token
+ token = @payment.source.gateway_payment_profile_id
+ response = @provider.store(token, options)
+
+ if response.success?
+ attrs = source_attrs_from(response)
+ @payment.source.update_attributes!(attrs)
+ else
+ @payment.send(:gateway_error, response.message)
+ end
+ end
+
+ private
+
+ def options
+ {
+ email: @payment.order.email,
+ login: Stripe.api_key,
+ address: address_for(@payment)
+ }
+ end
+
+ def address_for(payment)
+ {}.tap do |hash|
+ if address = payment.order.bill_address
+ hash = {
+ address1: address.address1,
+ address2: address.address2,
+ city: address.city,
+ zip: address.zipcode
+ }
+
+ if address.country
+ hash[:country] = address.country.name
+ end
+
+ if address.state
+ hash[:state] = address.state.name
+ end
+ end
+ end
+ end
+
+ def source_attrs_from(response)
+ {
+ cc_type: @payment.source.cc_type, # side-effect of update_source!
+ gateway_customer_profile_id: response.params['id'],
+ gateway_payment_profile_id: response.params['default_source'] || response.params['default_card']
+ }
+ end
+ end
+end
diff --git a/lib/stripe/webhook_handler.rb b/lib/stripe/webhook_handler.rb
new file mode 100644
index 0000000000..875dd9e52b
--- /dev/null
+++ b/lib/stripe/webhook_handler.rb
@@ -0,0 +1,34 @@
+module Stripe
+ class WebhookHandler
+ def initialize(event)
+ @event = event
+ end
+
+ def handle
+ return :unknown unless known_event?
+ send(event_mappings[@event.type])
+ end
+
+ private
+
+ def event_mappings
+ {
+ "account.application.deauthorized" => :deauthorize
+ }
+ end
+
+ def known_event?
+ event_mappings.keys.include? @event.type
+ end
+
+ def deauthorize
+ return :ignored unless @event.respond_to?(:account)
+ destroyed = destroy_stripe_accounts_linked_to(@event.account)
+ destroyed.any? ? :success : :ignored
+ end
+
+ def destroy_stripe_accounts_linked_to(account)
+ StripeAccount.where(stripe_user_id: account).destroy_all
+ end
+ end
+end
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index db5c2bad0c..cbbdc9d66a 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -9,6 +9,13 @@ namespace :openfoodnetwork do
require_relative '../../spec/support/spree/init'
task_name = "openfoodnetwork:dev:load_sample_data"
+ # -- MailMethod
+ # TODO: Remove me when in Spree 2.0. See http://guides.spreecommerce.org/release_notes/spree_2_0_0.html#mailmethod-model-no-longer-exists
+ Spree::MailMethod.create!(
+ environment: Rails.env,
+ preferred_mails_from: 'spree@example.com'
+ )
+
# -- Shipping / payment information
unless Spree::Zone.find_by_name 'Australia'
puts "[#{task_name}] Seeding shipping / payment information"
diff --git a/lib/tasks/karma.rake b/lib/tasks/karma.rake
index 3755dacc63..7cc245cfa4 100644
--- a/lib/tasks/karma.rake
+++ b/lib/tasks/karma.rake
@@ -1,28 +1,22 @@
+ENV["RAILS_ENV"] ||= 'test'
+
namespace :karma do
- task :start => :environment do |task|
- continue_only_in_test_env task
+ task :start => :environment do |_task|
with_tmp_config :start
end
- task :run => :environment do |task|
- continue_only_in_test_env task
+ task :run => :environment do |_task|
with_tmp_config :start, "--single-run"
end
private
- def continue_only_in_test_env task
- if Rails.env != 'test'
- raise "Task must be called in test environment:\n bundle exec rake #{task.name} RAILS_ENV=test"
- end
- end
-
def with_tmp_config(command, args = nil)
Tempfile.open('karma_unit.js', Rails.root.join('tmp') ) do |f|
f.write unit_js(application_spec_files << i18n_file)
f.flush
trap('SIGINT') { puts "Killing Karma"; exit }
- exec "karma #{command} #{f.path} #{args}"
+ exec "node_modules/.bin/karma #{command} #{f.path} #{args}"
end
end
diff --git a/public/embedded-group-preview.html b/public/embedded-group-preview.html
new file mode 100644
index 0000000000..a15a5c6a17
--- /dev/null
+++ b/public/embedded-group-preview.html
@@ -0,0 +1,20 @@
+
+ Embedded Group
+
+
+
+ This is a preview page for embedded groups.
+ Choose a group to display by copying its permalink id into the URL after the question mark.
+ Example: embedded-group-preview.html?flavour-crusader
+
+
+
+
+
+
+
diff --git a/spec/controllers/admin/bulk_line_items_controller_spec.rb b/spec/controllers/admin/bulk_line_items_controller_spec.rb
new file mode 100644
index 0000000000..da1f753dce
--- /dev/null
+++ b/spec/controllers/admin/bulk_line_items_controller_spec.rb
@@ -0,0 +1,258 @@
+require 'spec_helper'
+
+describe Admin::BulkLineItemsController do
+ include AuthenticationWorkflow
+
+ describe '#index' do
+ render_views
+
+ let(:line_item_attributes) { %i[id quantity max_quantity price supplier final_weight_volume units_product units_variant order] }
+ let!(:dist1) { FactoryGirl.create(:distributor_enterprise) }
+ let!(:order1) { FactoryGirl.create(:order, state: 'complete', completed_at: 1.day.ago, distributor: dist1, billing_address: FactoryGirl.create(:address) ) }
+ let!(:order2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.zone.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) }
+ let!(:order3) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.zone.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) }
+ let!(:line_item1) { FactoryGirl.create(:line_item, order: order1) }
+ let!(:line_item2) { FactoryGirl.create(:line_item, order: order2) }
+ let!(:line_item3) { FactoryGirl.create(:line_item, order: order2) }
+ let!(:line_item4) { FactoryGirl.create(:line_item, order: order3) }
+
+ context "as a normal user" do
+ before { controller.stub spree_current_user: create_enterprise_user }
+
+ it "should deny me access to the index action" do
+ spree_get :index, :format => :json
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "as an administrator" do
+ before do
+ controller.stub spree_current_user: quick_login_as_admin
+ end
+
+ context "when no ransack params are passed in" do
+ before do
+ spree_get :index, :format => :json
+ end
+
+ it "retrieves a list of line_items with appropriate attributes, including line items with appropriate attributes" do
+ keys = json_response.first.keys.map(&:to_sym)
+ line_item_attributes.all?{ |attr| keys.include? attr }.should == true
+ end
+
+ it "sorts line_items in ascending id line_item" do
+ ids = json_response.map{ |line_item| line_item['id'] }
+ ids[0].should < ids[1]
+ ids[1].should < ids[2]
+ end
+
+ it "formats final_weight_volume as a float" do
+ json_response.map{ |line_item| line_item['final_weight_volume'] }.all?{ |fwv| fwv.is_a?(Float) }.should == true
+ end
+
+ it "returns distributor object with id key" do
+ json_response.map{ |line_item| line_item['supplier'] }.all?{ |d| d.key?('id') }.should == true
+ end
+ end
+
+ context "when ransack params are passed in for line items" do
+ before do
+ spree_get :index, :format => :json, q: { order_id_eq: order2.id }
+ end
+
+ it "retrives a list of line items which match the criteria" do
+ expect(json_response.map{ |line_item| line_item['id'] }).to eq [line_item2.id, line_item3.id]
+ end
+ end
+
+ context "when ransack params are passed in for orders" do
+ before do
+ spree_get :index, :format => :json, q: { order: { completed_at_gt: 2.hours.ago } }
+ end
+
+ it "retrives a list of line items whose orders match the criteria" do
+ expect(json_response.map{ |line_item| line_item['id'] }).to eq [line_item2.id, line_item3.id, line_item4.id]
+ end
+ end
+ end
+
+ context "as an enterprise user" do
+ let(:supplier) { create(:supplier_enterprise) }
+ let(:distributor1) { create(:distributor_enterprise) }
+ let(:distributor2) { create(:distributor_enterprise) }
+ let(:coordinator) { create(:distributor_enterprise) }
+ let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) }
+ let!(:order1) { FactoryGirl.create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.zone.now, distributor: distributor1, billing_address: FactoryGirl.create(:address) ) }
+ let!(:line_item1) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) }
+ let!(:line_item2) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) }
+ let!(:order2) { FactoryGirl.create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.zone.now, distributor: distributor2, billing_address: FactoryGirl.create(:address) ) }
+ let!(:line_item3) { FactoryGirl.create(:line_item, order: order2, product: FactoryGirl.create(:product, supplier: supplier)) }
+
+ context "producer enterprise" do
+ before do
+ controller.stub spree_current_user: supplier.owner
+ spree_get :index, :format => :json
+ end
+
+ it "does not display line items for which my enterprise is a supplier" do
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "coordinator enterprise" do
+ before do
+ controller.stub spree_current_user: coordinator.owner
+ spree_get :index, :format => :json
+ end
+
+ it "retrieves a list of line_items" do
+ keys = json_response.first.keys.map(&:to_sym)
+ line_item_attributes.all?{ |attr| keys.include? attr }.should == true
+ end
+ end
+
+ context "hub enterprise" do
+ before do
+ controller.stub spree_current_user: distributor1.owner
+ spree_get :index, :format => :json
+ end
+
+ it "retrieves a list of line_items" do
+ keys = json_response.first.keys.map(&:to_sym)
+ line_item_attributes.all?{ |attr| keys.include? attr }.should == true
+ end
+ end
+ end
+ end
+
+ describe '#update' do
+ let(:supplier) { create(:supplier_enterprise) }
+ let(:distributor1) { create(:distributor_enterprise) }
+ let(:coordinator) { create(:distributor_enterprise) }
+ let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) }
+ let!(:order1) { FactoryGirl.create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.zone.now, distributor: distributor1, billing_address: FactoryGirl.create(:address) ) }
+ let!(:line_item1) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) }
+ let(:line_item_params) { { quantity: 3, final_weight_volume: 3000, price: 3.00 } }
+ let(:params) { { id: line_item1.id, order_id: order1.number, line_item: line_item_params } }
+
+ context "as an enterprise user" do
+ context "producer enterprise" do
+ before do
+ controller.stub spree_current_user: supplier.owner
+ spree_put :update, params
+ end
+
+ it "does not allow access" do
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "coordinator enterprise" do
+ render_views
+
+ before do
+ controller.stub spree_current_user: coordinator.owner
+ end
+
+ # Used in admin/orders/bulk_management
+ context 'when the request is JSON (angular)' do
+ before { params[:format] = :json }
+
+ it "updates the line item" do
+ spree_put :update, params
+ line_item1.reload
+ expect(line_item1.quantity).to eq 3
+ expect(line_item1.final_weight_volume).to eq 3000
+ expect(line_item1.price).to eq 3.00
+ end
+
+ it "returns an empty JSON response" do
+ spree_put :update, params
+ expect(response.body).to eq ' '
+ end
+
+ it 'returns a 204 response' do
+ spree_put :update, params
+ expect(response.status).to eq 204
+ end
+
+ it 'applies enterprise fees locking the order with an exclusive row lock' do
+ allow(Spree::LineItem)
+ .to receive(:find).with(line_item1.id.to_s).and_return(line_item1)
+
+ expect(line_item1.order).to receive(:reload).with(lock: true)
+ expect(line_item1.order).to receive(:update_distribution_charge!)
+
+ spree_put :update, params
+ end
+
+ context 'when the line item params are not correct' do
+ let(:line_item_params) { { price: 'hola' } }
+ let(:errors) { { 'price' => ['is not a number'] } }
+
+ it 'returns a JSON with the errors' do
+ spree_put :update, params
+ expect(JSON.parse(response.body)['errors']).to eq(errors)
+ end
+
+ it 'returns a 412 response' do
+ spree_put :update, params
+ expect(response.status).to eq 412
+ end
+ end
+ end
+ end
+
+ context "hub enterprise" do
+ before do
+ controller.stub spree_current_user: distributor1.owner
+ xhr :put, :update, params
+ end
+
+ it "updates the line item" do
+ line_item1.reload
+ expect(line_item1.quantity).to eq 3
+ expect(line_item1.final_weight_volume).to eq 3000
+ expect(line_item1.price).to eq 3.00
+ end
+ end
+ end
+ end
+
+ describe '#destroy' do
+ render_views
+
+ let(:supplier) { create(:supplier_enterprise) }
+ let(:distributor1) { create(:distributor_enterprise) }
+ let(:coordinator) { create(:distributor_enterprise) }
+ let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) }
+ let!(:order1) { FactoryGirl.create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.zone.now, distributor: distributor1, billing_address: FactoryGirl.create(:address) ) }
+ let!(:line_item1) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) }
+ let(:params) { { id: line_item1.id, order_id: order1.number } }
+
+ before do
+ controller.stub spree_current_user: coordinator.owner
+ end
+
+ # Used in admin/orders/bulk_management
+ context 'when the request is JSON (angular)' do
+ before { params[:format] = :json }
+
+ it 'destroys the line item' do
+ expect {
+ spree_delete :destroy, params
+ }.to change { Spree::LineItem.where(id: line_item1).count }.from(1).to(0)
+ end
+
+ it 'returns an empty JSON response' do
+ spree_delete :destroy, params
+ expect(response.body).to eq ' '
+ end
+
+ it 'returns a 204 response' do
+ spree_delete :destroy, params
+ expect(response.status).to eq 204
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/enterprises_controller_spec.rb b/spec/controllers/admin/enterprises_controller_spec.rb
index 94282e6c2c..5e2300d122 100644
--- a/spec/controllers/admin/enterprises_controller_spec.rb
+++ b/spec/controllers/admin/enterprises_controller_spec.rb
@@ -144,7 +144,6 @@ module Admin
expect(distributor.users).to_not include user
end
-
describe "enterprise properties" do
let(:producer) { create(:enterprise) }
let!(:property) { create(:property, name: "A nice name") }
diff --git a/spec/controllers/admin/stripe_accounts_controller_spec.rb b/spec/controllers/admin/stripe_accounts_controller_spec.rb
new file mode 100644
index 0000000000..9a6854ae85
--- /dev/null
+++ b/spec/controllers/admin/stripe_accounts_controller_spec.rb
@@ -0,0 +1,167 @@
+require 'spec_helper'
+
+describe Admin::StripeAccountsController, type: :controller do
+ let(:enterprise) { create(:distributor_enterprise) }
+
+ before do
+ allow(Stripe).to receive(:client_id) { "some_id" }
+ end
+
+ describe "#connect" do
+ before do
+ allow(controller).to receive(:spree_current_user) { enterprise.owner }
+ end
+
+ it "redirects to Stripe Authorization url constructed OAuth" do
+ spree_get :connect
+ expect(response.location).to match %r(\Ahttps://connect.stripe.com)
+ uri = URI.parse(response.location)
+ params = CGI.parse(uri.query)
+ expect(params.keys).to include 'client_id', 'response_type', 'state', 'scope'
+ end
+ end
+
+ describe "#destroy" do
+ let(:params) { { format: :json, id: "some_id" } }
+
+ context "when the specified stripe account doesn't exist" do
+ it "raises an error?" do
+ spree_delete :destroy, params
+ end
+ end
+
+ context "when the specified stripe account exists" do
+ let(:stripe_account) { create(:stripe_account, enterprise: enterprise) }
+
+ before do
+ # So that we can stub #deauthorize_and_destroy
+ allow(StripeAccount).to receive(:find) { stripe_account }
+ params[:id] = stripe_account.id
+ end
+
+ context "when I don't manage the enterprise linked to the stripe account" do
+ let(:some_user) { create(:user) }
+
+ before { allow(controller).to receive(:spree_current_user) { some_user } }
+
+ it "redirects to unauthorized" do
+ spree_delete :destroy, params
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "when I manage the enterprise linked to the stripe account" do
+ before { allow(controller).to receive(:spree_current_user) { enterprise.owner } }
+
+ context "and the attempt to deauthorize_and_destroy succeeds" do
+ before { allow(stripe_account).to receive(:deauthorize_and_destroy) { stripe_account } }
+
+ it "redirects to unauthorized" do
+ spree_delete :destroy, params
+ expect(response).to redirect_to edit_admin_enterprise_path(enterprise)
+ expect(flash[:success]).to eq "Stripe account disconnected."
+ end
+ end
+
+ context "and the attempt to deauthorize_and_destroy fails" do
+ before { allow(stripe_account).to receive(:deauthorize_and_destroy) { false } }
+
+ it "redirects to unauthorized" do
+ spree_delete :destroy, params
+ expect(response).to redirect_to edit_admin_enterprise_path(enterprise)
+ expect(flash[:error]).to eq "Failed to disconnect Stripe."
+ end
+ end
+ end
+ end
+ end
+
+ describe "#status" do
+ let(:params) { { format: :json, enterprise_id: enterprise.id } }
+
+ before do
+ allow(Stripe).to receive(:api_key) { "sk_test_12345" }
+ Spree::Config.set(stripe_connect_enabled: false)
+ end
+
+ context "when I don't manage the specified enterprise" do
+ let(:user) { create(:user) }
+
+ before do
+ allow(controller).to receive(:spree_current_user) { user }
+ end
+
+ it "redirects to unauthorized" do
+ spree_get :status, params
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "when I manage the specified enterprise" do
+ before do
+ allow(controller).to receive(:spree_current_user) { enterprise.owner }
+ end
+
+ context "when Stripe is not enabled" do
+ it "returns with a status of 'stripe_disabled'" do
+ spree_get :status, params
+ json_response = JSON.parse(response.body)
+ expect(json_response["status"]).to eq "stripe_disabled"
+ end
+ end
+
+ context "when Stripe is enabled" do
+ before { Spree::Config.set(stripe_connect_enabled: true) }
+
+ context "when no stripe account is associated with the specified enterprise" do
+ it "returns with a status of 'account_missing'" do
+ spree_get :status, params
+ json_response = JSON.parse(response.body)
+ expect(json_response["status"]).to eq "account_missing"
+ end
+ end
+
+ context "when a stripe account is associated with the specified enterprise" do
+ let!(:account) { create(:stripe_account, stripe_user_id: "acc_123", enterprise: enterprise) }
+
+ context "but access has been revoked or does not exist on stripe's servers" do
+ before do
+ stub_request(:get, "https://api.stripe.com/v1/accounts/acc_123").to_return(status: 404)
+ end
+
+ it "returns with a status of 'access_revoked'" do
+ spree_get :status, params
+ json_response = JSON.parse(response.body)
+ expect(json_response["status"]).to eq "access_revoked"
+ end
+ end
+
+ context "which is connected" do
+ let(:stripe_account_mock) do
+ {
+ id: "acc_123",
+ business_name: "My Org",
+ charges_enabled: true,
+ some_other_attr: "something"
+ }
+ end
+
+ before do
+ stub_request(:get, "https://api.stripe.com/v1/accounts/acc_123").to_return(body: JSON.generate(stripe_account_mock))
+ end
+
+ it "returns with a status of 'connected'" do
+ spree_get :status, params
+ json_response = JSON.parse(response.body)
+ expect(json_response["status"]).to eq "connected"
+ # serializes required attrs
+ expect(json_response["business_name"]).to eq "My Org"
+ # ignores other attrs
+ expect(json_response["some_other_attr"]).to be nil
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/stripe_connect_settings_controller_spec.rb b/spec/controllers/admin/stripe_connect_settings_controller_spec.rb
new file mode 100644
index 0000000000..7d0b47974e
--- /dev/null
+++ b/spec/controllers/admin/stripe_connect_settings_controller_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe Admin::StripeConnectSettingsController, type: :controller do
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin_user) }
+
+ before do
+ Spree::Config.set(stripe_connect_enabled: true)
+ end
+
+ describe "edit" do
+ context "as an enterprise user" do
+ before { allow(controller).to receive(:spree_current_user) { user } }
+
+ it "does not allow access" do
+ spree_get :edit
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "as super admin" do
+ before { allow(controller).to receive(:spree_current_user) { admin } }
+
+ context "when a Stripe API key is not set" do
+ before do
+ allow(Stripe).to receive(:api_key) { nil }
+ end
+
+ it "sets the account status to :empty_api_key" do
+ spree_get :edit
+ expect(assigns(:stripe_account)[:status]).to eq :empty_api_key
+ expect(assigns(:settings).stripe_connect_enabled).to be true
+ end
+ end
+
+ context "when a Stripe API key is set" do
+ before do
+ allow(Stripe).to receive(:api_key) { "sk_test_xxxx" }
+ end
+
+ context "and the request to retrieve Stripe account info fails" do
+ before do
+ stub_request(:get, "https://api.stripe.com/v1/account").
+ to_return(:status => 401, :body => "{\"error\": {\"message\": \"Invalid API Key provided: sk_test_****xxxx\"}}")
+ end
+
+ it "sets the account status to :auth_fail" do
+ spree_get :edit
+ expect(assigns(:stripe_account)[:status]).to eq :auth_fail
+ expect(assigns(:settings).stripe_connect_enabled).to be true
+ end
+ end
+
+ context "and the request to retrieve Stripe account info succeeds" do
+ before do
+ stub_request(:get, "https://api.stripe.com/v1/account").
+ to_return(:status => 200, :body => "{ \"id\": \"acct_1234\", \"business_name\": \"OFN\" }")
+ end
+
+ it "sets the account status to :ok, loads settings into Struct" do
+ spree_get :edit
+ expect(assigns(:stripe_account)[:status]).to eq :ok
+ expect(assigns(:obfuscated_secret_key)).to eq "sk_test_****xxxx"
+ expect(assigns(:settings).stripe_connect_enabled).to be true
+ end
+ end
+ end
+ end
+ end
+
+ describe "update" do
+ let(:params) { { settings: { stripe_connect_enabled: false } } }
+
+ context "as an enterprise user" do
+ before { allow(controller).to receive(:spree_current_user) { user } }
+
+ it "does not allow access" do
+ spree_get :update, params
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "as super admin" do
+ before { allow(controller).to receive(:spree_current_user) { admin } }
+
+ it "sets global config to the specified values" do
+ expect(Spree::Config.stripe_connect_enabled).to be true
+ spree_get :update, params
+ expect(Spree::Config.stripe_connect_enabled).to be false
+ end
+ end
+ end
+end
diff --git a/spec/controllers/checkout_controller_spec.rb b/spec/controllers/checkout_controller_spec.rb
index 1f5e40a841..70332b5ea5 100644
--- a/spec/controllers/checkout_controller_spec.rb
+++ b/spec/controllers/checkout_controller_spec.rb
@@ -4,6 +4,8 @@ describe CheckoutController do
let(:distributor) { double(:distributor) }
let(:order_cycle) { create(:simple_order_cycle) }
let(:order) { create(:order) }
+ let(:reset_order_service) { double(ResetOrderService) }
+
before do
order.stub(:checkout_allowed?).and_return true
controller.stub(:check_authorization).and_return true
@@ -67,14 +69,14 @@ describe CheckoutController do
it "clears the ship address when re-rendering edit" do
controller.should_receive(:clear_ship_address).and_return true
order.stub(:update_attributes).and_return false
- spree_post :update, order: {}
+ spree_post :update, format: :json, order: {}
end
it "clears the ship address when the order state cannot be advanced" do
controller.should_receive(:clear_ship_address).and_return true
order.stub(:update_attributes).and_return true
order.stub(:next).and_return false
- spree_post :update, order: {}
+ spree_post :update, format: :json, order: {}
end
it "only clears the ship address with a pickup shipping method" do
@@ -82,6 +84,44 @@ describe CheckoutController do
order.should_receive(:ship_address=)
controller.send(:clear_ship_address)
end
+
+ context 'when completing the order' do
+ before do
+ order.state = 'complete'
+ allow(order).to receive(:update_attributes).and_return(true)
+ allow(order).to receive(:next).and_return(true)
+ allow(order).to receive(:set_distributor!).and_return(true)
+ end
+
+ it "sets the new order's token to the same as the old order" do
+ order = controller.current_order(true)
+ spree_post :update, order: {}
+ expect(controller.current_order.token).to eq order.token
+ end
+
+ it 'expires the current order' do
+ allow(controller).to receive(:expire_current_order)
+ put :update, order: {}
+ expect(controller).to have_received(:expire_current_order)
+ end
+
+ it 'sets the access_token of the session' do
+ put :update, order: {}
+ expect(session[:access_token]).to eq(controller.current_order.token)
+ end
+ end
+ end
+
+ describe '#expire_current_order' do
+ it 'empties the order_id of the session' do
+ expect(session).to receive(:[]=).with(:order_id, nil)
+ controller.expire_current_order
+ end
+
+ it 'resets the @current_order ivar' do
+ controller.expire_current_order
+ expect(controller.instance_variable_get(:@current_order)).to be_nil
+ end
end
context "via xhr" do
@@ -93,7 +133,7 @@ describe CheckoutController do
end
it "returns errors" do
- xhr :post, :update, order: {}, use_route: :spree
+ spree_post :update, format: :json, order: {}
response.status.should == 400
response.body.should == {errors: assigns[:order].errors, flash: {}}.to_json
end
@@ -101,21 +141,27 @@ describe CheckoutController do
it "returns flash" do
order.stub(:update_attributes).and_return true
order.stub(:next).and_return false
- xhr :post, :update, order: {}, use_route: :spree
+ spree_post :update, format: :json, order: {}
response.body.should == {errors: assigns[:order].errors, flash: {error: "Payment could not be processed, please check the details you entered"}}.to_json
end
it "returns order confirmation url on success" do
+ allow(ResetOrderService).to receive(:new).with(controller, order) { reset_order_service }
+ expect(reset_order_service).to receive(:call)
+
order.stub(:update_attributes).and_return true
order.stub(:state).and_return "complete"
- xhr :post, :update, order: {}, use_route: :spree
+ spree_post :update, format: :json, order: {}
response.status.should == 200
response.body.should == {path: spree.order_path(order)}.to_json
end
describe "stale object handling" do
it "retries when a stale object error is encountered" do
+ allow(ResetOrderService).to receive(:new).with(controller, order) { reset_order_service }
+ expect(reset_order_service).to receive(:call)
+
order.stub(:update_attributes).and_return true
controller.stub(:state_callback)
@@ -127,7 +173,7 @@ describe CheckoutController do
true
end
- xhr :post, :update, order: {}, use_route: :spree
+ spree_post :update, format: :json, order: {}
response.status.should == 200
end
@@ -135,7 +181,7 @@ describe CheckoutController do
order.stub(:update_attributes).and_return true
order.stub(:next) { raise ActiveRecord::StaleObjectError.new(Spree::Variant.new, 'update') }
- xhr :post, :update, order: {}, use_route: :spree
+ spree_post :update, format: :json, order: {}
response.status.should == 400
end
end
@@ -144,16 +190,72 @@ describe CheckoutController do
describe "Paypal routing" do
let(:payment_method) { create(:payment_method, type: "Spree::Gateway::PayPalExpress") }
before do
- controller.stub(:current_distributor).and_return(distributor)
- controller.stub(:current_order_cycle).and_return(order_cycle)
- controller.stub(:current_order).and_return(order)
+ allow(controller).to receive(:current_distributor) { distributor }
+ allow(controller).to receive(:current_order_cycle) { order_cycle }
+ allow(controller).to receive(:current_order) { order }
+ allow(controller).to receive(:restart_checkout)
end
it "should check the payment method for Paypalness if we've selected one" do
- Spree::PaymentMethod.should_receive(:find).with(payment_method.id.to_s).and_return payment_method
- order.stub(:update_attributes).and_return true
- order.stub(:state).and_return "payment"
+ expect(Spree::PaymentMethod).to receive(:find).with(payment_method.id.to_s) { payment_method }
+ allow(order).to receive(:update_attributes) { true }
+ allow(order).to receive(:state) { "payment" }
spree_post :update, order: {payments_attributes: [{payment_method_id: payment_method.id}]}
end
end
+
+ describe "#update_failed" do
+ before do
+ controller.instance_variable_set(:@order, order)
+ end
+
+ it "clears the shipping address and restarts the checkout" do
+ expect(controller).to receive(:clear_ship_address)
+ expect(controller).to receive(:restart_checkout)
+ expect(controller).to receive(:respond_to)
+ controller.send(:update_failed)
+ end
+ end
+
+ describe "#restart_checkout" do
+ let!(:shipment_pending) { create(:shipment, order: order, state: 'pending') }
+ let!(:payment_checkout) { create(:payment, order: order, state: 'checkout') }
+ let!(:payment_failed) { create(:payment, order: order, state: 'failed') }
+
+ before do
+ order.update_attribute(:shipping_method_id, shipment_pending.shipping_method_id)
+ controller.instance_variable_set(:@order, order.reload)
+ end
+
+ context "when the order is already in the 'cart' state" do
+ it "does nothing" do
+ expect(order).to_not receive(:restart_checkout!)
+ controller.send(:restart_checkout)
+ end
+ end
+
+ context "when the order is in a subsequent state" do
+ before do
+ order.update_attribute(:state, "payment")
+ end
+
+ # NOTE: at the time of writing, it was not possible to create a shipment with a state other than
+ # 'pending' when the order has not been completed, so this is not a case that requires testing.
+ it "resets the order state, and clears incomplete shipments and payments" do
+ expect(order).to receive(:restart_checkout!).and_call_original
+ expect(order.shipping_method_id).to_not be nil
+ expect(order.shipments.count).to be 1
+ expect(order.adjustments.shipping.count).to be 1
+ expect(order.payments.count).to be 2
+ expect(order.adjustments.payment_fee.count).to be 2
+ controller.send(:restart_checkout)
+ expect(order.reload.state).to eq 'cart'
+ expect(order.shipping_method_id).to be nil
+ expect(order.shipments.count).to be 0
+ expect(order.adjustments.shipping.count).to be 0
+ expect(order.payments.count).to be 1
+ expect(order.adjustments.payment_fee.count).to be 1
+ end
+ end
+ end
end
diff --git a/spec/controllers/line_items_controller_spec.rb b/spec/controllers/line_items_controller_spec.rb
index 49c7a15742..db9ebabe8e 100644
--- a/spec/controllers/line_items_controller_spec.rb
+++ b/spec/controllers/line_items_controller_spec.rb
@@ -35,6 +35,9 @@ describe LineItemsController do
item
end
+ let(:order) { item.order }
+ let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], variants: [order.line_item_variants]) }
+
before { controller.stub spree_current_user: item.order.user }
context "without a line item id" do
@@ -55,42 +58,32 @@ describe LineItemsController do
end
context "where the item's order is associated with the current user" do
- before { item.order.update_attributes(user_id: user.id) }
+ before { order.update_attributes!(user_id: user.id) }
- context "without an order cycle" do
+ context "without an order cycle or distributor" do
it "denies deletion" do
delete :destroy, params
expect(response.status).to eq 403
end
end
- context "with an order cycle" do
- before { item.order.update_attributes(order_cycle_id: order_cycle.id) }
+ context "with an order cycle and distributor" do
+ before { order.update_attributes!(order_cycle_id: order_cycle.id, distributor_id: distributor.id) }
- context "without a distributor" do
+ context "where changes are not allowed" do
it "denies deletion" do
delete :destroy, params
expect(response.status).to eq 403
end
end
- context "where the item's order has a distributor" do
- before { item.order.update_attributes(distributor_id: distributor.id) }
- context "where changes are not allowed" do
- it "denies deletion" do
- delete :destroy, params
- expect(response.status).to eq 403
- end
- end
+ context "where changes are allowed" do
+ before { distributor.update_attributes!(allow_order_changes: true) }
- context "where changes are allowed" do
- before { distributor.update_attributes(allow_order_changes: true) }
-
- it "deletes the line item" do
- delete :destroy, params
- expect(response.status).to eq 204
- expect { item.reload }.to raise_error ActiveRecord::RecordNotFound
- end
+ it "deletes the line item" do
+ delete :destroy, params
+ expect(response.status).to eq 204
+ expect { item.reload }.to raise_error ActiveRecord::RecordNotFound
end
end
end
@@ -127,7 +120,7 @@ describe LineItemsController do
order.shipments.last.reload
expect(order.adjustment_total).to eq initial_fees - shipping_fee - payment_fee
expect(order.shipments.last.adjustment.amount).to eq shipping_fee
- expect(order.payment.adjustment.amount).to eq payment_fee
+ expect(order.payments.first.adjustment.amount).to eq payment_fee
expect(order.shipments.last.adjustment.included_tax).to eq 0.6
end
end
diff --git a/spec/controllers/spree/admin/line_items_controller_spec.rb b/spec/controllers/spree/admin/line_items_controller_spec.rb
index 907bec2bbb..b8b6cd2785 100644
--- a/spec/controllers/spree/admin/line_items_controller_spec.rb
+++ b/spec/controllers/spree/admin/line_items_controller_spec.rb
@@ -3,129 +3,6 @@ require 'spec_helper'
describe Spree::Admin::LineItemsController do
include AuthenticationWorkflow
- describe "#index" do
- render_views
-
- let(:line_item_attributes) { [:id, :quantity, :max_quantity, :price, :supplier, :final_weight_volume, :units_product, :units_variant, :order] }
- let!(:dist1) { FactoryGirl.create(:distributor_enterprise) }
- let!(:order1) { FactoryGirl.create(:order, state: 'complete', completed_at: 1.day.ago, distributor: dist1, billing_address: FactoryGirl.create(:address) ) }
- let!(:order2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.zone.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) }
- let!(:order3) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.zone.now, distributor: dist1, billing_address: FactoryGirl.create(:address) ) }
- let!(:line_item1) { FactoryGirl.create(:line_item, order: order1) }
- let!(:line_item2) { FactoryGirl.create(:line_item, order: order2) }
- let!(:line_item3) { FactoryGirl.create(:line_item, order: order2) }
- let!(:line_item4) { FactoryGirl.create(:line_item, order: order3) }
-
- context "as a normal user" do
- before { controller.stub spree_current_user: create_enterprise_user }
-
- it "should deny me access to the index action" do
- spree_get :index, :format => :json
- expect(response).to redirect_to spree.unauthorized_path
- end
- end
-
- context "as an administrator" do
-
- before do
- controller.stub spree_current_user: quick_login_as_admin
- end
-
- context "when no ransack params are passed in" do
- before do
- spree_get :index, :format => :json
- end
-
- it "retrieves a list of line_items with appropriate attributes, including line items with appropriate attributes" do
- keys = json_response.first.keys.map{ |key| key.to_sym }
- line_item_attributes.all?{ |attr| keys.include? attr }.should == true
- end
-
- it "sorts line_items in ascending id line_item" do
- ids = json_response.map{ |line_item| line_item['id'] }
- ids[0].should < ids[1]
- ids[1].should < ids[2]
- end
-
- it "formats final_weight_volume as a float" do
- json_response.map{ |line_item| line_item['final_weight_volume'] }.all?{ |fwv| fwv.is_a?(Float) }.should == true
- end
-
- it "returns distributor object with id key" do
- json_response.map{ |line_item| line_item['supplier'] }.all?{ |d| d.has_key?('id') }.should == true
- end
- end
-
- context "when ransack params are passed in for line items" do
- before do
- spree_get :index, :format => :json, q: { order_id_eq: order2.id }
- end
-
- it "retrives a list of line items which match the criteria" do
- expect(json_response.map{ |line_item| line_item['id'] }).to eq [line_item2.id, line_item3.id]
- end
- end
-
- context "when ransack params are passed in for orders" do
- before do
- spree_get :index, :format => :json, q: { order: { completed_at_gt: 2.hours.ago } }
- end
-
- it "retrives a list of line items whose orders match the criteria" do
- expect(json_response.map{ |line_item| line_item['id'] }).to eq [line_item2.id, line_item3.id, line_item4.id]
- end
- end
- end
-
- context "as an enterprise user" do
- let(:supplier) { create(:supplier_enterprise) }
- let(:distributor1) { create(:distributor_enterprise) }
- let(:distributor2) { create(:distributor_enterprise) }
- let(:coordinator) { create(:distributor_enterprise) }
- let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) }
- let!(:order1) { FactoryGirl.create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.zone.now, distributor: distributor1, billing_address: FactoryGirl.create(:address) ) }
- let!(:line_item1) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) }
- let!(:line_item2) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) }
- let!(:order2) { FactoryGirl.create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.zone.now, distributor: distributor2, billing_address: FactoryGirl.create(:address) ) }
- let!(:line_item3) { FactoryGirl.create(:line_item, order: order2, product: FactoryGirl.create(:product, supplier: supplier)) }
-
- context "producer enterprise" do
- before do
- controller.stub spree_current_user: supplier.owner
- spree_get :index, :format => :json
- end
-
- it "does not display line items for which my enterprise is a supplier" do
- expect(response).to redirect_to spree.unauthorized_path
- end
- end
-
- context "coordinator enterprise" do
- before do
- controller.stub spree_current_user: coordinator.owner
- spree_get :index, :format => :json
- end
-
- it "retrieves a list of line_items" do
- keys = json_response.first.keys.map{ |key| key.to_sym }
- line_item_attributes.all?{ |attr| keys.include? attr }.should == true
- end
- end
-
- context "hub enterprise" do
- before do
- controller.stub spree_current_user: distributor1.owner
- spree_get :index, :format => :json
- end
-
- it "retrieves a list of line_items" do
- keys = json_response.first.keys.map{ |key| key.to_sym }
- line_item_attributes.all?{ |attr| keys.include? attr }.should == true
- end
- end
- end
- end
-
describe "#create" do
let!(:variant) { create(:variant, price: 88) }
let!(:vo) { create(:variant_override, hub: distributor, variant: variant, price: 11.11) }
@@ -143,14 +20,15 @@ describe Spree::Admin::LineItemsController do
end
end
- describe "#update" do
+ describe '#update' do
let(:supplier) { create(:supplier_enterprise) }
let(:distributor1) { create(:distributor_enterprise) }
let(:coordinator) { create(:distributor_enterprise) }
let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator) }
let!(:order1) { FactoryGirl.create(:order, order_cycle: order_cycle, state: 'complete', completed_at: Time.zone.now, distributor: distributor1, billing_address: FactoryGirl.create(:address) ) }
let!(:line_item1) { FactoryGirl.create(:line_item, order: order1, product: FactoryGirl.create(:product, supplier: supplier)) }
- let(:params) { { format: :json, id: line_item1.id, order_id: order1.number, line_item: { quantity: 3, final_weight_volume: 3000, price: 3.00 } } }
+ let(:line_item_params) { { quantity: 3, final_weight_volume: 3000, price: 3.00 } }
+ let(:params) { { id: line_item1.id, order_id: order1.number, line_item: line_item_params } }
context "as an enterprise user" do
context "producer enterprise" do
@@ -165,26 +43,65 @@ describe Spree::Admin::LineItemsController do
end
context "coordinator enterprise" do
+ render_views
+
before do
controller.stub spree_current_user: coordinator.owner
- spree_put :update, params
end
- it "updates the line_item" do
- line_item1.reload
- expect(line_item1.quantity).to eq 3
- expect(line_item1.final_weight_volume).to eq 3000
- expect(line_item1.price).to eq 3.00
+ # Used in admin/orders/edit
+ context 'when the request is JS/XHR (jquery-rails gem)' do
+ it "updates the line item" do
+ xhr :put, :update, params
+ line_item1.reload
+ expect(line_item1.quantity).to eq 3
+ expect(line_item1.final_weight_volume).to eq 3000
+ expect(line_item1.price).to eq 3.00
+ end
+
+ it "returns an empty JSON response" do
+ xhr :put, :update, params
+ expect(response.body).to eq ' '
+ end
+
+ it 'returns a 204 response' do
+ xhr :put, :update, params
+ expect(response.status).to eq 204
+ end
+
+ context 'when the line item params are not correct' do
+ let(:line_item_params) { { price: 'hola' } }
+ let(:errors) { { 'price' => ['is not a number'] } }
+
+ it 'returns a JSON with the errors' do
+ xhr :put, :update, params
+ expect(JSON.parse(response.body)['errors']).to eq(errors)
+ end
+
+ it 'returns a 412 response' do
+ xhr :put, :update, params
+ expect(response.status).to eq 412
+ end
+ end
+ end
+
+ context 'when the request is HTML' do
+ before { params[:format] = :html }
+
+ it 'returns an HTML response with the order form' do
+ spree_put :update, params
+ expect(response.body).to match(/admin_order_form_fields/)
+ end
end
end
context "hub enterprise" do
before do
controller.stub spree_current_user: distributor1.owner
- spree_put :update, params
+ xhr :put, :update, params
end
- it "retrieves a list of line_items" do
+ it "updates the line item" do
line_item1.reload
expect(line_item1.quantity).to eq 3
expect(line_item1.final_weight_volume).to eq 3000
diff --git a/spec/controllers/spree/admin/orders_controller_spec.rb b/spec/controllers/spree/admin/orders_controller_spec.rb
index 5ecc393dab..60bdf9a3d9 100644
--- a/spec/controllers/spree/admin/orders_controller_spec.rb
+++ b/spec/controllers/spree/admin/orders_controller_spec.rb
@@ -179,6 +179,12 @@ describe Spree::Admin::OrdersController do
context "when the distributor's ABN has been set" do
before { distributor.update_attribute(:abn, "123") }
+ before do
+ Spree::MailMethod.create!(
+ environment: Rails.env,
+ preferred_mails_from: 'spree@example.com'
+ )
+ end
it "should allow me to send order invoices" do
expect do
spree_get :invoice, params
diff --git a/spec/controllers/spree/admin/payment_methods_controller_spec.rb b/spec/controllers/spree/admin/payment_methods_controller_spec.rb
index c27fe16f19..681d7c5309 100644
--- a/spec/controllers/spree/admin/payment_methods_controller_spec.rb
+++ b/spec/controllers/spree/admin/payment_methods_controller_spec.rb
@@ -1,6 +1,61 @@
require 'spec_helper'
describe Spree::Admin::PaymentMethodsController do
+ describe "#update" do
+ context "on a StripeConnect payment method" do
+ let!(:user) { create(:user, enterprise_limit: 2) }
+ let!(:enterprise1) { create(:distributor_enterprise, owner: user) }
+ let!(:enterprise2) { create(:distributor_enterprise, owner: create(:user)) }
+ let!(:payment_method) { create(:stripe_payment_method, distributor_ids: [enterprise1.id, enterprise2.id], preferred_enterprise_id: enterprise2.id) }
+
+ before { allow(controller).to receive(:spree_current_user) { user } }
+
+ context "when an attempt is made to change the stripe account holder (preferred_enterprise_id)" do
+ let(:params) { { id: payment_method.id, payment_method: { type: "Spree::Gateway::StripeConnect", preferred_enterprise_id: enterprise1.id } } }
+
+ context "as a user that does not manage the existing stripe account holder" do
+ it "prevents the stripe account holder from being updated" do
+ spree_put :update, params
+ expect(payment_method.reload.preferred_enterprise_id).to eq enterprise2.id
+ end
+ end
+
+ context "as a user that manages the existing stripe account holder" do
+ before { enterprise2.update_attributes!(owner_id: user.id) }
+
+ it "allows the stripe account holder to be updated" do
+ spree_put :update, params
+ expect(payment_method.reload.preferred_enterprise_id).to eq enterprise1.id
+ end
+
+ context "when no enterprise is selected as the account holder" do
+ before { payment_method.update_attribute(:preferred_enterprise_id, nil) }
+
+ context "id not provided at all" do
+ before { params[:payment_method].delete(:preferred_enterprise_id) }
+
+ it "does not save the payment method" do
+ spree_put :update, params
+ expect(response).to render_template :edit
+ expect(assigns(:payment_method).errors.messages[:stripe_account_owner]).to include I18n.t(:error_required)
+ end
+ end
+
+ context "enterprise_id of 0" do
+ before { params[:payment_method][:preferred_enterprise_id] = 0 }
+
+ it "does not save the payment method" do
+ spree_put :update, params
+ expect(response).to render_template :edit
+ expect(assigns(:payment_method).errors.messages[:stripe_account_owner]).to include I18n.t(:error_required)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
context "Requesting provider preference fields" do
let(:enterprise) { create(:distributor_enterprise) }
let(:user) do
diff --git a/spec/controllers/spree/admin/payments_controller_spec.rb b/spec/controllers/spree/admin/payments_controller_spec.rb
new file mode 100644
index 0000000000..cfc1b3336c
--- /dev/null
+++ b/spec/controllers/spree/admin/payments_controller_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe Spree::Admin::PaymentsController do
+ let!(:shop) { create(:enterprise) }
+ let!(:user) { shop.owner }
+ let!(:order) { create(:order, distributor: shop) }
+ let!(:line_item) { create(:line_item, order: order, price: 5.0) }
+
+ context "as an enterprise user" do
+ before do
+ allow(controller).to receive(:spree_current_user) { user }
+ order.reload.update_totals
+ end
+
+ context "requesting a refund on a payment" do
+ let(:params) { { id: payment.id, order_id: order.number, e: :void } }
+
+ # Required for the respond override in the controller decorator to work
+ before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) }
+
+ context "that was processed by stripe" do
+ let!(:payment_method) { create(:stripe_payment_method, distributors: [shop], preferred_enterprise_id: shop.id) }
+ # let!(:credit_card) { create(:credit_card, gateway_customer_profile_id: "cus_1", gateway_payment_profile_id: 'card_2') }
+ let!(:payment) { create(:payment, order: order, state: 'completed', payment_method: payment_method, response_code: 'ch_1a2b3c', amount: order.total) }
+
+
+ before do
+ allow(Stripe).to receive(:api_key) { "sk_test_12345" }
+ end
+
+ context "where the request succeeds" do
+ before do
+ stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges/ch_1a2b3c/refunds").
+ to_return(:status => 200, :body => JSON.generate(id: 're_123', object: 'refund', status: 'succeeded') )
+ end
+
+ it "voids the payment" do
+ order.reload
+ expect(order.payment_total).to_not eq 0
+ expect(order.outstanding_balance).to eq 0
+ spree_put :fire, params
+ expect(payment.reload.state).to eq 'void'
+ order.reload
+ expect(order.payment_total).to eq 0
+ expect(order.outstanding_balance).to_not eq 0
+ end
+ end
+
+ context "where the request fails" do
+ before do
+ stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges/ch_1a2b3c/refunds").
+ to_return(:status => 200, :body => JSON.generate(error: { message: "Bup-bow!"}) )
+ end
+
+ it "does not void the payment" do
+ order.reload
+ expect(order.payment_total).to_not eq 0
+ expect(order.outstanding_balance).to eq 0
+ spree_put :fire, params
+ expect(payment.reload.state).to eq 'completed'
+ order.reload
+ expect(order.payment_total).to_not eq 0
+ expect(order.outstanding_balance).to eq 0
+ expect(flash[:error]).to eq "Bup-bow!"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/spree/admin/products_controller_spec.rb b/spec/controllers/spree/admin/products_controller_spec.rb
index b8d76abd9c..02a5d6bced 100644
--- a/spec/controllers/spree/admin/products_controller_spec.rb
+++ b/spec/controllers/spree/admin/products_controller_spec.rb
@@ -12,7 +12,7 @@ describe Spree::Admin::ProductsController do
end
it "denies access" do
- response.should redirect_to "http://test.host/unauthorized"
+ response.should redirect_to spree.unauthorized_url
end
it "does not update any product" do
diff --git a/spec/controllers/spree/api/products_controller_spec.rb b/spec/controllers/spree/api/products_controller_spec.rb
index 312c6a29b4..1969a0abaf 100644
--- a/spec/controllers/spree/api/products_controller_spec.rb
+++ b/spec/controllers/spree/api/products_controller_spec.rb
@@ -107,5 +107,43 @@ module Spree
product1.deleted_at.should_not be_nil
end
end
+
+ describe '#clone' do
+ before do
+ spree_post :clone, product_id: product1.id, format: :json
+ end
+
+ context 'as a normal user' do
+ sign_in_as_user!
+
+ it 'denies access' do
+ assert_unauthorized!
+ end
+ end
+
+ context 'as an enterprise user' do
+ sign_in_as_enterprise_user! [:supplier]
+
+ it 'responds with a successful response' do
+ expect(response.status).to eq(201)
+ end
+
+ it 'clones the product' do
+ expect(json_response['name']).to eq("COPY OF #{product1.name}")
+ end
+ end
+
+ context 'as an administrator' do
+ sign_in_as_admin!
+
+ it 'responds with a successful response' do
+ expect(response.status).to eq(201)
+ end
+
+ it 'clones the product' do
+ expect(json_response['name']).to eq("COPY OF #{product1.name}")
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/spree/checkout_controller_spec.rb b/spec/controllers/spree/checkout_controller_spec.rb
index b9b85c92fa..116c18955b 100644
--- a/spec/controllers/spree/checkout_controller_spec.rb
+++ b/spec/controllers/spree/checkout_controller_spec.rb
@@ -4,51 +4,19 @@ require 'support/request/authentication_workflow'
describe Spree::CheckoutController do
- include AuthenticationWorkflow
+ context 'rendering edit from within spree for the current checkout state' do
+ let(:order) { controller.current_order(true) }
+ let(:user) { create(:user) }
- context "After completing an order" do
- it "should create a new empty order" do
- controller.current_order(true)
- controller.send(:after_complete)
- session[:order_id].should_not be_nil
+ before do
+ create(:line_item, order: order)
+
+ allow(controller).to receive(:skip_state_validation?) { true }
+ allow(controller).to receive(:spree_current_user) { user }
end
- it "should clear the current order cache" do
- order = controller.current_order(true)
- controller.send(:after_complete)
- controller.current_order.should_not == order
- end
-
- it "should set the new order's distributor to the same as the old order" do
- order = controller.current_order(true)
- distributor = create(:distributor_enterprise)
- order.set_distributor!(distributor)
-
- controller.send(:after_complete)
-
- controller.current_order.distributor.should == distributor
- end
-
- it "should set the new order's token to the same as the old order, and preserve the access token in the session" do
- order = controller.current_order(true)
-
- controller.send(:after_complete)
-
- controller.current_order.token.should == order.token
- session[:access_token].should == order.token
- end
- end
-
- context "rendering edit from within spree for the current checkout state" do
- let!(:order) { controller.current_order(true) }
- let!(:line_item) { create(:line_item, order: order) }
- let!(:user) { create_enterprise_user }
-
it "redirects to the OFN checkout page" do
- controller.stub(:skip_state_validation?) { true }
- controller.stub(:spree_current_user) { user }
- spree_get :edit
- response.should redirect_to checkout_path
+ expect(spree_get(:edit)).to redirect_to checkout_path
end
end
end
diff --git a/spec/controllers/spree/credit_cards_controller_spec.rb b/spec/controllers/spree/credit_cards_controller_spec.rb
new file mode 100644
index 0000000000..0fc910f2d2
--- /dev/null
+++ b/spec/controllers/spree/credit_cards_controller_spec.rb
@@ -0,0 +1,131 @@
+require 'spec_helper'
+require 'support/request/authentication_workflow'
+
+describe Spree::CreditCardsController do
+ include AuthenticationWorkflow
+ let(:user) { create_enterprise_user }
+ let(:token) { "tok_234bd2c22" }
+
+ before do
+ allow(Stripe).to receive(:api_key) { "sk_test_12345" }
+ allow(controller).to receive(:spree_current_user) { user }
+ end
+
+ describe "#new_from_token" do
+ let(:params) do
+ {
+ format: :json,
+ "exp_month" => 12,
+ "exp_year" => 2020,
+ "last4" => 4242,
+ "token" => token,
+ "cc_type" => "visa"
+ }
+ end
+
+ before do
+ stub_request(:post, "https://api.stripe.com/v1/customers")
+ .with(:body => { email: user.email, source: token })
+ .to_return(response_mock)
+ end
+
+ context "when the request to store the customer/card with Stripe is successful" do
+ let(:response_mock) { { status: 200, body: JSON.generate(id: "cus_AZNMJ", default_source: "card_1AEEb") } }
+
+ it "saves the card locally" do
+ expect{ post :new_from_token, params }.to change(Spree::CreditCard, :count).by(1)
+
+ card = Spree::CreditCard.last
+ card.gateway_payment_profile_id.should eq "card_1AEEb"
+ card.gateway_customer_profile_id.should eq "cus_AZNMJ"
+ card.user_id.should eq user.id
+ card.last_digits.should eq "4242"
+ end
+
+ context "when saving the card locally fails" do
+ before do
+ allow(controller).to receive(:stored_card_attributes) { {} }
+ end
+
+ it "renders a flash error" do
+ expect{ post :new_from_token, params }.to_not change(Spree::CreditCard, :count)
+
+ json_response = JSON.parse(response.body)
+ flash_message = I18n.t(:spree_gateway_error_flash_for_checkout, error: I18n.t(:card_could_not_be_saved))
+ expect(json_response["flash"]["error"]).to eq flash_message
+ end
+ end
+ end
+
+ context "when the request to store the customer/card with Stripe fails" do
+ let(:response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..." }) } }
+ it "doesn't save the card locally, and renders a flash error" do
+ expect{ post :new_from_token, params }.to_not change(Spree::CreditCard, :count)
+
+ json_response = JSON.parse(response.body)
+ flash_message = I18n.t(:spree_gateway_error_flash_for_checkout, error: "Bup-bow...")
+ expect(json_response["flash"]["error"]).to eq flash_message
+ end
+ end
+ end
+
+ describe "#destroy" do
+ context "when the specified credit card is not found" do
+ let(:params) { { id: 123 } }
+
+ it "redirects to /account with a flash error, does not request deletion with Stripe" do
+ expect(controller).to_not receive(:destroy_at_stripe)
+ delete :destroy, params
+ expect(flash[:error]).to eq I18n.t(:card_could_not_be_removed)
+ expect(response).to redirect_to spree.account_path(anchor: 'cards')
+ end
+ end
+
+ context "when the specified credit card is found" do
+ let!(:card) { create(:credit_card, gateway_customer_profile_id: 'cus_AZNMJ') }
+ let(:params) { { id: card.id } }
+
+ context "but the card is not owned by the user" do
+ it "redirects to unauthorized" do
+ delete :destroy, params
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "and the card is owned by the user" do
+ before do
+ card.update_attribute(:user_id, user.id)
+
+ stub_request(:get, "https://api.stripe.com/v1/customers/cus_AZNMJ").
+ to_return(:status => 200, :body => JSON.generate(id: "cus_AZNMJ"))
+ end
+
+ context "where the request to destroy the Stripe customer fails" do
+ before do
+ stub_request(:delete, "https://api.stripe.com/v1/customers/cus_AZNMJ").
+ to_return(:status => 402, :body => JSON.generate(error: { message: 'Bup-bow!' }))
+ end
+
+ it "doesn't delete the card" do
+ expect{ delete :destroy, params }.to_not change(Spree::CreditCard, :count)
+ expect(flash[:error]).to eq I18n.t(:card_could_not_be_removed)
+ expect(response).to redirect_to spree.account_path(anchor: 'cards')
+ end
+ end
+
+ context "where the request to destroy the Stripe customer succeeds" do
+ before do
+ stub_request(:delete, "https://api.stripe.com/v1/customers/cus_AZNMJ").
+ to_return(:status => 200, :body => JSON.generate(deleted: true, id: "cus_AZNMJ"))
+ end
+
+ it "deletes the card and redirects to account_path" do
+ expect{ delete :destroy, params }.to change(Spree::CreditCard, :count).by(-1)
+ expect(flash[:success]).to eq I18n.t(:card_has_been_removed, number: "x-#{card.last_digits}")
+ expect(response).to redirect_to spree.account_path(anchor: 'cards')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb
index e7b5a7e12a..e3a625784c 100644
--- a/spec/controllers/spree/orders_controller_spec.rb
+++ b/spec/controllers/spree/orders_controller_spec.rb
@@ -350,11 +350,17 @@ describe Spree::OrdersController do
end
context "and the order is editable" do
- let(:order_cycle) { create(:simple_order_cycle) }
let(:distributor) { create(:enterprise, allow_order_changes: true) }
+ let(:order_cycle) do
+ create(
+ :simple_order_cycle,
+ distributors: [distributor],
+ variants: order.line_item_variants
+ )
+ end
before do
- order.update_attributes(order_cycle_id: order_cycle.id, distributor_id: distributor.id)
+ order.update_attributes!(order_cycle_id: order_cycle.id, distributor_id: distributor.id)
end
it "returns the order" do
@@ -392,6 +398,13 @@ describe Spree::OrdersController do
context "when the order is complete" do
let(:order) { create(:completed_order_with_totals, user: user) }
+ before do
+ Spree::MailMethod.create!(
+ environment: Rails.env,
+ preferred_mails_from: 'spree@example.com'
+ )
+ end
+
it "responds with success" do
spree_put :cancel, params
expect(response.status).to redirect_to spree.order_path(order)
diff --git a/spec/controllers/spree/paypal_controller_spec.rb b/spec/controllers/spree/paypal_controller_spec.rb
index 200f4a9ff1..ff5f539e2d 100644
--- a/spec/controllers/spree/paypal_controller_spec.rb
+++ b/spec/controllers/spree/paypal_controller_spec.rb
@@ -2,9 +2,41 @@ require 'spec_helper'
module Spree
describe PaypalController do
- it "should redirect back to checkout after cancel" do
- spree_get :cancel
- response.should redirect_to checkout_path
+ context 'when cancelling' do
+ it 'redirects back to checkout' do
+ expect(spree_get(:cancel)).to redirect_to checkout_path
+ end
+ end
+
+ context 'when confirming' do
+ let(:previous_order) { controller.current_order(true) }
+ let(:payment_method) { create(:payment_method) }
+
+ before do
+ allow(previous_order).to receive(:complete?).and_return(true)
+ end
+
+ it 'resets the order' do
+ spree_post :confirm, payment_method_id: payment_method.id
+ expect(controller.current_order).not_to eq(previous_order)
+ end
+
+ it 'sets the access token of the session' do
+ spree_post :confirm, payment_method_id: payment_method.id
+ expect(session[:access_token]).to eq(controller.current_order.token)
+ end
+ end
+
+ describe '#expire_current_order' do
+ it 'empties the order_id of the session' do
+ expect(session).to receive(:[]=).with(:order_id, nil)
+ controller.expire_current_order
+ end
+
+ it 'resets the @current_order ivar' do
+ controller.expire_current_order
+ expect(controller.instance_variable_get(:@current_order)).to be_nil
+ end
end
end
end
diff --git a/spec/controllers/spree/users_controller_spec.rb b/spec/controllers/spree/users_controller_spec.rb
new file mode 100644
index 0000000000..f980e21498
--- /dev/null
+++ b/spec/controllers/spree/users_controller_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Spree::UsersController do
+ include AuthenticationWorkflow
+
+ describe "show" do
+ let!(:u1) { create(:user) }
+ let!(:u2) { create(:user) }
+ let!(:distributor1) { create(:distributor_enterprise) }
+ let!(:distributor2) { create(:distributor_enterprise) }
+ let!(:d1o1) { create(:completed_order_with_totals, distributor: distributor1, user_id: u1.id) }
+ let!(:d1o2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u1.id) }
+ let!(:d1_order_for_u2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u2.id) }
+ let!(:d1o3) { create(:order, state: 'cart', distributor: distributor1, user_id: u1.id) }
+ let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user_id: u2.id) }
+ let!(:accounts_distributor) { create :distributor_enterprise }
+ let!(:order_account_invoice) { create(:order, distributor: accounts_distributor, state: 'complete', user: u1) }
+
+ let(:orders) { assigns(:orders) }
+ let(:shops) { Enterprise.where(id: orders.pluck(:distributor_id)) }
+
+ before do
+ Spree::Config.set(accounts_distributor_id: accounts_distributor.id)
+ allow(controller).to receive(:spree_current_user) { u1 }
+ end
+
+ it "returns orders placed by the user at normal shops" do
+ spree_get :show
+
+ expect(orders).to eq [d1o1, d1o2]
+ expect(shops).to include distributor1
+
+ # Doesn't return orders belonging to the accounts distributor" do
+ expect(orders).to_not include order_account_invoice
+ expect(shops).to_not include accounts_distributor
+
+ # Doesn't return orders for irrelevant distributors" do
+ expect(orders).not_to include d2o1
+ expect(shops).not_to include distributor2
+
+ # Doesn't return other users' orders" do
+ expect(orders).not_to include d1_order_for_u2
+
+ # Doesn't return uncompleted orders" do
+ expect(orders).not_to include d1o3
+ end
+ end
+end
diff --git a/spec/controllers/stripe/callbacks_controller_spec.rb b/spec/controllers/stripe/callbacks_controller_spec.rb
new file mode 100644
index 0000000000..831dc5e348
--- /dev/null
+++ b/spec/controllers/stripe/callbacks_controller_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe Stripe::CallbacksController do
+ let(:enterprise) { create(:distributor_enterprise) }
+
+ context "#index" do
+ let(:params) { { id: enterprise.permalink } }
+ let(:connector) { double(:connector) }
+
+ before do
+ allow(controller).to receive(:spree_current_user) { enterprise.owner }
+ allow(Stripe::AccountConnector).to receive(:new) { connector }
+ end
+
+ context "when the connector.create_account raises a StripeError" do
+ before do
+ allow(connector).to receive(:create_account).and_raise Stripe::StripeError, "some error"
+ end
+
+ it "returns a 500 error" do
+ spree_get :index, params
+ expect(response.status).to be 500
+ end
+ end
+
+ context "when the connector.create_account raises an AccessDenied error" do
+ before do
+ allow(connector).to receive(:create_account).and_raise CanCan::AccessDenied, "some error"
+ end
+
+ it "redirects to unauthorized" do
+ spree_get :index, params
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "when the connector fails in creating a new stripe account record" do
+ before { allow(connector).to receive(:create_account) { false } }
+
+ context "when the user cancelled the connection" do
+ before { allow(connector).to receive(:connection_cancelled_by_user?) { true } }
+
+ it "renders a failure message" do
+ allow(connector).to receive(:enterprise) { enterprise }
+ spree_get :index, params
+ expect(flash[:notice]).to eq I18n.t('admin.controllers.enterprises.stripe_connect_cancelled')
+ expect(response).to redirect_to edit_admin_enterprise_path(enterprise, anchor: 'payment_methods')
+ end
+ end
+
+ context "when some other error caused the failure" do
+ before { allow(connector).to receive(:connection_cancelled_by_user?) { false } }
+
+ it "renders a failure message" do
+ allow(connector).to receive(:enterprise) { enterprise }
+ spree_get :index, params
+ expect(flash[:error]).to eq I18n.t('admin.controllers.enterprises.stripe_connect_fail')
+ expect(response).to redirect_to edit_admin_enterprise_path(enterprise, anchor: 'payment_methods')
+ end
+ end
+ end
+
+ context "when the connector succeeds in creating a new stripe account record" do
+ before { allow(connector).to receive(:create_account) { true } }
+
+ it "redirects to the enterprise edit path" do
+ allow(connector).to receive(:enterprise) { enterprise }
+ spree_get :index, params
+ expect(flash[:success]).to eq I18n.t('admin.controllers.enterprises.stripe_connect_success')
+ expect(response).to redirect_to edit_admin_enterprise_path(enterprise, anchor: 'payment_methods')
+ end
+ end
+ end
+end
diff --git a/spec/controllers/stripe/webhooks_controller_spec.rb b/spec/controllers/stripe/webhooks_controller_spec.rb
new file mode 100644
index 0000000000..8c0bda6c87
--- /dev/null
+++ b/spec/controllers/stripe/webhooks_controller_spec.rb
@@ -0,0 +1,92 @@
+require 'spec_helper'
+
+describe Stripe::WebhooksController do
+ describe "#create" do
+ let(:params) do
+ {
+ "format" => "json",
+ "id" => "evt_123",
+ "object" => "event",
+ "data" => { "object" => { "id" => "ca_9B" } },
+ "type" => "account.application.authorized",
+ "account" => "webhook_id1"
+ }
+ end
+
+ context "when invalid json is provided" do
+ before do
+ allow(Stripe::Webhook).to receive(:construct_event).and_raise JSON::ParserError, "parsing failed"
+ end
+
+ it "responds with a 400" do
+ post 'create', params
+ expect(response.status).to eq 400
+ end
+ end
+
+ context "when event signature verification fails" do
+ before do
+ allow(Stripe::Webhook).to receive(:construct_event).and_raise Stripe::SignatureVerificationError.new("verfication failed", "header")
+ end
+
+ it "responds with a 401" do
+ post 'create', params
+ expect(response.status).to eq 401
+ end
+ end
+
+ context "when event signature verification succeeds" do
+ before do
+ allow(Stripe::Webhook).to receive(:construct_event) { Stripe::Event.construct_from(params) }
+ end
+
+ describe "setting the response status" do
+ let(:handler) { double(:handler) }
+ before { allow(Stripe::WebhookHandler).to receive(:new) { handler } }
+
+ context "when an unknown result is returned by the handler" do
+ before { allow(handler).to receive(:handle) { :garbage } }
+
+ it "falls back to 200" do
+ post 'create', params
+ expect(response.status).to eq 200
+ end
+ end
+
+ context "when the result returned by the handler is :unknown" do
+ before { allow(handler).to receive(:handle) { :unknown } }
+
+ it "responds with 202" do
+ post 'create', params
+ expect(response.status).to eq 202
+ end
+ end
+ end
+
+ describe "when an account.application.deauthorized event is received" do
+ let!(:stripe_account) { create(:stripe_account, stripe_user_id: "webhook_id") }
+ before do
+ params["type"] = "account.application.deauthorized"
+ end
+
+ context "when the stripe_account id on the event does not match any known accounts" do
+ it "doesn't delete any Stripe accounts, responds with 204" do
+ post 'create', params
+ expect(response.status).to eq 204
+ expect(StripeAccount.all).to include stripe_account
+ end
+ end
+
+ context "when the stripe_account id on the event matches a known account" do
+ before { params["account"] = "webhook_id" }
+
+ it "deletes Stripe accounts in response to a webhook" do
+ post 'create', params
+ expect(response.status).to eq 200
+ expect(StripeAccount.all).not_to include stripe_account
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/user_passwords_controller_spec.rb b/spec/controllers/user_passwords_controller_spec.rb
index 59fadf8e99..26584a8619 100644
--- a/spec/controllers/user_passwords_controller_spec.rb
+++ b/spec/controllers/user_passwords_controller_spec.rb
@@ -6,7 +6,6 @@ describe UserPasswordsController do
before do
@request.env["devise.mapping"] = Devise.mappings[:spree_user]
- ActionMailer::Base.default_url_options[:host] = "test.host"
end
describe "create" do
diff --git a/spec/factories.rb b/spec/factories.rb
index 3b2f67e531..14c97d0f55 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -1,5 +1,5 @@
require 'ffaker'
-require 'spree/core/testing_support/factories'
+require 'spree/testing_support/factories'
# http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md
#
@@ -362,6 +362,17 @@ FactoryGirl.define do
tr.calculator = Spree::Calculator::FlatPercentItemTotal.new(calculable: tr)
end
end
+
+ factory :stripe_payment_method, :class => Spree::Gateway::StripeConnect do
+ name 'Stripe'
+ environment 'test'
+ end
+
+ factory :stripe_account do
+ enterprise { FactoryGirl.create :distributor_enterprise }
+ stripe_user_id "abc123"
+ stripe_publishable_key "xyz456"
+ end
end
diff --git a/spec/features/admin/multilingual_spec.rb b/spec/features/admin/multilingual_spec.rb
index bfad7c39a6..0f98e8bbde 100644
--- a/spec/features/admin/multilingual_spec.rb
+++ b/spec/features/admin/multilingual_spec.rb
@@ -3,9 +3,13 @@ require 'spec_helper'
feature 'Multilingual', js: true do
include AuthenticationWorkflow
include WebHelper
+ let(:admin_role) { Spree::Role.find_or_create_by_name!('admin') }
+ let(:admin_user) { create(:user) }
background do
- login_to_admin_section
+ admin_user.spree_roles << admin_role
+ quick_login_as admin_user
+ visit spree.admin_path
end
it 'has two locales available' do
@@ -18,11 +22,14 @@ feature 'Multilingual', js: true do
expect(get_i18n_locale).to eq 'en'
expect(get_i18n_translation('spree_admin_overview_enterprises_header')).to eq 'My Enterprises'
expect(page).to have_content 'My Enterprises'
+ expect(admin_user.locale).to be_nil
visit spree.admin_path(locale: 'es')
expect(get_i18n_locale).to eq 'es'
expect(get_i18n_translation('spree_admin_overview_enterprises_header')).to eq 'Mis Organizaciones'
expect(page).to have_content 'Mis Organizaciones'
+ admin_user.reload
+ expect(admin_user.locale).to eq 'es'
end
it 'fallbacks to default_locale' do
@@ -34,6 +41,7 @@ feature 'Multilingual', js: true do
visit spree.admin_path(locale: 'it')
expect(get_i18n_locale).to eq 'it'
expect(get_i18n_translation('spree_admin_overview_enterprises_header')).to eq 'My Enterprises'
+ expect(admin_user.locale).to be_nil
# This still is italian until we change enforce_available_locales to `true`
expect(page).to have_content 'Le Mie Aziende'
end
diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb
index 5b0a215dd8..4b5df630f1 100644
--- a/spec/features/admin/orders_spec.rb
+++ b/spec/features/admin/orders_spec.rb
@@ -204,7 +204,6 @@ feature %q{
end
context "viewing the edit page" do
- before { Rails.application.routes.default_url_options[:host] = "test.host" }
it "shows the dropdown menu" do
distributor1.update_attribute(:abn, '12345678')
order = create(:completed_order_with_totals, distributor: distributor1)
diff --git a/spec/features/admin/overview_spec.rb b/spec/features/admin/overview_spec.rb
index 30f2cc0336..31591349c7 100644
--- a/spec/features/admin/overview_spec.rb
+++ b/spec/features/admin/overview_spec.rb
@@ -5,8 +5,8 @@ feature %q{
I want to be given information about the state of my enterprises, products and order cycles
}, js: true do
include AuthenticationWorkflow
- include AuthorizationHelpers
include WebHelper
+ include ::Spree::TestingSupport::AuthorizationHelpers
context "as an enterprise user" do
before do
diff --git a/spec/features/admin/payment_method_spec.rb b/spec/features/admin/payment_method_spec.rb
index 45703a4d07..9df6ef0114 100644
--- a/spec/features/admin/payment_method_spec.rb
+++ b/spec/features/admin/payment_method_spec.rb
@@ -29,6 +29,44 @@ feature %q{
payment_method = Spree::PaymentMethod.find_by_name('Cheque payment method')
payment_method.distributors.should == [@distributors[0]]
end
+
+ context "using stripe connect" do
+ let(:user) { create(:user, enterprise_limit: 5) }
+ let!(:connected_enterprise) { create(:distributor_enterprise, name: "Connected", owner: user) }
+ let!(:revoked_account_enterprise) { create(:distributor_enterprise, name: "Revoked", owner: user) }
+ let!(:missing_account_enterprise) { create(:distributor_enterprise, name: "Missing", owner: user) }
+ let!(:valid_stripe_account) { create(:stripe_account, enterprise: connected_enterprise, stripe_user_id: "acc_connected123") }
+ let!(:disconnected_stripe_account) { create(:stripe_account, enterprise: revoked_account_enterprise, stripe_user_id: "acc_revoked123") }
+ let!(:stripe_account_mock) { { id: "acc_connected123", business_name: "My Org", charges_enabled: true } }
+
+ before do
+ Spree::Config.set(stripe_connect_enabled: true)
+ allow(Stripe).to receive(:api_key) { "sk_test_12345" }
+ stub_request(:get, "https://api.stripe.com/v1/accounts/acc_connected123").to_return(body: JSON.generate(stripe_account_mock))
+ stub_request(:get, "https://api.stripe.com/v1/accounts/acc_revoked123").to_return(status: 404)
+ end
+
+ it "communicates the status of the stripe connection to the user" do
+ login_as user
+ visit spree.new_admin_payment_method_path
+
+ select2_select "Stripe", from: "payment_method_type"
+
+ select2_select "Missing", from: "payment_method_preferred_enterprise_id"
+ expect(page).to have_selector "#stripe-account-status .alert-box.error", text: I18n.t("spree.admin.payment_methods.stripe_connect.account_missing_msg")
+ connect_one = I18n.t("spree.admin.payment_methods.stripe_connect.connect_one")
+ expect(page).to have_link connect_one, href: edit_admin_enterprise_path(missing_account_enterprise, anchor: "/payment_methods")
+
+ select2_select "Revoked", from: "payment_method_preferred_enterprise_id"
+ expect(page).to have_selector "#stripe-account-status .alert-box.error", text: I18n.t("spree.admin.payment_methods.stripe_connect.access_revoked_msg")
+
+ select2_select "Connected", from: "payment_method_preferred_enterprise_id"
+ expect(page).to have_selector "#stripe-account-status .status", text: "Status: Connected"
+ expect(page).to have_selector "#stripe-account-status .account_id", text: "Account ID: acc_connected123"
+ expect(page).to have_selector "#stripe-account-status .business_name", text: "Business Name: My Org"
+ expect(page).to have_selector "#stripe-account-status .charges_enabled", text: "Charges Enabled: Yes"
+ end
+ end
end
scenario "updating a payment method", js: true do
diff --git a/spec/features/admin/product_import_spec.rb b/spec/features/admin/product_import_spec.rb
index 82e1c6e91a..3ab66ca237 100644
--- a/spec/features/admin/product_import_spec.rb
+++ b/spec/features/admin/product_import_spec.rb
@@ -193,9 +193,10 @@ feature "Product Import", js: true do
it "returns and error if nothing was uploaded" do
visit main_app.admin_product_import_path
+ expect(page).to have_content 'Select a spreadsheet to upload'
click_button 'Import'
- expect(page).to have_content "File not found or could not be opened"
+ expect(flash_message).to eq I18n.t(:product_import_file_not_found_notice)
end
it "handles cases where no meaningful data can be read from the file" do
diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb
index 2cebd58dce..eb6e765b00 100644
--- a/spec/features/admin/products_spec.rb
+++ b/spec/features/admin/products_spec.rb
@@ -86,24 +86,6 @@ feature %q{
variant = product.variants.first
variant.on_demand.should be_true
end
-
- scenario "making a product into a group buy product" do
- product = create(:simple_product, name: 'group buy product')
-
- login_to_admin_section
-
- visit spree.edit_admin_product_path(product)
-
- choose 'product_group_buy_1'
- fill_in 'Bulk unit size', :with => '10'
-
- click_button 'Update'
-
- flash_message.should == 'Product "group buy product" has been successfully updated!'
- product.reload
- product.group_buy.should be_true
- product.group_buy_unit_size.should == 10.0
- end
end
context "as an enterprise user" do
@@ -121,15 +103,6 @@ feature %q{
login_to_admin_as @new_user
end
-
- context "additional fields" do
- it "should have a notes field" do
- product = create(:simple_product, supplier: @supplier2)
- visit spree.edit_admin_product_path product
- page.should have_content "Notes"
- end
- end
-
context "products do not require a tax category" do
scenario "creating a new product", js: true do
with_products_require_tax_category(false) do
@@ -174,6 +147,22 @@ feature %q{
product.tax_category.should == tax_category
end
+ scenario "editing product group buy options" do
+ product = product = create(:simple_product, supplier: @supplier2)
+
+ visit spree.edit_admin_product_path product
+ within('#sidebar') { click_link 'Group Buy Options' }
+ choose('product_group_buy_1')
+ fill_in 'Bulk unit size', :with => '10'
+
+ click_button 'Update'
+
+ flash_message.should == "Product \"#{product.name}\" has been successfully updated!"
+ product.reload
+ product.group_buy.should be_true
+ product.group_buy_unit_size.should == 10.0
+ end
+
scenario "editing product distributions" do
product = create(:simple_product, supplier: @supplier2)
@@ -195,6 +184,20 @@ feature %q{
product.distributors.should == [@distributors[0]]
end
+ scenario "editing product SEO" do
+ product = product = create(:simple_product, supplier: @supplier2)
+ visit spree.edit_admin_product_path product
+ within('#sidebar') { click_link 'SEO' }
+ fill_in "product_meta_keywords", :with => 'Meta Keywords'
+ fill_in 'Meta Description', :with => 'Meta Description'
+ fill_in 'Notes', :with => 'Just testing Notes'
+ click_button 'Update'
+ flash_message.should == "Product \"#{product.name}\" has been successfully updated!"
+ product.reload
+ product.notes.should == 'Just testing Notes'
+ product.meta_keywords.should == 'Meta Keywords'
+ product.meta_description.should == 'Meta Description'
+ end
scenario "deleting product properties", js: true do
# Given a product with a property
diff --git a/spec/features/admin/stripe_connect_spec.rb b/spec/features/admin/stripe_connect_spec.rb
new file mode 100644
index 0000000000..1c56f703e9
--- /dev/null
+++ b/spec/features/admin/stripe_connect_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+feature "Connecting a Stripe Account" do
+ include AuthenticationWorkflow
+ include WebHelper
+
+ let!(:enterprise_user) { create :enterprise_user }
+ before(:each) do
+ login_to_admin_as enterprise_user
+ end
+end
diff --git a/spec/features/admin/variant_overrides_spec.rb b/spec/features/admin/variant_overrides_spec.rb
index 44fa258eb3..9064913868 100644
--- a/spec/features/admin/variant_overrides_spec.rb
+++ b/spec/features/admin/variant_overrides_spec.rb
@@ -66,12 +66,12 @@ feature %q{
context "with no overrides" do
it "displays the list of products with variants" do
- page.should have_table_row ['PRODUCER', 'PRODUCT', 'PRICE', 'ON HAND']
- page.should have_table_row [producer.name, product.name, '', '']
+ page.should have_table_row ['PRODUCER', 'PRODUCT', 'PRICE', 'ON HAND', 'ON DEMAND?']
+ page.should have_table_row [producer.name, product.name, '', '', '']
page.should have_input "variant-overrides-#{variant.id}-price", placeholder: '1.23'
page.should have_input "variant-overrides-#{variant.id}-count_on_hand", placeholder: '12'
- page.should have_table_row [producer_related.name, product_related.name, '', '']
+ page.should have_table_row [producer_related.name, product_related.name, '', '', '']
page.should have_input "variant-overrides-#{variant_related.id}-price", placeholder: '2.34'
page.should have_input "variant-overrides-#{variant_related.id}-count_on_hand", placeholder: '23'
@@ -133,7 +133,6 @@ feature %q{
it "creates new overrides" do
first("div#columns-dropdown", :text => "COLUMNS").click
first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click
- first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click
first("div#columns-dropdown", :text => "COLUMNS").click
fill_in "variant-overrides-#{variant.id}-sku", with: 'NEWSKU'
@@ -320,24 +319,24 @@ feature %q{
describe "when manually placing an order" do
let!(:order_cycle) { create(:order_cycle_with_overrides, name: "Overidden") }
+ let(:distributor) { order_cycle.distributors.first }
+ let(:product) { order_cycle.products.first }
before do
- dist = order_cycle.distributors.first
login_to_admin_section
+
visit 'admin/orders/new'
- select2_select dist.name, from: 'order_distributor_id'
- page.should have_select2 'order_order_cycle_id', with_options: ['Overidden (open)']
+ select2_select distributor.name, from: 'order_distributor_id'
select2_select order_cycle.name, from: 'order_order_cycle_id'
click_button 'Next'
end
# Reproducing a bug, issue #1446
it "shows the overridden price" do
- product = order_cycle.products.first
targetted_select2_search product.name, from: '#add_variant_id', dropdown_css: '.select2-drop'
click_link 'Add'
- page.has_selector? "table.index tbody[data-hook='admin_order_form_line_items'] tr" # Wait for JS
- page.should have_content product.variants.first.variant_overrides.first.price
+ expect(page).to have_selector("table.index tbody[data-hook='admin_order_form_line_items'] tr") # Wait for JS
+ expect(page).to have_content(product.variants.first.variant_overrides.first.price)
end
end
diff --git a/spec/features/consumer/account/cards_spec.rb b/spec/features/consumer/account/cards_spec.rb
new file mode 100644
index 0000000000..3a9e32ddd6
--- /dev/null
+++ b/spec/features/consumer/account/cards_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+feature "Credit Cards", js: true do
+ include AuthenticationWorkflow
+ describe "as a logged in user" do
+ let(:user) { create(:user) }
+ let!(:card) { create(:credit_card, user_id: user.id, gateway_customer_profile_id: 'cus_AZNMJ') }
+
+ before do
+ quick_login_as user
+
+ allow(Stripe).to receive(:api_key) { "sk_test_xxxx" }
+ allow(Stripe).to receive(:publishable_key) { "some_token" }
+ Spree::Config.set(stripe_connect_enabled: true)
+
+ stub_request(:get, "https://api.stripe.com/v1/customers/cus_AZNMJ").
+ to_return(:status => 200, :body => JSON.generate(id: "cus_AZNMJ"))
+
+ stub_request(:delete, "https://api.stripe.com/v1/customers/cus_AZNMJ").
+ to_return(:status => 200, :body => JSON.generate(deleted: true, id: "cus_AZNMJ"))
+ end
+
+ it "lists saved cards, shows interface for adding new cards" do
+ visit "/account"
+
+ click_link I18n.t('spree.users.show.tabs.cards')
+
+ expect(page).to have_content I18n.t(:saved_cards)
+
+ within(".card#card#{card.id}") do
+ expect(page).to have_content card.cc_type.capitalize
+ expect(page).to have_content card.last_digits
+ end
+
+ # Shows the interface for adding a card
+ click_button I18n.t(:add_a_card)
+ expect(page).to have_field 'first_name'
+ expect(page).to have_selector '#card-element.StripeElement'
+
+ # Allows deletion of cards
+ click_link I18n.t(:delete)
+
+ expect(page).to have_content I18n.t(:card_has_been_removed, number: "x-#{card.last_digits}")
+ expect(page).to have_content I18n.t(:you_have_no_saved_cards)
+ end
+ end
+end
diff --git a/spec/features/consumer/account/settings_spec.rb b/spec/features/consumer/account/settings_spec.rb
new file mode 100644
index 0000000000..a7fbf9ce94
--- /dev/null
+++ b/spec/features/consumer/account/settings_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+feature "Account Settings", js: true do
+ include AuthenticationWorkflow
+
+ describe "as a logged in user" do
+ let(:user) { create(:user) }
+
+ before do
+ quick_login_as user
+ end
+
+ it "allows me to update my account details" do
+ visit "/account"
+
+ click_link I18n.t('spree.users.show.tabs.settings')
+ expect(page).to have_content I18n.t('spree.users.form.account_settings')
+ fill_in 'user_email', with: 'new@email.com'
+
+ click_button I18n.t(:update)
+
+ expect(find(".alert-box.success").text.strip).to eq "#{I18n.t(:account_updated)} ×"
+ user.reload
+ expect(user.email).to eq 'new@email.com'
+ end
+ end
+end
diff --git a/spec/features/consumer/account_spec.rb b/spec/features/consumer/account_spec.rb
index d5c7236b72..3e30bb4cd7 100644
--- a/spec/features/consumer/account_spec.rb
+++ b/spec/features/consumer/account_spec.rb
@@ -38,7 +38,21 @@ feature %q{
visit "/account"
# No distributors allow changes to orders
- expect(page).to_not have_content I18n.t('spree.users.show.open_orders')
+ expect(page).to_not have_content I18n.t('spree.users.orders.open_orders')
+
+ expect(page).to have_content I18n.t('spree.users.orders.past_orders')
+
+ # Doesn't show orders from the special Accounts & Billing distributor
+ expect(page).not_to have_content accounts_distributor.name
+
+ # Lists all other orders
+ expect(page).to have_content d1o1.number.to_s
+ expect(page).to have_content d1o2.number.to_s
+ expect(page).to have_content d2o1.number.to_s
+ expect(page).to have_content credit_order.number.to_s
+
+ # Viewing transaction history
+ click_link I18n.t('spree.users.show.tabs.transactions')
# It shows all hubs that have been ordered from with balance or credit
expect(page).to have_content distributor1.name
@@ -65,7 +79,7 @@ feature %q{
it "shows such orders in a section labelled 'Open Orders'" do
visit '/account'
- expect(page).to have_content I18n.t('spree.users.show.open_orders')
+ expect(page).to have_content I18n.t('spree.users.orders.open_orders')
expect(page).to have_link d1o1.number, href: spree.order_path(d1o1)
expect(page).to have_link d1o2.number, href: spree.order_path(d1o2)
diff --git a/spec/features/consumer/multilingual_spec.rb b/spec/features/consumer/multilingual_spec.rb
index 59858875e2..4f163dee01 100644
--- a/spec/features/consumer/multilingual_spec.rb
+++ b/spec/features/consumer/multilingual_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
feature 'Multilingual', js: true do
+ include AuthenticationWorkflow
include WebHelper
it 'has two locales available' do
@@ -9,24 +10,62 @@ feature 'Multilingual', js: true do
expect(Rails.application.config.i18n[:available_locales]).to eq ['en', 'es']
end
+ it '18n-js fallsback to default language' do # in backend it doesn't until we change enforce_available_locales to `true`
+ visit root_path
+ set_i18n_locale('it')
+ expect(get_i18n_translation('label_shops')).to eq 'Shops'
+ end
+
it 'can switch language by params' do
visit root_path
expect(get_i18n_locale).to eq 'en'
expect(get_i18n_translation('label_shops')).to eq 'Shops'
+ expect(page.driver.browser.cookies['locale']).to be_nil
expect(page).to have_content 'Interested in getting on the Open Food Network?'
expect(page).to have_content 'SHOPS'
visit root_path(locale: 'es')
expect(get_i18n_locale).to eq 'es'
expect(get_i18n_translation('label_shops')).to eq 'Tiendas'
+ expect(page.driver.browser.cookies['locale'].value).to eq 'es'
expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?'
expect(page).to have_content 'TIENDAS'
- # I18n-js fallsback to 'en'
+ # it is not in the list of available of available_locales
visit root_path(locale: 'it')
- expect(get_i18n_locale).to eq 'it'
- expect(get_i18n_translation('label_shops')).to eq 'Shops'
- # This still is italian until we change enforce_available_locales to `true`
- expect(page).to have_content 'NEGOZI'
+ expect(get_i18n_locale).to eq 'es'
+ expect(get_i18n_translation('label_shops')).to eq 'Tiendas'
+ expect(page.driver.browser.cookies['locale'].value).to eq 'es'
+ expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?'
+ expect(page).to have_content 'TIENDAS'
+ end
+
+ context 'with user' do
+ let(:user) { create(:user) }
+
+ it 'updates user locale from cookie if it is empty' do
+ visit root_path(locale: 'es')
+
+ expect(page.driver.browser.cookies['locale'].value).to eq 'es'
+ expect(user.locale).to be_nil
+ quick_login_as user
+ visit root_path
+
+ expect(page.driver.browser.cookies['locale'].value).to eq 'es'
+ end
+
+ it 'updates user locale and stays in cookie after logout' do
+ quick_login_as user
+ visit root_path(locale: 'es')
+ user.reload
+
+ expect(user.locale).to eq 'es'
+
+ logout
+
+ expect(page.driver.browser.cookies['locale'].value).to eq 'es'
+ expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?'
+ expect(page).to have_content 'TIENDAS'
+ end
end
end
diff --git a/spec/features/consumer/shopping/cart_spec.rb b/spec/features/consumer/shopping/cart_spec.rb
index 99c0f75450..2b824ef8d7 100644
--- a/spec/features/consumer/shopping/cart_spec.rb
+++ b/spec/features/consumer/shopping/cart_spec.rb
@@ -20,6 +20,13 @@ feature "full-page cart", js: true do
set_order order
end
+ around do |example|
+ allow_backorders = Spree::Config.allow_backorders
+ Spree::Config.allow_backorders = false
+ example.run
+ Spree::Config.allow_backorders = allow_backorders
+ end
+
describe "fees" do
let(:percentage_fee) { create(:enterprise_fee, calculator: Calculator::FlatPercentPerItem.new(preferred_flat_percent: 20)) }
diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb
index 220fdcb89b..0a55324b37 100644
--- a/spec/features/consumer/shopping/checkout_spec.rb
+++ b/spec/features/consumer/shopping/checkout_spec.rb
@@ -39,7 +39,6 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
end
end
-
before do
distributor.shipping_methods << sm1
distributor.shipping_methods << sm2
@@ -61,13 +60,11 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
page.should have_content "An item in your cart has become unavailable"
end
end
+
context 'login in as user' do
let(:user) { create(:user) }
- before do
- quick_login_as(user)
- visit checkout_path
-
+ def fill_out_form
toggle_shipping
choose sm1.name
toggle_payment
@@ -94,28 +91,39 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
check "Save as default shipping address"
end
- it "sets user's default billing address and shipping address" do
- user.bill_address.should be_nil
- user.ship_address.should be_nil
-
- order.bill_address.should be_nil
- order.ship_address.should be_nil
-
- place_order
- page.should have_content "Your order has been processed successfully"
-
- order.reload.bill_address.address1.should eq '123 Your Head'
- order.reload.ship_address.address1.should eq '123 Your Head'
-
- order.customer.bill_address.address1.should eq '123 Your Head'
- order.customer.ship_address.address1.should eq '123 Your Head'
-
- user.reload.bill_address.address1.should eq '123 Your Head'
- user.reload.ship_address.address1.should eq '123 Your Head'
+ before do
+ quick_login_as(user)
end
- it "it doesn't tell about previous orders" do
- expect(page).to_not have_content("You have an order for this order cycle already.")
+ context "with details filled out" do
+ before do
+ visit checkout_path
+ fill_out_form
+ end
+
+ it "sets user's default billing address and shipping address" do
+ user.bill_address.should be_nil
+ user.ship_address.should be_nil
+
+ order.bill_address.should be_nil
+ order.ship_address.should be_nil
+
+ place_order
+ page.should have_content "Your order has been processed successfully"
+
+ order.reload.bill_address.address1.should eq '123 Your Head'
+ order.reload.ship_address.address1.should eq '123 Your Head'
+
+ order.customer.bill_address.address1.should eq '123 Your Head'
+ order.customer.ship_address.address1.should eq '123 Your Head'
+
+ user.reload.bill_address.address1.should eq '123 Your Head'
+ user.reload.ship_address.address1.should eq '123 Your Head'
+ end
+
+ it "it doesn't tell about previous orders" do
+ expect(page).to_not have_content("You have an order for this order cycle already.")
+ end
end
context "with previous orders" do
@@ -124,13 +132,64 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
before do
order.distributor.allow_order_changes = true
order.distributor.save
+ visit checkout_path
end
it "informs about previous orders" do
- visit checkout_path
expect(page).to have_content("You have an order for this order cycle already.")
end
end
+
+ context "with Stripe" do
+ let!(:stripe_pm) do
+ create(:stripe_payment_method,
+ distributors: [distributor],
+ name: "Stripe",
+ preferred_enterprise_id: distributor.id)
+ end
+
+ let!(:saved_card) do
+ create(:credit_card,
+ user_id: user.id,
+ month: "01",
+ year: "2025",
+ cc_type: "visa",
+ number: "1111111111111111",
+ payment_method_id: stripe_pm.id,
+ gateway_customer_profile_id: "i_am_saved")
+ end
+
+ let!(:stripe_account) { create(:stripe_account, enterprise_id: distributor.id, stripe_user_id: 'some_id') }
+
+ let(:response_mock) { { id: "ch_1234", object: "charge", amount: 2000} }
+
+ before do
+ allow(Stripe).to receive(:api_key) { "sk_test_12345" }
+ allow(Stripe).to receive(:publishable_key) { "some_key" }
+ Spree::Config.set(stripe_connect_enabled: true)
+ stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges")
+ .to_return(status: 200, body: JSON.generate(response_mock))
+
+ visit checkout_path
+ fill_out_form
+ toggle_payment
+ choose stripe_pm.name
+ end
+
+ it "allows use of a saved card" do
+ # shows the saved credit card dropdown
+ expect(page).to have_content I18n.t("spree.checkout.payment.stripe.used_saved_card")
+
+ # removes the input fields when a saved card is selected"
+ expect(page).to have_selector "#card-element.StripeElement"
+ select "Visa x-1111 Exp:01/2025", from: "selected_card"
+ expect(page).to_not have_selector "#card-element.StripeElement"
+
+ # allows checkout
+ place_order
+ expect(page).to have_content "Your order has been processed successfully"
+ end
+ end
end
context "on the checkout page" do
@@ -245,13 +304,16 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
describe "purchasing" do
it "takes us to the order confirmation page when we submit a complete form" do
toggle_details
+
within "#details" do
fill_in "First Name", with: "Will"
fill_in "Last Name", with: "Marshall"
fill_in "Email", with: "test@test.com"
fill_in "Phone", with: "0468363090"
end
+
toggle_billing
+
within "#billing" do
fill_in "Address", with: "123 Your Face"
select "Australia", from: "Country"
@@ -259,35 +321,40 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
fill_in "City", with: "Melbourne"
fill_in "Postcode", with: "3066"
end
+
toggle_shipping
+
within "#shipping" do
choose sm2.name
fill_in 'Any comments or special instructions?', with: "SpEcIaL NoTeS"
end
+
toggle_payment
+
within "#payment" do
choose pm1.name
end
expect do
place_order
- page.should have_content "Your order has been processed successfully"
+ expect(page).to have_content "Your order has been processed successfully"
end.to enqueue_job ConfirmOrderJob
# And the order's special instructions should be set
- o = Spree::Order.complete.first
- expect(o.special_instructions).to eq "SpEcIaL NoTeS"
+ order = Spree::Order.complete.first
+ expect(order.special_instructions).to eq "SpEcIaL NoTeS"
# And the Spree tax summary should not be displayed
- page.should_not have_content product.tax_category.name
+ expect(page).not_to have_content product.tax_category.name
# And the total tax for the order, including shipping and fee tax, should be displayed
# product tax ($10.00 @ 10% = $0.91)
# + fee tax ($ 1.23 @ 10% = $0.11)
# + shipping tax ($ 4.56 @ 25% = $0.91)
# = $1.93
- page.should have_content "(includes tax)"
- page.should have_content with_currency(1.93)
+ expect(page).to have_content '(includes tax)'
+ expect(page).to have_content with_currency(1.93)
+ expect(page).to have_content 'Back To Store'
end
context "with basic details filled" do
@@ -396,7 +463,7 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
fill_in 'Security Code', with: '123'
place_order
- page.should have_content "Payment could not be processed, please check the details you entered"
+ page.should have_content 'Bogus Gateway: Forced failure'
# Does not show duplicate shipping fee
visit checkout_path
diff --git a/spec/features/consumer/shopping/embedded_shopfronts_spec.rb b/spec/features/consumer/shopping/embedded_shopfronts_spec.rb
index 4c88cd492d..4a4900e8c3 100644
--- a/spec/features/consumer/shopping/embedded_shopfronts_spec.rb
+++ b/spec/features/consumer/shopping/embedded_shopfronts_spec.rb
@@ -9,37 +9,6 @@ feature "Using embedded shopfront functionality", js: true do
Capybara.server_port = 9999
- describe "enabling embedded shopfronts" do
- before do
- Spree::Config[:enable_embedded_shopfronts] = false
- end
-
- it "disables iframes by default" do
- visit shops_path
- expect(page.response_headers['X-Frame-Options']).to eq 'DENY'
- expect(page.response_headers['Content-Security-Policy']).to eq "frame-ancestors 'none'"
- end
-
- it "allows iframes on certain pages when enabled in configuration" do
- quick_login_as_admin
-
- visit spree.edit_admin_general_settings_path
-
- check 'enable_embedded_shopfronts'
- fill_in 'embedded_shopfronts_whitelist', with: "test.com"
-
- click_button 'Update'
-
- visit shops_path
- expect(page.response_headers['X-Frame-Options']).to be_nil
- expect(page.response_headers['Content-Security-Policy']).to eq "frame-ancestors test.com"
-
- visit spree.admin_path
- expect(page.response_headers['X-Frame-Options']).to eq 'DENY'
- expect(page.response_headers['Content-Security-Policy']).to eq "frame-ancestors 'none'"
- end
- end
-
describe "using iframes" do
let(:distributor) { create(:distributor_enterprise, name: 'My Embedded Hub', permalink: 'test_enterprise', with_payment_and_shipping: true) }
let(:supplier) { create(:supplier_enterprise) }
diff --git a/spec/features/consumer/shopping/orders_spec.rb b/spec/features/consumer/shopping/orders_spec.rb
index 379f1b5197..66f2bcf4ce 100644
--- a/spec/features/consumer/shopping/orders_spec.rb
+++ b/spec/features/consumer/shopping/orders_spec.rb
@@ -36,6 +36,12 @@ feature "Order Management", js: true do
end
context "when the distributor allows changes to be made to orders" do
+ before do
+ Spree::MailMethod.create!(
+ environment: Rails.env,
+ preferred_mails_from: 'spree@example.com'
+ )
+ end
before do
order.distributor.update_attributes(allow_order_changes: true)
end
diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb
index 1ed7cd3615..5e28f8ae50 100644
--- a/spec/features/consumer/shops_spec.rb
+++ b/spec/features/consumer/shops_spec.rb
@@ -6,6 +6,7 @@ feature 'Shops', js: true do
let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let!(:invisible_distributor) { create(:distributor_enterprise, visible: false) }
+ let!(:profile) { create(:distributor_enterprise, sells: 'none') }
let!(:d1) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let!(:d2) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise)) }
@@ -22,39 +23,54 @@ feature 'Shops', js: true do
end
- context "on the shops path" do
+ describe "listing shops" do
before do
visit shops_path
end
it "shows hubs" do
- page.should have_content distributor.name
+ expect(page).to have_content distributor.name
expand_active_table_node distributor.name
- page.should have_content "OUR PRODUCERS"
+ expect(page).to have_content "OUR PRODUCERS"
end
it "does not show invisible hubs" do
- page.should_not have_content invisible_distributor.name
+ expect(page).not_to have_content invisible_distributor.name
end
- it "should not show hubs that are not in an order cycle" do
- create(:simple_product, distributors: [d1, d2])
- visit shops_path
- page.should have_no_selector 'hub.inactive'
- page.should have_no_selector 'hub', text: d2.name
+ it "does not show hubs that are not in an order cycle" do
+ expect(page).to have_no_selector 'hub.inactive'
+ expect(page).to have_no_selector 'hub', text: d2.name
end
- it "should show closed shops after clicking the button" do
- create(:simple_product, distributors: [d1, d2])
- visit shops_path
+ it "does not show profiles" do
+ expect(page).not_to have_content profile.name
+ end
+
+ it "shows closed shops after clicking the button" do
click_link_and_ensure("Show closed shops", -> { page.has_selector? 'hub.inactive' })
- page.should have_selector 'hub.inactive', text: d2.name
+ expect(page).to have_selector 'hub.inactive', text: d2.name
end
- it "should link to the hub page" do
+ it "links to the hub page" do
follow_active_table_node distributor.name
expect(page).to have_current_path enterprise_shop_path(distributor)
end
+
+ describe "showing profiles" do
+ before do
+ check "Show profiles"
+ end
+
+ it "still shows hubs" do
+ expect(page).to have_content distributor.name
+ end
+
+ # https://github.com/openfoodfoundation/openfoodnetwork/issues/1718
+ it "shows profiles" do
+ expect(page).to have_content profile.name
+ end
+ end
end
describe "showing available hubs" do
@@ -66,8 +82,8 @@ feature 'Shops', js: true do
it "does not show hubs that are not ready for checkout" do
visit shops_path
- Enterprise.ready_for_checkout.should_not include hub
- page.should_not have_content hub.name
+ expect(Enterprise.ready_for_checkout).not_to include hub
+ expect(page).not_to have_content hub.name
end
end
@@ -183,8 +199,7 @@ feature 'Shops', js: true do
end
it "shows closed shops" do
- #click_link_and_ensure("Show closed shops", -> { page.has_selector? 'hub.inactive' })
- page.should have_selector 'hub.inactive', text: d2.name
+ expect(page).to have_selector 'hub.inactive', text: d2.name
end
end
diff --git a/spec/helpers/enterprises_helper_spec.rb b/spec/helpers/enterprises_helper_spec.rb
index 8e8fa3ed5e..f63cb49c0e 100644
--- a/spec/helpers/enterprises_helper_spec.rb
+++ b/spec/helpers/enterprises_helper_spec.rb
@@ -219,5 +219,37 @@ describe EnterprisesHelper do
end
end
end
+
+ context "when StripeConnect payment methods are present" do
+ let!(:pm3) { create(:stripe_payment_method, distributors: [distributor], preferred_enterprise_id: distributor.id) }
+ let!(:pm4) { create(:stripe_payment_method, distributors: [distributor], preferred_enterprise_id: some_other_distributor.id) }
+ let(:available_payment_methods) { helper.available_payment_methods }
+
+ before do
+ allow(helper).to receive(:current_distributor) { distributor }
+ end
+
+ context "and Stripe Connect is disabled" do
+ before { Spree::Config.set(stripe_connect_enabled: false) }
+
+ it "ignores Stripe payment methods" do
+ expect(available_payment_methods).to_not include pm3, pm4
+ end
+ end
+
+ context "and Stripe Connect is enabled" do
+ let!(:stripe_account) { create(:stripe_account, enterprise_id: distributor.id) }
+
+ before do
+ Spree::Config.set(stripe_connect_enabled: true)
+ allow(Stripe).to receive(:publishable_key) { "some_key" }
+ end
+
+ it "includes Stripe payment methods with a valid stripe accounts" do
+ expect(available_payment_methods).to include pm3
+ expect(available_payment_methods).to_not include pm4
+ end
+ end
+ end
end
end
diff --git a/spec/helpers/i18n_helper_spec.rb b/spec/helpers/i18n_helper_spec.rb
new file mode 100644
index 0000000000..4e044b3ca2
--- /dev/null
+++ b/spec/helpers/i18n_helper_spec.rb
@@ -0,0 +1,145 @@
+require 'spec_helper'
+
+describe I18nHelper do
+ let(:user) { create(:user) }
+
+ # In the real world, the helper is called in every request and sets
+ # I18n.locale to the chosen locale or the default. For testing purposes we
+ # have to restore I18n.locale for unit tests that don't call the helper, but
+ # rely on translated strings.
+ around do |example|
+ locale = I18n.locale
+ example.run
+ I18n.locale = locale
+ end
+
+ context "as guest" do
+ before do
+ allow(helper).to receive(:spree_current_user) { nil }
+ end
+
+ it "sets the default locale" do
+ helper.set_locale
+ expect(I18n.locale).to eq :en
+ end
+
+ it "sets the chosen locale" do
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+ expect(I18n.locale).to eq :es
+ end
+
+ it "remembers the chosen locale" do
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+
+ allow(helper).to receive(:params) { {} }
+ helper.set_locale
+ expect(I18n.locale).to eq :es
+ end
+
+ it "ignores unavailable locales" do
+ allow(helper).to receive(:params) { {locale: "xx"} }
+ helper.set_locale
+ expect(I18n.locale).to eq :en
+ end
+
+ it "remembers the last chosen locale" do
+ allow(helper).to receive(:params) { {locale: "en"} }
+ helper.set_locale
+
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+
+ allow(helper).to receive(:params) { {} }
+ helper.set_locale
+ expect(I18n.locale).to eq :es
+ end
+
+ it "remembers the chosen locale after logging in" do
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+
+ # log in
+ allow(helper).to receive(:spree_current_user) { user }
+ allow(helper).to receive(:params) { {} }
+ helper.set_locale
+ expect(I18n.locale).to eq :es
+ end
+
+ it "forgets the chosen locale without cookies" do
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+
+ # clean up cookies
+ cookies.delete :locale
+
+ allow(helper).to receive(:params) { {} }
+ helper.set_locale
+ expect(I18n.locale).to eq :en
+ end
+ end
+
+ context "logged in" do
+ before do
+ allow(helper).to receive(:spree_current_user) { user }
+ end
+
+ it "sets the default locale" do
+ helper.set_locale
+ expect(I18n.locale).to eq :en
+ end
+
+ it "sets the chosen locale" do
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+ expect(I18n.locale).to eq :es
+ expect(user.locale).to eq "es"
+ end
+
+ it "remembers the chosen locale" do
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+
+ allow(helper).to receive(:params) { {} }
+ helper.set_locale
+ expect(I18n.locale).to eq :es
+ end
+
+ it "remembers the last chosen locale" do
+ allow(helper).to receive(:params) { {locale: "en"} }
+ helper.set_locale
+
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+
+ allow(helper).to receive(:params) { {} }
+ helper.set_locale
+ expect(I18n.locale).to eq :es
+ end
+
+ it "remembers the chosen locale after logging out" do
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+
+ # log out
+ allow(helper).to receive(:spree_current_user) { nil }
+ allow(helper).to receive(:params) { {} }
+ helper.set_locale
+ expect(I18n.locale).to eq :es
+ end
+
+ it "remembers the chosen locale on another computer" do
+ allow(helper).to receive(:params) { {locale: "es"} }
+ helper.set_locale
+ expect(cookies[:locale]).to eq "es"
+
+ # switch computer / browser or loose cookies
+ cookies.delete :locale
+
+ allow(helper).to receive(:params) { {} }
+ helper.set_locale
+ expect(I18n.locale).to eq :es
+ end
+ end
+end
diff --git a/spec/helpers/injection_helper_spec.rb b/spec/helpers/injection_helper_spec.rb
index acec197b20..1cdeca6a3a 100644
--- a/spec/helpers/injection_helper_spec.rb
+++ b/spec/helpers/injection_helper_spec.rb
@@ -54,4 +54,12 @@ describe InjectionHelper do
helper.inject_taxons.should match taxon.name
end
+ it "only injects credit cards with a payment profile" do
+ allow(helper).to receive(:spree_current_user) { user }
+ card1 = create(:credit_card, last_digits: "1234", user_id: user.id, gateway_customer_profile_id: 'cust_123')
+ card2 = create(:credit_card, last_digits: "4321", user_id: user.id, gateway_customer_profile_id: nil)
+ injected_cards = helper.inject_saved_credit_cards
+ expect(injected_cards).to match "1234"
+ expect(injected_cards).to_not match "4321"
+ end
end
diff --git a/spec/helpers/spree/admin/base_helper_spec.rb b/spec/helpers/spree/admin/base_helper_spec.rb
new file mode 100644
index 0000000000..a47b04113f
--- /dev/null
+++ b/spec/helpers/spree/admin/base_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Spree::BaseHelper, type: :helper do
+ describe "#link_to_remove_fields" do
+ let(:name) { 'Hola' }
+ let(:form) { double('form_for', hidden_field: '') }
+ let(:options) { {} }
+
+ subject { helper.link_to_remove_fields(name, form, options) }
+
+ it 'returns an `a` tag followed by a hidden `input` tag' do
+ expect(subject).to eq("Hola<input type="hidden" name="_method" value="destroy">")
+ end
+ end
+end
diff --git a/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee
index c4a724a137..c168047ccf 100644
--- a/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee
+++ b/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee
@@ -17,6 +17,7 @@ describe "menuCtrl", ->
inject ($rootScope, $controller, _SideMenu_) ->
scope = $rootScope
SideMenu = _SideMenu_
+ spyOn(SideMenu, "init").and.callThrough()
spyOn(SideMenu, "select").and.callThrough()
spyOn(SideMenu, "setItems").and.callThrough()
ctrl = $controller 'sideMenuCtrl', {$scope: scope, enterprise: enterprise, SideMenu: SideMenu, enterprisePermissions: {}}
@@ -30,7 +31,7 @@ describe "menuCtrl", ->
expect(scope.menu.items).toBe SideMenu.items
it "sets the initally selected value", ->
- expect(SideMenu.select).toHaveBeenCalledWith 0
+ expect(SideMenu.init).toHaveBeenCalled()
describe "selecting an item", ->
diff --git a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee
index 23c42efde0..16a70c8abd 100644
--- a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee
+++ b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee
@@ -34,7 +34,7 @@ describe "LineItemsCtrl", ->
lineItem = { id: 7, quantity: 3, order: { id: 9 }, supplier: { id: 1 } }
httpBackend.expectGET("/admin/orders.json?q%5Bcompleted_at_gt%5D=SomeDate&q%5Bcompleted_at_lt%5D=SomeDate&q%5Bcompleted_at_not_null%5D=true&q%5Bstate_not_eq%5D=canceled").respond [order]
- httpBackend.expectGET("/admin/line_items.json?q%5Border%5D%5Bcompleted_at_gt%5D=SomeDate&q%5Border%5D%5Bcompleted_at_lt%5D=SomeDate&q%5Border%5D%5Bcompleted_at_not_null%5D=true&q%5Border%5D%5Bstate_not_eq%5D=canceled").respond [lineItem]
+ httpBackend.expectGET("/admin/bulk_line_items.json?q%5Border%5D%5Bcompleted_at_gt%5D=SomeDate&q%5Border%5D%5Bcompleted_at_lt%5D=SomeDate&q%5Border%5D%5Bcompleted_at_not_null%5D=true&q%5Border%5D%5Bstate_not_eq%5D=canceled").respond [lineItem]
httpBackend.expectGET("/admin/enterprises/for_line_items.json?ams_prefix=basic&q%5Bsells_in%5D%5B%5D=own&q%5Bsells_in%5D%5B%5D=any").respond [distributor]
httpBackend.expectGET("/admin/order_cycles.json?ams_prefix=basic&as=distributor&q%5Borders_close_at_gt%5D=SomeDate").respond [orderCycle]
httpBackend.expectGET("/admin/enterprises/for_line_items.json?ams_prefix=basic&q%5Bis_primary_producer_eq%5D=true").respond [supplier]
@@ -106,7 +106,7 @@ describe "LineItemsCtrl", ->
describe "where the request is successful", ->
beforeEach ->
- httpBackend.expectDELETE("/admin/orders/R12345678/line_items/1.json").respond "nothing"
+ httpBackend.expectDELETE("/admin/bulk_line_items/1.json").respond "nothing"
scope.deleteLineItem line_item1
httpBackend.flush()
@@ -115,7 +115,7 @@ describe "LineItemsCtrl", ->
describe "where the request is unsuccessful", ->
beforeEach ->
- httpBackend.expectDELETE("/admin/orders/R12345678/line_items/1.json").respond 404, "NO CONTENT"
+ httpBackend.expectDELETE("/admin/bulk_line_items/1.json").respond 404, "NO CONTENT"
scope.deleteLineItem line_item1
httpBackend.flush()
diff --git a/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee b/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee
index 3de04003e5..7367e06d88 100644
--- a/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee
+++ b/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee
@@ -19,7 +19,7 @@ describe "LineItems service", ->
beforeEach ->
response = [{ id: 5, name: 'LineItem 1'}]
- $httpBackend.expectGET('/admin/line_items.json').respond 200, response
+ $httpBackend.expectGET('/admin/bulk_line_items.json').respond 200, response
result = LineItems.index()
$httpBackend.flush()
@@ -41,7 +41,7 @@ describe "LineItems service", ->
beforeEach ->
lineItem = new LineItemResource({ id: 15, order: { number: '12345678'} })
- $httpBackend.expectPUT('/admin/orders/12345678/line_items/15.json').respond 200, { id: 15, name: 'LineItem 1'}
+ $httpBackend.expectPUT('/admin/bulk_line_items/15.json').respond 200, { id: 15, name: 'LineItem 1'}
LineItems.save(lineItem).then( -> resolved = true)
$httpBackend.flush()
@@ -60,7 +60,7 @@ describe "LineItems service", ->
beforeEach ->
lineItem = new LineItemResource( { id: 15, order: { number: '12345678'} } )
- $httpBackend.expectPUT('/admin/orders/12345678/line_items/15.json').respond 422, { error: 'obj' }
+ $httpBackend.expectPUT('/admin/bulk_line_items/15.json').respond 422, { error: 'obj' }
LineItems.save(lineItem).catch( -> rejected = true)
$httpBackend.flush()
@@ -115,7 +115,7 @@ describe "LineItems service", ->
lineItem = new LineItemResource({ id: 15, order: { number: '12345678'} })
LineItems.pristineByID[15] = lineItem
LineItems.byID[15] = lineItem
- $httpBackend.expectDELETE('/admin/orders/12345678/line_items/15.json').respond 200, { id: 15, name: 'LineItem 1'}
+ $httpBackend.expectDELETE('/admin/bulk_line_items/15.json').respond 200, { id: 15, name: 'LineItem 1'}
LineItems.delete(lineItem, callback).then( -> resolved = true).catch( -> rejected = true)
$httpBackend.flush()
@@ -140,7 +140,7 @@ describe "LineItems service", ->
lineItem = new LineItemResource({ id: 15, order: { number: '12345678'} })
LineItems.pristineByID[15] = lineItem
LineItems.byID[15] = lineItem
- $httpBackend.expectDELETE('/admin/orders/12345678/line_items/15.json').respond 422, { error: 'obj' }
+ $httpBackend.expectDELETE('/admin/bulk_line_items/15.json').respond 422, { error: 'obj' }
LineItems.delete(lineItem, callback).then( -> resolved = true).catch( -> rejected = true)
$httpBackend.flush()
diff --git a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee
index 7d4c5d1d7c..fc46c2a30d 100644
--- a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee
+++ b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee
@@ -38,18 +38,14 @@ describe "BulkProducts service", ->
describe "cloning products", ->
- it "clones products using a http get request to /admin/products/(permalink)/clone.json", ->
+ it "clones products using a http post request to /api/products/(id)/clone", ->
BulkProducts.products = [
id: 13
- permalink_live: "oranges"
]
- $httpBackend.expectGET("/admin/products/oranges/clone.json").respond 200,
- product:
- id: 17
- name: "new_product"
+ $httpBackend.expectPOST("/api/products/13/clone").respond 201,
+ id: 17
$httpBackend.expectGET("/api/products/17?template=bulk_show").respond 200, [
id: 17
- name: "new_product"
]
BulkProducts.cloneProduct BulkProducts.products[0]
$httpBackend.flush()
@@ -57,15 +53,13 @@ describe "BulkProducts service", ->
it "adds the product", ->
originalProduct =
id: 16
- permalink_live: "oranges"
clonedProduct =
id: 17
spyOn(BulkProducts, "insertProductAfter")
spyOn(BulkProducts, "unpackProduct")
BulkProducts.products = [originalProduct]
- $httpBackend.expectGET("/admin/products/oranges/clone.json").respond 200,
- product: clonedProduct
+ $httpBackend.expectPOST("/api/products/16/clone").respond 201, clonedProduct
$httpBackend.expectGET("/api/products/17?template=bulk_show").respond 200, clonedProduct
BulkProducts.cloneProduct BulkProducts.products[0]
$httpBackend.flush()
diff --git a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee
index f3ad508c4d..75d6fd8d36 100644
--- a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee
+++ b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee
@@ -16,6 +16,7 @@ describe "CheckoutCtrl", ->
$provide.value "CurrentHub", CurrentHubMock
null
Checkout =
+ purchase: ->
submit: ->
navigate: ->
bindFieldsToLocalStorage: ->
@@ -42,17 +43,17 @@ describe "CheckoutCtrl", ->
preventDefault: ->
beforeEach ->
- spyOn(Checkout, "submit")
+ spyOn(Checkout, "purchase")
scope.submitted = false
it "delegates to the service when valid", ->
scope.purchase(event, {$valid: true})
- expect(Checkout.submit).toHaveBeenCalled()
+ expect(Checkout.purchase).toHaveBeenCalled()
expect(scope.submitted).toBe(true)
it "does nothing when invalid", ->
scope.purchase(event, {$valid: false})
- expect(Checkout.submit).not.toHaveBeenCalled()
+ expect(Checkout.purchase).not.toHaveBeenCalled()
expect(scope.submitted).toBe(true)
it "is enabled", ->
diff --git a/spec/javascripts/unit/darkswarm/filters/closed_shops_spec.js.coffee b/spec/javascripts/unit/darkswarm/filters/closed_shops_spec.js.coffee
new file mode 100644
index 0000000000..8580ba7f3d
--- /dev/null
+++ b/spec/javascripts/unit/darkswarm/filters/closed_shops_spec.js.coffee
@@ -0,0 +1,30 @@
+describe "filtering closed shops", ->
+ enterprises = [{
+ name: "open shop"
+ active: true
+ is_distributor: true
+ }, {
+ name: "closed shop"
+ active: false
+ is_distributor: true
+ }, {
+ name: "profile"
+ active: false
+ is_distributor: false
+ }, {
+ name: "errornous entry"
+ does_not_have: "required attributes"
+ }
+ ]
+ closedShops = null
+
+ beforeEach ->
+ module 'Darkswarm'
+ inject ($filter) ->
+ closedShops = $filter('closedShops')
+
+ it "filters closed shops, but ignores profiles and invalid entries", ->
+ expect(closedShops(enterprises, false)).toEqual [enterprises[0], enterprises[2], enterprises[3]]
+
+ it "does not filter closed shops", ->
+ expect(closedShops(enterprises, true)).toEqual enterprises
diff --git a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee
index 78fd10f054..5ca05a344f 100644
--- a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee
+++ b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee
@@ -15,6 +15,11 @@ describe 'Checkout service', ->
id: 123
test: "bar"
method_type: "check"
+ },
+ {
+ id: 666
+ test: "qux"
+ method_type: "stripe"
}]
shippingMethods = [
{
@@ -46,6 +51,7 @@ describe 'Checkout service', ->
$provide.value "currentOrder", orderData
$provide.value "shippingMethods", shippingMethods
$provide.value "paymentMethods", paymentMethods
+ $provide.value "StripeInstancePublishableKey", "instance_publishable_key"
null
inject ($injector, _$httpBackend_, $rootScope)->
@@ -83,31 +89,47 @@ describe 'Checkout service', ->
Checkout.order.payment_method_id = 99
expect(Checkout.paymentMethod()).toEqual paymentMethods[0]
- it "Posts the Checkout to the server", ->
- $httpBackend.expectPUT("/checkout", {order: Checkout.preprocess()}).respond 200, {path: "test"}
- Checkout.submit()
- $httpBackend.flush()
-
- describe "when there is an error", ->
- it "redirects when a redirect is given", ->
- $httpBackend.expectPUT("/checkout").respond 400, {path: 'path'}
+ describe "submitting", ->
+ it "Posts the Checkout to the server", ->
+ $httpBackend.expectPUT("/checkout.json", {order: Checkout.preprocess()}).respond 200, {path: "test"}
Checkout.submit()
$httpBackend.flush()
- expect(Navigation.go).toHaveBeenCalledWith 'path'
- it "sends flash messages to the flash service", ->
- spyOn(FlashLoaderMock, "loadFlash") # Stubbing out writes to window.location
- $httpBackend.expectPUT("/checkout").respond 400, {flash: {error: "frogs"}}
- Checkout.submit()
+ describe "when there is an error", ->
+ it "redirects when a redirect is given", ->
+ $httpBackend.expectPUT("/checkout.json").respond 400, {path: 'path'}
+ Checkout.submit()
+ $httpBackend.flush()
+ expect(Navigation.go).toHaveBeenCalledWith 'path'
- $httpBackend.flush()
- expect(FlashLoaderMock.loadFlash).toHaveBeenCalledWith {error: "frogs"}
+ it "sends flash messages to the flash service", ->
+ spyOn(FlashLoaderMock, "loadFlash") # Stubbing out writes to window.location
+ $httpBackend.expectPUT("/checkout.json").respond 400, {flash: {error: "frogs"}}
+ Checkout.submit()
- it "puts errors into the scope", ->
- $httpBackend.expectPUT("/checkout").respond 400, {errors: {error: "frogs"}}
- Checkout.submit()
- $httpBackend.flush()
- expect(Checkout.errors).toEqual {error: "frogs"}
+ $httpBackend.flush()
+ expect(FlashLoaderMock.loadFlash).toHaveBeenCalledWith {error: "frogs"}
+
+ it "puts errors into the scope", ->
+ $httpBackend.expectPUT("/checkout.json").respond 400, {errors: {error: "frogs"}}
+ Checkout.submit()
+ $httpBackend.flush()
+ expect(Checkout.errors).toEqual {error: "frogs"}
+
+ describe "when using the Stripe Connect gateway", ->
+ beforeEach inject ($injector, StripeElements) ->
+ Checkout.order.payment_method_id = 666
+
+ it "requests a Stripe token before submitting", inject (StripeElements) ->
+ spyOn(StripeElements, "requestToken")
+ Checkout.purchase()
+ expect(StripeElements.requestToken).toHaveBeenCalled()
+
+ it "doesn't hit Stripe when reusing a credit card", inject (StripeElements) ->
+ spyOn(StripeElements, "requestToken")
+ Checkout.secrets.selected_card = 1
+ Checkout.purchase()
+ expect(StripeElements.requestToken).not.toHaveBeenCalled()
describe "data preprocessing", ->
beforeEach ->
@@ -155,3 +177,35 @@ describe 'Checkout service', ->
Checkout.order.payment_method_id = 123
source_attributes = Checkout.preprocess().payments_attributes[0].source_attributes
expect(source_attributes).not.toBeDefined()
+
+ describe "when the payment method is the Stripe Connect gateway", ->
+ beforeEach ->
+ Checkout.order.payment_method_id = 666
+ Checkout.secrets =
+ token: "stripe_token"
+ cc_type: "mastercard"
+ card:
+ last4: "1234"
+ exp_year: "2099"
+ exp_month: "10"
+
+ it "creates source attributes for the submitted card", ->
+ source_attributes = Checkout.preprocess().payments_attributes[0].source_attributes
+ expect(source_attributes).toBeDefined()
+ expect(source_attributes.gateway_payment_profile_id).toBe "stripe_token"
+ expect(source_attributes.cc_type).toBe "mastercard"
+ expect(source_attributes.last_digits).toBe "1234"
+ expect(source_attributes.year).toBe "2099"
+ expect(source_attributes.month).toBe "10"
+ expect(source_attributes.first_name).toBe orderData.bill_address.firstname
+ expect(source_attributes.last_name).toBe orderData.bill_address.lastname
+
+ describe "when a saved card from Stripe is used", ->
+ beforeEach ->
+ Checkout.order.payment_method_id = 666
+
+ it "passes the card ID in source attributes if a saved card is selected", ->
+ Checkout.secrets.selected_card = 1
+ source_attributes = Checkout.preprocess()
+ expect(source_attributes).toBeDefined()
+ expect(source_attributes.existing_card_id).toBe 1
diff --git a/spec/javascripts/unit/darkswarm/services/credit_card_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/credit_card_spec.js.coffee
new file mode 100644
index 0000000000..f6b6993859
--- /dev/null
+++ b/spec/javascripts/unit/darkswarm/services/credit_card_spec.js.coffee
@@ -0,0 +1,31 @@
+describe 'CreditCard service', ->
+ CreditCard = null
+
+ beforeEach ->
+ module 'Darkswarm'
+ module ($provide)->
+ $provide.value "savedCreditCards", []
+ $provide.value "railsFlash", null
+ null
+
+ inject (_CreditCard_)->
+ CreditCard = _CreditCard_
+
+ describe "process_params", ->
+ beforeEach ->
+ CreditCard.secrets =
+ card:
+ exp_month: "12"
+ exp_year: "2030"
+ last4: "1234"
+ cc_type: 'mastercard'
+ token: "token123"
+
+ it "uses cc_type, rather than fetching the brand from the card", ->
+ # This is important for processing the card with activemerchant
+ process_params = CreditCard.process_params()
+ expect(process_params['exp_month']).toEqual "12"
+ expect(process_params['exp_year']).toEqual "2030"
+ expect(process_params['last4']).toEqual "1234"
+ expect(process_params['token']).toEqual "token123"
+ expect(process_params['cc_type']).toEqual "mastercard"
diff --git a/spec/javascripts/unit/darkswarm/services/stripe_elements_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/stripe_elements_spec.js.coffee
new file mode 100644
index 0000000000..0252298f82
--- /dev/null
+++ b/spec/javascripts/unit/darkswarm/services/stripe_elements_spec.js.coffee
@@ -0,0 +1,62 @@
+describe 'StripeElements Service', ->
+ $httpBackend = $q = $rootScope = StripeElements = null
+ StripeMock = { createToken: null }
+ CardMock = { some: "card" }
+
+ beforeEach ->
+ module 'Darkswarm'
+ module ($provide) ->
+ $provide.value "railsFlash", null
+ null
+
+ inject (_StripeElements_, _$httpBackend_, _$q_, _$rootScope_) ->
+ $httpBackend = _$httpBackend_
+ StripeElements = _StripeElements_
+ $q = _$q_
+ $rootScope = _$rootScope_
+
+ describe "requestToken", ->
+ secrets = {}
+ submit = null
+ response = null
+
+ beforeEach inject ($window) ->
+ StripeElements.stripe = StripeMock
+ StripeElements.card = CardMock
+
+ describe "with satifactory data", ->
+ beforeEach ->
+ submit = jasmine.createSpy()
+ response = { token: { id: "token", card: { brand: 'MasterCard', last4: "5678", exp_month: 10, exp_year: 2099 } } }
+ StripeMock.createToken = => $q.when(response)
+
+ it "saves the response data to secrets, and submits the form", ->
+ StripeElements.requestToken(secrets, submit)
+ $rootScope.$digest() # required for #then to by called
+ expect(secrets.token).toEqual "token"
+ expect(secrets.cc_type).toEqual "master"
+ expect(submit).toHaveBeenCalled()
+
+ describe "with unsatifactory data", ->
+ beforeEach ->
+ submit = jasmine.createSpy()
+ response = { token: {id: "token" }, error: { message: 'There was a problem' } }
+ StripeMock.createToken = => $q.when(response)
+
+ it "doesn't submit the form, shows an error message instead", inject (Loading, RailsFlashLoader) ->
+ spyOn(Loading, "clear")
+ spyOn(RailsFlashLoader, "loadFlash")
+ StripeElements.requestToken(secrets, submit)
+ $rootScope.$digest() # required for #then to by called
+ expect(submit).not.toHaveBeenCalled()
+ expect(Loading.clear).toHaveBeenCalled()
+ expect(RailsFlashLoader.loadFlash).toHaveBeenCalledWith({error: "Error: There was a problem"})
+
+ describe 'mapCC', ->
+ it "maps the brand returned by Stripe to that required by activemerchant", ->
+ expect(StripeElements.mapCC('MasterCard')).toEqual "master"
+ expect(StripeElements.mapCC('Visa')).toEqual "visa"
+ expect(StripeElements.mapCC('American Express')).toEqual "american_express"
+ expect(StripeElements.mapCC('Discover')).toEqual "discover"
+ expect(StripeElements.mapCC('JCB')).toEqual "jcb"
+ expect(StripeElements.mapCC('Diners Club')).toEqual "diners_club"
diff --git a/spec/lib/stripe/account_connector_spec.rb b/spec/lib/stripe/account_connector_spec.rb
new file mode 100644
index 0000000000..1b5721ba33
--- /dev/null
+++ b/spec/lib/stripe/account_connector_spec.rb
@@ -0,0 +1,109 @@
+require 'spec_helper'
+require 'stripe/account_connector'
+require 'stripe/oauth'
+
+module Stripe
+ describe AccountConnector do
+ describe "create_account" do
+ let(:user) { create(:user) }
+ let(:enterprise) { create(:enterprise) }
+ let(:payload) { { "junk" => "Ssfs" } }
+ let(:state) { JWT.encode(payload, Openfoodnetwork::Application.config.secret_token) }
+ let(:params) { { "state" => state } }
+ let(:connector) { AccountConnector.new(user, params) }
+
+ before do
+ allow(Stripe).to receive(:api_key) { "sk_test_12345" }
+ end
+
+ context "when the connection was cancelled by the user" do
+ before do
+ params[:action] = "connect_callback"
+ params[:error] = "access_denied"
+ end
+
+ it "returns false and does not create a new StripeAccount" do
+ expect do
+ expect(connector.create_account).to be false
+ end.to_not change(StripeAccount, :count)
+ end
+ end
+
+ context "when the connection was not cancelled by the user" do
+ context "when params have no 'code' key" do
+ it "raises a StripeError" do
+ expect do
+ expect{ connector.create_account }.to raise_error StripeError
+ end.to_not change(StripeAccount, :count)
+ end
+ end
+
+ context "when params have a 'code' key" do
+ before { params["code"] = 'code' }
+
+ context "and the decoded state param doesn't contain an 'enterprise_id' key" do
+ it "raises an AccessDenied error" do
+ expect do
+ expect{ connector.create_account }.to raise_error CanCan::AccessDenied
+ end.to_not change(StripeAccount, :count)
+ end
+ end
+
+ context "and the decoded state param contains an 'enterprise_id' key" do
+ let(:payload) { { enterprise_id: enterprise.permalink } }
+ let(:token_response) { { "stripe_user_id" => "some_user_id", "stripe_publishable_key" => "some_key" } }
+
+ before do
+ stub_request(:post, "https://connect.stripe.com/oauth/token").
+ with(body: {"code"=>"code", "grant_type"=>"authorization_code"}).
+ to_return(status: 200, body: JSON.generate(token_response) )
+ end
+
+ context "but the user doesn't manage own or manage the corresponding enterprise" do
+ it "makes a request to cancel the Stripe connection and raises an error" do
+ expect(OAuth).to receive(:deauthorize).with(stripe_user_id: "some_user_id")
+ expect do
+ expect{ connector.create_account }.to raise_error CanCan::AccessDenied
+ end.to_not change(StripeAccount, :count)
+ end
+ end
+
+ context "and the user manages the corresponding enterprise" do
+ before do
+ user.enterprise_roles.create(enterprise: enterprise)
+ end
+
+ it "raises no errors" do
+ expect(OAuth).to_not receive(:deauthorize)
+ connector.create_account
+ end
+
+ it "allows creations of a new Stripe Account from the callback params" do
+ expect{ connector.create_account }.to change(StripeAccount, :count).by(1)
+ account = StripeAccount.last
+ expect(account.stripe_user_id).to eq "some_user_id"
+ expect(account.stripe_publishable_key).to eq "some_key"
+ end
+ end
+
+ context "and the user owns the corresponding enterprise" do
+ let(:user) { enterprise.owner }
+
+ it "raises no errors" do
+ expect(OAuth).to_not receive(:deauthorize)
+ connector.create_account
+ end
+
+ it "allows creations of a new Stripe Account from the callback params" do
+ expect{ connector.create_account }.to change(StripeAccount, :count).by(1)
+ account = StripeAccount.last
+ expect(account.stripe_user_id).to eq "some_user_id"
+ expect(account.stripe_publishable_key).to eq "some_key"
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/stripe/webhook_handler_spec.rb b/spec/lib/stripe/webhook_handler_spec.rb
new file mode 100644
index 0000000000..e83cf01805
--- /dev/null
+++ b/spec/lib/stripe/webhook_handler_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+require 'stripe/webhook_handler'
+
+module Stripe
+ describe WebhookHandler do
+ let(:event) { double(:event, type: 'some.event') }
+ let(:handler) { WebhookHandler.new(event) }
+
+ describe "event_mappings" do
+ it { expect(handler.send(:event_mappings)).to be_a Hash }
+ end
+
+ describe "known_event?" do
+ context "when event mappings know about the event type" do
+ before do
+ allow(handler).to receive(:event_mappings) { { 'some.event' => :something } }
+ end
+
+ it { expect(handler.send(:known_event?)).to be true }
+ end
+
+ context "when event mappings do not know about the event type" do
+ before do
+ allow(handler).to receive(:event_mappings) { { 'some.other.event' => :something } }
+ end
+
+ it { expect(handler.send(:known_event?)).to be false }
+ end
+ end
+
+ describe "handle" do
+ context "when the event is known" do
+ before do
+ allow(handler).to receive(:event_mappings) { { 'some.event' => :some_method } }
+ end
+
+ it "calls the handler method, and returns the result" do
+ expect(handler).to receive(:some_method) { :result }
+ expect(handler.handle).to eq :result
+ end
+ end
+
+ context "when the event is unknown" do
+ before do
+ allow(handler).to receive(:event_mappings) { { 'some.other.event' => :some_method } }
+ end
+
+ it "does not call the handler method, and returns :unknown" do
+ expect(handler).to_not receive(:some_method)
+ expect(handler.handle).to be :unknown
+ end
+ end
+ end
+
+ describe "deauthorize" do
+ context "when the event has no 'account' attribute" do
+ it "does destroy stripe accounts, returns :ignored" do
+ expect(handler).to_not receive(:destroy_stripe_accounts_linked_to)
+ expect(handler.send(:deauthorize)).to be :ignored
+ end
+ end
+
+ context "when the event has an 'account' attribute" do
+ before do
+ allow(event).to receive(:account) { 'some.account' }
+ end
+
+ context "when some stripe accounts are destroyed" do
+ before do
+ allow(handler).to receive(:destroy_stripe_accounts_linked_to).with('some.account') { [double(:destroyed_stripe_account)] }
+ end
+
+ it { expect(handler.send(:deauthorize)).to be :success }
+ end
+
+ context "when no stripe accounts are destroyed" do
+ before do
+ allow(handler).to receive(:destroy_stripe_accounts_linked_to).with('some.account') { [] }
+ end
+
+ it { expect(handler.send(:deauthorize)).to be :ignored }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/mailers/order_mailer_spec.rb b/spec/mailers/order_mailer_spec.rb
index 6e88742fe7..18e864f7cd 100644
--- a/spec/mailers/order_mailer_spec.rb
+++ b/spec/mailers/order_mailer_spec.rb
@@ -19,6 +19,10 @@ describe Spree::OrderMailer do
ship_address = create(:address, :address1 => "distributor address", :city => 'The Shire', :zipcode => "1234")
@order1 = create(:order, :distributor => @distributor, :bill_address => @bill_address, ship_address: ship_address, :special_instructions => @shipping_instructions)
ActionMailer::Base.deliveries = []
+ Spree::MailMethod.create!(
+ environment: Rails.env,
+ preferred_mails_from: 'spree@example.com'
+ )
end
describe "order confirmation for customers" do
diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb
index c964342073..0ae398dd27 100644
--- a/spec/mailers/producer_mailer_spec.rb
+++ b/spec/mailers/producer_mailer_spec.rb
@@ -2,6 +2,12 @@ require 'spec_helper'
require 'yaml'
describe ProducerMailer do
+ before do
+ Spree::MailMethod.create!(
+ environment: Rails.env,
+ preferred_mails_from: 'spree@example.com'
+ )
+ end
let!(:zone) { create(:zone_with_member) }
let!(:tax_rate) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, zone: zone, amount: 0.1) }
let!(:tax_category) { create(:tax_category, tax_rates: [tax_rate]) }
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
index 5e389b71bf..d5044449de 100644
--- a/spec/mailers/user_mailer_spec.rb
+++ b/spec/mailers/user_mailer_spec.rb
@@ -27,7 +27,7 @@ describe Spree::UserMailer do
context 'subject includes' do
it 'translated devise instructions' do
- expect(@message.subject).to include "Password Reset Instructions"
+ expect(@message.subject).to include "Reset password instructions"
end
it 'Spree site name' do
diff --git a/spec/models/enterprise_fee_spec.rb b/spec/models/enterprise_fee_spec.rb
index e0df4ce5d0..6c4245c875 100644
--- a/spec/models/enterprise_fee_spec.rb
+++ b/spec/models/enterprise_fee_spec.rb
@@ -170,7 +170,7 @@ describe EnterpriseFee do
order.adjustments.create({:amount => 12.34,
:source => order,
:originator => tax_rate,
- :locked => true,
+ :state => 'closed',
:label => 'hello' }, :without_protection => true)
expect do
diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb
index 8715e1ef5c..59d71c59ef 100644
--- a/spec/models/order_cycle_spec.rb
+++ b/spec/models/order_cycle_spec.rb
@@ -516,6 +516,12 @@ describe OrderCycle do
let!(:order4) { create(:completed_order_with_totals, distributor: shop, user: user, order_cycle: create(:order_cycle)) }
let!(:order5) { create(:completed_order_with_totals, distributor: shop, user: user, order_cycle: oc) }
+ before do
+ Spree::MailMethod.create!(
+ environment: Rails.env,
+ preferred_mails_from: 'spree@example.com'
+ )
+ end
before { order5.cancel }
it "only returns items from non-cancelled orders in the OC, placed by the user at the shop" do
diff --git a/spec/models/product_distribution_spec.rb b/spec/models/product_distribution_spec.rb
index 7e1a6933bc..2fa2bab427 100644
--- a/spec/models/product_distribution_spec.rb
+++ b/spec/models/product_distribution_spec.rb
@@ -78,7 +78,7 @@ describe ProductDistribution do
it "returns the adjustment when present" do
pd = create(:product_distribution)
line_item = create(:line_item)
- adjustment = pd.enterprise_fee.create_locked_adjustment('foo', line_item.order, line_item, true)
+ adjustment = pd.enterprise_fee.create_adjustment('foo', line_item.order, line_item, true)
pd.send(:adjustment_for, line_item).should == adjustment
end
@@ -86,8 +86,8 @@ describe ProductDistribution do
it "raises an error when there are multiple adjustments for this enterprise fee" do
pd = create(:product_distribution)
line_item = create(:line_item)
- pd.enterprise_fee.create_locked_adjustment('one', line_item.order, line_item, true)
- pd.enterprise_fee.create_locked_adjustment('two', line_item.order, line_item, true)
+ pd.enterprise_fee.create_adjustment('one', line_item.order, line_item, true)
+ pd.enterprise_fee.create_adjustment('two', line_item.order, line_item, true)
expect do
pd.send(:adjustment_for, line_item)
diff --git a/spec/models/spree/gateway/stripe_connect_spec.rb b/spec/models/spree/gateway/stripe_connect_spec.rb
new file mode 100644
index 0000000000..4e39a7812e
--- /dev/null
+++ b/spec/models/spree/gateway/stripe_connect_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Spree::Gateway::StripeConnect, type: :model do
+ let(:provider) do
+ double('provider').tap do |p|
+ p.stub(:purchase)
+ p.stub(:authorize)
+ p.stub(:capture)
+ end
+ end
+
+ let(:stripe_account_id) { "acct_123" }
+
+ before do
+ allow(Stripe).to receive(:api_key) { "sk_test_123456" }
+ allow(subject).to receive(:stripe_account_id) { stripe_account_id }
+ subject.stub(:options_for_purchase_or_auth).and_return(['money', 'cc', 'opts'])
+ subject.stub(:provider).and_return provider
+ end
+
+ describe "#token_from_card_profile_ids" do
+ let(:creditcard) { double(:creditcard) }
+ context "when the credit card provided has a gateway_payment_profile_id" do
+ before do
+ allow(creditcard).to receive(:gateway_payment_profile_id) { "token_or_card_id123" }
+ allow(subject).to receive(:tokenize_instance_customer_card) { "tokenized" }
+ end
+
+ context "when the credit card provided has a gateway_customer_profile_id" do
+ before { allow(creditcard).to receive(:gateway_customer_profile_id) { "customer_id123" } }
+
+ it "requests a new token via tokenize_instance_customer_card" do
+ result = subject.send(:token_from_card_profile_ids, creditcard)
+ expect(result).to eq "tokenized"
+ end
+ end
+
+ context "when the credit card provided does not have a gateway_customer_profile_id" do
+ before { allow(creditcard).to receive(:gateway_customer_profile_id) { nil } }
+ it "returns the gateway_payment_profile_id (assumed to be a token already)" do
+ result = subject.send(:token_from_card_profile_ids, creditcard)
+ expect(result).to eq "token_or_card_id123"
+ end
+ end
+ end
+
+ context "when the credit card provided does not have a gateway_payment_profile_id" do
+ before { allow(creditcard).to receive(:gateway_payment_profile_id) { nil } }
+ before { allow(creditcard).to receive(:gateway_customer_profile_id) { "customer_id123" } }
+
+ it "returns nil....?" do
+ result = subject.send(:token_from_card_profile_ids, creditcard)
+ expect(result).to be nil
+ end
+ end
+ end
+
+ describe "#tokenize_instance_customer_card" do
+ let(:customer_id) { "customer123" }
+ let(:card_id) { "card123" }
+ let(:token_mock) { { id: "test_token123" } }
+
+ before do
+ stub_request(:post, "https://api.stripe.com/v1/tokens")
+ .with(body: { "card" => "card123", "customer" => "customer123"})
+ .to_return(body: JSON.generate(token_mock))
+ end
+
+ it "requests a new token for the customer and card from Stripe, and returns the id of the response" do
+ expect(subject.send(:tokenize_instance_customer_card, customer_id, card_id)).to eq token_mock[:id]
+ end
+ end
+end
diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb
index e7c46c27cb..d722e234d2 100644
--- a/spec/models/spree/order_spec.rb
+++ b/spec/models/spree/order_spec.rb
@@ -140,7 +140,7 @@ describe Spree::Order do
it "returns the sum of eligible enterprise fee adjustments" do
ef = create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new )
ef.calculator.set_preference :amount, 123.45
- a = ef.create_locked_adjustment("adjustment", o, o, true)
+ a = ef.create_adjustment("adjustment", o, o, true)
o.admin_and_handling_total.should == 123.45
end
@@ -148,7 +148,7 @@ describe Spree::Order do
it "does not include ineligible adjustments" do
ef = create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new )
ef.calculator.set_preference :amount, 123.45
- a = ef.create_locked_adjustment("adjustment", o, o, true)
+ a = ef.create_adjustment("adjustment", o, o, true)
a.update_column :eligible, false
@@ -487,6 +487,13 @@ describe Spree::Order do
describe "scopes" do
describe "not_state" do
+ before do
+ Spree::MailMethod.create!(
+ environment: Rails.env,
+ preferred_mails_from: 'spree@example.com'
+ )
+ end
+
it "finds only orders not in specified state" do
o = FactoryGirl.create(:completed_order_with_totals)
o.cancel!
@@ -702,7 +709,7 @@ describe Spree::Order do
it "removes transaction fees" do
# Change the payment method
- order.payment.update_attributes(payment_method_id: payment_method.id)
+ order.payments.first.update_attributes(payment_method_id: payment_method.id)
order.save
# Check if fees got updated
@@ -743,4 +750,15 @@ describe Spree::Order do
end
end
end
+
+ describe "determining checkout steps for an order" do
+ let!(:enterprise) { create(:enterprise) }
+ let!(:order) { create(:order, distributor: enterprise) }
+ let!(:payment_method) { create(:stripe_payment_method, distributor_ids: [enterprise.id], preferred_enterprise_id: enterprise.id) }
+ let!(:payment) { create(:payment, order: order, payment_method: payment_method) }
+
+ it "does not include the :confirm step" do
+ expect(order.checkout_steps).to_not include "confirm"
+ end
+ end
end
diff --git a/spec/models/spree/payment_method_spec.rb b/spec/models/spree/payment_method_spec.rb
index 70808c231e..28707876d3 100644
--- a/spec/models/spree/payment_method_spec.rb
+++ b/spec/models/spree/payment_method_spec.rb
@@ -21,6 +21,7 @@ module Spree
Spree::Gateway::Migs.clean_name.should == "MasterCard Internet Gateway Service (MIGS)"
Spree::Gateway::Pin.clean_name.should == "Pin Payments"
Spree::Gateway::PayPalExpress.clean_name.should == "PayPal Express"
+ Spree::Gateway::StripeConnect.clean_name.should == "Stripe"
# Testing else condition
Spree::Gateway::BogusSimple.clean_name.should == "BogusSimple"
diff --git a/spec/models/spree/payment_spec.rb b/spec/models/spree/payment_spec.rb
index 0877959cbb..36fb1ceaea 100644
--- a/spec/models/spree/payment_spec.rb
+++ b/spec/models/spree/payment_spec.rb
@@ -124,5 +124,89 @@ module Spree
payment.refund!
end
end
+
+ describe "applying transaction fees" do
+ let!(:order) { create(:order) }
+ let!(:line_item) { create(:line_item, order: order, quantity: 3, price: 5.00) }
+
+ before do
+ order.reload.update!
+ end
+
+ context "to Stripe payments" do
+ let(:shop) { create(:enterprise) }
+ let(:payment_method) { create(:stripe_payment_method, distributor_ids: [create(:distributor_enterprise).id], preferred_enterprise_id: shop.id) }
+ let(:payment) { create(:payment, order: order, payment_method: payment_method, amount: order.total) }
+ let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }
+
+ before do
+ payment_method.calculator = calculator
+ payment_method.save!
+
+ allow(order).to receive(:pending_payments) { [payment] }
+ end
+
+ context "when the payment fails" do
+ let(:failed_response) { ActiveMerchant::Billing::Response.new(false, "This is an error message") }
+
+ before do
+ allow(payment_method).to receive(:purchase) { failed_response }
+ end
+
+ it "makes the transaction fee ineligible and finalizes it" do
+ # Decided to wrap the save process in order.process_payments!
+ # since that is the context it is usually performed in
+ order.process_payments!
+ expect(order.payments.count).to eq 1
+ expect(order.payments).to include payment
+ expect(payment.state).to eq "failed"
+ expect(payment.adjustment.eligible?).to be false
+ expect(payment.adjustment.finalized?).to be true
+ expect(order.adjustments.payment_fee.count).to eq 1
+ expect(order.adjustments.payment_fee.eligible).to_not include payment.adjustment
+ end
+ end
+
+ context "when the payment information is invalid" do
+ before do
+ allow(payment_method).to receive(:supports?) { false }
+ end
+
+ it "makes the transaction fee ineligible and finalizes it" do
+ # Decided to wrap the save process in order.process_payments!
+ # since that is the context it is usually performed in
+ order.process_payments!
+ expect(order.payments.count).to eq 1
+ expect(order.payments).to include payment
+ expect(payment.state).to eq "invalid"
+ expect(payment.adjustment.eligible?).to be false
+ expect(payment.adjustment.finalized?).to be true
+ expect(order.adjustments.payment_fee.count).to eq 1
+ expect(order.adjustments.payment_fee.eligible).to_not include payment.adjustment
+ end
+ end
+
+ context "when the payment is processed successfully" do
+ let(:successful_response) { ActiveMerchant::Billing::Response.new(true, "Yay!") }
+
+ before do
+ allow(payment_method).to receive(:purchase) { successful_response }
+ end
+
+ it "creates an appropriate adjustment" do
+ # Decided to wrap the save process in order.process_payments!
+ # since that is the context it is usually performed in
+ order.process_payments!
+ expect(order.payments.count).to eq 1
+ expect(order.payments).to include payment
+ expect(payment.state).to eq "completed"
+ expect(payment.adjustment.eligible?).to be true
+ expect(order.adjustments.payment_fee.count).to eq 1
+ expect(order.adjustments.payment_fee.eligible).to include payment.adjustment
+ expect(payment.adjustment.amount).to eq 1.5
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb
index 0b57414677..ea22d42ec0 100644
--- a/spec/models/spree/user_spec.rb
+++ b/spec/models/spree/user_spec.rb
@@ -1,6 +1,4 @@
describe Spree.user_class do
- include AuthenticationWorkflow
-
describe "associations" do
it { should have_many(:owned_enterprises) }
@@ -95,56 +93,11 @@ describe Spree.user_class do
end
describe "as admin" do
- let(:admin) { quick_login_as_admin }
+ let(:admin) { create(:admin_user) }
it "returns all users" do
expect(admin.known_users).to include u1, u2, u3
end
end
end
-
- describe "retrieving orders for /account page" do
- let!(:u1) { create(:user) }
- let!(:u2) { create(:user) }
- let!(:distributor1) { create(:distributor_enterprise) }
- let!(:distributor2) { create(:distributor_enterprise) }
- let!(:d1o1) { create(:completed_order_with_totals, distributor: distributor1, user_id: u1.id) }
- let!(:d1o2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u1.id) }
- let!(:d1_order_for_u2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u2.id) }
- let!(:d1o3) { create(:order, state: 'cart', distributor: distributor1, user_id: u1.id) }
- let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user_id: u2.id) }
- let!(:accounts_distributor) {create :distributor_enterprise}
- let!(:order_account_invoice) { create(:order, distributor: accounts_distributor, state: 'complete', user: u1) }
-
- let!(:completed_payment) { create(:payment, order: d1o1, state: 'completed') }
- let!(:payment) { create(:payment, order: d1o2, state: 'checkout') }
-
- before do
- Spree::Config.accounts_distributor_id = accounts_distributor.id
- end
-
- it "returns enterprises that the user has ordered from, excluding accounts distributor" do
- expect(u1.enterprises_ordered_from).to eq [distributor1.id]
- end
-
- it "returns orders and payments for the user, organised by distributor" do
- expect(u1.orders_by_distributor).to include distributor1
- expect(u1.orders_by_distributor.first.distributed_orders).to include d1o1
- end
-
- it "doesn't return irrelevant distributors" do
- expect(u1.orders_by_distributor).not_to include distributor2
- end
- it "doesn't return other users' orders" do
- expect(u1.orders_by_distributor.first.distributed_orders).not_to include d1_order_for_u2
- end
-
- it "doesn't return uncompleted orders" do
- expect(u1.orders_by_distributor.first.distributed_orders).not_to include d1o3
- end
-
- it "doesn't return payments that are still at checkout stage" do
- expect(u1.orders_by_distributor.first.distributed_orders.map{|o| o.payments}.flatten).not_to include payment
- end
- end
end
diff --git a/spec/models/stripe_account_spec.rb b/spec/models/stripe_account_spec.rb
new file mode 100644
index 0000000000..b4a1cbfb00
--- /dev/null
+++ b/spec/models/stripe_account_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+require 'stripe/oauth'
+
+describe StripeAccount do
+ describe "deauthorize_and_destroy" do
+ let!(:enterprise) { create(:enterprise) }
+ let!(:enterprise2) { create(:enterprise) }
+ let(:client_id) { 'ca_abc123' }
+ let(:stripe_user_id) { 'acct_abc123' }
+ let!(:stripe_account) { create(:stripe_account, enterprise: enterprise, stripe_user_id: stripe_user_id) }
+
+ before do
+ allow(Stripe).to receive(:api_key) { "sk_test_12345" }
+ allow(Stripe).to receive(:client_id) { client_id }
+ end
+
+ context "when the Stripe API disconnect fails" do
+ before do
+ stub_request(:post, "https://connect.stripe.com/oauth/deauthorize").
+ with(body: { "client_id" => client_id, "stripe_user_id" => stripe_user_id }).
+ to_return(status: 400, body: JSON.generate(error: 'invalid_grant', error_description: "Some Message"))
+ end
+
+ it "destroys the record and notifies Bugsnag" do
+ expect(Bugsnag).to receive(:notify)
+ stripe_account.deauthorize_and_destroy
+ expect(StripeAccount.all).to_not include(stripe_account)
+ end
+ end
+
+ context "when the Stripe API disconnect succeeds" do
+ before do
+ stub_request(:post, "https://connect.stripe.com/oauth/deauthorize").
+ with(body: { "client_id" => client_id, "stripe_user_id" => stripe_user_id }).
+ to_return(status: 200, body: JSON.generate(stripe_user_id: stripe_user_id))
+ end
+
+ it "destroys the record" do
+ stripe_account.deauthorize_and_destroy
+ expect(StripeAccount.all).not_to include(stripe_account)
+ end
+ end
+
+ context "if the account is also associated with another Enterprise" do
+ let!(:another_stripe_account) { create(:stripe_account, enterprise: enterprise2, stripe_user_id: stripe_user_id) }
+
+ it "Doesn't make a Stripe API disconnection request " do
+ expect(Stripe::OAuth).to_not receive(:deauthorize)
+ stripe_account.deauthorize_and_destroy
+ expect(StripeAccount.all).not_to include(stripe_account)
+ end
+ end
+ end
+end
diff --git a/spec/requests/checkout/failed_checkout_spec.rb b/spec/requests/checkout/failed_checkout_spec.rb
new file mode 100644
index 0000000000..6246beadf0
--- /dev/null
+++ b/spec/requests/checkout/failed_checkout_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe "checking out an order that initially fails", type: :request do
+ include ShopWorkflow
+
+ let!(:shop) { create(:enterprise) }
+ let!(:order_cycle) { create(:simple_order_cycle) }
+ let!(:exchange) { create(:exchange, order_cycle: order_cycle, sender: order_cycle.coordinator, receiver: shop, incoming: false, pickup_time: "Monday") }
+ let!(:address) { create(:address) }
+ let!(:order) { create(:order, distributor: shop, order_cycle: order_cycle) }
+ let!(:line_item) { create(:line_item, order: order, quantity: 3, price: 5.00) }
+ let!(:payment_method) { create(:bogus_payment_method, distributor_ids: [shop.id], environment: Rails.env) }
+ let!(:check_payment_method) { create(:payment_method, distributor_ids: [shop.id], environment: Rails.env) }
+ let!(:shipping_method) { create(:shipping_method, distributor_ids: [shop.id]) }
+ let!(:shipment) { create(:shipment, order: order, shipping_method: shipping_method) }
+ let(:params) do
+ { format: :json, order: {
+ shipping_method_id: shipping_method.id,
+ payments_attributes: [{payment_method_id: payment_method.id}],
+ bill_address_attributes: address.attributes.slice("firstname", "lastname", "address1", "address2", "phone", "city", "zipcode", "state_id", "country_id"),
+ ship_address_attributes: address.attributes.slice("firstname", "lastname", "address1", "address2", "phone", "city", "zipcode", "state_id", "country_id")
+ } }
+ end
+
+ before do
+ order.reload.update_totals
+ set_order order
+ end
+
+ context "when shipping and payment fees apply" do
+ let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }
+
+ before do
+ payment_method.calculator = calculator.dup
+ payment_method.save!
+ check_payment_method.calculator = calculator.dup
+ check_payment_method.save!
+ shipping_method.calculator = calculator.dup
+ shipping_method.save!
+ end
+
+ it "clears shipments and payments before rendering the checkout" do
+ put update_checkout_path, params
+
+ # Checking out a BogusGateway without a source fails at :payment
+ # Shipments and payments should then be cleared before rendering checkout
+ expect(response.status).to be 400
+ expect(flash[:error]).to eq I18n.t(:payment_processing_failed)
+ order.reload
+ expect(order.shipments.count).to be 0
+ expect(order.payments.count).to be 0
+ expect(order.adjustment_total).to eq 0
+
+ # Add another line item to change the fee totals
+ create(:line_item, order: order, quantity: 3, price: 5.00)
+
+ # Use a check payment method, which should work
+ params[:order][:payments_attributes][0][:payment_method_id] = check_payment_method.id
+ put update_checkout_path, params
+
+ expect(response.status).to be 200
+ order.reload
+ expect(order.total).to eq 36
+ expect(order.adjustment_total).to eq 6
+ expect(order.item_total).to eq 30
+ expect(order.shipments.count).to eq 1
+ expect(order.payments.count).to eq 1
+ end
+ end
+end
diff --git a/spec/requests/checkout/paypal_spec.rb b/spec/requests/checkout/paypal_spec.rb
new file mode 100644
index 0000000000..0537770474
--- /dev/null
+++ b/spec/requests/checkout/paypal_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+describe "checking out an order with a paypal express payment method", type: :request do
+ include ShopWorkflow
+
+ let!(:address) { create(:address) }
+ let!(:shop) { create(:enterprise) }
+ let!(:shipping_method) { create(:shipping_method, distributor_ids: [shop.id]) }
+ let!(:order) { create(:order, distributor: shop, ship_address: address.dup, bill_address: address.dup) }
+ let!(:shipment) { create(:shipment, order: order, shipping_method: shipping_method) }
+ let!(:line_item) { create(:line_item, order: order, quantity: 3, price: 5.00) }
+ let!(:payment_method) { Spree::Gateway::PayPalExpress.create!(name: "PayPalExpress", distributor_ids: [create(:distributor_enterprise).id], environment: Rails.env) }
+ let(:params) { { token: 'lalalala', PayerID: 'payer1', payment_method_id: payment_method.id } }
+ let(:mocked_xml_response) {
+ "
+
+
+ Success
+ Something
+
+ s0metran$act10n
+
+
+ "
+ }
+
+ before do
+ order.reload.update_totals
+ order.shipping_method = shipping_method
+ expect(order.next).to be true # => address
+ expect(order.next).to be true # => delivery
+ expect(order.next).to be true # => payment
+ set_order order
+
+ stub_request(:post, "https://api-3t.sandbox.paypal.com/2.0/")
+ .to_return(:status => 200, :body => mocked_xml_response )
+ end
+
+ context "with a flat percent calculator" do
+ let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }
+
+ before do
+ payment_method.calculator = calculator
+ payment_method.save!
+ order.payments.create!(payment_method_id: payment_method.id, amount: order.total)
+ end
+
+ it "destroys the old payment and processes the order" do
+ # Sanity check to condition of the order before we confirm the payment
+ expect(order.payments.count).to eq 1
+ expect(order.payments.first.state).to eq "checkout"
+ expect(order.adjustments.payment_fee.count).to eq 1
+ expect(order.adjustments.payment_fee.first.amount).to eq 1.5
+
+ get spree.confirm_paypal_path, params
+
+ # Processing was successful, order is complete
+ expect(response).to redirect_to spree.order_path(order, :token => order.token)
+ expect(order.reload.complete?).to be true
+
+ # We have only one payment, and one transaction fee
+ expect(order.payments.count).to eq 1
+ expect(order.payments.first.state).to eq "completed"
+ expect(order.adjustments.payment_fee.count).to eq 1
+ expect(order.adjustments.payment_fee.first.amount).to eq 1.5
+ end
+ end
+end
diff --git a/spec/requests/checkout/stripe_connect_spec.rb b/spec/requests/checkout/stripe_connect_spec.rb
new file mode 100644
index 0000000000..d78b315e34
--- /dev/null
+++ b/spec/requests/checkout/stripe_connect_spec.rb
@@ -0,0 +1,223 @@
+require 'spec_helper'
+
+describe "checking out an order with a Stripe Connect payment method", type: :request do
+ include ShopWorkflow
+ include AuthenticationWorkflow
+
+ let!(:order_cycle) { create(:simple_order_cycle) }
+ let!(:enterprise) { create(:distributor_enterprise) }
+ let!(:exchange) { create(:exchange, order_cycle: order_cycle, sender: order_cycle.coordinator, receiver: enterprise, incoming: false, pickup_time: "Monday") }
+ let!(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0), distributors: [enterprise]) }
+ let!(:payment_method) { create(:stripe_payment_method, distributors: [enterprise], preferred_enterprise_id: enterprise.id) }
+ let!(:stripe_account) { create(:stripe_account, enterprise: enterprise) }
+ let!(:line_item) { create(:line_item, price: 12.34) }
+ let!(:order) { line_item.order }
+ let(:address) { create(:address) }
+ let(:token) { "token123" }
+ let(:new_token) { "newtoken123" }
+ let(:card_id) { "card_XyZ456" }
+ let(:customer_id) { "cus_A123" }
+ let(:params) do
+ { format: :json, order: {
+ shipping_method_id: shipping_method.id,
+ payments_attributes: [{payment_method_id: payment_method.id, source_attributes: { gateway_payment_profile_id: token, cc_type: "visa", last_digits: "4242", month: 10, year: 2025, first_name: 'Jill', last_name: 'Jeffreys' }}],
+ bill_address_attributes: address.attributes.slice("firstname", "lastname", "address1", "address2", "phone", "city", "zipcode", "state_id", "country_id"),
+ ship_address_attributes: address.attributes.slice("firstname", "lastname", "address1", "address2", "phone", "city", "zipcode", "state_id", "country_id")
+ } }
+ end
+
+ before do
+ allow(Stripe).to receive(:api_key) { "sk_test_12345" }
+ order.update_attributes(distributor_id: enterprise.id, order_cycle_id: order_cycle.id)
+ order.reload.update_totals
+ set_order order
+ end
+
+ context "when a new card is submitted" do
+ let(:store_response_mock) { { status: 200, body: JSON.generate(id: customer_id, default_card: card_id, sources: { data: [{id: "1"}] }) } }
+ let(:token_response_mock) { { status: 200, body: JSON.generate(id: new_token) } }
+ let(:charge_response_mock) { { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } }
+
+ context "and the user doesn't request that the card is saved for later" do
+ before do
+ # Charges the card
+ stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges")
+ .with(body: /#{token}.*#{order.number}/).to_return(charge_response_mock)
+ end
+
+ context "and the charge request is successful" do
+ it "should process the payment without storing card details" do
+ put update_checkout_path, params
+ json_response = JSON.parse(response.body)
+ expect(json_response["path"]).to eq spree.order_path(order)
+ expect(order.payments.completed.count).to be 1
+ card = order.payments.completed.first.source
+ expect(card.gateway_customer_profile_id).to eq nil
+ expect(card.gateway_payment_profile_id).to eq token
+ expect(card.cc_type).to eq "visa"
+ expect(card.last_digits).to eq "4242"
+ expect(card.first_name).to eq "Jill"
+ expect(card.last_name).to eq "Jeffreys"
+ end
+ end
+
+ context "when the charge request returns an error message" do
+ let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "charge-failure"}) } }
+
+ it "should not process the payment" do
+ put update_checkout_path, params
+ expect(response.status).to be 400
+ json_response = JSON.parse(response.body)
+ expect(json_response["flash"]["error"]).to eq "charge-failure"
+ expect(order.payments.completed.count).to be 0
+ end
+ end
+ end
+
+ context "and the customer requests that the card is saved for later" do
+ before do
+ params[:order][:payments_attributes][0][:source_attributes][:save_requested_by_customer] = '1'
+
+ # Saves the card against the user
+ stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/customers")
+ .with(:body => { card: token, email: order.email})
+ .to_return(store_response_mock)
+
+ # Requests a token from the newly saved card
+ stub_request(:post, "https://api.stripe.com/v1/tokens")
+ .with(:body => { card: card_id, customer: customer_id})
+ .to_return(token_response_mock)
+
+ # Charges the card
+ stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges")
+ .with(body: /#{token}.*#{order.number}/).to_return(charge_response_mock)
+ end
+
+ context "and the store, token and charge requests are successful" do
+ it "should process the payment, and stores the card/customer details" do
+ put update_checkout_path, params
+ json_response = JSON.parse(response.body)
+ expect(json_response["path"]).to eq spree.order_path(order)
+ expect(order.payments.completed.count).to be 1
+ card = order.payments.completed.first.source
+ expect(card.gateway_customer_profile_id).to eq customer_id
+ expect(card.gateway_payment_profile_id).to eq card_id
+ expect(card.cc_type).to eq "visa"
+ expect(card.last_digits).to eq "4242"
+ expect(card.first_name).to eq "Jill"
+ expect(card.last_name).to eq "Jeffreys"
+ end
+ end
+
+ context "when the store request returns an error message" do
+ let(:store_response_mock) { { status: 402, body: JSON.generate(error: { message: "store-failure"}) } }
+
+ it "should not process the payment" do
+ put update_checkout_path, params
+ expect(response.status).to be 400
+ json_response = JSON.parse(response.body)
+ expect(json_response["flash"]["error"]).to eq I18n.t(:spree_gateway_error_flash_for_checkout, error: 'store-failure')
+ expect(order.payments.completed.count).to be 0
+ end
+ end
+
+ context "when the charge request returns an error message" do
+ let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "charge-failure"}) } }
+
+ it "should not process the payment" do
+ put update_checkout_path, params
+ expect(response.status).to be 400
+ json_response = JSON.parse(response.body)
+ expect(json_response["flash"]["error"]).to eq "charge-failure"
+ expect(order.payments.completed.count).to be 0
+ end
+ end
+
+ context "when the token request returns an error message" do
+ let(:token_response_mock) { { status: 402, body: JSON.generate(error: { message: "token-failure"}) } }
+
+ # Note, no requests have been stubbed
+ it "should not process the payment" do
+ put update_checkout_path, params
+ expect(response.status).to be 400
+ json_response = JSON.parse(response.body)
+ expect(json_response["flash"]["error"]).to eq "token-failure"
+ expect(order.payments.completed.count).to be 0
+ end
+ end
+ end
+ end
+
+ context "when an existing card is submitted" do
+ let(:credit_card) do
+ create(
+ :credit_card,
+ user_id: order.user_id,
+ gateway_payment_profile_id: card_id,
+ gateway_customer_profile_id: customer_id,
+ last_digits: "4321",
+ cc_type: "master",
+ first_name: "Sammy",
+ last_name: "Signpost",
+ month: 11, year: 2026
+ )
+ end
+
+ let(:token_response_mock) { { status: 200, body: JSON.generate(id: new_token) } }
+ let(:charge_response_mock) { { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } }
+
+ before do
+ params[:order][:existing_card_id] = credit_card.id
+ quick_login_as(order.user)
+
+ # Requests a token
+ stub_request(:post, "https://api.stripe.com/v1/tokens")
+ .with(:body => {"card" => card_id, "customer" => customer_id})
+ .to_return(token_response_mock)
+
+ # Charges the card
+ stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges")
+ .with(body: /#{token}.*#{order.number}/).to_return(charge_response_mock)
+ end
+
+ context "and the charge and token requests are accepted" do
+ it "should process the payment, and keep the profile ids and other card details" do
+ put update_checkout_path, params
+ json_response = JSON.parse(response.body)
+ expect(json_response["path"]).to eq spree.order_path(order)
+ expect(order.payments.completed.count).to be 1
+ card = order.payments.completed.first.source
+ expect(card.gateway_customer_profile_id).to eq customer_id
+ expect(card.gateway_payment_profile_id).to eq card_id
+ expect(card.cc_type).to eq "master"
+ expect(card.last_digits).to eq "4321"
+ expect(card.first_name).to eq "Sammy"
+ expect(card.last_name).to eq "Signpost"
+ end
+ end
+
+ context "when the charge request returns an error message" do
+ let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "charge-failure"}) } }
+
+ it "should not process the payment" do
+ put update_checkout_path, params
+ expect(response.status).to be 400
+ json_response = JSON.parse(response.body)
+ expect(json_response["flash"]["error"]).to eq "charge-failure"
+ expect(order.payments.completed.count).to be 0
+ end
+ end
+
+ context "when the token request returns an error message" do
+ let(:token_response_mock) { { status: 402, body: JSON.generate(error: { message: "token-error"}) } }
+
+ it "should not process the payment" do
+ put update_checkout_path, params
+ expect(response.status).to be 400
+ json_response = JSON.parse(response.body)
+ expect(json_response["flash"]["error"]).to eq "token-error"
+ expect(order.payments.completed.count).to be 0
+ end
+ end
+ end
+end
diff --git a/spec/requests/embedded_shopfronts_headers_spec.rb b/spec/requests/embedded_shopfronts_headers_spec.rb
new file mode 100644
index 0000000000..a991d7362c
--- /dev/null
+++ b/spec/requests/embedded_shopfronts_headers_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe "setting response headers for embedded shopfronts", type: :request do
+ include AuthenticationWorkflow
+
+ let(:enterprise) { create(:distributor_enterprise) }
+ let(:user) { enterprise.owner }
+
+ before do
+ quick_login_as(user)
+ end
+
+ context "with embedded shopfront disabled" do
+ before do
+ Spree::Config[:enable_embedded_shopfronts] = false
+ end
+
+ it "disables iframes by default" do
+ get shops_path
+ expect(response.status).to be 200
+ expect(response.headers['X-Frame-Options']).to eq 'DENY'
+ expect(response.headers['Content-Security-Policy']).to eq "frame-ancestors 'none'"
+ end
+ end
+
+ context "with embedded shopfronts enabled" do
+ before do
+ Spree::Config[:enable_embedded_shopfronts] = true
+ end
+
+ context "but no whitelist" do
+ before do
+ Spree::Config[:embedded_shopfronts_whitelist] = ""
+ end
+
+ it "disables iframes" do
+ get shops_path
+ expect(response.status).to be 200
+ expect(response.headers['X-Frame-Options']).to eq 'DENY'
+ expect(response.headers['Content-Security-Policy']).to eq "frame-ancestors 'none'"
+ end
+ end
+
+ context "with a valid whitelist" do
+ before do
+ Spree::Config[:embedded_shopfronts_whitelist] = "test.com"
+ end
+
+ it "allows iframes on certain pages when enabled in configuration" do
+ get shops_path
+ expect(response.status).to be 200
+ expect(response.headers['X-Frame-Options']).to be_nil
+ expect(response.headers['Content-Security-Policy']).to eq "frame-ancestors test.com"
+
+ get spree.admin_path
+ expect(response.status).to be 200
+ expect(response.headers['X-Frame-Options']).to eq 'DENY'
+ expect(response.headers['Content-Security-Policy']).to eq "frame-ancestors 'none'"
+ end
+ end
+ end
+end
diff --git a/spec/serializers/credit_card_serializer_spec.rb b/spec/serializers/credit_card_serializer_spec.rb
new file mode 100644
index 0000000000..baa9c2a3e9
--- /dev/null
+++ b/spec/serializers/credit_card_serializer_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Api::CreditCardSerializer do
+ let(:card) { create(:credit_card) }
+ let(:serializer) { Api::CreditCardSerializer.new card }
+
+
+ it "serializes a credit card" do
+ expect(serializer.to_json).to match card.last_digits.to_s
+ end
+
+ it "formats an identifying string with the card number masked" do
+ expect(serializer.formatted).to eq "Visa x-1111 Exp:12/2013"
+ end
+end
diff --git a/spec/serializers/order_serializer_spec.rb b/spec/serializers/order_serializer_spec.rb
index 2c327537eb..cbbe13eb69 100644
--- a/spec/serializers/order_serializer_spec.rb
+++ b/spec/serializers/order_serializer_spec.rb
@@ -4,14 +4,21 @@ describe Api::OrderSerializer do
let(:serializer) { Api::OrderSerializer.new order }
let(:order) { create(:completed_order_with_totals) }
+ let!(:completed_payment) { create(:payment, order: order, state: 'completed', amount: order.total - 1) }
+ let!(:payment) { create(:payment, order: order, state: 'checkout', amount: 123.45) }
it "serializes an order" do
expect(serializer.to_json).to match order.number.to_s
end
it "convert the state attributes to translatable keys" do
+ # byebug if serializer.to_json =~ /balance_due/
expect(serializer.to_json).to match "complete"
expect(serializer.to_json).to match "balance_due"
end
+ it "only serializes completed payments" do
+ expect(serializer.to_json).to match completed_payment.amount.to_s
+ expect(serializer.to_json).to_not match payment.amount.to_s
+ end
end
diff --git a/spec/serializers/orders_by_distributor_serializer_spec.rb b/spec/serializers/orders_by_distributor_serializer_spec.rb
deleted file mode 100644
index 5976e9ad48..0000000000
--- a/spec/serializers/orders_by_distributor_serializer_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe Api::OrdersByDistributorSerializer do
-
- # Banged lets ensure entered into test database
- let!(:distributor1) { create(:distributor_enterprise) }
- let!(:distributor2) { create(:distributor_enterprise) }
- let!(:user) { create(:user)}
- let!(:d1o1) { create(:completed_order_with_totals, distributor: distributor1, user_id: user.id, total: 10000)}
- let!(:d1o2) { create(:completed_order_with_totals, distributor: distributor1, user_id: user.id, total: 5000)}
- let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user_id: user.id)}
-
- before do
- @data = Enterprise.includes(:distributed_orders).where(enterprises: {id: user.enterprises_ordered_from }, spree_orders: {state: :complete, user_id: user.id}).to_a
- @serializer = ActiveModel::ArraySerializer.new(@data, {each_serializer: Api::OrdersByDistributorSerializer})
- end
-
- it "serializes orders" do
- expect(@serializer.to_json).to match "distributed_orders"
- end
-
- it "serializes the balance for each distributor" do
- expect(@serializer.serializable_array[0].keys).to include :balance
- # Would be good to test adding up balance properly but can't get a non-zero total from the factories...
- expect(@serializer.serializable_array[0][:balance]).to eq "0.00"
- end
-
-end
diff --git a/spec/services/reset_order_service_spec.rb b/spec/services/reset_order_service_spec.rb
new file mode 100644
index 0000000000..e02cdb25e3
--- /dev/null
+++ b/spec/services/reset_order_service_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe ResetOrderService do
+ let(:current_token) { double(:current_token) }
+ let(:current_distributor) { double(:distributor) }
+ let(:current_order) do
+ double(
+ :current_order,
+ token: current_token,
+ distributor: current_distributor
+ )
+ end
+ let(:tokenized_permission) { double(:tokenized_permission, save!: true) }
+ let(:new_order) do
+ double(
+ :new_order,
+ set_distributor!: true,
+ tokenized_permission: tokenized_permission,
+ )
+ end
+ let(:controller) do
+ double(
+ :controller,
+ current_order: new_order,
+ expire_current_order: true
+ )
+ end
+ let(:reset_order_service) { described_class.new(controller, current_order) }
+
+ before do
+ allow(new_order)
+ .to receive(:tokenized_permission)
+ .and_return(tokenized_permission)
+
+ allow(tokenized_permission).to receive(:token=)
+ end
+
+ describe '#call' do
+ it 'creates a new order' do
+ reset_order_service.call
+ expect(controller).to have_received(:current_order).once.with(true)
+ end
+
+ it 'sets the new order\'s distributor to the same as the old order' do
+ reset_order_service.call
+
+ expect(new_order)
+ .to have_received(:set_distributor!)
+ .with(current_distributor)
+ end
+
+ it 'sets the token of the tokenized permissions' do
+ reset_order_service.call
+
+ expect(new_order.tokenized_permission)
+ .to have_received(:token=).with(current_token)
+ end
+
+ it 'persists the tokenized permissions' do
+ reset_order_service.call
+ expect(tokenized_permission).to have_received(:save!)
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 00e0b152ba..4ef7c63b40 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -23,12 +23,12 @@ WebMock.disable_net_connect!(:allow_localhost => true)
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
-require 'spree/core/testing_support/controller_requests'
-require 'spree/core/testing_support/capybara_ext'
+require 'spree/testing_support/controller_requests'
+require 'spree/testing_support/capybara_ext'
require 'spree/api/testing_support/setup'
require 'spree/api/testing_support/helpers'
require 'spree/api/testing_support/helpers_decorator'
-require 'spree/core/testing_support/authorization_helpers'
+require 'spree/testing_support/authorization_helpers'
# Capybara config
require 'capybara/poltergeist'
@@ -47,6 +47,9 @@ Capybara.default_max_wait_time = 30
require "paperclip/matchers"
+# Override setting in Spree engine: Spree::Core::MailSettings
+ActionMailer::Base.default_url_options[:host] = 'test.host'
+
RSpec.configure do |config|
# ## Mock Framework
#
@@ -98,7 +101,7 @@ RSpec.configure do |config|
config.include Spree::UrlHelpers
config.include Spree::CheckoutHelpers
config.include Spree::MoneyHelper
- config.include Spree::Core::TestingSupport::ControllerRequests, :type => :controller
+ config.include Spree::TestingSupport::ControllerRequests, :type => :controller
config.include Devise::TestHelpers, :type => :controller
config.extend Spree::Api::TestingSupport::Setup, :type => :controller
config.include Spree::Api::TestingSupport::Helpers, :type => :controller
diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb
index 1bd22b010c..8d9c66c148 100644
--- a/spec/support/request/web_helper.rb
+++ b/spec/support/request/web_helper.rb
@@ -113,6 +113,10 @@ module WebHelper
DirtyFormDialog.new(page)
end
+ def set_i18n_locale(locale = 'en')
+ page.evaluate_script("I18n.locale = '#{locale}'")
+ end
+
def get_i18n_locale
page.evaluate_script("I18n.locale;")
end
diff --git a/spec/support/spree/money_helper.rb b/spec/support/spree/money_helper.rb
index f0ca8f73d9..dc5a374c39 100644
--- a/spec/support/spree/money_helper.rb
+++ b/spec/support/spree/money_helper.rb
@@ -1,7 +1,7 @@
module Spree
module MoneyHelper
def with_currency(amount, options = {})
- Spree::Money.new(amount, {delimiter: ''}.merge(options)).to_s # Delimiter is to match js localizeCurrency
+ Spree::Money.new(amount, options).to_s
end
end
end