spree_loyalty_points 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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