spree_loyalty_points 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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,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
|