diff --git a/.ruby-version b/.ruby-version index 5859406..2bf1c1c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.2.3 +2.3.1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a26d0ca --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: ruby + +rvm: + - 2.3.1 + +script: + - bundle exec rake + +before_install: + - "phantomjs --version" + - "export PATH=$PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH" + - "phantomjs --version" + - "if [ $(phantomjs --version) != '2.1.1' ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi" + - "if [ $(phantomjs --version) != '2.1.1' ]; then wget https://assets.membergetmember.co/software/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2; fi" + - "if [ $(phantomjs --version) != '2.1.1' ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi" + - "phantomjs --version" diff --git a/Gemfile b/Gemfile index ccc6604..6429a7a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,11 +1,13 @@ source 'http://rubygems.org' -gem 'solidus', github: 'solidusio/solidus', branch: 'master' +gem 'rake', '< 11.0' +gem 'solidus', github: 'solidusio/solidus', branch: 'v1.4' # Provides basic authentication functionality for testing parts of your engine gem 'solidus_auth_devise', github: 'solidusio/solidus_auth_devise', branch: 'master' gem 'active_model_serializers', '~> 0.8.3' gem 'stripe' gem 'slim-rails' +gem 'deface' group :test do gem 'factory_girl', '4.5.0' @@ -17,6 +19,10 @@ group :test do gem 'guard-rspec', require: false gem 'simplecov', require: false gem 'selenium-webdriver' + gem 'poltergeist' + gem 'capybara-screenshot' + gem 'vcr' + gem 'webmock' end group :development do diff --git a/app/assets/javascripts/spree/backend/spree_subscriptions.js.coffee b/app/assets/javascripts/spree/backend/solidus_subscriptions.js.coffee similarity index 81% rename from app/assets/javascripts/spree/backend/spree_subscriptions.js.coffee rename to app/assets/javascripts/spree/backend/solidus_subscriptions.js.coffee index ca07d4d..afef53c 100644 --- a/app/assets/javascripts/spree/backend/spree_subscriptions.js.coffee +++ b/app/assets/javascripts/spree/backend/solidus_subscriptions.js.coffee @@ -11,7 +11,6 @@ $(document).ready -> subscription_item_id = save.data('subscription-item-id') quantity = parseInt(save.parents('tr').find('input.subscription_item_quantity').val()) - toggleItemEdit() adjustSubscriptionItem(subscription_item_id, quantity) false @@ -21,7 +20,6 @@ $(document).ready -> del = $(this); subscription_item_id = del.data('subscription-item-id'); - toggleItemEdit() deleteSubscriptionItem(subscription_item_id) # handle adding @@ -30,16 +28,16 @@ $(document).ready -> variant = _.find(window.variants, (variant) -> variant.id == variant_id ) - + variantLineItemTemplate = HandlebarsTemplates["variants/line_items_autocomplete_stock"]; $('#stock_details').html variantLineItemTemplate(variant: variant) $('#stock_details').show() $('button.add_variant').click addSubscriptionVariant - # Add some tips - $('.with-tip').powerTip - smartPlacement: true - fadeInTime: 50 - fadeOutTime: 50 - intentPollInterval: 300 + + # handle the tabs + $('.subscription.tabs li > a').click -> + targetTab = $(this).data('target') + $('.subscription.tab-container > div').hide() + $('div#' + targetTab).show() toggleSubscriptionItemEdit = -> @@ -66,9 +64,11 @@ adjustSubscriptionItem = (subscription_item_id, quantity) -> quantity: quantity token: Spree.api_key ).done (msg) -> - show_flash 'success', 'Successfully updated the quantity.' - $('.subscription-item-qty-show').text(quantity) - $('a.edit-subscription-item').trigger 'click' + show_flash 'success', 'Successfully updated the item quantity.' + findSubscriptionItemRow(subscription_item_id) + .find('.subscription-item-qty-show').text(quantity) + findSubscriptionItemRow(subscription_item_id) + .find('a.edit-subscription-item').trigger 'click' deleteSubscriptionItem = (subscription_item_id) -> url = Spree.pathFor('api/subscriptions/' + subscription_id + '/subscription_items/' + subscription_item_id) @@ -101,4 +101,7 @@ adjustSubscriptionItems = (subscription_id, variant_id, quantity) -> subscription_item: variant_id: variant_id quantity: quantity - token: Spree.api_key).done (msg) -> \ No newline at end of file + token: Spree.api_key).done (msg) -> + +findSubscriptionItemRow = (subscription_item_id) -> + $('tr#subscription-item-' + subscription_item_id) diff --git a/app/assets/javascripts/spree/frontend/spree_subscriptions.js.coffee b/app/assets/javascripts/spree/frontend/solidus_subscriptions.js.coffee similarity index 100% rename from app/assets/javascripts/spree/frontend/spree_subscriptions.js.coffee rename to app/assets/javascripts/spree/frontend/solidus_subscriptions.js.coffee diff --git a/app/assets/stylesheets/spree/backend/solidus_subscriptions.css b/app/assets/stylesheets/spree/backend/solidus_subscriptions.css new file mode 100644 index 0000000..4c86b5a --- /dev/null +++ b/app/assets/stylesheets/spree/backend/solidus_subscriptions.css @@ -0,0 +1,5 @@ +/* css for spree_subscription gem specific */ + +.tab-container > div:not(:first-child) { + display: none; +} diff --git a/app/assets/stylesheets/spree/backend/spree_subscriptions.css b/app/assets/stylesheets/spree/backend/spree_subscriptions.css deleted file mode 100644 index b55b2e4..0000000 --- a/app/assets/stylesheets/spree/backend/spree_subscriptions.css +++ /dev/null @@ -1 +0,0 @@ -/* css for spree_subscription gem specific */ \ No newline at end of file diff --git a/app/assets/stylesheets/spree/frontend/spree_subscriptions.css b/app/assets/stylesheets/spree/frontend/solidus_subscriptions.css similarity index 100% rename from app/assets/stylesheets/spree/frontend/spree_subscriptions.css rename to app/assets/stylesheets/spree/frontend/solidus_subscriptions.css diff --git a/app/controllers/spree/admin/subscriptions_controller.rb b/app/controllers/spree/admin/subscriptions_controller.rb index ba1cf21..473e589 100644 --- a/app/controllers/spree/admin/subscriptions_controller.rb +++ b/app/controllers/spree/admin/subscriptions_controller.rb @@ -14,8 +14,9 @@ def new # build subscription addresses user = order.user - @subscription.build_ship_address(order.ship_address.dup.attributes.merge({user_id: user.id})) - @subscription.build_bill_address(order.bill_address.dup.attributes.merge({user_id: user.id})) + non_existing_attributes = Spree::Address.attribute_names - Spree::SubscriptionAddress.dup.attribute_names + @subscription.build_ship_address(order.ship_address.dup.attributes.except(*non_existing_attributes).merge({user_id: user.id})) + @subscription.build_bill_address(order.bill_address.dup.attributes.except(*non_existing_attributes).merge({user_id: user.id})) # build items build_subscription_items(@subscription, order) @@ -38,15 +39,9 @@ def create end def renew - before_failure_count = @object.failure_count - ::GenerateSubscriptionOrder.new(@object).call + SubscriptionRenewalJob.perform_later @subscription.id + flash[:success] = flash_message_for(@object, :being_renewed) - # check if the failure count has increase, that means we have an error - if @object.failure_count > before_failure_count - flash[:error] = flash_message_for(@object, :error_renew) - else - flash[:success] = flash_message_for(@object, :successfully_renewed) - end respond_with(@object) do |format| format.html { redirect_to location_after_save } end @@ -96,7 +91,13 @@ def credit_card end def failures - @subscriptions = Spree::Subscription.active.where('failure_count > 0').order('created_at desc') + params[:q] = { + combinator: 'and', + state_in: ['active', 'renewing'], + failure_count_gt: 0, + s: 'last_renewal_at desc' + } + @subscriptions = collection end def adjust_sku @@ -142,11 +143,12 @@ def require_order_id def build_subscription_from_order(order) attrs = { user_id: order.user.id, + email: order.email, state: 'active', interval: order.subscription_interval, - credit_card_id: order.credit_card_id_if_available + credit_card_id: order.credit_card_id_if_available, } - order.build_subscription(attrs) + order.subscriptions.build(attrs) end def build_subscription_items(subscription, order) diff --git a/app/controllers/spree/api/subscriptions_controller.rb b/app/controllers/spree/api/subscriptions_controller.rb index b0b7b5c..a439a59 100644 --- a/app/controllers/spree/api/subscriptions_controller.rb +++ b/app/controllers/spree/api/subscriptions_controller.rb @@ -3,12 +3,10 @@ module Api class SubscriptionsController < Spree::Api::BaseController before_action :find_subscription, except: [:index] - def self.prepended(base) - base.prepend_after_action :deliver_cancellation_email, only: [:cancel] - base.prepend_after_action :deliver_pause_email, only: [:pause] - # need to touch user so the address list is updated - base.prepend_after_action :touch_user, only: [:update_address, :create_address, :select_address] - end + after_action :deliver_cancellation_email, only: [:cancel] + after_action :deliver_pause_email, only: [:pause] + # need to touch user so the address list is updated + after_action :touch_user, only: [:update_address, :create_address, :select_address] def index render json: current_api_user.subscriptions, @@ -32,6 +30,12 @@ def update end end + def renew + SubscriptionRenewalJob.perform_later @subscription.id + + render_subscription + end + def skip_next_order @subscription.skip_next_order @@ -100,7 +104,7 @@ def select_address # create a new credit card # then assign it to the subscription def create_credit_card - order = @subscription.last_order + order = @subscription.last_completed_order credit_card = nil begin ::Spree::CreditCard.transaction do @@ -165,7 +169,7 @@ def permitted_address_params end def permitted_subscription_attributes - [:interval, :credit_card_id, :email] + [:interval, :credit_card_id, :email, :next_renewal_at] end end end diff --git a/app/jobs/create_subscription_job.rb b/app/jobs/create_subscription_job.rb index 891786e..c3abbcd 100644 --- a/app/jobs/create_subscription_job.rb +++ b/app/jobs/create_subscription_job.rb @@ -36,8 +36,12 @@ def eligible_line_items(order) end def create_subscription_addresses(order, subscription, user) - subscription.create_ship_address!(order.ship_address.dup.attributes.merge({user_id: user.id})) - subscription.create_bill_address!(order.bill_address.dup.attributes.merge({user_id: user.id})) + non_existing_attributes = Spree::Address.attribute_names - Spree::SubscriptionAddress.dup.attribute_names + order_ship_address = order.ship_address.dup.attributes.except(*non_existing_attributes) + order_bill_address = order.bill_address.dup.attributes.except(*non_existing_attributes) + + subscription.create_ship_address!(order_ship_address.merge({user_id: user.id})) + subscription.create_bill_address!(order_bill_address.merge({user_id: user.id})) end def create_subscription_items(eligible_line_items, subscription, interval) diff --git a/app/jobs/subscription_renewal_job.rb b/app/jobs/subscription_renewal_job.rb new file mode 100644 index 0000000..18cd3c0 --- /dev/null +++ b/app/jobs/subscription_renewal_job.rb @@ -0,0 +1,19 @@ +class SubscriptionRenewalJob < ActiveJob::Base + queue_as :default + + def perform(subscription_id) + subscription = Spree::Subscription.find(subscription_id) + subscription.renew! + + before_failure_count = subscription.failure_count + ::GenerateSubscriptionOrder.new(subscription).call + + # check if the failure count has increase, that means we have an error + if subscription.failure_count > before_failure_count + failed_order = subscription.orders.reorder('created_at desc').first + log = SubscriptionLog.find_by_order_id(failed_order.id) + else + subscription.renewed! + end + end +end diff --git a/app/mailers/spree/subscription_mailer.rb b/app/mailers/spree/subscription_mailer.rb index a6cec99..e6e884b 100644 --- a/app/mailers/spree/subscription_mailer.rb +++ b/app/mailers/spree/subscription_mailer.rb @@ -28,7 +28,7 @@ def pause(subscription) def set_default_variables(subject, campaign) @subject = subject @campaign = campaign - @order = @subscription.last_order + @order = @subscription.last_completed_order @to_address = @order.email @from_addess = from_address end diff --git a/app/models/concerns/subscription_state_machine.rb b/app/models/concerns/subscription_state_machine.rb new file mode 100644 index 0000000..ed8aceb --- /dev/null +++ b/app/models/concerns/subscription_state_machine.rb @@ -0,0 +1,44 @@ +module SubscriptionStateMachine + extend ActiveSupport::Concern + included do + class << self + def active + with_states %w(active renewing) + end + end + + state_machine initial: :active do + event(:renew) { transition %i(active renewing) => :renewing } + event(:renewed) { transition renewing: :active } + + after_transition any => :renewing, do: :mark_last_renewal! + after_transition renewing: :active, do: :adjust_next_renewal! + + + event :cancel do + transition all => :cancelled + end + event(:pause) { transition active: :paused } + event(:resume) { transition paused: :active } + + after_transition on: :cancel do |subscription| + subscription.update_attributes(cancelled_at: Time.now) + end + + after_transition on: :pause do |subscription| + subscription.update_attributes(pause_at: Time.now, resume_at: nil) + end + + after_transition on: :resume do |subscription, transition| + resume_at = transition.args.first || Time.now + subscription.update_attributes(resume_at: resume_at) + # if resume is set at a future date, do not unpause + if resume_at.to_date > Date.today + subscription.update_attributes(state: 'paused') + else + subscription.update_attributes(pause_at: nil) + end + end + end + end +end diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 1708c1a..a48bf59 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -17,9 +17,8 @@ def line_item_interval_match(line_item, options) line_item.interval == options[:interval] end - def subscription_interval - subscription ? subscription.interval : 4 + subscription ? subscription.interval : 1 end def subscription_products diff --git a/app/models/spree/order_subscription.rb b/app/models/spree/order_subscription.rb new file mode 100644 index 0000000..e304b9e --- /dev/null +++ b/app/models/spree/order_subscription.rb @@ -0,0 +1,7 @@ +module Spree + class OrderSubscription < Spree::Base + self.table_name = :spree_orders_subscriptions + belongs_to :order + belongs_to :subscription + end +end diff --git a/app/models/spree/subscription.rb b/app/models/spree/subscription.rb index 3833886..1d77e70 100644 --- a/app/models/spree/subscription.rb +++ b/app/models/spree/subscription.rb @@ -1,7 +1,8 @@ module Spree class Subscription < ActiveRecord::Base + include SubscriptionStateMachine + has_many :subscription_items, dependent: :destroy, inverse_of: :subscription - has_and_belongs_to_many :orders, join_table: :spree_orders_subscriptions belongs_to :user belongs_to :credit_card alias_attribute :items, :subscription_items @@ -15,6 +16,9 @@ class Subscription < ActiveRecord::Base has_many :subscription_skips, dependent: :destroy, inverse_of: :subscription alias_attribute :skips, :subscription_skips + has_many :order_subscriptions + has_many :orders, through: :order_subscriptions + accepts_nested_attributes_for :ship_address accepts_nested_attributes_for :bill_address @@ -23,12 +27,18 @@ class Subscription < ActiveRecord::Base validates_presence_of :user after_save :reset_failure_count, if: :credit_card_id_changed? + after_create :mark_last_renewal! + after_touch :adjust_next_renewal! class << self def active where(state: 'active') end + def renewing + where(state: 'renewing') + end + def paused where(state: 'paused') end @@ -47,8 +57,7 @@ def good_standing def ready_for_next_order subscriptions = active.with_interval.good_standing.select do |subscription| - last_order = subscription.last_order - next unless last_order + next unless subscription.last_completed_order next if subscription.prepaid? subscription.next_shipment_date.to_date <= Date.today end @@ -66,50 +75,35 @@ def products end def last_shipment_date - last_order.completed_at if last_order + last_completed_order.completed_at if last_completed_order end def next_shipment_date - if skip_order_at - skip_order_at.advance(calc_next_renewal_date) - elsif last_order - last_order.completed_at.advance(calc_next_renewal_date) - end + next_renewal_at end def calc_next_renewal_date - { weeks: interval } + { months: interval } end def active? - self.state == 'active' - end - - def cancelled? - state == 'cancelled' + %w(active renewing).include? state end - def paused? - state == 'paused' - end - - def cancel - update_attribute(:state, 'cancelled') - update_attribute(:cancelled_at, Time.now) + def last_order + orders.reorder('created_at desc').first end - alias_method :cancel!, :cancel - - def last_order - orders.complete.reorder("completed_at desc").first + def last_completed_order + completed_orders.reorder('completed_at desc').first end def last_order_credit_card - last_order.payments.where('amount > 0').where(state: 'completed').last.source + last_completed_order.payments.where('amount > 0').where(state: 'completed').last.source end def last_order_date - orders.first.complete? ? orders.first.completed_at : orders.first.created_at + last_completed_order ? last_completed_order.completed_at : last_order.created_at end def next_order @@ -124,20 +118,23 @@ def created_at next_order end + def last_order_currency + orders.complete.last.currency + end + def create_next_order! # just keeping safe non_existing_attributes = Spree::SubscriptionAddress.dup.attribute_names - Spree::Address.attribute_names - # use subscription's addresses for the new order and email created_order = orders.create!( - user: last_order.user, + user: last_completed_order.user, repeat_order: true, bill_address: Spree::Address.new(bill_address.dup.attributes.except(*non_existing_attributes)), ship_address: Spree::Address.new(ship_address.dup.attributes.except(*non_existing_attributes)), channel: 'subscription', - store: Spree::Store.current + store: last_completed_order.store, + currency: last_completed_order.currency ) - created_order.update_column(:email, email) if email created_order end @@ -194,22 +191,13 @@ def clear_skip_order alias_attribute :can_skip?, :can_skip - def pause - update_attributes(pause_at: Time.now, resume_at: nil, state: 'paused') - end - - def resume(resume_at_date = Time.now) - update_attributes(resume_at: resume_at_date) - update_attributes(pause_at: nil, state: 'active') if (resume_at_date.to_date <= Date.today) - end - def completed_orders orders.complete end # fetch the last completed order shipment def shipment - last_order.shipments.last + last_completed_order.shipments.last end # fetch the last completed order shipping method @@ -244,5 +232,19 @@ def as_json(options = { }) })) end + private + + def mark_last_renewal! + touch(:last_renewal_at) + end + + def adjust_next_renewal! + return if renewing? + + last_renewal_at = Date.today if last_renewal_at.nil? + + update_column(:next_renewal_at, + last_renewal_at.advance(calc_next_renewal_date)) + end end end diff --git a/app/models/spree/subscription_item.rb b/app/models/spree/subscription_item.rb index 11c133a..58aa042 100644 --- a/app/models/spree/subscription_item.rb +++ b/app/models/spree/subscription_item.rb @@ -5,7 +5,7 @@ class SubscriptionItem < ActiveRecord::Base belongs_to :tax_category, class_name: "Spree::TaxCategory" has_one :product, through: :variant - + before_validation :copy_price before_validation :copy_tax_category @@ -21,7 +21,6 @@ def copy_price if variant self.price = variant.price if price.nil? self.cost_price = variant.cost_price if cost_price.nil? - self.currency = variant.currency if currency.nil? end end diff --git a/app/overrides/admin_orders_edit.rb b/app/overrides/admin_orders_edit.rb index 42fd3e0..76385f0 100644 --- a/app/overrides/admin_orders_edit.rb +++ b/app/overrides/admin_orders_edit.rb @@ -3,10 +3,10 @@ :insert_bottom => ".additional-info", :original => '3a09af526d991bcbb51fcee781d28f7d7cbc981e', :text => ' -
<%= Spree.t(:subscription) %>:
+
<%= Spree.t(:subscriptions) %>:
<% if @order.subscription %>
<%= link_to(@order.subscription.id, edit_admin_subscription_path(@order.subscription), target: "_blank") %>
<% else %>
<%= link_to Spree.t(:create), new_admin_subscription_path(order_id: @order.id) %>
<% end %> -') \ No newline at end of file +') diff --git a/app/overrides/admin_orders_index.rb b/app/overrides/admin_orders_index.rb index 2ab2084..a9e4cbf 100644 --- a/app/overrides/admin_orders_index.rb +++ b/app/overrides/admin_orders_index.rb @@ -1,7 +1,7 @@ Deface::Override.new(:virtual_path => 'spree/admin/orders/index', :name => 'subscriptions_to_header', :insert_before => "[data-hook='admin_orders_index_header_actions']", - :text => "<%= sort_link @search, :interval, t('frequency') %>") + :text => "<%= sort_link @search, :interval, Spree.t('frequency') %>") Deface::Override.new(:virtual_path => 'spree/admin/orders/index', :name => 'subscription_to_table', diff --git a/app/overrides/admin_subscriptions.rb b/app/overrides/admin_subscriptions.rb deleted file mode 100644 index 2dac612..0000000 --- a/app/overrides/admin_subscriptions.rb +++ /dev/null @@ -1,4 +0,0 @@ -Deface::Override.new(:virtual_path => "spree/admin/shared/_menu", - :name => "subscriptions_admin_tab", - :insert_bottom => "[data-hook='admin_tabs']", - :text => "<%= tab(:subscriptions, icon: 'refresh') %>") diff --git a/app/serializers/subscription_item_serializer.rb b/app/serializers/subscription_item_serializer.rb index a8354d7..6dc413d 100644 --- a/app/serializers/subscription_item_serializer.rb +++ b/app/serializers/subscription_item_serializer.rb @@ -12,8 +12,7 @@ def variant { id: variant.id, name: variant.product.name, - options_text: variant.options_text, - portrait_small_url: variant.images.first.attachment.url(:portrait_small) + options_text: variant.options_text } end end diff --git a/app/services/adjust_sku_service.rb b/app/services/adjust_sku_service.rb index 0dcd6fe..bdd67e4 100644 --- a/app/services/adjust_sku_service.rb +++ b/app/services/adjust_sku_service.rb @@ -1,14 +1,14 @@ class AdjustSkuService def update_subscriptions(old_sku, new_sku) - variant1 = Spree::Variant.find_by(sku: old_sku) - variant2 = Spree::Variant.find_by(sku: new_sku) + old_variant = Spree::Variant.find_by(sku: old_sku) + new_variant = Spree::Variant.find_by(sku: new_sku) subscriptions = [] - items = subscription_items(variant1) - for item in items - create_subscription_item(item, variant2) + items = subscription_items(old_variant) + items.each do |item| + next if item.subscription.cancelled_at + item.update_column(:variant_id, new_variant.id) subscriptions << item.subscription - items.delete(item) end subscriptions end @@ -16,13 +16,4 @@ def update_subscriptions(old_sku, new_sku) def subscription_items(variant) subscription_items = Spree::SubscriptionItem.where(variant_id: variant.id) end - - def create_subscription_item(old_subscription_item, variant) - subscription = old_subscription_item.subscription - subscription.subscription_items.create!( - variant_id: variant.id, quantity: old_subscription_item.quantity, price: old_subscription_item.price, cost_price: old_subscription_item.cost_price, \ - tax_category_id: old_subscription_item.tax_category_id, adjustment_total: old_subscription_item.adjustment_total, additional_tax_total: old_subscription_item.additional_tax_total, \ - promo_total: old_subscription_item.promo_total, included_tax_total: old_subscription_item.included_tax_total, pre_tax_amount: old_subscription_item.pre_tax_amount, interval: old_subscription_item.interval) - end - end diff --git a/app/services/generate_subscription_order.rb b/app/services/generate_subscription_order.rb index d24d57e..b03043d 100644 --- a/app/services/generate_subscription_order.rb +++ b/app/services/generate_subscription_order.rb @@ -19,7 +19,7 @@ def call end def create_next_order_with_payment - previous_order = subscription.last_order + previous_order = subscription.last_completed_order # create a new order and populate the next order with the same line items subscription.subscription_items.each do |line_item| diff --git a/app/views/spree/admin/shared/_subscriptions_sub_menu.html.erb b/app/views/spree/admin/shared/_subscriptions_sub_menu.html.erb new file mode 100644 index 0000000..2adfa52 --- /dev/null +++ b/app/views/spree/admin/shared/_subscriptions_sub_menu.html.erb @@ -0,0 +1,4 @@ + diff --git a/app/views/spree/admin/subscriptions/_add_subscription_item.html.erb b/app/views/spree/admin/subscriptions/_add_subscription_item.html.erb index 5c79298..f2730cc 100644 --- a/app/views/spree/admin/subscriptions/_add_subscription_item.html.erb +++ b/app/views/spree/admin/subscriptions/_add_subscription_item.html.erb @@ -6,6 +6,7 @@ <%= label_tag :add_subscription_item_variant_id, Spree.t(:name_or_sku) %> <%= hidden_field_tag :add_subscription_item_variant_id, "", :class => "variant_autocomplete fullwidth" %> + diff --git a/app/views/spree/admin/subscriptions/_addresses.erb b/app/views/spree/admin/subscriptions/_addresses.erb index 3b6dae9..9e566bf 100644 --- a/app/views/spree/admin/subscriptions/_addresses.erb +++ b/app/views/spree/admin/subscriptions/_addresses.erb @@ -23,11 +23,11 @@ <% unless @subscription.new_record? %> -
+
<%= button Spree.t('actions.update'), 'refresh' %> <%= Spree.t(:or) %> <%= button_link_to Spree.t('actions.back'), collection_url, :icon => 'arrow-left' %>
-
+ <% end %> diff --git a/app/views/spree/admin/subscriptions/_cancellation.erb b/app/views/spree/admin/subscriptions/_cancellation.erb new file mode 100644 index 0000000..f0f1543 --- /dev/null +++ b/app/views/spree/admin/subscriptions/_cancellation.erb @@ -0,0 +1,26 @@ +<% if @subscription.cancelled? %> +
+
+ + Reasons + + +
+
+ + Feedback + +

+ <%= @subscription.cancel_feedback %> +

+
+
+<% else %> +
+ <%= Spree.t(:empty) %> +
+<% end %> diff --git a/app/views/spree/admin/subscriptions/_items.erb b/app/views/spree/admin/subscriptions/_items.erb index 73a71f6..db7bb67 100644 --- a/app/views/spree/admin/subscriptions/_items.erb +++ b/app/views/spree/admin/subscriptions/_items.erb @@ -29,6 +29,9 @@ <%= mini_image(item.variant) %> <%= item.variant.product.name %>
<%= "(" + variant_options(item.variant) + ")" unless item.variant.option_values.empty? %> + <% if item.variant.sku.present? %> + <%= Spree::Variant.human_attribute_name(:sku) %>: <%= item.variant.sku %> + <% end %> <%= number_to_currency(item.variant.price) %> diff --git a/app/views/spree/admin/subscriptions/_orders.erb b/app/views/spree/admin/subscriptions/_orders.erb index 26da95a..be49dc6 100644 --- a/app/views/spree/admin/subscriptions/_orders.erb +++ b/app/views/spree/admin/subscriptions/_orders.erb @@ -1,7 +1,4 @@ <% if @subscription.orders.exists? %> -
- <%= Spree.t("admin.subscription.orders") %> -
@@ -32,7 +29,7 @@ - <% @subscription.orders.each do |order| %> + <% @subscription.orders.order('updated_at desc').each do |order| %> @@ -56,7 +53,7 @@ <% if @subscription.subscription_log_for(order) %> - + <% end %> <% end %> diff --git a/app/views/spree/admin/subscriptions/_sidebar.html.erb b/app/views/spree/admin/subscriptions/_sidebar.html.erb index 8f97c73..c7c95bb 100644 --- a/app/views/spree/admin/subscriptions/_sidebar.html.erb +++ b/app/views/spree/admin/subscriptions/_sidebar.html.erb @@ -50,8 +50,10 @@ <% else %>
<%= link_to Spree.t(:create), credit_card_admin_subscription_path(@subscription) %>
<% end %> +
Currency
+
Last updated
<%= l(@subscription.updated_at, format: :long) %>
-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/spree/admin/subscriptions/_skips.erb b/app/views/spree/admin/subscriptions/_skips.erb index 8e46cb0..b047154 100644 --- a/app/views/spree/admin/subscriptions/_skips.erb +++ b/app/views/spree/admin/subscriptions/_skips.erb @@ -22,4 +22,8 @@ <% end %>
<%= l (order.created_at).to_date %>
<%= @subscription.subscription_log_for(order).reason %><%= @subscription.subscription_log_for(order).reason %>
-<% end %> \ No newline at end of file +<% else %> +
+ <%= Spree.t(:empty) %> +
+<% end %> diff --git a/app/views/spree/admin/subscriptions/adjust_sku.html.erb b/app/views/spree/admin/subscriptions/adjust_sku.html.erb index fa2e055..35b3521 100644 --- a/app/views/spree/admin/subscriptions/adjust_sku.html.erb +++ b/app/views/spree/admin/subscriptions/adjust_sku.html.erb @@ -27,8 +27,8 @@ <%= subscription.id %> <%= subscription.email %> - - <%= link_to subscription.orders.first.number, edit_admin_order_path(subscription.last_order), target: '_blank' %> + + <%= link_to subscription.last_completed_order.number, edit_admin_order_path(subscription.last_completed_order), target: '_blank' %>
<%= l(subscription.last_order_date, format: :long) %> diff --git a/app/views/spree/admin/subscriptions/edit.html.erb b/app/views/spree/admin/subscriptions/edit.html.erb index acb9089..425891e 100644 --- a/app/views/spree/admin/subscriptions/edit.html.erb +++ b/app/views/spree/admin/subscriptions/edit.html.erb @@ -2,18 +2,48 @@ <%= Spree.t(:editing_subscription) %> <% end %> -<%= render partial: 'spree/admin/subscriptions/sidebar' %> +<% content_for :tabs do %> +
+ +
+<% end %> -<%= form_for @subscription, :url => admin_subscription_path(@subscription) do |f| %> +
+ <%= form_for @subscription, :url => admin_subscription_path(@subscription) do |f| %> - <%= render :partial => 'form', :locals => { :f => f } %> +
+
+ <%= render :partial => 'form', :locals => { :f => f } %> - <%= render partial: 'spree/admin/subscriptions/addresses', :locals => { :f => f } %> + <%= render partial: 'spree/admin/subscriptions/addresses', :locals => { :f => f } %> +
- <%= render partial: 'spree/admin/subscriptions/items' %> +
+ <%= render partial: 'spree/admin/subscriptions/items' %> +
- <%= render partial: 'spree/admin/subscriptions/orders' %> +
+ <%= render partial: 'spree/admin/subscriptions/orders' %> +
- <%= render partial: 'spree/admin/subscriptions/skips' %> +
+ <%= render partial: 'spree/admin/subscriptions/skips' %> +
-<% end %> +
+ <%= render partial: 'spree/admin/subscriptions/cancellation' %> +
+
+ + <% end %> +
+ +<%= render partial: 'spree/admin/subscriptions/sidebar' %> diff --git a/app/views/spree/admin/subscriptions/failures.html.erb b/app/views/spree/admin/subscriptions/failures.html.erb index 52a49f8..a10c4a2 100644 --- a/app/views/spree/admin/subscriptions/failures.html.erb +++ b/app/views/spree/admin/subscriptions/failures.html.erb @@ -1,28 +1,52 @@ -

Showing subscriptions that failed renewing

- Total: <%= @subscriptions.count %> -
-
+<% content_for :page_title do %> + <%= Spree.t(:'admin.subscription.failures') %> +<% end %> + +<% unless @subscriptions.empty? %> + - - - - - - + + + + + + <% @subscriptions.each do |subscription| %> - + + + + + <% end %> -
iddateorderemailrenewal dateattemptsIDStatusLast OrderOrderRenew DateAttempts
<%= link_to subscription.id, edit_admin_subscription_path(subscription), target: '_blank' %> - <%= l(subscription.last_order_date, format: :subscription_date_format) %> - <%= link_to subscription.orders.first.number, edit_admin_order_path(subscription.orders.first), target: '_blank' %> - <%=link_to subscription.user.email, edit_admin_user_path(subscription.user), target: '_blank' %> - <%= l(subscription.next_shipment_date, format: :subscription_date_format) %> - <%= subscription.failure_count %> + <%= link_to subscription.id, edit_admin_subscription_path(subscription), target: '_blank' %> + <%= subscription.state %> + + <%= l(subscription.last_order_date, format: :subscription_date_format) %> + + <% if subscription.last_order %> + <%= link_to subscription.last_order.number, edit_admin_order_path(subscription.last_order), target: '_blank' %> + <% end %> +     + <%=link_to subscription.user.email, edit_admin_user_path(subscription.user), target: '_blank' %> +
+ <% if subscription.active? %> + <%= subscription.subscription_log_for(subscription.last_order)&.reason %> + <% end %> +
<%= l(subscription.next_shipment_date, format: :subscription_date_format) if subscription.can_renew? %> <%= subscription.failure_count %>
\ No newline at end of file + +<% else %> +
+ <%= Spree.t(:'admin.subscription.no_subscriptions_found') %> +
+<% end %> +<%= paginate @subscriptions %> diff --git a/app/views/spree/admin/subscriptions/index.html.erb b/app/views/spree/admin/subscriptions/index.html.erb index ab88500..faebc59 100644 --- a/app/views/spree/admin/subscriptions/index.html.erb +++ b/app/views/spree/admin/subscriptions/index.html.erb @@ -1,10 +1,5 @@ <% content_for :page_title do %> - <%= t(:listing_subscriptions) %> -<% end %> - -<% content_for :page_actions do %> -
  • <%= button_link_to('Failed Renewals', failures_admin_subscriptions_path) %>
  • -
  • <%= button_link_to('Adjust SKUs', adjust_sku_admin_subscriptions_path) %>
  • + <%= Spree.t(:subscriptions) %> <% end %> <% content_for :table_filter_title do %> @@ -57,6 +52,13 @@ +
    +
    + <%= label_tag :q_state_eq, Spree.t(:status) %> + <%= f.select :state_eq, Spree::Subscription.state_machines[:state].states.collect {|s| [Spree.t("subscription_state.#{s.name}"), s.value]}, {:include_blank => true}, :class => 'select2' %> +
    +
    +
    @@ -80,18 +82,20 @@ - + + ID - State + Status Frequency Last Order User Skip Renew Date + Cur @@ -108,19 +112,22 @@ <% end %> - - <%= link_to subscription.orders.first.number, edit_admin_order_path(subscription.last_order), target: '_blank' %> + <% if subscription.orders.any? %> + + <%= link_to subscription.last_order.number, edit_admin_order_path(subscription.last_order), target: '_blank' %>
    <%= l(subscription.last_order_date, format: :long) %>
    <%= subscription.shipping_method.name if subscription.shipment && subscription.shipment.shipping_method %> + <% end %> <%= link_to(subscription.user.email, edit_admin_user_path(subscription.user), target: '_blank') if subscription.user %> <%= subscription.skip_order_at ? 'Yes' : '' if subscription.can_renew? %> <%= l(subscription.next_shipment_date, format: :subscription_date_format) if subscription.can_renew? %> + <%= link_to_with_icon 'edit', 'Edit', edit_admin_subscription_path(subscription), no_text: true @@ -138,7 +145,7 @@ <% else %>
    - <%= t(:no_subscriptions_found)%> + <%= Spree.t(:'admin.subscription.no_subscriptions_found') %>
    <% end %> diff --git a/app/views/spree/admin/subscriptions/new.html.erb b/app/views/spree/admin/subscriptions/new.html.erb index fa71614..7a489f4 100644 --- a/app/views/spree/admin/subscriptions/new.html.erb +++ b/app/views/spree/admin/subscriptions/new.html.erb @@ -1,24 +1,40 @@ <% content_for :page_title do %> - <%= t(:new_subscription) %> + <%= Spree.t('admin.subscription.new') %> <% end %> +<% content_for :tabs do %> +
    + +
    +<% end %> + +
    <%= form_for @subscription, :url => admin_subscriptions_path(@subscription) do |f| %> <%= f.hidden_field :order_ids, value: params[:order_id] %> <%= f.hidden_field :state %> <%= f.hidden_field :user_id %> - <%= f.hidden_field :duration %> + <%#= f.hidden_field :duration %> <%= f.hidden_field :prepaid_amount %> <%= f.hidden_field :credit_card_id %> -
    - <%= Spree.t("admin.subscription") %> +
    + +
    <%= render :partial => 'form', :locals => { :f => f } %> -
    + <%= render partial: 'spree/admin/subscriptions/addresses', :locals => { :f => f } %> +
    - <%= render partial: 'spree/admin/subscriptions/addresses', :locals => { :f => f } %> +
    + <%= render partial: 'spree/admin/subscriptions/items' %> +
    - <%= render partial: 'spree/admin/subscriptions/items' %> +
    @@ -27,4 +43,6 @@
    -<% end %> \ No newline at end of file +<% end %> + + diff --git a/app/views/spree/users/_subscriptions.html.erb b/app/views/spree/users/_subscriptions.html.erb index c0c827d..b9a64ac 100644 --- a/app/views/spree/users/_subscriptions.html.erb +++ b/app/views/spree/users/_subscriptions.html.erb @@ -1,6 +1,6 @@
    -

    <%= t(:my_subscriptions) %>

    +

    <%= Spree.t(:my_subscriptions) %>

    <% if subscriptions.present? %> @@ -21,29 +21,29 @@ - + diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb index bbe5bd7..2749ba3 100644 --- a/config/initializers/spree.rb +++ b/config/initializers/spree.rb @@ -13,3 +13,12 @@ Spree.user_class = "Spree::User" Spree::PermittedAttributes.line_item_attributes << :interval + +Spree::Backend::Config.configure do |config| + config.menu_items << config.class::MenuItem.new( + [:subscriptions], + 'clock-o', + partial: 'spree/admin/shared/subscriptions_sub_menu', + url: :admin_subscriptions_path + ) +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 213e441..bd719cc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2,11 +2,6 @@ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: - subscribable: 'subscribable' - subscription: "subscription" - subscriptions: "subscriptions" - my_subscriptions: "My subscriptions" - listing_subscriptions: Subscriptions products: "Products" last_shipped_on: "Last Shipment" next_shipment: "Next Shipment" @@ -19,13 +14,15 @@ en: id: "ID" successfully_cancelled: successfully cancelled subscription successfully_renewed: successfully renewed subscription + being_renewed: subscription being renewed error_renew: failed to renew subscription order_number: Order Number requires_order_id: Requires order id subscription_created: subscription created successfully_skipped: subscription next order has been skipped successfully_undo_skip: subscription next order skip has been undone - subscription: Subscription + subscriptions: Subscriptions + my_subscriptions: "My subscriptions" frequency: Frequency editing_subscription: Editing Subscription addl_information: Addl Information @@ -40,12 +37,28 @@ en: back: Back admin: tab: - subscriptions: "Subscriptions" + subscriptions: Subscriptions + failures: Failed Renewals + adjust_sku: Adjust SKUs subscription: + new: New Subscription subscription: Subscription current_items: Current Items orders: Order skips_history: Skips History + cancellation: Cancellation + failures: Failed Renewals + renewing: Renewing + no_subscriptions_found: No Subscriptions Found + + subscription: + cancellation: Cancellation + cancel_reasons: + replenish_often: My products were replenished too often + replenish_seldom: My product were not replensihed often enough + no_longer_want: I no longer want to use the products + want_single_purchase: I want to make single purchases + not_say: I’d prefer not to say activerecord: attributes: diff --git a/config/routes.rb b/config/routes.rb index b1d03d5..3737829 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,7 @@ namespace :api, defaults: { format: 'json' } do resources :subscriptions, except: [:create, :new, :destroy] do member do + put :renew put :skip_next_order put :undo_skip_next_order put :pause diff --git a/db/migrate/20150922153455_add_email_to_subscriptions.rb b/db/migrate/20150922153455_add_email_to_subscriptions.rb index 8836107..2acac62 100644 --- a/db/migrate/20150922153455_add_email_to_subscriptions.rb +++ b/db/migrate/20150922153455_add_email_to_subscriptions.rb @@ -3,7 +3,7 @@ def up add_column :spree_subscriptions, :email, :string, default: nil Spree::Subscription.active.each do |subscription| - subscription.update_column(:email, subscription.last_order.email) + subscription.update_column(:email, subscription.last_completed_order.email) end end diff --git a/db/migrate/20160707074723_add_cancel_reason_and_feedback_to_subscriptions.rb b/db/migrate/20160707074723_add_cancel_reason_and_feedback_to_subscriptions.rb new file mode 100644 index 0000000..66ab6cf --- /dev/null +++ b/db/migrate/20160707074723_add_cancel_reason_and_feedback_to_subscriptions.rb @@ -0,0 +1,6 @@ +class AddCancelReasonAndFeedbackToSubscriptions < ActiveRecord::Migration + def change + add_column :spree_subscriptions, :cancel_reasons, :text, array: true, default: [] + add_column :spree_subscriptions, :cancel_feedback, :text + end +end diff --git a/db/migrate/20160711000407_add_renewal_dates_to_subscriptions.rb b/db/migrate/20160711000407_add_renewal_dates_to_subscriptions.rb new file mode 100644 index 0000000..6ad5a48 --- /dev/null +++ b/db/migrate/20160711000407_add_renewal_dates_to_subscriptions.rb @@ -0,0 +1,12 @@ +class AddRenewalDatesToSubscriptions < ActiveRecord::Migration + def change + add_column :spree_subscriptions, :next_renewal_at, :datetime + add_column :spree_subscriptions, :last_renewal_at, :datetime + + Spree::Subscription.active.each do |subscription| + subscription.update_column(:last_renewal_at, + subscription.last_completed_order.completed_at) + subscription.touch + end + end +end diff --git a/db/migrate/20171011152133_add_address3_to_subscription_addresses.rb b/db/migrate/20171011152133_add_address3_to_subscription_addresses.rb new file mode 100644 index 0000000..8ceda45 --- /dev/null +++ b/db/migrate/20171011152133_add_address3_to_subscription_addresses.rb @@ -0,0 +1,5 @@ +class AddAddress3ToSubscriptionAddresses < ActiveRecord::Migration + def change + add_column :spree_subscription_addresses, :address3, :string, limit: 255 + end +end diff --git a/solidus_subscriptions.gemspec b/solidus_subscriptions.gemspec index 5110f55..85f45e3 100644 --- a/solidus_subscriptions.gemspec +++ b/solidus_subscriptions.gemspec @@ -2,13 +2,13 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'solidus_subscriptions' - s.version = '1.3.3.beta' + s.version = '2' s.summary = 'A Solidus extension to manage subscribable products.' s.description = """ This Solidus extension enables an e-commerce owner manage products that can be subscribed to, via recurring payments and shipments at set intervals. """ - s.required_ruby_version = '>= 1.8.7' + s.required_ruby_version = '>= 2.3.0' s.author = 'Bryan Mahoney' s.email = 'bryan@godynamo.com' @@ -17,7 +17,7 @@ Gem::Specification.new do |s| s.require_path = 'lib' s.requirements << 'none' - s.add_dependency "solidus", [">= 1.0.6", "< 2"] + s.add_dependency "solidus", [">= 1.4", "< 2"] s.add_development_dependency 'capybara', '~> 2.4' s.add_development_dependency 'coffee-rails' diff --git a/spec/cassettes/Editing_a_subscription_payment_info/can_update_payment_info.yml b/spec/cassettes/Editing_a_subscription_payment_info/can_update_payment_info.yml new file mode 100644 index 0000000..281c5ee --- /dev/null +++ b/spec/cassettes/Editing_a_subscription_payment_info/can_update_payment_info.yml @@ -0,0 +1,171 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.stripe.com/v1/customers + body: + encoding: UTF-8 + string: email=desirae.borer%40green.com + headers: + User-Agent: + - Stripe/v1 RubyBindings/2.1.0 + Authorization: + - Bearer sk_test_jTiNI1BxjFxBr4TUqdHefc1f + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"2.1.0","lang":"ruby","lang_version":"2.3.1 p112 (2016-04-26)","platform":"x86_64-darwin16","engine":"ruby","publisher":"stripe","uname":"Darwin + Hugos-MacBook-Pro.local 16.5.0 Darwin Kernel Version 16.5.0: Fri Mar 3 16:52:33 + PST 2017; root:xnu-3789.51.2~3/RELEASE_X86_64 x86_64","hostname":"Hugos-MacBook-Pro.local"}' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Tue, 11 Apr 2017 15:56:32 GMT + Content-Type: + - application/json + Content-Length: + - '641' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_ASM4kFe9NDHHeK + Stripe-Version: + - '2015-02-18' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "cus_ASM44Uyh4b6zok", + "object": "customer", + "account_balance": 0, + "created": 1491926192, + "currency": null, + "default_source": null, + "delinquent": false, + "description": null, + "discount": null, + "email": "desirae.borer@green.com", + "livemode": false, + "metadata": {}, + "shipping": null, + "sources": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_ASM44Uyh4b6zok/sources" + }, + "subscriptions": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_ASM44Uyh4b6zok/subscriptions" + } + } + http_version: + recorded_at: Tue, 11 Apr 2017 15:56:17 GMT +- request: + method: post + uri: https://api.stripe.com/v1/customers/cus_ASM44Uyh4b6zok/sources + body: + encoding: UTF-8 + string: source[object]=card&source[number]=4242424242424242&source[cvc]=123&source[exp_month]=6&source[exp_year]=2020 + headers: + User-Agent: + - Stripe/v1 RubyBindings/2.1.0 + Authorization: + - Bearer sk_test_jTiNI1BxjFxBr4TUqdHefc1f + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-User-Agent: + - '{"bindings_version":"2.1.0","lang":"ruby","lang_version":"2.3.1 p112 (2016-04-26)","platform":"x86_64-darwin16","engine":"ruby","publisher":"stripe","uname":"Darwin + Hugos-MacBook-Pro.local 16.5.0 Darwin Kernel Version 16.5.0: Fri Mar 3 16:52:33 + PST 2017; root:xnu-3789.51.2~3/RELEASE_X86_64 x86_64","hostname":"Hugos-MacBook-Pro.local"}' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Tue, 11 Apr 2017 15:56:33 GMT + Content-Type: + - application/json + Content-Length: + - '577' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Request-Id: + - req_ASM4q7db4QCCvh + Stripe-Version: + - '2015-02-18' + Strict-Transport-Security: + - max-age=31556926; includeSubDomains + body: + encoding: UTF-8 + string: | + { + "id": "card_1A7RM5LrlCqAeZVWm3KKmS6J", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": "cus_ASM44Uyh4b6zok", + "cvc_check": "pass", + "dynamic_last4": null, + "exp_month": 6, + "exp_year": 2020, + "fingerprint": "Nlkrx3hFLJB0Ad6I", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": null, + "tokenization_method": null + } + http_version: + recorded_at: Tue, 11 Apr 2017 15:56:17 GMT +recorded_with: VCR 3.0.3 diff --git a/spec/controllers/spree/api/subscriptions_controller_spec.rb b/spec/controllers/spree/api/subscriptions_controller_spec.rb index 3e19cd3..7311391 100644 --- a/spec/controllers/spree/api/subscriptions_controller_spec.rb +++ b/spec/controllers/spree/api/subscriptions_controller_spec.rb @@ -31,18 +31,18 @@ module Spree context "skipping" do it "should skip next order" do - expect(@subscription.next_shipment_date.to_date).to eql 4.weeks.from_now.to_date + expect(next_shipment_date).to eql original_shipment_date api_get :skip_next_order, id: @subscription.id - expect(@subscription.next_shipment_date.to_date).to eql 8.weeks.from_now.to_date + expect(next_shipment_date).to eql next_calc_shipment_date end it "should be able to undo a skip next order" do @subscription.skip_next_order - expect(@subscription.next_shipment_date.to_date).to eql 8.weeks.from_now.to_date + expect(next_shipment_date).to eql next_calc_shipment_date api_get :undo_skip_next_order, id: @subscription.id - expect(@subscription.next_shipment_date.to_date).to eql 4.weeks.from_now.to_date + expect(next_shipment_date).to eql original_shipment_date end end diff --git a/spec/factories/subscription_factory.rb b/spec/factories/subscription_factory.rb index 1978889..6ffee48 100644 --- a/spec/factories/subscription_factory.rb +++ b/spec/factories/subscription_factory.rb @@ -1,8 +1,9 @@ FactoryGirl.define do factory :subscription, :class => Spree::Subscription do - state nil + state 'active' interval 2 - # prepaid false + next_renewal_at 1.month.from_now + # prepaid false ship_address { FactoryGirl.create(:subscription_address) @@ -11,6 +12,10 @@ FactoryGirl.create(:subscription_address) } + orders { + [FactoryGirl.create(:completed_order_with_totals)] + } + association(:user) end end diff --git a/spec/features/admin/adjust_sku_spec.rb b/spec/features/admin/adjust_sku_spec.rb index 4a6b7f6..f1e786b 100644 --- a/spec/features/admin/adjust_sku_spec.rb +++ b/spec/features/admin/adjust_sku_spec.rb @@ -16,7 +16,7 @@ expect(@subscription.subscription_items.first.variant.sku).to eq("GPM100-2") expect(page).to have_content(@subscription.id) expect(page).to have_content(@subscription.email) - expect(page).to have_content(@subscription.last_order.number) + expect(page).to have_content(@subscription.last_completed_order.number) end end diff --git a/spec/features/admin/failures_spec.rb b/spec/features/admin/failures_spec.rb new file mode 100644 index 0000000..f0ee909 --- /dev/null +++ b/spec/features/admin/failures_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe "Subscription Failures", type: :feature do + stub_authorization! + + let!(:failed_subscription) { FactoryGirl.create(:subscription, state: 'renewing', failure_count: 1) } + let!(:cancelled_subscription) { FactoryGirl.create(:subscription, state: 'cancelled', failure_count: 1) } + let!(:active_subscription) { FactoryGirl.create(:subscription) } + + before do + visit spree.failures_admin_subscriptions_path + end + + context "admin subscription renewal failures", js: true do + it "shows the only subscription that failed renewing" do + expect(page).not_to have_content('active') + expect(page).not_to have_content('cancelled') + + expect(page).to have_content('renewing') + end + end +end diff --git a/spec/features/admin/subscription_index_spec.rb b/spec/features/admin/subscription_index_spec.rb index 009b53d..11f45e3 100644 --- a/spec/features/admin/subscription_index_spec.rb +++ b/spec/features/admin/subscription_index_spec.rb @@ -5,17 +5,28 @@ include_context "setup subscriptions for adjusting skus" stub_authorization! + before do + visit spree.admin_subscriptions_path + end + context "admin subscriptions index page", js: true do it "users can filter subscriptions by the SKU of their items" do - visit spree.admin_path - visit spree.admin_subscriptions_path - fill_in("q[subscription_items_variant_sku_eq]", with: "GPM100") + click_button('Filter Results') + expect(page).to have_content(@subscription.id) expect(page).to have_content(@subscription.email) expect(page).to have_content(@subscription.orders.first.number) end - end + it "users can filter by state" do + @subscription.update_column(:state, 'renewing') + + select("Renewing", from: "q[state_eq]") + click_button('Filter Results') + + expect(page).to have_content('renewing') + end + end end diff --git a/spec/jobs/subscription_renewal_job_spec.rb b/spec/jobs/subscription_renewal_job_spec.rb new file mode 100644 index 0000000..9f445a4 --- /dev/null +++ b/spec/jobs/subscription_renewal_job_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe SubscriptionRenewalJob do + include SubscriptionMacros + + let(:user) { create(:user) } + + before(:each) do + setup_subscriptions_for user + end + + it "renews a subscription with a new order" do + subscription = user.subscriptions.first + + expect(user.orders.count).to eq(1) + + SubscriptionRenewalJob.new.perform(subscription.id) + + expect(user.orders.count).to eq(2) + end +end diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index 943a8d3..f2e8d1c 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -79,7 +79,7 @@ it "returns 4 weeks if it doesn't have previous subscriptions" do order = create(:order, subscriptions: []) - expect(order.subscription_interval).to eq(4) + expect(order.subscription_interval).to eq(1) end end diff --git a/spec/models/spree/subscription_spec.rb b/spec/models/spree/subscription_spec.rb index cb51852..90b5902 100644 --- a/spec/models/spree/subscription_spec.rb +++ b/spec/models/spree/subscription_spec.rb @@ -3,7 +3,7 @@ describe Spree::Subscription do include OrderMacros - it { should have_and_belong_to_many(:orders) } + it { should have_many(:orders) } it { should belong_to(:user) } it { should belong_to(:credit_card)} it { should respond_to(:resume_on)} @@ -36,7 +36,7 @@ end it "should be able to calculate the date of the next shipment" do - @order.subscriptions.last.next_shipment_date.to_i.should == 4.weeks.from_now.to_i + next_shipment_date.should == original_shipment_date end after do @@ -53,12 +53,12 @@ end it "should calculate the correct next shipment date if user decides to skip" do - @order.subscriptions.last.next_shipment_date.to_i.should == 8.weeks.from_now.to_i + next_shipment_date.should == next_calc_shipment_date end it "should fall back to the original shipment date after undoing" do @order.subscriptions.last.undo_skip_next_order - @order.subscriptions.last.next_shipment_date.to_i.should == 4.weeks.from_now.to_i + next_shipment_date.should == original_shipment_date end end @@ -110,7 +110,7 @@ end context "when the subscription has not been cancelled" do - let(:subscription_state) { nil } + let(:subscription_state) { 'active' } it "returns false" do expect(subscription.cancelled?).to be false @@ -119,14 +119,14 @@ end describe "#cancel!" do - let(:subscription) { FactoryGirl.create(:subscription, state: nil) } + let(:subscription) { FactoryGirl.create(:subscription, state: :active) } it "cancels the subscription" do expect { subscription.cancel! }.to change { subscription.state - }.from(nil).to('cancelled') + }.from('active').to('cancelled') end end @@ -137,7 +137,7 @@ subscription.pause }.to change { subscription.state - }.from(nil).to('paused') + }.from('active').to('paused') end end @@ -198,6 +198,24 @@ def create_subscriptions(subscriptions_attr) end end + context '#create_next_order!' do + let(:second_store) { create(:store) } + + before do + create_completed_subscription_order + + subscription.last_order.update_column(:currency, 'CAD') + subscription.last_order.update_column(:store_id, second_store.id) + end + + it "has the last order's currency and store" do + subscription.create_next_order! + + expect(subscription.last_order.currency).to eq 'CAD' + expect(subscription.last_order.store_id).to eq second_store.id + end + end + describe "can_renew?" do let(:subscription) { FactoryGirl.create(:subscription) } diff --git a/spec/requests/store/my_account/edit_subscription_payment_info_spec.rb b/spec/requests/store/my_account/edit_subscription_payment_info_spec.rb index 6f8dcf2..15f5b6f 100644 --- a/spec/requests/store/my_account/edit_subscription_payment_info_spec.rb +++ b/spec/requests/store/my_account/edit_subscription_payment_info_spec.rb @@ -18,7 +18,7 @@ expect(@credit_card.current_payment_info).to_not be_nil end - scenario "can update payment info" do + scenario "can update payment info", :vcr do @credit_card.number = "4242424242424242" @credit_card.name = "John Doe" @credit_card.expiry = "06/20" diff --git a/spec/requests/store/my_account/pause_subscription_spec.rb b/spec/requests/store/my_account/pause_subscription_spec.rb index c42f69f..ed6a545 100644 --- a/spec/requests/store/my_account/pause_subscription_spec.rb +++ b/spec/requests/store/my_account/pause_subscription_spec.rb @@ -57,7 +57,7 @@ def next_month end def pause_all_subscriptions_for(user) - user.subscriptions.each { |s| s.pause } + user.subscriptions.each { |s| s.pause! } end end diff --git a/spec/services/adjust_sku_service_spec.rb b/spec/services/adjust_sku_service_spec.rb index 2a1a229..d9e4fea 100644 --- a/spec/services/adjust_sku_service_spec.rb +++ b/spec/services/adjust_sku_service_spec.rb @@ -28,8 +28,8 @@ AdjustSkuService.new.update_subscriptions("bdc1", "bdc2") expect(@subscription.subscription_items.count).to eq(2) - expect(@subscription.subscription_items[0].variant_id).to be(variant2.id) - expect(@subscription.subscription_items[1].variant_id).to be(@new_variant.id) + expect(@subscription.subscription_items[0].variant.sku).to eq(@new_variant.sku) + expect(@subscription.subscription_items[1].variant.sku).to eq(variant2.sku) end it "all affected subscriptions are updated" do @@ -51,24 +51,4 @@ expect(@subscription.subscription_items.first).to have_attributes(quantity: 5, interval: 2, variant_id: @new_variant.id) end end - - - it "gets all subscription items with a specific sku" do - user2 = FactoryGirl.create(:user) - subscription2 = user2.subscriptions.create!(ship_address_id: address.id, bill_address_id: address.id) - subscription2.subscription_items.create!(variant_id: @old_variant.id, quantity: 1, price: 10.00) - - subscription_items = AdjustSkuService.new.subscription_items(@old_variant) - - expect(subscription_items.count).to eq(2) - end - - it "creates a subscription item with the updated variant sku" do - item = @subscription.subscription_items.first - - AdjustSkuService.new.create_subscription_item(item, @new_variant) - - expect(@subscription.subscription_items.last.variant.sku).to eq("bdc2") - end - end diff --git a/spec/support/poltergeist.rb b/spec/support/poltergeist.rb new file mode 100644 index 0000000..a20cc66 --- /dev/null +++ b/spec/support/poltergeist.rb @@ -0,0 +1,27 @@ +require 'capybara/poltergeist' + +Capybara.register_driver :poltergeist do |app| + Capybara::Poltergeist::Driver.new(app, + js_errors: false, + inspector: true, + timeout: 240, + url_blacklist: %w( + typekit.net + fontdeck.com + facebook.net + facebook.com + optimizely.com + ravenjs.com + google.com + googleapis.com + googleadservices.com + googletagmanager.com + google-analytics.com) + ) +end + +Capybara.raise_server_errors = false +Capybara.always_include_port = true +Capybara.javascript_driver = :poltergeist +Capybara.default_max_wait_time = 10 +Capybara.server_port = 61_454 diff --git a/spec/support/subscription_helpers.rb b/spec/support/subscription_helpers.rb new file mode 100644 index 0000000..c8d719d --- /dev/null +++ b/spec/support/subscription_helpers.rb @@ -0,0 +1,15 @@ +def subscription + @order.subscriptions.last +end + +def next_shipment_date + @order.subscriptions.last.next_shipment_date.to_date +end + +def next_calc_shipment_date + subscription.last_shipment_date.advance(subscription.calc_next_renewal_date).to_date +end + +def original_shipment_date + (subscription.interval).months.from_now.to_date +end diff --git a/spec/support/vcr.rb b/spec/support/vcr.rb new file mode 100644 index 0000000..cfd44f0 --- /dev/null +++ b/spec/support/vcr.rb @@ -0,0 +1,6 @@ +VCR.configure do |c| + c.allow_http_connections_when_no_cassette = true + c.cassette_library_dir = 'spec/cassettes' + c.hook_into :webmock + c.configure_rspec_metadata! +end
    <%= subscription.products.collect(&:name).to_sentence %> <%= subscription.last_shipment_date %> <%= subscription.next_shipment_date %><%= t(subscription.state, scope: 'subscription_state', default: t('subscription_state.active')).titleize %><%= Spree.t(subscription.state, scope: 'subscription_state', default: Spree.t('subscription_state.active')).titleize %> <% if subscription.active? %> - <%= button_to t('action.pause'), pause_subscription_path(subscription), method: :put, class: "pause-subscription" %> + <%= button_to Spree.t('actions.pause'), pause_subscription_path(subscription), method: :put, class: "pause-subscription" %> <% else %>
    > <%= form_for subscription, url: resume_subscription_path(subscription), method: :put do |f| %> <%= f.label(:resume_at, t('resume_at')) %> <%= f.date_field(:resume_at, value: subscription.resume_at ? subscription.resume_at.to_date : Date.today, min: Date.today) %> - <%= f.submit(t('action.resume'), class: "resume-subscription") %> + <%= f.submit(Spree.t('actions.resume'), class: "resume-subscription") %> <% end %>
    <% if !subscription.resume_at.nil? && !subscription.cancelled? %>
    Will be resumed on <%= subscription.resume_at.to_date %> - <%= tag :input, { type: :button, value: t('action.change'), class: 'change-resume-subscription-on' } %> + <%= tag :input, { type: :button, value: Spree.t('actions.change'), class: 'change-resume-subscription-on' } %>
    <% end %> <% end %>
    <% if !subscription.cancelled? %> - <%= button_to t('action.cancel'), cancel_subscription_path(subscription), method: :put, class: "cancel-subscription" %> + <%= button_to Spree.t('actions.cancel'), cancel_subscription_path(subscription), method: :put, class: "cancel-subscription" %> <% end %>