spree_loyalty_points 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/LICENSE +26 -0
  2. data/README.md +68 -0
  3. data/app/assets/javascripts/admin/spree_loyalty_points.js +83 -0
  4. data/app/assets/javascripts/store/spree_loyalty_points.js +1 -0
  5. data/app/assets/stylesheets/admin/spree_loyalty_points.css +3 -0
  6. data/app/assets/stylesheets/store/spree_loyalty_points.css +13 -0
  7. data/app/controllers/spree/admin/general_settings_controller_decorator.rb +17 -0
  8. data/app/controllers/spree/admin/loyalty_points_transactions_controller.rb +80 -0
  9. data/app/controllers/spree/admin/resource_controller_decorator.rb +21 -0
  10. data/app/controllers/spree/admin/return_authorizations_controller_decorator.rb +11 -0
  11. data/app/controllers/spree/checkout_controller_decorator.rb +17 -0
  12. data/app/controllers/spree/loyalty_points_controller.rb +9 -0
  13. data/app/models/concerns/spree/loyalty_points.rb +22 -0
  14. data/app/models/concerns/spree/order/loyalty_points.rb +55 -0
  15. data/app/models/concerns/spree/payment/loyalty_points.rb +39 -0
  16. data/app/models/concerns/spree/transactions_total_validation.rb +25 -0
  17. data/app/models/spree/app_configuration_decorator.rb +8 -0
  18. data/app/models/spree/loyalty_points_credit_transaction.rb +18 -0
  19. data/app/models/spree/loyalty_points_debit_transaction.rb +18 -0
  20. data/app/models/spree/loyalty_points_transaction.rb +45 -0
  21. data/app/models/spree/order_decorator.rb +18 -0
  22. data/app/models/spree/payment_decorator.rb +34 -0
  23. data/app/models/spree/payment_method/loyalty_points.rb +27 -0
  24. data/app/models/spree/payment_method_decorator.rb +7 -0
  25. data/app/models/spree/return_authorization_decorator.rb +23 -0
  26. data/app/models/spree/user_decorator.rb +20 -0
  27. data/app/overrides/add_condition_to_amount_on_return_authorization_page.rb +8 -0
  28. data/app/overrides/add_loyalty_points_balance_to_account_page.rb +9 -0
  29. data/app/overrides/add_loyalty_points_debiting_to_return_authorization_page.rb +28 -0
  30. data/app/overrides/add_loyalty_points_preferences_to_general_settings_page.rb +18 -0
  31. data/app/overrides/add_loyalty_points_to_admin_user_show_page.rb +15 -0
  32. data/app/overrides/add_loyalty_points_to_cart_page.rb +8 -0
  33. data/app/overrides/add_loyalty_points_to_order_checkout_page.rb +16 -0
  34. data/app/overrides/add_loyalty_points_to_return_authorization_index_page.rb +13 -0
  35. data/app/overrides/add_loyalty_points_transaction_table_to_return_authorization_form.rb +6 -0
  36. data/app/views/spree/admin/loyalty_points_transactions/index.html.erb +16 -0
  37. data/app/views/spree/admin/loyalty_points_transactions/new.html.erb +62 -0
  38. data/app/views/spree/admin/payments/source_forms/_loyaltypoints.html.erb +0 -0
  39. data/app/views/spree/admin/payments/source_views/_loyaltypoints.html.erb +0 -0
  40. data/app/views/spree/checkout/payment/_loyaltypoints.html.erb +8 -0
  41. data/app/views/spree/loyalty_points/_transaction_table.html.erb +44 -0
  42. data/app/views/spree/loyalty_points/index.html.erb +4 -0
  43. data/config/locales/en.yml +18 -0
  44. data/config/routes.rb +14 -0
  45. data/db/migrate/20140116090042_add_loyalty_points_balance_to_spree_user.rb +5 -0
  46. data/db/migrate/20140116110437_create_loyalty_points_transactions.rb +10 -0
  47. data/db/migrate/20140117062720_add_timestamps_to_loyalty_points_transaction.rb +7 -0
  48. data/db/migrate/20140117065314_add_fields_to_spree_loyalty_points_transaction.rb +7 -0
  49. data/db/migrate/20140122084005_add_comment_to_spree_loyalty_points_transaction.rb +5 -0
  50. data/db/migrate/20140123092709_add_paid_at_to_spree_order.rb +5 -0
  51. data/db/migrate/20140123120355_add_loyalty_points_to_spree_return_authorization.rb +6 -0
  52. data/db/migrate/20140124121728_add_loyalty_points_awarded_to_spree_order.rb +5 -0
  53. data/db/migrate/20140130131957_rename_fields_in_spree_loyalty_points_transaction.rb +6 -0
  54. data/db/migrate/20140131072702_remove_loyalty_points_awarded_from_spree_order.rb +5 -0
  55. data/db/migrate/20140203063433_add_indexing_to_spree_loyalty_points_transaction.rb +7 -0
  56. data/db/migrate/20140205111553_add_transaction_id_to_spree_loyalty_points_transactions.rb +5 -0
  57. data/db/migrate/20140207055836_add_lock_version_to_spree_user.rb +5 -0
  58. data/lib/generators/spree_loyalty_points/install/install_generator.rb +31 -0
  59. data/lib/spree_loyalty_points/engine.rb +26 -0
  60. data/lib/spree_loyalty_points/factories.rb +75 -0
  61. data/lib/spree_loyalty_points.rb +2 -0
  62. data/lib/tasks/loyalty_points/award.rake +10 -0
  63. metadata +124 -0
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2014 vinsol.com
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name Spree nor the names of its contributors may be used to
13
+ endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ Spree Loyalty Points
2
+ ====================
3
+
4
+ This extension adds Loyalty Points for users.
5
+
6
+
7
+ Installation
8
+ ------------
9
+
10
+ Add spree_loyalty_points to your Gemfile:
11
+
12
+ ```ruby
13
+ gem 'spree_loyalty_points', '~> 1.0.0'
14
+ ```
15
+
16
+ Bundle your dependencies and run the installation generator:
17
+
18
+ ```shell
19
+ bundle
20
+ bundle exec rails g spree_loyalty_points:install
21
+ bundle exec rake db:migrate
22
+ ```
23
+
24
+ Usage
25
+ -----
26
+
27
+ From Admin end, create a payment method of LoyaltyPoints type for payment with Loyalty Points.
28
+
29
+ Go to the Admin end of the website and open the Configurations Tab. Set up the values for the following Loyalty Points Settings under General Settings:
30
+
31
+ * Minimum Amount to be spent per Order(Item Total, excluding Shipping and any other Charges) to award Loyalty Points : This is the minimum amount the user has to spend on an order to receive Loyalty Points for that order
32
+ * Number of Loyalty Points to be awarded per Unit Amount Spent : Loyalty Points to be awarded against Unit Amount spent on Order
33
+ * Minimum Loyalty Points Balance Required for Redeeming : Minimum Loyalty Points Balance a user should have for spending Loyalty Points
34
+ * Loyalty Point to Amount Conversion Rate : Conversion Rate for getting Amount equivalent value of Loyalty Points
35
+ * Time(in hours) after payment to award Loyalty Points : Time period to wait from the time payment is received for the order to award Loyalty Points
36
+
37
+ After an order's payment is received, Loyalty Points will be awarded (only after the Time period specified in the above setting) when you run the rake task.
38
+ Add a Cron Job to run the following Rake Task to Award Loyalty Points to Users:
39
+
40
+ ```shell
41
+ bundle exec rake spree:loyalty_points:award
42
+ ```
43
+
44
+ Testing
45
+ -------
46
+
47
+ Be sure to bundle your dependencies and then create a dummy test app for the specs to run against.
48
+
49
+ ```shell
50
+ bundle
51
+ bundle exec rake test_app
52
+ bundle exec rspec spec
53
+ ```
54
+
55
+ When testing your applications integration with this extension you may use it's factories.
56
+ Simply add this require statement to your spec_helper:
57
+
58
+ ```ruby
59
+ require 'spree_loyalty_points/factories'
60
+ ```
61
+
62
+
63
+ Credits
64
+ -------
65
+
66
+ [![vinsol.com: Ruby on Rails, iOS and Android developers](http://vinsol.com/vin_logo.png "Ruby on Rails, iOS and Android developers")](http://vinsol.com)
67
+
68
+ Copyright (c) 2014 [vinsol.com](http://vinsol.com "Ruby on Rails, iOS and Android developers"), released under the New MIT License
@@ -0,0 +1,83 @@
1
+ //= require admin/spree_backend
2
+
3
+ function SelectOptionChange(selectBoxId, containerId, heading) {
4
+ var that = this;
5
+ this.selectBoxId = selectBoxId;
6
+ this.containerId = containerId;
7
+ $('#' + this.selectBoxId).on('change', function(event) {
8
+ var appendPath = event.target.value;
9
+ $.ajax({
10
+ url: $(this).attr('data-transactions-link') + "/order_transactions/" + appendPath,
11
+ dataType: "json",
12
+ success: function(responseData, returnStatus, xhr) {
13
+ var $mainContainer = $('#' + containerId);
14
+ // Remove table for last order transactions
15
+ $mainContainer.html("");
16
+ // Add heading
17
+ $mainContainer.append($('<h5>' + heading + '</h5>'));
18
+ // Parse response data from ajax
19
+ that.displayData(responseData, $mainContainer);
20
+ }
21
+ });
22
+ });
23
+ }
24
+ $(document).ready(function() {
25
+ var orderSelection = new SelectOptionChange('loyalty_points_transaction_source_id', 'loyalty-points-order-transactions', 'Loyalty Point Transactions')
26
+ orderSelection.displayData = function(responseData, container) {
27
+ var $table = this.appendTable(container);
28
+ this.appendHeaderRow($table);
29
+ var $tableBody = this.appendTableBody($table);
30
+
31
+ $.each(responseData, function(index, transaction) {
32
+ var $transactionRow = this.appendTableRow($tableBody);
33
+ transaction_date = new Date(transaction['updated_at']).toLocaleString();
34
+ this.appendTableRowData(transaction_date, $transactionRow);
35
+ this.appendSourceReference(transaction['source_type'], transaction['source']['number'], $transactionRow);
36
+ this.appendTableRowData(transaction['comment'], $transactionRow);
37
+ this.appendTableRowData(transaction['transaction_type'], $transactionRow);
38
+ this.appendTableRowData(transaction['loyalty_points'], $transactionRow);
39
+ this.appendTableRowData(transaction['balance'], $transactionRow);
40
+ }.bind(this));
41
+ }
42
+ orderSelection.appendTable = function(container) {
43
+ return $('<table></table>').appendTo(container);
44
+ }
45
+ orderSelection.appendHeaderRow = function(container) {
46
+ $tableHead = this.appendTableHeader(container);
47
+ $tableHeadRow = this.appendTableRow($tableHead);
48
+ this.appendHeadData('Date', $tableHeadRow);
49
+ this.appendHeadData('Source', $tableHeadRow);
50
+ this.appendHeadData('Source Reference', $tableHeadRow);
51
+ this.appendHeadData('Comment', $tableHeadRow);
52
+ this.appendHeadData('Transaction Type', $tableHeadRow);
53
+ this.appendHeadData('Loyalty Points', $tableHeadRow);
54
+ this.appendHeadData('Updated Balance', $tableHeadRow);
55
+ }
56
+ orderSelection.appendTableBody = function(container) {
57
+ return $('<tbody></tbody>').appendTo(container);
58
+ }
59
+ orderSelection.appendTableHeader = function(container) {
60
+ return $('<thead></thead>').appendTo(container);
61
+ }
62
+ orderSelection.appendTableRow = function(container) {
63
+ return $('<tr></tr>').appendTo(container);
64
+ }
65
+ orderSelection.appendTableRowData = function(content, container) {
66
+ if (!content) {
67
+ content = "";
68
+ }
69
+ return $('<td>' + content + '</td>').appendTo(container);
70
+ }
71
+ orderSelection.appendHeadData = function(content, container) {
72
+ return $('<th>' + content + '</th>').appendTo(container);
73
+ }
74
+ orderSelection.appendSourceReference = function(source_type, source_reference, container) {
75
+ this.appendTableRowData(source_type.replace('Spree::', ''), container);
76
+ if (source_type == "Spree::Order") {
77
+ $("<td><a href='/admin/orders/" + source_reference + "/edit'>" + source_reference + "</a></td>").appendTo(container);
78
+ }
79
+ else {
80
+ this.appendTableRowData(source_reference, container);
81
+ }
82
+ }
83
+ });
@@ -0,0 +1 @@
1
+ //= require store/spree_frontend
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require admin/spree_backend
3
+ */
@@ -0,0 +1,13 @@
1
+ /*
2
+ *= require store/spree_frontend
3
+ */
4
+
5
+ #loyalty-points-info {
6
+ border: 1px solid #D9D9DB;
7
+ margin-bottom: 15px;
8
+ padding: 10px;
9
+ }
10
+
11
+ #loyalty-points-info dd {
12
+ margin-left: 0;
13
+ }
@@ -0,0 +1,17 @@
1
+ Spree::Admin::GeneralSettingsController.class_eval do
2
+
3
+ before_action :set_loyalty_points_settings, only: [:edit]
4
+
5
+ private
6
+
7
+ def set_loyalty_points_settings
8
+ @preferences_loyalty_points = {
9
+ :min_amount_required_to_get_loyalty_points => [""],
10
+ :loyalty_points_awarding_unit => ["For example: Set this as 10 if we wish to award 10 points for $1 spent on the site."],
11
+ :loyalty_points_redeeming_balance => [""],
12
+ :loyalty_points_conversion_rate => ["For example: Set this value to 5 if we wish 1 loyalty point is equivalent to $5"],
13
+ :loyalty_points_award_period => [""]
14
+ }
15
+ end
16
+
17
+ end
@@ -0,0 +1,80 @@
1
+ class Spree::Admin::LoyaltyPointsTransactionsController < Spree::Admin::ResourceController
2
+ before_action :set_user, only: [:order_transactions]
3
+ belongs_to 'spree/user'
4
+ before_action :set_ordered_transactions, only: [:index]
5
+
6
+ def order_transactions
7
+ order = Spree::Order.find_by(id: params[:order_id])
8
+ @loyalty_points_transactions = @user.loyalty_points_transactions.for_order(order).includes(:source).order(updated_at: :desc)
9
+ respond_to do |format|
10
+ format.json do
11
+ render json: @loyalty_points_transactions.to_json(
12
+ :include => {
13
+ :source => {
14
+ :only => [:id, :number]
15
+ }
16
+ },
17
+ :only => [:source_type, :comment, :updated_at, :loyalty_points, :balance],
18
+ :methods => [:transaction_type]
19
+ )
20
+ end
21
+ end
22
+ end
23
+
24
+ def create
25
+ invoke_callbacks(:create, :before)
26
+ @object.attributes = loyalty_points_transaction_params
27
+ if @object.save
28
+ invoke_callbacks(:create, :after)
29
+ flash[:success] = flash_message_for(@object, :successfully_created)
30
+ respond_with(@object) do |format|
31
+ format.html { redirect_to location_after_save }
32
+ format.js { render :layout => false }
33
+ end
34
+ else
35
+ invoke_callbacks(:create, :fails)
36
+ respond_with(@object) do |format|
37
+ format.html { render action: :new }
38
+ end
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def set_user
45
+ unless @user = Spree::User.find_by(id: params[:user_id])
46
+ redirect_to admin_users_path, notice: 'User not found'
47
+ end
48
+ end
49
+
50
+ def loyalty_points_transaction_params
51
+ params.require(:loyalty_points_transaction).permit(:loyalty_points, :type, :comment, :source_id, :source_type)
52
+ end
53
+
54
+ def build_resource
55
+ if params[:loyalty_points_transaction].present? && params[:loyalty_points_transaction][:type].present?
56
+ parent.send(association_name(params[:loyalty_points_transaction][:type])).build
57
+ else
58
+ parent.send(controller_name).build
59
+ end
60
+ end
61
+
62
+ def association_name(klass)
63
+ klass.gsub('Spree::', '').pluralize.underscore
64
+ end
65
+
66
+ def collection_url
67
+ if (parent_data.present? && @parent.nil?) || parent_data.blank?
68
+ admin_users_url
69
+ else
70
+ admin_user_loyalty_points_url(parent)
71
+ end
72
+ end
73
+
74
+ def set_ordered_transactions
75
+ @loyalty_points_transactions = @loyalty_points_transactions.order(updated_at: :desc).
76
+ page(params[:page]).
77
+ per(params[:per_page] || Spree::Config[:orders_per_page])
78
+ end
79
+
80
+ end
@@ -0,0 +1,21 @@
1
+ Spree::Admin::ResourceController.class_eval do
2
+
3
+ protected
4
+
5
+ def parent
6
+ if parent_data.present?
7
+ @parent ||= parent_data[:model_class].send("find_by_#{parent_data[:find_by]}", params["#{model_name}_id"])
8
+ raise ActiveRecord::RecordNotFound unless @parent
9
+ instance_variable_set("@#{model_name}", @parent)
10
+ else
11
+ nil
12
+ end
13
+ end
14
+
15
+ def resource_not_found
16
+ flash[:error] = flash_message_for(model_class.new, :not_found)
17
+ flash[:error] = flash_message_for(parent_data[:model_class].new, :not_found) if parent_data.present? && @parent.nil?
18
+ redirect_to collection_url
19
+ end
20
+
21
+ end
@@ -0,0 +1,11 @@
1
+ Spree::Admin::ReturnAuthorizationsController.class_eval do
2
+ before_action :set_loyalty_points_transactions, only: [:new, :edit, :create, :update]
3
+
4
+ private
5
+
6
+ def set_loyalty_points_transactions
7
+ @loyalty_points_transactions = @return_authorization.order.loyalty_points_transactions.
8
+ page(params[:page]).
9
+ per(params[:per_page] || Spree::Config[:orders_per_page])
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ CheckoutController.class_eval do
3
+ before_filter :sufficient_loyalty_points, only: [:update], if: -> { params[:state] == 'payment' }
4
+
5
+ private
6
+
7
+ def sufficient_loyalty_points
8
+ payment_method_ids = params[:order][:payments_attributes].collect do |payment|
9
+ payment["payment_method_id"]
10
+ end
11
+ if Spree::PaymentMethod.loyalty_points_id_included?(payment_method_ids) && !@order.user.has_sufficient_loyalty_points?(@order)
12
+ flash[:error] = Spree.t(:insufficient_loyalty_points)
13
+ redirect_to checkout_state_path(@order.state)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ class Spree::LoyaltyPointsController < Spree::StoreController
2
+
3
+ def index
4
+ @loyalty_points_transactions = spree_current_user.loyalty_points_transactions.includes(:source).order(updated_at: :desc).
5
+ page(params[:page]).
6
+ per(params[:per_page] || Spree::Config[:orders_per_page])
7
+ end
8
+
9
+ end
@@ -0,0 +1,22 @@
1
+ require 'active_support/concern'
2
+
3
+ module Spree
4
+ module LoyaltyPoints
5
+ extend ActiveSupport::Concern
6
+
7
+ def loyalty_points_for(amount, purpose = 'award')
8
+ loyalty_points = if purpose == 'award' && eligible_for_loyalty_points?(amount)
9
+ (amount * Spree::Config.loyalty_points_awarding_unit).floor
10
+ elsif purpose == 'redeem'
11
+ (amount / Spree::Config.loyalty_points_conversion_rate).ceil
12
+ else
13
+ 0
14
+ end
15
+ end
16
+
17
+ def eligible_for_loyalty_points?(amount)
18
+ amount >= Spree::Config.min_amount_required_to_get_loyalty_points
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,55 @@
1
+ require 'active_support/concern'
2
+
3
+ module Spree
4
+ class Order < ActiveRecord::Base
5
+ module LoyaltyPoints
6
+ extend ActiveSupport::Concern
7
+
8
+ def loyalty_points_total
9
+ loyalty_points_credit_transactions.sum(:loyalty_points) - loyalty_points_debit_transactions.sum(:loyalty_points)
10
+ end
11
+
12
+ def award_loyalty_points
13
+ loyalty_points_earned = loyalty_points_for(item_total)
14
+ if !loyalty_points_used?
15
+ create_credit_transaction(loyalty_points_earned)
16
+ end
17
+ end
18
+
19
+ def loyalty_points_awarded?
20
+ loyalty_points_credit_transactions.count > 0
21
+ end
22
+
23
+ def loyalty_points_used?
24
+ payments.any_with_loyalty_points?
25
+ end
26
+
27
+ module ClassMethods
28
+
29
+ def credit_loyalty_points_to_user
30
+ points_award_period = Spree::Config.loyalty_points_award_period
31
+ uncredited_orders = Spree::Order.with_uncredited_loyalty_points(points_award_period)
32
+ uncredited_orders.each do |order|
33
+ order.award_loyalty_points
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ def create_credit_transaction(points)
40
+ user.loyalty_points_credit_transactions.create(source: self, loyalty_points: points)
41
+ end
42
+
43
+ def create_debit_transaction(points)
44
+ user.loyalty_points_debit_transactions.create(source: self, loyalty_points: points)
45
+ end
46
+
47
+ private
48
+
49
+ def complete_loyalty_points_payments
50
+ payments.by_loyalty_points.with_state('checkout').each { |payment| payment.complete! }
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,39 @@
1
+ require 'active_support/concern'
2
+
3
+ module Spree
4
+ class Payment < ActiveRecord::Base
5
+ module LoyaltyPoints
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ def any_with_loyalty_points?
11
+ by_loyalty_points.size != 0
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def redeem_loyalty_points
18
+ loyalty_points_redeemed = loyalty_points_for(amount, 'redeem')
19
+ if by_loyalty_points? && redeemable_loyalty_points_balance?
20
+ order.create_debit_transaction(loyalty_points_redeemed)
21
+ end
22
+ end
23
+
24
+ def return_loyalty_points
25
+ loyalty_points_redeemed = loyalty_points_for(amount, 'redeem')
26
+ order.create_credit_transaction(loyalty_points_redeemed)
27
+ end
28
+
29
+ def by_loyalty_points?
30
+ payment_method.type == "Spree::PaymentMethod::LoyaltyPoints"
31
+ end
32
+
33
+ def redeemable_loyalty_points_balance?
34
+ amount >= Spree::Config.loyalty_points_redeeming_balance
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ require 'active_support/concern'
2
+
3
+ module Spree
4
+ module TransactionsTotalValidation
5
+ extend ActiveSupport::Concern
6
+
7
+ def net_transactions_sum(trans_type, relation)
8
+ credit_transactions_total = relation.loyalty_points_credit_transactions.sum(:loyalty_points)
9
+ debit_transactions_total = relation.loyalty_points_debit_transactions.sum(:loyalty_points)
10
+ trans_type == "Debit" ? debit_transactions_total += loyalty_points : credit_transactions_total += loyalty_points
11
+ credit_transactions_total - debit_transactions_total
12
+ end
13
+
14
+ def validate_transactions_total_range(trans_type, relation)
15
+ net_transactions_total = net_transactions_sum(trans_type, relation)
16
+ first_transaction = relation.loyalty_points_transactions.first
17
+ if first_transaction.transaction_type == "Debit"
18
+ errors.add :base, "Loyalty Points Net Debit Total should be in the range [0, #{ first_transaction.loyalty_points }]" if net_transactions_total > 0 || net_transactions_total < -first_transaction.loyalty_points
19
+ else
20
+ errors.add :base, "Loyalty Points Net Credit Total should be in the range [0, #{ first_transaction.loyalty_points }]" if net_transactions_total < 0 || net_transactions_total > first_transaction.loyalty_points
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ #TODO -> Rspecs for this model.
2
+ Spree::AppConfiguration.class_eval do
3
+ preference :min_amount_required_to_get_loyalty_points, :decimal, :default => 20.0
4
+ preference :loyalty_points_awarding_unit, :decimal, :default => 0.0
5
+ preference :loyalty_points_redeeming_balance, :integer, :default => 50
6
+ preference :loyalty_points_conversion_rate, :decimal, :default => 5.0
7
+ preference :loyalty_points_award_period, :integer, :default => 1
8
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ class LoyaltyPointsCreditTransaction < LoyaltyPointsTransaction
3
+ after_create :update_user_balance
4
+ before_create :update_balance
5
+
6
+ private
7
+
8
+ def update_user_balance
9
+ user.increment(:loyalty_points_balance, loyalty_points)
10
+ user.save!
11
+ end
12
+
13
+ def update_balance
14
+ self.balance = user.loyalty_points_balance + loyalty_points
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ class LoyaltyPointsDebitTransaction < LoyaltyPointsTransaction
3
+ after_create :update_user_balance
4
+ before_create :update_balance
5
+
6
+ private
7
+
8
+ def update_user_balance
9
+ user.decrement(:loyalty_points_balance, loyalty_points)
10
+ user.save!
11
+ end
12
+
13
+ def update_balance
14
+ self.balance = user.loyalty_points_balance - loyalty_points
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ module Spree
2
+ class LoyaltyPointsTransaction < ActiveRecord::Base
3
+ include Spree::TransactionsTotalValidation
4
+ TRANSACTION_TYPES = ['Spree::LoyaltyPointsCreditTransaction', 'Spree::LoyaltyPointsDebitTransaction']
5
+ CLASS_TO_TRANSACTION_TYPE = { 'Spree::LoyaltyPointsCreditTransaction' => 'Credit', 'Spree::LoyaltyPointsDebitTransaction' => 'Debit'}
6
+ belongs_to :user
7
+ belongs_to :source, polymorphic: true
8
+
9
+ validates :loyalty_points, :numericality => { :only_integer => true, :message => Spree.t('validation.must_be_int'), :greater_than => 0 }
10
+ validates :type, inclusion: { in: TRANSACTION_TYPES }
11
+ validates :balance, presence: true
12
+ validate :source_or_comment_present
13
+ validate :transactions_total_range, if: -> { source.present? && source.loyalty_points_transactions.present? }
14
+
15
+ #TODO -> rspecs missing for this scope
16
+ scope :for_order, ->(order) { where(source: order) }
17
+
18
+ #TODO -> Rspecs missing for this callback.
19
+ before_create :generate_transaction_id
20
+
21
+ #TODO -> Rspecs missing for this method.
22
+ def transaction_type
23
+ CLASS_TO_TRANSACTION_TYPE[type]
24
+ end
25
+
26
+ private
27
+
28
+ def source_or_comment_present
29
+ unless source.present? || comment.present?
30
+ errors.add :base, 'Source or Comment should be present'
31
+ end
32
+ end
33
+
34
+ def generate_transaction_id
35
+ begin
36
+ self.transaction_id = (Time.current.strftime("%s") + rand(999999).to_s).to(15)
37
+ end while Spree::LoyaltyPointsTransaction.where(:transaction_id => transaction_id).present?
38
+ end
39
+
40
+ def transactions_total_range
41
+ validate_transactions_total_range(transaction_type, source)
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ Spree::Order.class_eval do
2
+ include Spree::LoyaltyPoints
3
+ include Spree::Order::LoyaltyPoints
4
+
5
+ has_many :loyalty_points_transactions, as: :source
6
+ has_many :loyalty_points_credit_transactions, as: :source
7
+ has_many :loyalty_points_debit_transactions, as: :source
8
+
9
+ scope :loyalty_points_not_awarded, -> { includes(:loyalty_points_credit_transactions).where(:spree_loyalty_points_transactions => { :source_id => nil } ) }
10
+
11
+ scope :with_hours_since_payment, ->(num) { where('`spree_orders`.`paid_at` < ? ', num.hours.ago) }
12
+
13
+ scope :with_uncredited_loyalty_points, ->(num) { with_hours_since_payment(num).loyalty_points_not_awarded }
14
+
15
+ fsm = self.state_machines[:state]
16
+ fsm.before_transition :from => fsm.states.map(&:name) - [:complete], :to => [:complete], :do => :complete_loyalty_points_payments
17
+
18
+ end
@@ -0,0 +1,34 @@
1
+ Spree::Payment.class_eval do
2
+
3
+ include Spree::LoyaltyPoints
4
+ include Spree::Payment::LoyaltyPoints
5
+
6
+ validates :amount, numericality: { greater_than: 0 }, :if => :by_loyalty_points?
7
+ scope :state_not, ->(s) { where('state != ?', s) }
8
+ scope :by_loyalty_points, -> { joins(:payment_method).readonly(false).where(:spree_payment_methods => { type: 'Spree::PaymentMethod::LoyaltyPoints'}) }
9
+
10
+ fsm = self.state_machines[:state]
11
+ fsm.after_transition :from => fsm.states.map(&:name) - [:completed], :to => [:completed], :do => :notify_paid_order
12
+
13
+ fsm.after_transition :from => fsm.states.map(&:name) - [:completed], :to => [:completed], :do => :redeem_loyalty_points, :if => :by_loyalty_points?
14
+ fsm.after_transition :from => [:completed], :to => fsm.states.map(&:name) - [:completed] , :do => :return_loyalty_points, :if => :by_loyalty_points?
15
+
16
+ def invalidate_old_payments
17
+ order.payments.with_state('checkout').where("id != ?", self.id).each do |payment|
18
+ payment.invalidate!
19
+ end unless by_loyalty_points?
20
+ end
21
+
22
+ private
23
+
24
+ def notify_paid_order
25
+ if all_payments_completed?
26
+ order.touch :paid_at
27
+ end
28
+ end
29
+
30
+ def all_payments_completed?
31
+ order.payments.state_not('invalid').all? { |payment| payment.completed? }
32
+ end
33
+
34
+ end