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.
- data/LICENSE +26 -0
- data/README.md +68 -0
- data/app/assets/javascripts/admin/spree_loyalty_points.js +83 -0
- data/app/assets/javascripts/store/spree_loyalty_points.js +1 -0
- data/app/assets/stylesheets/admin/spree_loyalty_points.css +3 -0
- data/app/assets/stylesheets/store/spree_loyalty_points.css +13 -0
- data/app/controllers/spree/admin/general_settings_controller_decorator.rb +17 -0
- data/app/controllers/spree/admin/loyalty_points_transactions_controller.rb +80 -0
- data/app/controllers/spree/admin/resource_controller_decorator.rb +21 -0
- data/app/controllers/spree/admin/return_authorizations_controller_decorator.rb +11 -0
- data/app/controllers/spree/checkout_controller_decorator.rb +17 -0
- data/app/controllers/spree/loyalty_points_controller.rb +9 -0
- data/app/models/concerns/spree/loyalty_points.rb +22 -0
- data/app/models/concerns/spree/order/loyalty_points.rb +55 -0
- data/app/models/concerns/spree/payment/loyalty_points.rb +39 -0
- data/app/models/concerns/spree/transactions_total_validation.rb +25 -0
- data/app/models/spree/app_configuration_decorator.rb +8 -0
- data/app/models/spree/loyalty_points_credit_transaction.rb +18 -0
- data/app/models/spree/loyalty_points_debit_transaction.rb +18 -0
- data/app/models/spree/loyalty_points_transaction.rb +45 -0
- data/app/models/spree/order_decorator.rb +18 -0
- data/app/models/spree/payment_decorator.rb +34 -0
- data/app/models/spree/payment_method/loyalty_points.rb +27 -0
- data/app/models/spree/payment_method_decorator.rb +7 -0
- data/app/models/spree/return_authorization_decorator.rb +23 -0
- data/app/models/spree/user_decorator.rb +20 -0
- data/app/overrides/add_condition_to_amount_on_return_authorization_page.rb +8 -0
- data/app/overrides/add_loyalty_points_balance_to_account_page.rb +9 -0
- data/app/overrides/add_loyalty_points_debiting_to_return_authorization_page.rb +28 -0
- data/app/overrides/add_loyalty_points_preferences_to_general_settings_page.rb +18 -0
- data/app/overrides/add_loyalty_points_to_admin_user_show_page.rb +15 -0
- data/app/overrides/add_loyalty_points_to_cart_page.rb +8 -0
- data/app/overrides/add_loyalty_points_to_order_checkout_page.rb +16 -0
- data/app/overrides/add_loyalty_points_to_return_authorization_index_page.rb +13 -0
- data/app/overrides/add_loyalty_points_transaction_table_to_return_authorization_form.rb +6 -0
- data/app/views/spree/admin/loyalty_points_transactions/index.html.erb +16 -0
- data/app/views/spree/admin/loyalty_points_transactions/new.html.erb +62 -0
- data/app/views/spree/admin/payments/source_forms/_loyaltypoints.html.erb +0 -0
- data/app/views/spree/admin/payments/source_views/_loyaltypoints.html.erb +0 -0
- data/app/views/spree/checkout/payment/_loyaltypoints.html.erb +8 -0
- data/app/views/spree/loyalty_points/_transaction_table.html.erb +44 -0
- data/app/views/spree/loyalty_points/index.html.erb +4 -0
- data/config/locales/en.yml +18 -0
- data/config/routes.rb +14 -0
- data/db/migrate/20140116090042_add_loyalty_points_balance_to_spree_user.rb +5 -0
- data/db/migrate/20140116110437_create_loyalty_points_transactions.rb +10 -0
- data/db/migrate/20140117062720_add_timestamps_to_loyalty_points_transaction.rb +7 -0
- data/db/migrate/20140117065314_add_fields_to_spree_loyalty_points_transaction.rb +7 -0
- data/db/migrate/20140122084005_add_comment_to_spree_loyalty_points_transaction.rb +5 -0
- data/db/migrate/20140123092709_add_paid_at_to_spree_order.rb +5 -0
- data/db/migrate/20140123120355_add_loyalty_points_to_spree_return_authorization.rb +6 -0
- data/db/migrate/20140124121728_add_loyalty_points_awarded_to_spree_order.rb +5 -0
- data/db/migrate/20140130131957_rename_fields_in_spree_loyalty_points_transaction.rb +6 -0
- data/db/migrate/20140131072702_remove_loyalty_points_awarded_from_spree_order.rb +5 -0
- data/db/migrate/20140203063433_add_indexing_to_spree_loyalty_points_transaction.rb +7 -0
- data/db/migrate/20140205111553_add_transaction_id_to_spree_loyalty_points_transactions.rb +5 -0
- data/db/migrate/20140207055836_add_lock_version_to_spree_user.rb +5 -0
- data/lib/generators/spree_loyalty_points/install/install_generator.rb +31 -0
- data/lib/spree_loyalty_points/engine.rb +26 -0
- data/lib/spree_loyalty_points/factories.rb +75 -0
- data/lib/spree_loyalty_points.rb +2 -0
- data/lib/tasks/loyalty_points/award.rake +10 -0
- 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
|
+
[](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,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
|