├── .rspec ├── app ├── views │ ├── layouts │ │ ├── mailer.text.erb │ │ └── mailer.html.erb │ └── spree │ │ ├── subscription_notifier │ │ ├── notify_reoccurrence.text.erb │ │ ├── notify_for_next_delivery.text.erb │ │ ├── notify_cancellation.text.erb │ │ ├── notify_confirmation.text.erb │ │ ├── notify_for_next_delivery.html.erb │ │ ├── notify_cancellation.html.erb │ │ ├── notify_reoccurrence.html.erb │ │ └── notify_confirmation.html.erb │ │ ├── orders │ │ ├── _cart_subscription_footer.html.erb │ │ ├── _cart_subscription_header.html.erb │ │ ├── _subscription_number.html.erb │ │ └── _subscription_field.html.erb │ │ ├── admin │ │ ├── products │ │ │ ├── _subscribable_listing_header.html.erb │ │ │ ├── _subscribable_listing_content.html.erb │ │ │ ├── _subscribable_filter.html.erb │ │ │ └── _subscribable.html.erb │ │ ├── shared │ │ │ ├── _flash_messages.html.erb │ │ │ └── _subscriptions_sidebar_menu.html.erb │ │ ├── subscription_frequencies │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── _form.html.erb │ │ │ └── index.html.erb │ │ ├── subscriptions │ │ │ ├── _pause_links.html.erb │ │ │ ├── _payment_info.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── cancellation.html.erb │ │ │ ├── _addresses.html.erb │ │ │ ├── _subscription.html.erb │ │ │ ├── index.html.erb │ │ │ ├── _summary.html.erb │ │ │ ├── _item.html.erb │ │ │ └── _orders.html.erb │ │ └── variants │ │ │ └── _autocomplete_line_items_stock.js.erb │ │ ├── shared │ │ └── _subscription_field.html.erb │ │ ├── subscriptions │ │ ├── _pause_links.html.erb │ │ ├── _payment_info.html.erb │ │ ├── edit.html.erb │ │ ├── _addresses.html.erb │ │ ├── _orders.html.erb │ │ ├── _subscription.html.erb │ │ ├── _item.html.erb │ │ └── _summary.html.erb │ │ ├── products │ │ ├── _cart_checkboxes.html.erb │ │ └── _subscription_fields.html.erb │ │ ├── api │ │ └── variants │ │ │ └── small.v1.rabl │ │ └── users │ │ └── _subscriptions.html.erb ├── assets │ ├── stylesheets │ │ └── spree │ │ │ ├── frontend │ │ │ ├── datepicker.css │ │ │ └── spree_product_subscriptions.scss │ │ │ └── backend │ │ │ ├── spree_product_subscriptions.css │ │ │ └── subscribable.scss │ └── javascripts │ │ └── spree │ │ ├── frontend │ │ ├── datepicker.js │ │ ├── spree_product_subscriptions.js │ │ ├── update_quantity_in_cart.js │ │ ├── cart_radio_button.js │ │ └── ajax_handler.js │ │ └── backend │ │ ├── spree_product_subscriptions.js │ │ ├── subscribable.js │ │ └── line_items_on_order_edit.js ├── controllers │ └── spree │ │ ├── base_controller_decorator.rb │ │ ├── admin │ │ ├── subscription_frequencies_controller.rb │ │ ├── payments_controller_decorator.rb │ │ └── subscriptions_controller.rb │ │ ├── api │ │ └── v1 │ │ │ └── line_items_controller_decorator.rb │ │ ├── users_controller_decorator.rb │ │ ├── orders_controller_decorator.rb │ │ └── subscriptions_controller.rb ├── mailers │ ├── application_mailer.rb │ └── spree │ │ └── subscription_notifier.rb ├── helpers │ └── spree │ │ └── subscriptions_helper.rb ├── overrides │ ├── add_checkboxes_to_product_show_page.rb │ ├── add_subscriptions_admin_tab.rb │ ├── add_subscriptions_to_users_account.rb │ ├── add_subscription_number_to_order_page.rb │ ├── add_subscribable_fields_to_products_show_page.rb │ ├── add_subscribable_to_products.rb │ ├── add_subscribable_checkbox_to_products_filter.rb │ ├── add_subscription_frequency_in_configurations.rb │ ├── add_subscribable_field_to_admin_product_listing.rb │ ├── add_subscribed_field_in_cart_listing.rb │ └── add_subscription_details_to_order_details.rb └── models │ ├── spree │ ├── variant_decorator.rb │ ├── product_subscription_frequency.rb │ ├── order_subscription.rb │ ├── subscription_frequency.rb │ ├── product_decorator.rb │ ├── order_decorator.rb │ ├── line_item_decorator.rb │ └── subscription.rb │ └── subscription_ability.rb ├── gemfiles ├── .bundle │ └── config ├── spree_3_3.gemfile ├── spree_3_4.gemfile ├── spree_3_5.gemfile └── spree_master.gemfile ├── config ├── initializers │ ├── spree_subscription_ability.rb │ └── permitted_attributes_ext.rb ├── routes.rb └── locales │ └── en.yml ├── lib ├── spree_product_subscriptions.rb ├── tasks │ ├── subscription_process.rake │ └── order_prior_notification.rake ├── generators │ └── spree_product_subscriptions │ │ ├── seed_generator.rb │ │ └── install │ │ └── install_generator.rb └── spree_product_subscriptions │ ├── engine.rb │ └── factories.rb ├── spec ├── controllers │ └── spree │ │ ├── admin │ │ ├── subscription_frequencies_controller_spec.rb │ │ ├── payments_controller_decorator_spec.rb │ │ └── subscriptions_controller_spec.rb │ │ ├── api │ │ └── v1 │ │ │ └── line_items_controller_decorator_spec.rb │ │ ├── users_controller_decorator_spec.rb │ │ ├── orders_controller_decorator_spec.rb │ │ └── subscriptions_controller_spec.rb ├── models │ ├── spree │ │ ├── product_subscription_frequency_spec.rb │ │ ├── order_subscription_spec.rb │ │ ├── variant_decorator_spec.rb │ │ ├── subscription_frequency_spec.rb │ │ ├── product_decorator_spec.rb │ │ ├── order_decorator_spec.rb │ │ └── line_item_decorator_spec.rb │ └── subscription_ability_spec.rb ├── mailers │ └── spree │ │ └── subscription_notifier_spec.rb └── spec_helper.rb ├── db ├── migrate │ ├── 20160311061107_remove_end_date_from_subscriptions.rb │ ├── 20160229121236_remove_frequency_from_spree_subscriptions.rb │ ├── 20160301075415_add_enabled_to_spree_subscriptions.rb │ ├── 20160311061332_add_delivery_number_to_spree_subscriptions.rb │ ├── 20160301124600_add_payment_reference_to_spree_subscriptions.rb │ ├── 20160229082140_add_subscribable_to_spree_products.rb │ ├── 20160303145717_add_months_count_column_to_spree_subscription_frequencies.rb │ ├── 20160426131235_add_prior_notification_time_to_subscription.rb │ ├── 20160301080555_add_subscription_frequency_reference_to_spre_subscriptions.rb │ ├── 20160302110531_add_columns_to_spree_subscriptions.rb │ ├── 20180508100649_add_occurence_possible_column_to_spree_subscriptions.rb │ ├── 20160303190542_add_polymorphic_source_reference_to_subscription.rb │ ├── 20160229122244_create_spree_subscription_frequencies.rb │ ├── 20160406115147_rename_pause_in_spree_subscriptions.rb │ ├── 20160405111047_add_pause_and_day_column_to_spree_subscriptions.rb │ ├── 20160229151248_add_default_to_subscribable_in_products.rb │ ├── 20160229122857_create_spree_product_subscription_frequencies.rb │ ├── 20160229110330_create_spree_order_subscriptions.rb │ ├── 20160406100301_add_next_occurrence_at_column_to_spree_subscriptions.rb │ ├── 20160411121318_change_pause_null_in_spree_subscriptions.rb │ ├── 20160229104012_create_spree_subscriptions.rb │ └── 20160301200702_change_names_of_tables_and_columns.rb └── seeds.rb ├── Gemfile ├── bin └── rails ├── Appraisals ├── Rakefile ├── .gitignore ├── LICENSE ├── spree_product_subscriptions.gemspec └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /gemfiles/.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_RETRY: "1" 3 | -------------------------------------------------------------------------------- /app/views/spree/subscription_notifier/notify_reoccurrence.text.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/spree/subscription_notifier/notify_for_next_delivery.text.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/spree/orders/_cart_subscription_footer.html.erb: -------------------------------------------------------------------------------- 1 |
Hi <%= @subscription.parent_order.email %>
2 |4 | <%= Spree.t('subscription_notifier.notify_for_next_delivery.order_details') %> 5 |
6 | <% variant = @subscription.variant %> 7 | PRODUCT CODE: <%= variant.sku %>15 | Your next order is going to be delivered in <%= @subscription.prior_notification_days_gap %> days. 16 | Your <%= @subscription.frequency.title.capitalize %>(every <%= @subscription.frequency.months_count.to_s %>) subscription has <%= @subscription.number_of_deliveries_left %> deliveries left. 17 |
18 |Regards
19 | -------------------------------------------------------------------------------- /app/assets/javascripts/spree/frontend/cart_radio_button.js: -------------------------------------------------------------------------------- 1 | function CartRadioButton($radioButtons) { 2 | this.$radioButtons = $radioButtons; 3 | } 4 | 5 | CartRadioButton.prototype.init = function() { 6 | this.bindEvents(); 7 | }; 8 | 9 | CartRadioButton.prototype.bindEvents = function() { 10 | var _this = this; 11 | this.$radioButtons.on("change", function() { 12 | _this.toggleDiv($(this)); 13 | }); 14 | }; 15 | 16 | CartRadioButton.prototype.toggleDiv = function($checkBox) { 17 | $($checkBox.val()).show(); 18 | this.hideOtherDivs(); 19 | }; 20 | 21 | CartRadioButton.prototype.hideOtherDivs = function() { 22 | $.each(this.$radioButtons, function(index, value) { 23 | $checkBox = $(value); 24 | if (!$checkBox.prop("checked")) { 25 | $($checkBox.val()).hide(); 26 | } 27 | }); 28 | }; 29 | 30 | $(function() { 31 | var cartRadioButton = new CartRadioButton($(".cart_radio_button")); 32 | cartRadioButton.init(); 33 | }); 34 | -------------------------------------------------------------------------------- /spec/models/spree/subscription_frequency_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Spree::SubscriptionFrequency, type: :model do 4 | 5 | describe "associations" do 6 | it { is_expected.to have_many(:product_subscription_frequencies).class_name("Spree::ProductSubscriptionFrequency").dependent(:destroy) } 7 | end 8 | 9 | describe "validations" do 10 | it { is_expected.to validate_presence_of(:title) } 11 | it { is_expected.to validate_presence_of(:months_count) } 12 | it { is_expected.to validate_numericality_of(:months_count).is_greater_than(0).only_integer } 13 | context "uniqueness of title" do 14 | let!(:subscription_frequency_1) { create(:monthly_subscription_frequency) } 15 | let(:subscription_frequency_2) { build(:monthly_subscription_frequency) } 16 | before { subscription_frequency_2.save } 17 | it { expect(subscription_frequency_2.errors[:title]).to include I18n.t "errors.messages.taken" } 18 | end 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 VinSol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/mailers/spree/subscription_notifier.rb: -------------------------------------------------------------------------------- 1 | class Spree::SubscriptionNotifier < ApplicationMailer 2 | 3 | default from: "spree-commerce@example.com" 4 | 5 | def notify_confirmation(subscription) 6 | @subscription = subscription 7 | 8 | mail to: subscription.parent_order.email, 9 | subject: t('.subject', number: subscription.number, frequency: subscription.frequency.title.capitalize) 10 | end 11 | 12 | def notify_cancellation(subscription) 13 | @subscription = subscription 14 | 15 | mail to: subscription.parent_order.email, 16 | subject: t('.subject', number: subscription.number, frequency: subscription.frequency.title.capitalize) 17 | end 18 | 19 | def notify_reoccurrence(subscription) 20 | @subscription = subscription 21 | 22 | mail to: subscription.parent_order.email, 23 | subject: t('.subject', number: subscription.number, frequency: subscription.frequency.title.capitalize) 24 | end 25 | 26 | def notify_for_next_delivery(subscription) 27 | @subscription = subscription 28 | 29 | mail to: subscription.parent_order.email, 30 | subject: t('.subject', number: subscription.number, frequency: subscription.frequency.title.capitalize) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/assets/javascripts/spree/backend/subscribable.js: -------------------------------------------------------------------------------- 1 | function Subscribable($parentCheckbox, $childCheckboxesDiv) { 2 | this.$parentCheckbox = $parentCheckbox; 3 | this.$childCheckboxesDiv = $childCheckboxesDiv; 4 | } 5 | 6 | Subscribable.prototype.init = function() { 7 | this.bindEvents(); 8 | }; 9 | 10 | Subscribable.prototype.bindEvents = function() { 11 | this.bindParentCheckboxEvent(); 12 | }; 13 | 14 | Subscribable.prototype.bindParentCheckboxEvent = function() { 15 | var _this = this; 16 | this.$parentCheckbox.on("change", function() { 17 | if ($(this).prop("checked")) { 18 | _this.enableChildCheckboxes(); 19 | } else { 20 | _this.disableChildCheckboxes(); 21 | } 22 | }); 23 | }; 24 | 25 | Subscribable.prototype.enableChildCheckboxes = function() { 26 | this.$childCheckboxesDiv.removeClass("hidden"); 27 | this.$childCheckboxesDiv.find("input").removeAttr("disabled"); 28 | }; 29 | 30 | Subscribable.prototype.disableChildCheckboxes = function() { 31 | this.$childCheckboxesDiv.addClass("hidden"); 32 | this.$childCheckboxesDiv.find("input").attr("disabled", "disabled"); 33 | } 34 | 35 | $(function() { 36 | var subscribable = new Subscribable($("#product_subscribable"), $("#subscribable_options")); 37 | subscribable.init(); 38 | }); 39 | -------------------------------------------------------------------------------- /app/views/spree/products/_subscription_fields.html.erb: -------------------------------------------------------------------------------- 1 | <% if @product.subscribable? %> 2 | 28 | <% end %> 29 | -------------------------------------------------------------------------------- /app/views/spree/admin/subscriptions/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | Spree Administration: <%= Spree::Subscription.model_name.human %> <%= @subscription.number %> 3 | <% end %> 4 | 5 | <% content_for :page_title do %> 6 | <%= Spree::Subscription.model_name.human %> <%= @subscription.number %> 7 | <% end %> 8 | 9 | <% content_for :page_actions do %> 10 | 11 | <% if !@subscription.not_changeable? %> 12 | 13 | <%= render partial: 'pause_links' %> 14 | 15 | <% end %> 16 | <%= button_link_to Spree.t(:back_to_subscriptions_list), spree.admin_subscriptions_path, class: 'btn-success', icon: 'chevron-left' %> 17 | 18 | <% end %> 19 | 20 | <%= render partial: 'spree/admin/shared/flash_messages' %> 21 | <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @subscription } %> 22 | 23 | <%= form_for [:admin, @subscription] do |f| %> 24 | <%= render partial: 'summary', locals: { f: f } %> 25 | <%= render partial: 'item', locals: { f: f } %> 26 | <%= render partial: 'payment_info', locals: { f: f } %> 27 | <%= render partial: 'addresses', locals: { f: f } %> 28 | <%= render partial: 'orders' %> 29 | <% unless @subscription.not_changeable? %> 30 | <%= render partial: 'spree/admin/shared/edit_resource_links' %> 31 | <% end %> 32 | <% end %> 33 | -------------------------------------------------------------------------------- /app/views/spree/admin/subscription_frequencies/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :page_title do %> 2 | <%= plural_resource_name(Spree::SubscriptionFrequency) %> 3 | <% end %> 4 | 5 | <% content_for :page_actions do %> 6 | <%= button_link_to Spree.t(:new_subscription_frequency), new_object_url, icon: 'add', class: 'btn-success' %> 7 | <% end %> 8 | 9 | <% if @subscription_frequencies.present? %> 10 || <%= Spree::SubscriptionFrequency.human_attribute_name :title %> | 13 |<%= Spree::SubscriptionFrequency.human_attribute_name :months_count %> | 14 |15 | 16 | 17 | <% @subscription_frequencies.each do |subscription_frequency| %> 18 | |
|---|---|---|
| <%= subscription_frequency.title.titleize %> | 20 |<%= subscription_frequency.months_count %> months | 21 |22 | <%= link_to_edit subscription_frequency, no_text: true %> 23 | <%= link_to_delete subscription_frequency, no_text: true %> 24 | | 25 |
| <%= Spree::Subscription.human_attribute_name(:number) %> | 11 |<%= Spree.t(:product_name) %> | 12 |<%= Spree.t(:price) %> | 13 |<%= Spree.t(:started_at) %> | 14 |<%= Spree.t(:recurring_delivery_interval) %> | 15 |<%= Spree.t(:next_occurrence_possible) %> | 16 |<%= Spree.t(:total_deliveries) %> | 17 |<%= Spree.t(:deliveries_left) %> | 18 |<%= Spree.t(:action_links) %> | 19 |
|---|
| <%= Spree::Order.human_attribute_name(:number) %> | 9 |<%= Spree.t(:date) %> | 10 |<%= Spree.t(:status) %> | 11 |<%= Spree.t(:payment_state) %> | 12 |<%= Spree.t(:shipment_state) %> | 13 |<%= Spree.t(:total) %> | 14 |
|---|---|---|---|---|---|
| <%= link_to order.number, order_url(order) %> | 20 |<%= order.completed_at ? l(order.completed_at.to_date) : '-' %> | 21 |<%= Spree.t("order_state.#{order.state}").titleize %> | 22 |<%= Spree.t("payment_states.#{order.payment_state}").titleize if order.payment_state %> | 23 |<%= Spree.t("shipment_states.#{order.shipment_state}").titleize if order.shipment_state %> | 24 |<%= order.display_total %> | 25 |
| <%= Spree.t(:number) %> | 34 |<%= Spree.t(:product_name) %> | 35 |<%= Spree.t(:price) %> | 36 |<%= Spree.t(:recurring_delivery_interval) %> | 37 |<%= Spree.t(:total_deliveries) %> | 38 |<%= Spree.t(:deliveries_left) %> | 39 |<%= Spree.t(:next_occurrence_possible) %> | 40 |<%= Spree.t(:action_links)%> | 41 |
|---|
| <%= Spree.t(:name) %> | 11 |12 | <%= f.label :price, Spree.t(:price) %>* 13 | | 14 |15 | <%= f.label :quantity, Spree.t(:quantity) %>* 16 | | 17 |18 | <%= f.label :frequency, Spree.t(:recurring_delivery_interval) %>* 19 | | 20 | 21 | 22 ||
|---|---|---|---|---|
| 24 | <%= mini_image(@subscription.variant) %> 25 | | 26 |27 | <%= collection_select(:subscription, :variant_id, @subscription.variant.product_variants, :id, :name, {}, data: { subscription_id: @subscription.id }) %> 28 | | 29 |
30 |
31 | <%= @subscription.price %>
32 |
33 | |
34 |
35 |
36 | <%= f.field_container :quantity, class: ["form-group"] do %>
37 | <%= number_field :subscription, :quantity, { min: 1, disabled: @subscription.not_changeable? } %>
38 | <%= f.error_message_on :quantity %>
39 | <% end %>
40 |
41 | |
42 |
43 |
44 | <%= f.field_container :frequency, class: ["form-group"] do %>
45 | <%= f.collection_select :subscription_frequency_id, @subscription.variant.product.subscription_frequencies, :id, :title, {}, { class: "form-control", disabled: @subscription.not_changeable? } %>
46 | <%= f.error_message_on :frequency %>
47 | <% end %>
48 |
49 | |
50 |
| <%= Spree.t(:customer_email) %> | 10 |<%= @subscription.parent_order.email %> | 11 |
| 14 | <%= Spree.t(:original_order) %>: 15 | | 16 |17 | 18 | <%= link_to @subscription.parent_order.number, edit_admin_order_path(@subscription.parent_order) %> 19 | 20 | | 21 |
| 24 | <%= Spree.t(:next_occurrence_at) %>: 25 | | 26 |27 | <%= f.field_container :next_occurrence_at, class: ["form-group"] do %> 28 | <%= f.text_field :next_occurrence_at, class: "form-control datepicker subscription_next_occurrence_at", disabled: @subscription.not_changeable? || @subscription.paused? %> 29 | <%= f.error_message_on :next_occurrence_at %> 30 | <% end %> 31 | | 32 |
| 35 | <%= Spree.t(:total_deliveries) %> 36 | | 37 |
38 |
39 | <%= f.field_container :delivery_number, class: ["form-group"] do %>
40 | <%= number_field :subscription, :delivery_number, class: "form-control", disabled: @subscription.not_changeable? %>
41 | <%= f.error_message_on :delivery_number %>
42 | <% end %>
43 |
44 | |
45 |
| <%= Spree.t(:deliveries_left) %> | 48 |<%= @subscription.number_of_deliveries_left %> | 49 |
| 53 | <%= Spree.t(:cancelled_at) %> 54 | | 55 |56 | <%= @subscription.cancelled_at.to_formatted_s(:long) %> 57 | | 58 |
| 61 | <%= Spree.t(:cancellation_reasons) %> 62 | | 63 |64 | <%= @subscription.cancellation_reasons.html_safe %> 65 | | 66 |
| <%= Spree.t(:name) %> | 11 |12 | <%= f.label :price, Spree.t(:price) %>* 13 | | 14 |15 | <%= f.label :quantity, Spree.t(:quantity) %>* 16 | | 17 |18 | <%= f.label :frequency, Spree.t(:recurring_delivery_interval) %>* 19 | | 20 | 21 | 22 ||
|---|---|---|---|---|
| 24 | <%= mini_image(@subscription.variant) %> 25 | | 26 |27 | <%= collection_select(:subscription, :variant_id, @subscription.variant.product_variants, :id, :name, {}, data: { subscription_id: @subscription.id }) %> 28 | | 29 |
30 |
31 | <%= f.field_container :price, class: ["form-group"] do %>
32 | <%= f.text_field :price, class: "form-control", disabled: @subscription.not_changeable? %>
33 | <%= f.error_message_on :price %>
34 | <% end %>
35 |
36 | |
37 |
38 |
39 | <%= f.field_container :quantity, class: ["form-group"] do %>
40 | <%= number_field :subscription, :quantity, { min: 1, disabled: @subscription.not_changeable? } %>
41 | <%= f.error_message_on :quantity %>
42 | <% end %>
43 |
44 | |
45 |
46 |
47 | <%= f.field_container :frequency, class: ["form-group"] do %>
48 | <%= f.collection_select :subscription_frequency_id, @subscription.variant.product.subscription_frequencies, :id, :title, {}, { class: "form-control", disabled: @subscription.not_changeable? } %>
49 | <%= f.error_message_on :frequency %>
50 | <% end %>
51 |
52 | |
53 |
| <%= Spree.t :completed_at %> | 10 | <% else %> 11 |<%= Spree.t :created_at %> | 12 | <% end %> 13 |<%= Spree.t :number %> | 14 |<%= Spree.t :considered_risky %> | 15 |<%= Spree.t :state %> | 16 |<%= Spree.t :payment_state %> | 17 | <% if Spree::Order.checkout_step_names.include?(:delivery) %> 18 |<%= Spree.t :shipment_state %> | 19 | <% end %> 20 |<%= Spree.t :total %> | 21 |22 | |
|---|---|---|---|---|---|---|---|---|
| <%= l (@show_only_completed ? order.completed_at : order.created_at).try!(:to_date) %> | 28 |<%= link_to order.number, edit_admin_order_path(order) %> | 29 |30 | 31 | <%= order.considered_risky ? Spree.t("risky") : Spree.t("safe") %> 32 | 33 | | 34 |35 | <%= Spree.t("order_state.#{order.state.downcase}") %> 36 | | 37 |38 | <% if order.payment_state %> 39 | <%= link_to Spree.t("payment_states.#{order.payment_state}"), admin_order_payments_path(order) %> 40 | <% end %> 41 | | 42 | <% if Spree::Order.checkout_step_names.include?(:delivery) %> 43 |44 | <% if order.shipment_state %> 45 | <%= Spree.t("shipment_states.#{order.shipment_state}") %> 46 | <% end %> 47 | | 48 | <% end %> 49 |<%= order.display_total.to_html %> | 50 |51 | <%= link_to_edit_url edit_admin_order_path(order), title: "admin_edit_#{dom_id(order)}", no_text: true %> 52 | | 53 |
Hi <%= @subscription.parent_order.email %>
2 |Your <%= @subscription.frequency.title %> subscription is cancelled
3 |5 | <%= Spree.t('order_mailer.confirm_email.order_summary', number: @subscription.parent_order.number) %> 6 |
7 || <%= item.variant.sku %> | 11 |12 | <%= raw(item.variant.product.name) %> 13 | <%= raw(item.variant.options_text) -%> 14 | | 15 |(<%=item.quantity%>) <%= Spree.t('at_symbol') %> <%= item.single_money %> = <%= item.display_amount %> | 16 |
| 20 | | 21 | <%= Spree.t('order_mailer.confirm_email.subtotal') %> 22 | | 23 |24 | <%= @subscription.parent_order.display_item_total %> 25 | | 26 |
| 32 | | <%= Spree.t(:promotion) %> <%= label %>: | 33 |<%= Spree::Money.new(adjustments.sum(&:amount), currency: @subscription.parent_order.currency) %> | 34 |
| 41 | | <%= Spree.t(:shipping) %> <%= name %>: | 42 |<%= Spree::Money.new(shipments.sum(&:discounted_cost), currency: @subscription.parent_order.currency) %> | 43 |
| 49 | | <%= Spree.t(:tax) %> <%= label %>: | 50 |<%= Spree::Money.new(adjustments.sum(&:amount), currency: @subscription.parent_order.currency) %> | 51 |
| 58 | | <%= adjustment.label %>: | 59 |<%= adjustment.display_amount %> | 60 |
| 64 | | 65 | <%= Spree.t('order_mailer.confirm_email.total') %> 66 | | 67 |68 | <%= @subscription.parent_order.display_total %> 69 | | 70 |
Regards
75 | -------------------------------------------------------------------------------- /app/views/spree/subscription_notifier/notify_reoccurrence.html.erb: -------------------------------------------------------------------------------- 1 |Hi <%= @subscription.parent_order.email %>
2 |4 | <%= Spree.t('order_mailer.confirm_email.order_summary', number: @subscription.parent_order.number) %> 5 |
6 || <%= item.variant.sku %> | 10 |11 | <%= raw(item.variant.product.name) %> 12 | <%= raw(item.variant.options_text) -%> 13 | | 14 |(<%=item.quantity%>) <%= Spree.t('at_symbol') %> <%= item.single_money %> = <%= item.display_amount %> | 15 |
| 19 | | 20 | <%= Spree.t('order_mailer.confirm_email.subtotal') %> 21 | | 22 |23 | <%= @subscription.parent_order.display_item_total %> 24 | | 25 |
| 31 | | <%= Spree.t(:promotion) %> <%= label %>: | 32 |<%= Spree::Money.new(adjustments.sum(&:amount), currency: @subscription.parent_order.currency) %> | 33 |
| 40 | | <%= Spree.t(:shipping) %> <%= name %>: | 41 |<%= Spree::Money.new(shipments.sum(&:discounted_cost), currency: @subscription.parent_order.currency) %> | 42 |
| 48 | | <%= Spree.t(:tax) %> <%= label %>: | 49 |<%= Spree::Money.new(adjustments.sum(&:amount), currency: @subscription.parent_order.currency) %> | 50 |
| 57 | | <%= adjustment.label %>: | 58 |<%= adjustment.display_amount %> | 59 |
| 63 | | 64 | <%= Spree.t('order_mailer.confirm_email.total') %> 65 | | 66 |67 | <%= @subscription.parent_order.display_total %> 68 | | 69 |
Your <%= @subscription.frequency.title.capitalize %> subscription has <%= @subscription.number_of_deliveries_left %> deliveries left
74 |Regards
75 | -------------------------------------------------------------------------------- /app/views/spree/subscription_notifier/notify_confirmation.html.erb: -------------------------------------------------------------------------------- 1 |Hi <%= @subscription.parent_order.email %>
2 |Your <%= @subscription.frequency.title %> subscription is confirmed
3 |5 | <%= Spree.t('order_mailer.confirm_email.order_summary', number: @subscription.parent_order.number) %> 6 |
7 || <%= item.variant.sku %> | 11 |12 | <%= raw(item.variant.product.name) %> 13 | <%= raw(item.variant.options_text) -%> 14 | | 15 |(<%=item.quantity%>) <%= Spree.t('at_symbol') %> <%= item.single_money %> = <%= item.display_amount %> | 16 |
| 20 | | 21 | <%= Spree.t('order_mailer.confirm_email.subtotal') %> 22 | | 23 |24 | <%= @subscription.parent_order.display_item_total %> 25 | | 26 |
| 32 | | <%= Spree.t(:promotion) %> <%= label %>: | 33 |<%= Spree::Money.new(adjustments.sum(&:amount), currency: @subscription.parent_order.currency) %> | 34 |
| 41 | | <%= Spree.t(:shipping) %> <%= name %>: | 42 |<%= Spree::Money.new(shipments.sum(&:discounted_cost), currency: @subscription.parent_order.currency) %> | 43 |
| 49 | | <%= Spree.t(:tax) %> <%= label %>: | 50 |<%= Spree::Money.new(adjustments.sum(&:amount), currency: @subscription.parent_order.currency) %> | 51 |
| 58 | | <%= adjustment.label %>: | 59 |<%= adjustment.display_amount %> | 60 |
| 64 | | 65 | <%= Spree.t('order_mailer.confirm_email.total') %> 66 | | 67 |68 | <%= @subscription.parent_order.display_total %> 69 | | 70 |
Your <%= @subscription.frequency.title.capitalize %> subscription has <%= @subscription.number_of_deliveries_left %> deliveries left
75 |Regards
76 | -------------------------------------------------------------------------------- /spec/models/spree/order_decorator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Spree::Order, type: :model do 4 | 5 | let(:disabled_subscription) { create(:valid_subscription, enabled: false) } 6 | let(:subscriptions) { [disabled_subscription] } 7 | let(:order_with_subscriptions) { create(:completed_order_with_pending_payment, subscriptions: subscriptions) } 8 | let(:incompleted_order) { create(:order_with_line_items, subscriptions: subscriptions, state: "confirm", payments: order_with_subscriptions.payments) } 9 | 10 | describe "associations" do 11 | it { is_expected.to have_one(:order_subscription).class_name("Spree::OrderSubscription").dependent(:destroy) } 12 | it { is_expected.to have_one(:parent_subscription).through(:order_subscription).source(:subscription) } 13 | it { is_expected.to have_many(:subscriptions).class_name("Spree::Subscription").with_foreign_key(:parent_order_id).dependent(:restrict_with_error) } 14 | end 15 | 16 | describe "callbacks" do 17 | it { is_expected.to callback(:update_subscriptions).after(:update) } 18 | end 19 | 20 | describe "methods" do 21 | context "#available_payment_methods" do 22 | let(:order_without_subscriptions) { create(:order_with_line_items) } 23 | let(:credit_card_payment_method) { create(:credit_card_payment_method) } 24 | let(:check_payment_method) { create(:check_payment_method) } 25 | context "order with subscriptions" do 26 | it { expect(order_with_subscriptions.available_payment_methods).to include credit_card_payment_method } 27 | it { expect(order_with_subscriptions.available_payment_methods).to_not include check_payment_method } 28 | end 29 | 30 | context "order without subscriptions" do 31 | it { expect(order_without_subscriptions.available_payment_methods).to include check_payment_method } 32 | it { expect(order_without_subscriptions.available_payment_methods).to include credit_card_payment_method } 33 | end 34 | end 35 | 36 | context "#any_disabled_subscription?" do 37 | it { expect(order_with_subscriptions.send :any_disabled_subscription?).to eq true } 38 | end 39 | 40 | context "#enable_subscriptions" do 41 | it { expect { order_with_subscriptions.send :enable_subscriptions }.to change { order_with_subscriptions.subscriptions.disabled.count }.by -1 } 42 | end 43 | 44 | context "#update_subscriptions" do 45 | context "when subscription attributes present" do 46 | let(:line_item) { create(:line_item, variant: disabled_subscription.variant) } 47 | def add_new_line_item 48 | line_item.delivery_number = 6 49 | order_with_subscriptions.line_items << line_item 50 | order_with_subscriptions.send :update_subscriptions 51 | end 52 | it { expect { add_new_line_item }.to change { disabled_subscription.reload.delivery_number }.from(4).to(6) } 53 | end 54 | 55 | context "when subscription attributes not present" do 56 | it { expect { order_with_subscriptions.send :update_subscriptions }.to change { disabled_subscription.delivery_number }.by 0 } 57 | end 58 | end 59 | 60 | context "state machine" do 61 | it { expect { incompleted_order.next }.to change { incompleted_order.subscriptions.disabled.count }.by -1 } 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /app/views/spree/subscriptions/_summary.html.erb: -------------------------------------------------------------------------------- 1 || <%= Spree.t(:customer_email) %> | 10 |<%= @subscription.parent_order.email %> | 11 |
| 14 | <%= Spree.t(:original_order) %>: 15 | | 16 |17 | 18 | <%= link_to @subscription.parent_order.number, order_url(@subscription.parent_order) %> 19 | 20 | | 21 |
| 24 | <%= Spree.t(:next_occurrence_at) %>: 25 | | 26 |27 | <%= f.field_container :next_occurrence_at, class: ["form-group"] do %> 28 | <%= f.text_field :next_occurrence_at, class: "form-control datepicker subscription_next_occurrence_at", disabled: @subscription.not_changeable? || @subscription.paused?, value: @subscription.next_occurrence_at.to_date %> 29 | <%= f.error_message_on :next_occurrence_at %> 30 | <% end %> 31 | | 32 |
| 35 | <%= Spree.t(:total_deliveries) %> 36 | | 37 |
38 |
39 | <%= f.field_container :delivery_number, class: ["form-group"] do %>
40 | <%= number_field :subscription, :delivery_number, class: "form-control", disabled: @subscription.not_changeable? %>
41 | <%= f.error_message_on :delivery_number %>
42 | <% end %>
43 |
44 | |
45 |
| 48 | <%= Spree.t(:prior_notification_days_gap) %> 49 | | 50 |
51 |
52 | <%= f.field_container :prior_notification_days_gap, class: ["form-group"] do %>
53 | <%= number_field :subscription, :prior_notification_days_gap, class: "form-control", disabled: @subscription.not_changeable?, max: days_left_for_next_occurrence, min: 0 %>
54 | <%= f.error_message_on :prior_notification_days_gap %>
55 | <% end %>
56 |
57 | |
58 |
| <%= Spree.t(:deliveries_left) %> | 61 |<%= @subscription.number_of_deliveries_left %> | 62 |
| 66 | <%= Spree.t(:cancelled_at) %> 67 | | 68 |69 | <%= @subscription.cancelled_at.to_formatted_s(:long) %> 70 | | 71 |
| 74 | <%= Spree.t(:cancellation_reasons) %> 75 | | 76 |77 | <%= @subscription.cancellation_reasons.html_safe %> 78 | | 79 |
96 |
97 | Credits
98 | -------
99 |
100 | [](http://vinsol.com)
101 |
102 | Copyright (c) 2016 [vinsol.com](http://vinsol.com "Ruby on Rails, iOS and Android developers"), released under the New MIT License
103 |
--------------------------------------------------------------------------------
/app/assets/javascripts/spree/frontend/ajax_handler.js:
--------------------------------------------------------------------------------
1 | function AjaxHandler(targets, lineItemsTable) {
2 | this.targets = targets;
3 | this.lineItemsTable = lineItemsTable;
4 | }
5 |
6 | AjaxHandler.prototype.init = function() {
7 | this.bindEvents();
8 | };
9 |
10 | AjaxHandler.prototype.bindEvents = function() {
11 | var _this = this;
12 |
13 | $.each(_this.targets, function(index, target) {
14 | var $target = $(target);
15 | $target.unbind('click').on('click', function() {
16 | var data = $target.data();
17 | if(confirm(data.confirmation)) {
18 | _this.sendRequest($target, data);
19 | }
20 | });
21 | });
22 |
23 | _this.lineItemsTable.on('change', '#subscription_variant_id', function(){
24 | _this.updateVariant(this);
25 | });
26 |
27 | };
28 |
29 | AjaxHandler.prototype.updateVariant = function(variant_select) {
30 | var _this = this;
31 | $.ajax({
32 | url: '/subscriptions/' + $(variant_select).data('subscription-id'),
33 | dataType: "JSON",
34 | method: 'PUT',
35 | data: {
36 | id: $(variant_select).data('subscription-id'),
37 | subscription: {
38 | variant_id: $(variant_select).children(':selected').val()
39 | }
40 | },
41 | success: function(response) {
42 | var subscription = response['subscription'];
43 | var $lineItemPrice = _this.lineItemsTable.find('td.line-item-price')
44 | .find('[data-subscription-id="'+ subscription['id'] +'"]');
45 | $lineItemPrice.html(subscription['price']);
46 | show_flash('success', 'Variant has been updated.');
47 | },
48 | error: function(response) {
49 | errors = JSON.parse(response.responseText).errors;
50 | show_flash('danger', errors);
51 | }
52 | });
53 | };
54 |
55 | AjaxHandler.prototype.sendRequest = function($target, data) {
56 | var _this = this;
57 | $.ajax({
58 | url: data.url,
59 | dataType: "JSON",
60 | method: data.method,
61 | success: function(response) {
62 | if (response.method == "CANCEL") {
63 | _this.handleCancelSuccess($target, response);
64 | } else {
65 | _this.handlePatchSuccess($target, response)
66 | }
67 | },
68 | error: function(response) {
69 | _this.handleErrorResponse($target, response);
70 | }
71 | });
72 | };
73 |
74 | show_flash = function(type, message) {
75 | var flash_div = $('.flash.' + type);
76 | if (flash_div.length == 0) {
77 | flash_div = $('');
78 | $('#content').prepend(flash_div);
79 | }
80 | flash_div.html(message).show().delay(5000).slideUp();
81 | }
82 |
83 | AjaxHandler.prototype.handlePatchSuccess = function($target, response) {
84 | this.hideFlashDivs();
85 | $target.data("url", response.url);
86 | if (response.url.match("unpause")) {
87 | $(".subscription_next_occurrence_at").attr("disabled", "disabled");
88 | } else {
89 | $(".subscription_next_occurrence_at").val(response.next_occurrence_at);
90 | $(".subscription_next_occurrence_at").removeAttr("disabled");
91 | }
92 | $target.toggleClass("btn-success");
93 | $target.toggleClass("btn-warning");
94 | $("#success_flash_message").html(response.flash).removeClass("hidden");
95 | var $symbol = $target.find(".icon");
96 | $symbol.toggleClass("icon-pause").toggleClass("icon-play");
97 | if (!$target.find(".translation_missing").length && !$symbol.length) {
98 | $target.html(response.button_text);
99 | }
100 | $target.find(".translation_missing").html(response.button_text);
101 | $target.data("confirmation", response.confirmation);
102 | };
103 |
104 | AjaxHandler.prototype.handleCancelSuccess = function($target, response) {
105 | this.hideFlashDivs();
106 | $("#success_flash_message").html(response.flash).removeClass("hidden");
107 | $('[data-id="' + response.subscription_id + '"] .subscription-action-links').html("Subscription Cancelled");
108 | };
109 |
110 | AjaxHandler.prototype.handleErrorResponse = function($target, response) {
111 | this.hideFlashDivs();
112 | $("#error_flash_message").html(response.flash).removeClass("hidden");
113 | };
114 |
115 | AjaxHandler.prototype.hideFlashDivs = function() {
116 | $("#html_error_flash_message").remove();
117 | $("#error_flash_message").addClass("hidden");
118 | $("#html_success_flash_message").remove();
119 | $("#success_flash_message").addClass("hidden");
120 | };
121 |
122 | $(function (){
123 | var ajaxHandler = new AjaxHandler($('.ajax_handler'), $('table.line-items'));
124 | ajaxHandler.init();
125 | });
126 |
--------------------------------------------------------------------------------
/spec/controllers/spree/admin/payments_controller_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Spree::Admin::PaymentsController, type: :controller do
4 |
5 | let(:order) { create(:completed_order_with_pending_payment) }
6 | let(:subscriptions) { double(ActiveRecord::Relation) }
7 |
8 | stub_authorization!
9 |
10 | def do_new
11 | spree_get :new, order_id: order.number
12 | end
13 |
14 | describe "method overrides" do
15 |
16 | let(:orders) { double(ActiveRecord::Relation) }
17 | let(:credit_card_payment_method) { create(:credit_card_payment_method) }
18 | let(:check_payment_method) { create(:check_payment_method) }
19 | let(:payment_methods) { [check_payment_method, credit_card_payment_method] }
20 | let(:payment_methods_without_check) { [credit_card_payment_method] }
21 |
22 | before do
23 | allow(Spree::Order).to receive(:find_by!).and_return(order)
24 | allow(order).to receive(:subscriptions).and_return(subscriptions)
25 | end
26 |
27 | describe "#load_data" do
28 | context "when subscriptions are present" do
29 | before do
30 | allow(subscriptions).to receive(:any?).and_return(true)
31 | allow(Spree::Gateway).to receive(:available_on_back_end).and_return(payment_methods_without_check)
32 | end
33 |
34 | context "expects to receive" do
35 | after { do_new }
36 | it { expect(Spree::Order).to receive(:find_by!).and_return(order) }
37 | it { expect(order).to receive(:subscriptions).and_return(subscriptions) }
38 | it { expect(subscriptions).to receive(:any?).and_return(true) }
39 | it { expect(Spree::Gateway).to receive(:available_on_back_end).and_return(payment_methods_without_check) }
40 | end
41 |
42 | context "assigns" do
43 | before { do_new }
44 | it { expect(assigns(:payment_method)).to eq credit_card_payment_method }
45 | end
46 | end
47 |
48 | context "when subscriptions are not present" do
49 | before do
50 | allow(subscriptions).to receive(:any?).and_return(false)
51 | allow(Spree::PaymentMethod).to receive(:available_on_back_end).and_return(payment_methods)
52 | end
53 |
54 | context "expects to receive" do
55 | after { do_new }
56 | it { expect(Spree::Order).to receive(:find_by!).and_return(order) }
57 | it { expect(order).to receive(:subscriptions).and_return(subscriptions) }
58 | it { expect(subscriptions).to receive(:any?).and_return(false) }
59 | it { expect(Spree::PaymentMethod).to receive(:available_on_back_end).and_return(payment_methods) }
60 | end
61 |
62 | context "assigns" do
63 | before { do_new }
64 | it { expect(assigns(:payment_method)).to eq check_payment_method }
65 | end
66 | end
67 | end
68 |
69 | describe "#available_payment_methods" do
70 | context "when subscriptions are present" do
71 | before do
72 | allow(subscriptions).to receive(:any?).and_return(true)
73 | allow(Spree::Gateway).to receive(:available_on_back_end).and_return(payment_methods_without_check)
74 | end
75 |
76 | context "expects to receive" do
77 | after { do_new }
78 | it { expect(Spree::Order).to receive(:find_by!).and_return(order) }
79 | it { expect(order).to receive(:subscriptions).and_return(subscriptions) }
80 | it { expect(subscriptions).to receive(:any?).and_return(true) }
81 | it { expect(Spree::Gateway).to receive(:available_on_back_end).and_return(payment_methods_without_check) }
82 | end
83 |
84 | context "returns" do
85 | before { do_new }
86 | it { expect(controller.send :available_payment_methods).to eq payment_methods_without_check }
87 | it { expect(controller.send :available_payment_methods).to_not include check_payment_method }
88 | end
89 | end
90 |
91 | context "when subscriptions are not present" do
92 | before do
93 | allow(subscriptions).to receive(:any?).and_return(false)
94 | allow(Spree::PaymentMethod).to receive(:available_on_back_end).and_return(payment_methods)
95 | end
96 |
97 | context "expects to receive" do
98 | after { do_new }
99 | it { expect(Spree::Order).to receive(:find_by!).and_return(order) }
100 | it { expect(order).to receive(:subscriptions).and_return(subscriptions) }
101 | it { expect(subscriptions).to receive(:any?).and_return(false) }
102 | it { expect(Spree::PaymentMethod).to receive(:available_on_back_end).and_return(payment_methods) }
103 | end
104 |
105 | context "returns" do
106 | before { do_new }
107 | it { expect(controller.send :available_payment_methods).to eq payment_methods }
108 | it { expect(controller.send :available_payment_methods).to include check_payment_method }
109 | end
110 | end
111 | end
112 | end
113 |
114 | end
115 |
--------------------------------------------------------------------------------
/spec/models/spree/line_item_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::LineItem, type: :model do
4 |
5 | let(:order) { create(:completed_order_with_totals) }
6 | let(:frequency) { create(:monthly_subscription_frequency, title: "monthly1") }
7 | let(:variant) { create(:base_variant) }
8 | let!(:line_item_without_subscription_attributes) { create(:line_item, order: order, variant: variant) }
9 | let!(:line_item_with_subscription_attributes) { create(:line_item, order: order, subscription_frequency_id: 1, delivery_number: 6) }
10 | let!(:active_subscription) { create(:valid_subscription, enabled: true, variant: line_item_with_subscription_attributes.variant, subscription_frequency_id: frequency.id, delivery_number: 6, parent_order: order) }
11 | let!(:line_item_without_subscription) { create(:line_item) }
12 |
13 | describe "callbacks" do
14 | it { is_expected.to callback(:create_subscription!).after(:create).if(:subscribable?) }
15 | it { is_expected.to callback(:update_subscription_quantity).after(:update).if(:can_update_subscription_quantity?) }
16 | it { is_expected.to callback(:update_subscription_attributes).after(:update).if(:can_update_subscription_attributes?) }
17 | it { is_expected.to callback(:destroy_associated_subscription!).after(:destroy).if(:subscription?) }
18 | end
19 |
20 | describe "attr_accessors" do
21 | it { is_expected.to respond_to :delivery_number }
22 | it { is_expected.to respond_to :delivery_number= }
23 | it { is_expected.to respond_to :subscribe }
24 | it { is_expected.to respond_to :subscribe= }
25 | it { is_expected.to respond_to :subscription_frequency_id }
26 | it { is_expected.to respond_to :subscription_frequency_id= }
27 | end
28 |
29 | describe "methods" do
30 | context "#subscription_attributes_present?" do
31 | it { expect(line_item_with_subscription_attributes).to be_subscription_attributes_present }
32 | it { expect(line_item_without_subscription_attributes).to_not be_subscription_attributes_present }
33 | end
34 |
35 | context "#updatable_subscription_attributes" do
36 | it { expect(line_item_with_subscription_attributes.updatable_subscription_attributes).to eq({ subscription_frequency_id: 1, delivery_number: 6 }) }
37 | end
38 |
39 | context "#subscription" do
40 | it { expect(line_item_with_subscription_attributes.send :subscription).to eq active_subscription }
41 | it { expect(line_item_without_subscription.send :subscription).to be_nil }
42 | end
43 |
44 | context "#subscription?" do
45 | it { expect(line_item_without_subscription.send :subscription?).to eq false }
46 | it { expect(line_item_with_subscription_attributes.send :subscription?).to eq true }
47 | end
48 |
49 | context "#can_update_subscription_attributes?" do
50 | it { expect(line_item_without_subscription_attributes.send :can_update_subscription_attributes?).to eq false }
51 | it { expect(line_item_with_subscription_attributes.send :can_update_subscription_attributes?).to eq true }
52 | end
53 |
54 | context "#can_update_subscription_quantity?" do
55 | context "when subscription not present" do
56 | it { expect(line_item_without_subscription.send :can_update_subscription_quantity?).to eq false }
57 | end
58 |
59 | context "when subscription is present but quantity not changed" do
60 | it { expect(line_item_with_subscription_attributes.send :can_update_subscription_quantity?).to eq false }
61 | end
62 |
63 | context "when subscription is present and quantity is changed" do
64 | before { line_item_with_subscription_attributes.quantity = 5 }
65 | it { expect(line_item_with_subscription_attributes.send :can_update_subscription_quantity?).to eq true }
66 | end
67 | end
68 |
69 | context "#destroy_associated_subscription!" do
70 | it { expect(line_item_with_subscription_attributes.send :destroy_associated_subscription!).to eq active_subscription }
71 | end
72 |
73 | context "#update_subscription_quantity" do
74 | def update_line_item_subscription_quantity
75 | line_item_with_subscription_attributes.quantity = 9
76 | line_item_with_subscription_attributes.send :update_subscription_quantity
77 | end
78 | it { expect { update_line_item_subscription_quantity }.to change { active_subscription.reload.quantity }.from(2).to(9) }
79 | end
80 |
81 | context "#update_subscription_attributes" do
82 | context "when subscription attributes are changed" do
83 | def update_line_item_subscription_attributes
84 | line_item_with_subscription_attributes.delivery_number = 8
85 | line_item_with_subscription_attributes.subscription_frequency_id = 2
86 | line_item_with_subscription_attributes.send :update_subscription_attributes
87 | end
88 | it { expect { update_line_item_subscription_attributes }.to change { active_subscription.reload.delivery_number }.from(6).to(8) }
89 | it { expect { update_line_item_subscription_attributes }.to change{ active_subscription.reload.subscription_frequency_id }.from(1).to(2) }
90 | end
91 | end
92 |
93 | context "#subscribable?" do
94 | context "when subscribe is present" do
95 | before { line_item_with_subscription_attributes.subscribe = true }
96 | it { expect(line_item_with_subscription_attributes.send :subscribable?).to eq true }
97 | end
98 | context "when subscribe is not present" do
99 | it { expect(line_item_with_subscription_attributes.send :subscribable?).to eq false }
100 | end
101 | end
102 |
103 | context "#subscription_attributes" do
104 | it { expect(line_item_with_subscription_attributes.send :subscription_attributes).to eq({
105 | subscription_frequency_id: 1,
106 | delivery_number: 6,
107 | variant: active_subscription.variant,
108 | quantity: 1,
109 | price: active_subscription.variant.price
110 | }) }
111 | end
112 |
113 | context "#create_subscription!" do
114 | def create_subscription
115 | line_item_without_subscription_attributes.subscription_frequency_id = frequency.id
116 | line_item_without_subscription_attributes.delivery_number = 5
117 | line_item_without_subscription_attributes.send :create_subscription!
118 | end
119 | it { expect { create_subscription }.to change { order.subscriptions.count }.by 1 }
120 | end
121 | end
122 |
123 | end
124 |
--------------------------------------------------------------------------------
/app/models/spree/subscription.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class Subscription < Spree::Base
3 |
4 | attr_accessor :cancelled
5 |
6 | include Spree::Core::NumberGenerator.new(prefix: 'S')
7 |
8 | ACTION_REPRESENTATIONS = {
9 | pause: "Pause",
10 | unpause: "Activate",
11 | cancel: "Cancel"
12 | }
13 |
14 | USER_DEFAULT_CANCELLATION_REASON = "Cancelled By User"
15 |
16 | belongs_to :ship_address, class_name: "Spree::Address"
17 | belongs_to :bill_address, class_name: "Spree::Address"
18 | belongs_to :parent_order, class_name: "Spree::Order"
19 | belongs_to :variant, inverse_of: :subscriptions
20 | belongs_to :frequency, foreign_key: :subscription_frequency_id, class_name: "Spree::SubscriptionFrequency"
21 | belongs_to :source, polymorphic: true
22 |
23 | accepts_nested_attributes_for :ship_address, :bill_address
24 |
25 | has_many :orders_subscriptions, class_name: "Spree::OrderSubscription", dependent: :destroy
26 | has_many :orders, through: :orders_subscriptions
27 | has_many :complete_orders, -> { complete }, through: :orders_subscriptions, source: :order
28 |
29 | self.whitelisted_ransackable_associations = %w( parent_order )
30 |
31 | scope :paused, -> { where(paused: true) }
32 | scope :unpaused, -> { where(paused: false) }
33 | scope :disabled, -> { where(enabled: false) }
34 | scope :active, -> { where(enabled: true) }
35 | scope :not_cancelled, -> { where(cancelled_at: nil) }
36 | scope :with_appropriate_delivery_time, -> { where("next_occurrence_at <= :current_date", current_date: Time.current) }
37 | scope :processable, -> { unpaused.active.not_cancelled }
38 | scope :eligible_for_subscription, -> { processable.with_appropriate_delivery_time }
39 | scope :with_parent_orders, -> (orders) { where(parent_order: orders) }
40 |
41 | with_options allow_blank: true do
42 | validates :price, numericality: { greater_than_or_equal_to: 0 }
43 | validates :quantity, numericality: { greater_than: 0, only_integer: true }
44 | validates :delivery_number, numericality: { greater_than_or_equal_to: :recurring_orders_size, only_integer: true }
45 | validates :parent_order, uniqueness: { scope: :variant }
46 | end
47 | with_options presence: true do
48 | validates :quantity, :delivery_number, :price, :number, :variant, :parent_order, :frequency, :prior_notification_days_gap
49 | validates :cancellation_reasons, :cancelled_at, if: :cancelled
50 | validates :ship_address, :bill_address, :next_occurrence_at, :source, if: :enabled?
51 | end
52 | validate :next_occurrence_at_range, if: :next_occurrence_at
53 | validate :prior_notification_days_gap_value, if: :prior_notification_days_gap
54 |
55 | define_model_callbacks :pause, only: [:before]
56 | before_pause :can_pause?
57 | define_model_callbacks :unpause, only: [:before]
58 | before_unpause :can_unpause?, :set_next_occurrence_at_after_unpause
59 | define_model_callbacks :process, only: [:after]
60 | after_process :notify_reoccurrence, if: :reoccurrence_notifiable?
61 | define_model_callbacks :cancel, only: [:before]
62 | before_cancel :set_cancellation_reason, if: :can_set_cancellation_reason?
63 |
64 | before_validation :set_next_occurrence_at, if: :can_set_next_occurrence_at?
65 | before_validation :set_cancelled_at, if: :can_set_cancelled_at?
66 | before_update :not_cancelled?
67 | before_validation :update_price, on: :update, if: :variant_id_changed?
68 | before_update :next_occurrence_at_not_changed?, if: :paused?
69 | after_update :notify_user, if: :user_notifiable?
70 | after_update :notify_cancellation, if: :cancellation_notifiable?
71 | after_update :update_next_occurrence_at
72 |
73 | def process
74 | if (variant.stock_items.sum(:count_on_hand) >= quantity || variant.stock_items.any? { |stock| stock.backorderable? }) && (!variant.product.discontinued?)
75 | update_column(:next_occurrence_possible, true)
76 | else
77 | update_column(:next_occurrence_possible, false)
78 | end
79 | new_order = recreate_order if (deliveries_remaining? && next_occurrence_possible)
80 | update(next_occurrence_at: next_occurrence_at_value) if new_order.try :completed?
81 | end
82 |
83 | def cancel_with_reason(attributes)
84 | self.cancelled = true
85 | update(attributes)
86 | end
87 |
88 | def cancelled?
89 | !!cancelled_at_was
90 | end
91 |
92 | def number_of_deliveries_left
93 | delivery_number.to_i - complete_orders.size - 1
94 | end
95 |
96 | def pause
97 | run_callbacks :pause do
98 | update_attributes(paused: true)
99 | end
100 | end
101 |
102 | def unpause
103 | run_callbacks :unpause do
104 | update_attributes(paused: false)
105 | end
106 | end
107 |
108 | def cancel
109 | self.cancelled = true
110 | run_callbacks :cancel do
111 | update_attributes(cancelled_at: Time.current)
112 | end
113 | end
114 |
115 | def deliveries_remaining?
116 | number_of_deliveries_left > 0
117 | end
118 |
119 | def not_changeable?
120 | cancelled? || !deliveries_remaining?
121 | end
122 |
123 | def send_prior_notification
124 | if eligible_for_prior_notification?
125 | SubscriptionNotifier.notify_for_next_delivery(self).deliver_later
126 | end
127 | end
128 |
129 | private
130 |
131 | def eligible_for_prior_notification?
132 | (next_occurrence_at.to_date - Time.current.to_date).round == prior_notification_days_gap
133 | end
134 |
135 | def update_price
136 | if valid_variant?
137 | self.price = variant.price
138 | else
139 | self.errors.add(:variant_id, :does_not_belong_to_product)
140 | end
141 | end
142 |
143 | def valid_variant?
144 | variant_was = Spree::Variant.find_by(id: variant_id_was)
145 | variant.present? && variant_was.try(:product_id) == variant.product_id
146 | end
147 |
148 | def set_cancelled_at
149 | self.cancelled_at = Time.current
150 | end
151 |
152 | def set_next_occurrence_at
153 | self.next_occurrence_at = next_occurrence_at_value
154 | end
155 |
156 | def next_occurrence_at_value
157 | deliveries_remaining? ? Time.current + frequency.months_count.month : next_occurrence_at
158 | end
159 |
160 | def can_set_next_occurrence_at?
161 | enabled? && next_occurrence_at.nil? && deliveries_remaining?
162 | end
163 |
164 | def set_next_occurrence_at_after_unpause
165 | self.next_occurrence_at = (Time.current > next_occurrence_at) ? next_occurrence_at + frequency.months_count.month : next_occurrence_at
166 | end
167 |
168 | def can_pause?
169 | enabled? && !cancelled? && deliveries_remaining? && !paused?
170 | end
171 |
172 | def can_unpause?
173 | enabled? && !cancelled? && deliveries_remaining? && paused?
174 | end
175 |
176 | def recreate_order
177 | order = make_new_order
178 | add_variant_to_order(order)
179 | add_shipping_address(order)
180 | add_delivery_method_to_order(order)
181 | add_shipping_costs_to_order(order)
182 | add_payment_method_to_order(order)
183 | confirm_order(order)
184 | order
185 | end
186 |
187 | def make_new_order
188 | orders.create(order_attributes)
189 | end
190 |
191 | def add_variant_to_order(order)
192 | order.contents.add(variant, quantity)
193 | order.next
194 | end
195 |
196 | def add_shipping_address(order)
197 | order.ship_address = ship_address.clone
198 | order.bill_address = bill_address.clone
199 | order.next
200 | end
201 |
202 | # select shipping method which was selected in original order.
203 | def add_delivery_method_to_order(order)
204 | selected_shipping_method_id = parent_order.inventory_units.where(variant_id: variant.id).first.shipment.shipping_method.id
205 |
206 | order.shipments.each do |shipment|
207 | current_shipping_rate = shipment.shipping_rates.find_by(selected: true)
208 | proposed_shipping_rate = shipment.shipping_rates.find_by(shipping_method_id: selected_shipping_method_id)
209 |
210 | if proposed_shipping_rate.present? && current_shipping_rate != proposed_shipping_rate
211 | current_shipping_rate.update(selected: false)
212 | proposed_shipping_rate.update(selected: true)
213 | end
214 | end
215 |
216 | order.next
217 | end
218 |
219 | def add_shipping_costs_to_order(order)
220 | order.set_shipments_cost
221 | end
222 |
223 | def add_payment_method_to_order(order)
224 | if order.payments.exists?
225 | order.payments.first.update(source: source, payment_method: source.payment_method)
226 | else
227 | order.payments.create(source: source, payment_method: source.payment_method, amount: order.total)
228 | end
229 | order.next
230 | end
231 |
232 | def confirm_order(order)
233 | order.next
234 | end
235 |
236 | def order_attributes
237 | {
238 | currency: parent_order.currency,
239 | guest_token: parent_order.guest_token,
240 | store: parent_order.store,
241 | user: parent_order.user,
242 | created_by: parent_order.user,
243 | last_ip_address: parent_order.last_ip_address
244 | }
245 | end
246 |
247 | def notify_user
248 | SubscriptionNotifier.notify_confirmation(self).deliver_later
249 | end
250 |
251 | def not_cancelled?
252 | !cancelled?
253 | end
254 |
255 | def can_set_cancelled_at?
256 | cancelled.present? && deliveries_remaining?
257 | end
258 |
259 | def set_cancellation_reason
260 | self.cancellation_reasons = USER_DEFAULT_CANCELLATION_REASON
261 | end
262 |
263 | def can_set_cancellation_reason?
264 | cancelled.present? && deliveries_remaining? && cancellation_reasons.nil?
265 | end
266 |
267 | def notify_cancellation
268 | SubscriptionNotifier.notify_cancellation(self).deliver_later
269 | end
270 |
271 | def cancellation_notifiable?
272 | cancelled_at.present? && cancelled_at_changed?
273 | end
274 |
275 | def reoccurrence_notifiable?
276 | next_occurrence_at_changed? && !!next_occurrence_at_was
277 | end
278 |
279 | def notify_reoccurrence
280 | SubscriptionNotifier.notify_reoccurrence(self).deliver_later
281 | end
282 |
283 | def recurring_orders_size
284 | complete_orders.size + 1
285 | end
286 |
287 | def user_notifiable?
288 | enabled? && enabled_changed?
289 | end
290 |
291 | def next_occurrence_at_not_changed?
292 | !next_occurrence_at_changed?
293 | end
294 |
295 | def next_occurrence_at_range
296 | unless next_occurrence_at >= Time.current.to_date
297 | errors.add(:next_occurrence_at, Spree.t('subscriptions.error.out_of_range'))
298 | end
299 | end
300 |
301 | def update_next_occurrence_at
302 | update_column(:next_occurrence_at, next_occurrence_at_value)
303 | end
304 |
305 | def prior_notification_days_gap_value
306 | return if next_occurrence_at_value.nil?
307 |
308 | if Time.current + prior_notification_days_gap.days >= next_occurrence_at_value
309 | errors.add(:prior_notification_days_gap, Spree.t('subscriptions.error.should_be_earlier_than_next_delivery'))
310 | end
311 | end
312 | end
313 | end
314 |
--------------------------------------------------------------------------------
/spec/controllers/spree/admin/subscriptions_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Admin::SubscriptionsController, type: :controller do
4 |
5 | stub_authorization!
6 |
7 | let(:active_subscription) { mock_model(Spree::Subscription, id: 1, enabled: true, next_occurrence_at: Time.current) }
8 | let(:cancelled_subscription) { mock_model(Spree::Subscription, id: 2, cancelled_at: Time.current, cancellation_reasons: "Test") }
9 |
10 | describe "#cancellation" do
11 | def do_cancellation params
12 | spree_get :cancellation, params
13 | end
14 |
15 | let(:params) { { id: active_subscription.id } }
16 |
17 | before do
18 | allow(Spree::Subscription).to receive(:find).and_return(active_subscription)
19 | allow(active_subscription).to receive(:cancelled?).and_return(false)
20 | end
21 |
22 | context "expects to receive" do
23 | after { do_cancellation params }
24 | it { expect(Spree::Subscription).to receive(:find).with(params[:id].to_s).and_return(active_subscription) }
25 | it { expect(active_subscription).to receive(:cancelled?).and_return(false) }
26 | end
27 |
28 | context "response" do
29 | before { do_cancellation params }
30 | it { expect(response).to have_http_status 200 }
31 | it { expect(response).to render_template :cancellation }
32 | end
33 | end
34 |
35 | describe "pause" do
36 | def do_pause
37 | spree_post :pause, format: :json, id: active_subscription.id
38 | end
39 |
40 | before do
41 | allow(Spree::Subscription).to receive(:find).and_return(active_subscription)
42 | allow(active_subscription).to receive(:cancelled?).and_return(false)
43 | end
44 |
45 | describe "when pause returns success" do
46 | before do
47 | allow(active_subscription).to receive(:pause).and_return(true)
48 | end
49 |
50 | describe "expects to receive" do
51 | after { do_pause }
52 | it { expect(Spree::Subscription).to receive(:find).and_return(active_subscription) }
53 | it { expect(active_subscription).to receive(:cancelled?).and_return(false) }
54 | it { expect(active_subscription).to receive(:pause).and_return(true) }
55 | end
56 |
57 | describe "response" do
58 | before { do_pause }
59 | it { expect(response).to have_http_status 200 }
60 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("admin.subscriptions.pause.success") }
61 | end
62 | end
63 |
64 | describe "when pause is not successful" do
65 | before do
66 | allow(active_subscription).to receive(:pause).and_return(false)
67 | end
68 |
69 | describe "expects to receive" do
70 | after { do_pause }
71 | it { expect(Spree::Subscription).to receive(:find).and_return(active_subscription) }
72 | it { expect(active_subscription).to receive(:cancelled?).and_return(false) }
73 | it { expect(active_subscription).to receive(:pause).and_return(false) }
74 | end
75 |
76 | describe "response" do
77 | before { do_pause }
78 | it { expect(response).to have_http_status 422 }
79 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("admin.subscriptions.pause.error") }
80 | end
81 | end
82 | end
83 |
84 | describe "unpause" do
85 | def do_unpause
86 | spree_post :unpause, format: :json, id: active_subscription.id
87 | end
88 |
89 | before do
90 | allow(Spree::Subscription).to receive(:find).and_return(active_subscription)
91 | allow(active_subscription).to receive(:cancelled?).and_return(false)
92 | end
93 |
94 | describe "when unpause returns success" do
95 | before do
96 | allow(active_subscription).to receive(:unpause).and_return(true)
97 | end
98 |
99 | describe "expects to receive" do
100 | after { do_unpause }
101 | it { expect(Spree::Subscription).to receive(:find).and_return(active_subscription) }
102 | it { expect(active_subscription).to receive(:cancelled?).and_return(false) }
103 | it { expect(active_subscription).to receive(:unpause).and_return(true) }
104 | end
105 |
106 | describe "response" do
107 | before { do_unpause }
108 | it { expect(response).to have_http_status 200 }
109 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("admin.subscriptions.unpause.success", next_occurrence_at: active_subscription.next_occurrence_at.to_date.to_formatted_s(:rfc822)) }
110 | end
111 | end
112 |
113 | describe "when unpause is not successful" do
114 | before do
115 | allow(active_subscription).to receive(:unpause).and_return(false)
116 | end
117 |
118 | describe "expects to receive" do
119 | after { do_unpause }
120 | it { expect(Spree::Subscription).to receive(:find).and_return(active_subscription) }
121 | it { expect(active_subscription).to receive(:cancelled?).and_return(false) }
122 | it { expect(active_subscription).to receive(:unpause).and_return(false) }
123 | end
124 |
125 | describe "response" do
126 | before { do_unpause }
127 | it { expect(response).to have_http_status 422 }
128 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("admin.subscriptions.unpause.error") }
129 | end
130 | end
131 | end
132 |
133 | def do_cancel params
134 | spree_post :cancel, params
135 | end
136 |
137 | describe "#Cancel" do
138 | context "when cancel_with_reason returns true" do
139 |
140 | let(:params) { { id: active_subscription.id, subscription: { cancellation_reasons: "Test" } } }
141 |
142 | before do
143 | allow(Spree::Subscription).to receive(:find).and_return(active_subscription)
144 | allow(controller).to receive(:cancel_subscription_attributes).and_return(params[:subscription])
145 | allow(active_subscription).to receive(:cancel_with_reason).and_return(true)
146 | allow(active_subscription).to receive(:cancelled?).and_return(false)
147 | end
148 |
149 | context "expects to receive" do
150 | after { do_cancel params }
151 | it { expect(Spree::Subscription).to receive(:find).with(params[:id].to_s).and_return(active_subscription) }
152 | it { expect(controller).to receive(:cancel_subscription_attributes).and_call_original }
153 | it { expect(active_subscription).to receive(:cancel_with_reason).with(controller.send :cancel_subscription_attributes).and_return(true) }
154 | it { expect(active_subscription).to receive(:cancelled?).and_return(false) }
155 | end
156 |
157 | context "response" do
158 | before { do_cancel params }
159 | it { expect(response).to have_http_status 302 }
160 | it { expect(response).to redirect_to controller.send :collection_url }
161 | it { expect(flash[:success]).to eq I18n.t("spree.admin.subscriptions.cancel.success") }
162 | end
163 | end
164 |
165 | context "when cancel_with_reason returns false" do
166 |
167 | let(:params) { { id: active_subscription.id, subscription: { cancellation_reasons: nil } } }
168 |
169 | before do
170 | allow(Spree::Subscription).to receive(:find).and_return(active_subscription)
171 | allow(active_subscription).to receive(:cancelled?).and_return(false)
172 | allow(controller).to receive(:cancel_subscription_attributes).and_return(params[:subscription])
173 | allow(active_subscription).to receive(:cancel_with_reason).and_return(false)
174 | end
175 |
176 | context "expects to receive" do
177 | after { do_cancel params }
178 | it { expect(Spree::Subscription).to receive(:find).with(params[:id].to_s).and_return(active_subscription) }
179 | it { expect(active_subscription).to receive(:cancelled?).and_return(false) }
180 | it { expect(controller).to receive(:cancel_subscription_attributes).and_call_original }
181 | it { expect(active_subscription).to receive(:cancel_with_reason).with(controller.send :cancel_subscription_attributes).and_return(false) }
182 | end
183 |
184 | context "response" do
185 | before { do_cancel params }
186 | it { expect(response).to have_http_status 200 }
187 | it { expect(response).to render_template :cancellation }
188 | end
189 | end
190 | end
191 |
192 | describe "callbacks" do
193 | describe "#ensure_not_cancelled" do
194 | def do_cancellation params
195 | spree_get :cancellation, params
196 | end
197 |
198 | context "when subscription is cancelled" do
199 |
200 | let(:params) { { id: cancelled_subscription.id } }
201 |
202 | before do
203 | allow(Spree::Subscription).to receive(:find).and_return(cancelled_subscription)
204 | allow(cancelled_subscription).to receive(:cancelled?).and_return(true)
205 | end
206 |
207 | context "expects to receive" do
208 | after { do_cancellation params }
209 | it { expect(Spree::Subscription).to receive(:find).with(params[:id].to_s).and_return(cancelled_subscription) }
210 | it { expect(cancelled_subscription).to receive(:cancelled?).and_return(true) }
211 | end
212 |
213 | context "response" do
214 | before { do_cancellation params }
215 | it { expect(response).to have_http_status 302 }
216 | it { expect(response).to redirect_to controller.send :collection_url }
217 | it { expect(flash[:error]).to eq I18n.t("spree.admin.subscriptions.error_on_already_cancelled") }
218 | end
219 | end
220 |
221 | context "when subscription is not cancelled" do
222 | let(:params) { { id: active_subscription.id } }
223 |
224 | before do
225 | allow(Spree::Subscription).to receive(:find).and_return(active_subscription)
226 | allow(active_subscription).to receive(:cancelled?).and_return(false)
227 | end
228 |
229 | context "expects to receive" do
230 | after { do_cancellation params }
231 | it { expect(Spree::Subscription).to receive(:find).with(params[:id].to_s).and_return(active_subscription) }
232 | it { expect(active_subscription).to receive(:cancelled?).and_return(false) }
233 | end
234 |
235 | context "response" do
236 | before { do_cancellation params }
237 | it { expect(response).to have_http_status 200 }
238 | it { expect(response).to render_template :cancellation }
239 | end
240 | end
241 | end
242 |
243 | describe "#collection" do
244 | def do_index
245 | spree_get :index
246 | end
247 |
248 | let(:subscriptions) { double(ActiveRecord::Relation) }
249 | let(:search_subscriptions) { double(Ransack::Search) }
250 | let(:result_subscriptions) { double(ActiveRecord::Relation) }
251 |
252 | before do
253 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
254 | allow(subscriptions).to receive(:ransack).and_return(search_subscriptions)
255 | allow(search_subscriptions).to receive(:result).and_return(result_subscriptions)
256 | allow(result_subscriptions).to receive(:includes).and_return(result_subscriptions)
257 | allow(result_subscriptions).to receive(:references).and_return(result_subscriptions)
258 | allow(result_subscriptions).to receive(:order).and_return(result_subscriptions)
259 | allow(result_subscriptions).to receive(:page).and_return(result_subscriptions)
260 | end
261 |
262 | context "expects to receive" do
263 | after { do_index }
264 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
265 | it { expect(subscriptions).to receive(:ransack).with(controller.params[:q]).and_return(search_subscriptions) }
266 | it { expect(search_subscriptions).to receive(:result).and_return(result_subscriptions) }
267 | it { expect(result_subscriptions).to receive(:includes).with(:frequency, :complete_orders, variant: :product).and_return(result_subscriptions) }
268 | it { expect(result_subscriptions).to receive(:references).with(:complete_orders).and_return(result_subscriptions) }
269 | it { expect(result_subscriptions).to receive(:order).with(created_at: :desc).and_return(result_subscriptions) }
270 | it { expect(result_subscriptions).to receive(:page).with(controller.params[:page]).and_return(result_subscriptions) }
271 | end
272 |
273 | context "assigns" do
274 | before { do_index }
275 | it { expect(assigns(:search)).to eq search_subscriptions }
276 | it { expect(assigns(:collection)).to eq result_subscriptions }
277 | end
278 | end
279 | end
280 |
281 | end
282 |
--------------------------------------------------------------------------------
/spec/controllers/spree/subscriptions_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Spree::SubscriptionsController, type: :controller do
4 |
5 | stub_authorization!
6 |
7 | let(:active_subscription) { mock_model(Spree::Subscription, id: 1, enabled: true, next_occurrence_at: Time.current) }
8 | let(:cancelled_subscription) { mock_model(Spree::Subscription, id: 2, cancelled_at: Time.current, cancellation_reasons: "Test", enabled: true) }
9 | let(:subscriptions) { double(ActiveRecord::Relation) }
10 |
11 | describe "Callbacks" do
12 | def do_cancel params
13 | spree_post :cancel, params
14 | end
15 |
16 | describe "#ensure_subscription" do
17 | context "html request" do
18 | context "when subscription is present" do
19 | let(:params) { { id: active_subscription.id } }
20 |
21 | before do
22 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
23 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
24 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
25 | allow(active_subscription).to receive(:cancel).and_return(true)
26 | end
27 |
28 | context "expects to receive" do
29 | after { do_cancel params }
30 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
31 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
32 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
33 | it { expect(active_subscription).to receive(:cancel).and_return(true) }
34 | end
35 |
36 | context "assigns" do
37 | before { do_cancel params }
38 | it { expect(response).to have_http_status 302 }
39 | it { expect(flash[:error]).to be_nil }
40 | end
41 | end
42 |
43 | context "when subscription is not present" do
44 | let(:params) { { id: "" } }
45 |
46 | before do
47 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
48 | allow(subscriptions).to receive(:find_by).and_return(nil)
49 | end
50 |
51 | context "expects to receive" do
52 | after { do_cancel params }
53 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
54 | it { expect(subscriptions).to receive(:find_by).and_return(nil) }
55 | end
56 |
57 | context "assigns" do
58 | before { do_cancel params }
59 | it { expect(response).to have_http_status 302 }
60 | it { expect(response).to redirect_to account_path }
61 | it { expect(flash[:error]).to eq Spree.t("subscriptions.alert.missing") }
62 | end
63 | end
64 | end
65 |
66 | context "json request" do
67 | context "when subscription is present" do
68 | let(:params) { { id: active_subscription.id, format: :json } }
69 |
70 | before do
71 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
72 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
73 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
74 | allow(active_subscription).to receive(:cancel).and_return(true)
75 | end
76 |
77 | context "expects to receive" do
78 | after { do_cancel params }
79 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
80 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
81 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
82 | it { expect(active_subscription).to receive(:cancel).and_return(true) }
83 | end
84 |
85 | context "assigns" do
86 | before { do_cancel params }
87 | it { expect(response).to have_http_status 200 }
88 | it { expect(JSON.parse(response.body)["flash"]).to_not eq Spree.t("subscriptions.alert.missing") }
89 | end
90 | end
91 |
92 | context "when subscription is not present" do
93 | let(:params) { { id: "", format: :json } }
94 |
95 | before do
96 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
97 | allow(subscriptions).to receive(:find_by).and_return(nil)
98 | end
99 |
100 | context "expects to receive" do
101 | after { do_cancel params }
102 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
103 | it { expect(subscriptions).to receive(:find_by).and_return(nil) }
104 | end
105 |
106 | context "assigns" do
107 | before { do_cancel params }
108 | it { expect(response).to have_http_status 422 }
109 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("subscriptions.alert.missing") }
110 | end
111 | end
112 | end
113 | end
114 |
115 | describe "#ensure_not_cancelled" do
116 | context "html request" do
117 | context "when subscription is cancelled" do
118 | let(:params) { { id: cancelled_subscription.id } }
119 |
120 | before do
121 | request.env["HTTP_REFERER"] = account_path
122 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
123 | allow(subscriptions).to receive(:find_by).and_return(cancelled_subscription)
124 | allow(cancelled_subscription).to receive(:not_changeable?).and_return(true)
125 | end
126 |
127 | context "expects to receive" do
128 | after { do_cancel params }
129 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
130 | it { expect(subscriptions).to receive(:find_by).and_return(cancelled_subscription) }
131 | it { expect(cancelled_subscription).to receive(:not_changeable?).and_return(true) }
132 | end
133 |
134 | context "response" do
135 | before { do_cancel params }
136 | it { expect(response).to have_http_status 302 }
137 | it { expect(response).to redirect_to account_path }
138 | it { expect(flash[:error]).to eq Spree.t("subscriptions.error.not_changeable") }
139 | end
140 | end
141 |
142 | context "when subscription is not cancelled" do
143 | let(:params) { { id: active_subscription.id } }
144 |
145 | before do
146 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
147 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
148 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
149 | allow(active_subscription).to receive(:cancel).and_return(true)
150 | end
151 |
152 | context "expects to receive" do
153 | after { do_cancel params }
154 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
155 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
156 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
157 | it { expect(active_subscription).to receive(:cancel).and_return(true) }
158 | end
159 |
160 | context "response" do
161 | before { do_cancel params }
162 | it { expect(response).to have_http_status 302 }
163 | it { expect(flash[:error]).to be_nil }
164 | end
165 | end
166 | end
167 |
168 | context "json request" do
169 | context "when subscription is cancelled" do
170 | let(:params) { { id: cancelled_subscription.id, format: :json } }
171 |
172 | before do
173 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
174 | allow(subscriptions).to receive(:find_by).and_return(cancelled_subscription)
175 | allow(cancelled_subscription).to receive(:not_changeable?).and_return(true)
176 | end
177 |
178 | context "expects to receive" do
179 | after { do_cancel params }
180 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
181 | it { expect(subscriptions).to receive(:find_by).and_return(cancelled_subscription) }
182 | it { expect(cancelled_subscription).to receive(:not_changeable?).and_return(true) }
183 | end
184 |
185 | context "response" do
186 | before { do_cancel params }
187 | it { expect(response).to have_http_status 422 }
188 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("subscriptions.error.not_changeable") }
189 | end
190 | end
191 |
192 | context "when subscription is not cancelled" do
193 | let(:params) { { id: active_subscription.id, format: :json } }
194 |
195 | before do
196 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
197 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
198 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
199 | allow(active_subscription).to receive(:cancel).and_return(true)
200 | end
201 |
202 | context "expects to receive" do
203 | after { do_cancel params }
204 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
205 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
206 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
207 | it { expect(active_subscription).to receive(:cancel).and_return(true) }
208 | end
209 |
210 | context "response" do
211 | before { do_cancel params }
212 | it { expect(response).to have_http_status 200 }
213 | it { expect(JSON.parse(response.body)["flash"]).to_not eq Spree.t("subscriptions.error.not_changeable") }
214 | end
215 | end
216 | end
217 | end
218 | end
219 |
220 | describe "edit" do
221 | def do_edit params
222 | spree_get :edit, params
223 | end
224 |
225 | it { is_expected.to use_before_action(:ensure_subscription_belongs_to_user) }
226 |
227 | describe "when subscription is found" do
228 | before do
229 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
230 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
231 | end
232 |
233 | describe "expects to receive" do
234 | after { do_edit({ id: active_subscription.id }) }
235 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
236 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
237 | end
238 |
239 | describe "response" do
240 | before { do_edit({ id: active_subscription.id }) }
241 | it { expect(response).to have_http_status 200 }
242 | it { expect(response).to render_template :edit }
243 | end
244 | end
245 |
246 | describe "when subscription is not found" do
247 | before do
248 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
249 | allow(subscriptions).to receive(:find_by).and_return(nil)
250 | end
251 |
252 | describe "expects to receive" do
253 | after { do_edit({ id: "" }) }
254 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
255 | it { expect(subscriptions).to receive(:find_by).and_return(nil) }
256 | end
257 |
258 | describe "response" do
259 | before { do_edit({ id: "" }) }
260 | it { expect(response).to have_http_status 302 }
261 | it { expect(response).to redirect_to account_path }
262 | it { expect(flash[:error]).to eq Spree.t("subscriptions.alert.missing") }
263 | end
264 | end
265 |
266 | describe '#ensure_subscription_belongs_to_user' do
267 | let(:user) { create(:user) }
268 | let(:order) { create(:completed_order_with_totals) }
269 | let(:subscription) { create(:valid_subscription, enabled: true, parent_order: order, next_occurrence_at: Time.current) }
270 |
271 | it "is expected to authorize user's ability to update subscription" do
272 | expect(controller).to receive(:authorize!).with(:update, subscription)
273 | end
274 |
275 | after { do_edit({ id: subscription.id }) }
276 | end
277 | end
278 |
279 | describe "update" do
280 | def do_update params
281 | spree_put :update, params
282 | end
283 |
284 | describe "when subscription is found" do
285 |
286 | let(:params) { { id: active_subscription.id, subscription: { quantity: 2 } } }
287 |
288 | describe "when subscription is successfully updated" do
289 | before do
290 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
291 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
292 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
293 | allow(controller).to receive(:subscription_attributes).and_return(params[:subscription])
294 | allow(active_subscription).to receive(:update).and_return(true)
295 | end
296 |
297 | describe "expects to receive" do
298 | after { do_update(params) }
299 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
300 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
301 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
302 | it { expect(controller).to receive(:subscription_attributes).and_call_original }
303 | it { expect(active_subscription).to receive(:update).with(controller.send :subscription_attributes).and_return(true) }
304 | end
305 |
306 | describe "response" do
307 | context 'when request.json?' do
308 | before { do_update(params.merge(format: :json)) }
309 | it { expect(response).to have_http_status 200 }
310 | it { expect(response.body['subscription']).not_to be_nil }
311 | end
312 | context 'when request.html?' do
313 | before { do_update(params) }
314 | it { expect(response).to have_http_status 302 }
315 | it { expect(response).to redirect_to edit_subscription_path(active_subscription) }
316 | it { expect(flash[:success]).to eq Spree.t("subscriptions.update.success") }
317 | end
318 | end
319 | end
320 |
321 | describe "when subscription is not successfully updated" do
322 | before do
323 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
324 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
325 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
326 | allow(controller).to receive(:subscription_attributes).and_return(params[:subscription])
327 | allow(active_subscription).to receive(:update).and_return(false)
328 | end
329 |
330 | describe "expects to receive" do
331 | after { do_update(params) }
332 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
333 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
334 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
335 | it { expect(controller).to receive(:subscription_attributes).and_call_original }
336 | it { expect(active_subscription).to receive(:update).with(controller.send :subscription_attributes).and_return(false) }
337 | end
338 |
339 | describe "response" do
340 | context 'when request.json?' do
341 | before { do_update(params.merge(format: :json)) }
342 | it { expect(response).to have_http_status 422 }
343 | it { expect(response.body['errors']).not_to be_nil }
344 | end
345 | context 'when request.html?' do
346 | before { do_update(params) }
347 | it { expect(response).to have_http_status 200 }
348 | it { expect(response).to render_template :edit }
349 | end
350 | end
351 | end
352 | end
353 |
354 | describe "when subscription is not found" do
355 | before do
356 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
357 | allow(subscriptions).to receive(:find_by).and_return(nil)
358 | end
359 |
360 | describe "expects to receive" do
361 | after { do_update({ id: "" }) }
362 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
363 | it { expect(subscriptions).to receive(:find_by).and_return(nil) }
364 | end
365 |
366 | describe "response" do
367 | before { do_update({ id: "" }) }
368 | it { expect(response).to have_http_status 302 }
369 | it { expect(response).to redirect_to account_path }
370 | it { expect(flash[:error]).to eq Spree.t("subscriptions.alert.missing") }
371 | end
372 | end
373 | end
374 |
375 | describe "pause" do
376 | def do_pause
377 | spree_post :pause, format: :json, id: active_subscription.id
378 | end
379 |
380 | before do
381 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
382 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
383 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
384 | end
385 |
386 | describe "when pause returns success" do
387 | before do
388 | allow(active_subscription).to receive(:pause).and_return(true)
389 | end
390 |
391 | describe "expects to receive" do
392 | after { do_pause }
393 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
394 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
395 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
396 | it { expect(active_subscription).to receive(:pause).and_return(true) }
397 | end
398 |
399 | describe "response" do
400 | before { do_pause }
401 | it { expect(response).to have_http_status 200 }
402 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("subscriptions.pause.success") }
403 | end
404 | end
405 |
406 | describe "when pause is not successful" do
407 | before do
408 | allow(active_subscription).to receive(:pause).and_return(false)
409 | end
410 |
411 | describe "expects to receive" do
412 | after { do_pause }
413 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
414 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
415 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
416 | it { expect(active_subscription).to receive(:pause).and_return(false) }
417 | end
418 |
419 | describe "response" do
420 | before { do_pause }
421 | it { expect(response).to have_http_status 422 }
422 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("subscriptions.pause.error") }
423 | end
424 | end
425 | end
426 |
427 | describe "unpause" do
428 | def do_unpause
429 | spree_post :unpause, format: :json, id: active_subscription.id
430 | end
431 |
432 | before do
433 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
434 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
435 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
436 | end
437 |
438 | describe "when unpause returns success" do
439 | before do
440 | allow(active_subscription).to receive(:unpause).and_return(true)
441 | end
442 |
443 | describe "expects to receive" do
444 | after { do_unpause }
445 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
446 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
447 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
448 | it { expect(active_subscription).to receive(:unpause).and_return(true) }
449 | end
450 |
451 | describe "response" do
452 | before { do_unpause }
453 | it { expect(response).to have_http_status 200 }
454 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("subscriptions.unpause.success", next_occurrence_at: active_subscription.next_occurrence_at.to_date.to_formatted_s(:rfc822)) }
455 | end
456 | end
457 |
458 | describe "when unpause is not successful" do
459 | before do
460 | allow(active_subscription).to receive(:unpause).and_return(false)
461 | end
462 |
463 | describe "expects to receive" do
464 | after { do_unpause }
465 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
466 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
467 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
468 | it { expect(active_subscription).to receive(:unpause).and_return(false) }
469 | end
470 |
471 | describe "response" do
472 | before { do_unpause }
473 | it { expect(response).to have_http_status 422 }
474 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("subscriptions.unpause.error") }
475 | end
476 | end
477 | end
478 |
479 | describe "cancel" do
480 | describe "html response" do
481 | def do_cancel params
482 | spree_post :cancel, params
483 | end
484 |
485 | before do
486 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
487 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
488 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
489 | end
490 |
491 | describe "when subscription cancel is successful" do
492 | before do
493 | allow(active_subscription).to receive(:cancel).and_return(true)
494 | end
495 |
496 | describe "expects to receive" do
497 | after { do_cancel({ id: active_subscription.id }) }
498 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
499 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
500 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
501 | it { expect(active_subscription).to receive(:cancel).and_return(true) }
502 | end
503 |
504 | describe "response" do
505 | before { do_cancel({ id: active_subscription.id }) }
506 | it { expect(response).to have_http_status 302 }
507 | it { expect(response).to redirect_to edit_subscription_path(active_subscription) }
508 | it { expect(flash[:success]).to eq Spree.t("subscriptions.cancel.success") }
509 | end
510 | end
511 |
512 | describe "when subscription cancel is not successful" do
513 | before do
514 | allow(active_subscription).to receive(:cancel).and_return(false)
515 | end
516 |
517 | describe "expects to receive" do
518 | after { do_cancel({ id: active_subscription.id }) }
519 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
520 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
521 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
522 | it { expect(active_subscription).to receive(:cancel).and_return(false) }
523 | end
524 |
525 | describe "response" do
526 | before { do_cancel({ id: active_subscription.id }) }
527 | it { expect(response).to have_http_status 302 }
528 | it { expect(response).to redirect_to edit_subscription_path(active_subscription) }
529 | it { expect(flash[:error]).to eq Spree.t("subscriptions.cancel.error") }
530 | end
531 | end
532 | end
533 |
534 | describe "json response" do
535 | def do_cancel
536 | spree_post :cancel, format: :json, id: active_subscription.id
537 | end
538 |
539 | before do
540 | allow(Spree::Subscription).to receive(:active).and_return(subscriptions)
541 | allow(subscriptions).to receive(:find_by).and_return(active_subscription)
542 | allow(active_subscription).to receive(:not_changeable?).and_return(false)
543 | end
544 |
545 | describe "when cancel returns success" do
546 | before do
547 | allow(active_subscription).to receive(:cancel).and_return(true)
548 | end
549 |
550 | describe "expects to receive" do
551 | after { do_cancel }
552 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
553 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
554 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
555 | it { expect(active_subscription).to receive(:cancel).and_return(true) }
556 | end
557 |
558 | describe "response" do
559 | before { do_cancel }
560 | it { expect(response).to have_http_status 200 }
561 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("subscriptions.cancel.success") }
562 | end
563 | end
564 |
565 | describe "when cancel is not successful" do
566 | before do
567 | allow(active_subscription).to receive(:cancel).and_return(false)
568 | end
569 |
570 | describe "expects to receive" do
571 | after { do_cancel }
572 | it { expect(Spree::Subscription).to receive(:active).and_return(subscriptions) }
573 | it { expect(subscriptions).to receive(:find_by).and_return(active_subscription) }
574 | it { expect(active_subscription).to receive(:not_changeable?).and_return(false) }
575 | it { expect(active_subscription).to receive(:cancel).and_return(false) }
576 | end
577 |
578 | describe "response" do
579 | before { do_cancel }
580 | it { expect(response).to have_http_status 422 }
581 | it { expect(JSON.parse(response.body)["flash"]).to eq Spree.t("subscriptions.cancel.error") }
582 | end
583 | end
584 | end
585 | end
586 |
587 | end
588 |
--------------------------------------------------------------------------------