ba_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.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/README.md +77 -0
- data/app/assets/javascripts/spree/backend/spree_loyalty_points.js +81 -0
- data/app/assets/javascripts/spree/frontend/spree_loyalty_points.js +0 -0
- data/app/assets/stylesheets/spree/backend/spree_loyalty_points.css +0 -0
- data/app/assets/stylesheets/spree/frontend/spree_loyalty_points.css +10 -0
- data/app/controllers/spree/admin/loyalty_points_transactions_controller.rb +70 -0
- data/app/controllers/spree/admin/resource_controller_decorator.rb +25 -0
- data/app/controllers/spree/admin/return_authorizations_controller_decorator.rb +19 -0
- data/app/controllers/spree/checkout_controller_decorator.rb +21 -0
- data/app/controllers/spree/loyalty_points_controller.rb +11 -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 +48 -0
- data/app/models/concerns/spree/transactions_total_validation.rb +25 -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 +42 -0
- data/app/models/spree/order_decorator.rb +33 -0
- data/app/models/spree/payment_decorator.rb +38 -0
- data/app/models/spree/payment_method/loyalty_points.rb +37 -0
- data/app/models/spree/payment_method_decorator.rb +16 -0
- data/app/models/spree/return_authorization_decorator.rb +29 -0
- data/app/models/spree/store_decorator.rb +23 -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_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 +12 -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/overrides/spree/admin/stores/add_loyalty_points_settings.rb +6 -0
- data/app/views/spree/admin/loyalty_points_transactions/index.html.erb +12 -0
- data/app/views/spree/admin/loyalty_points_transactions/new.html.erb +62 -0
- data/app/views/spree/admin/loyalty_points_transactions/order_transactions.rabl +5 -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/admin/stores/_loyalty_points_settings.html.erb +45 -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/initializers/spree_permitted_attributes.rb +13 -0
- data/config/locales/en.yml +27 -0
- data/config/locales/ja.yml +27 -0
- data/config/routes.rb +14 -0
- data/db/migrate/20140116090042_add_loyalty_points_balance_to_spree_user.rb +6 -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 +6 -0
- data/lib/ba_spree_loyalty_points.rb +2 -0
- data/lib/generators/spree_loyalty_points/install/install_generator.rb +31 -0
- data/lib/spree_loyalty_points/engine.rb +55 -0
- data/lib/spree_loyalty_points/factories.rb +84 -0
- data/lib/spree_loyalty_points/version.rb +3 -0
- data/lib/tasks/loyalty_points/award.rake +8 -0
- metadata +320 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9d41af755522c53281c393fab438993ccb713ab81216ad88f6db168fbbc26f97
|
4
|
+
data.tar.gz: 7ef5c5a668852db2f6e06bbf2fb02bf965116d3f076399ba013b8f7de4b644f1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4422b91ac2aecc54fda2d4cc27dc9ac3f8a03f625b70821ec6891871b48031c0af407f0ab6c2c027df5a7c7d7980409ceeb163cdb10de78e338d271040fd10f7
|
7
|
+
data.tar.gz: 8eec429bba7907c9d2955e5351cecbf97e578a3d5e71051a3d0c5a76200a91d055839931dbd488078a002be12cb5b2591f352d33bfc8813f44d63f423056babb
|
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,77 @@
|
|
1
|
+
# ba_spree_loyalty_points
|
2
|
+
|
3
|
+
This is a fork of [spree_loyalty_points](https://github.com/vinsol-spree-contrib/spree-loyalty-points) with additional features and improvements.
|
4
|
+
|
5
|
+
Loyalty Points extension allows customers to earn loyalty points on the basis of their purchases. Admin can also reward Loyalty Points to it's customers manually. Customer can use these loyalty points to pay for their future orders.
|
6
|
+
|
7
|
+
This extension allows admin to create a new payment method "Loyalty Points" in the system. Once this payment method is created and active, it would appear on checkout screen and customers can use this payment method for payments.
|
8
|
+
|
9
|
+
This extension also automates the awarding of loyalty points to customers based on the configuration done by admin and updating loyalty points based on the transactions on Spree Commerce platform.
|
10
|
+
|
11
|
+
This extension allows only Loyalty Points payment method for making a purchase and does not allow the payment through other payment modes like cash, check, credit card etc.
|
12
|
+
|
13
|
+
## Features
|
14
|
+
|
15
|
+
* Supports Ruby 3.1
|
16
|
+
* Supports Rails 7.1
|
17
|
+
* Supports Spree 4.10
|
18
|
+
* Japanese translations included
|
19
|
+
* Actively maintained with contributions back to the original project
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
In your `Gemfile`, add:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem 'ba_spree_loyalty_points', github: 'your-github-username/ba_spree_loyalty_points'
|
27
|
+
```
|
28
|
+
|
29
|
+
Bundle your dependencies and run the installation generator:
|
30
|
+
|
31
|
+
```shell
|
32
|
+
bundle
|
33
|
+
bundle exec rails g ba_spree_loyalty_points:install
|
34
|
+
```
|
35
|
+
|
36
|
+
## How it works
|
37
|
+
|
38
|
+
* To add loyalty point feature, Spree admin needs to create "Loyalty Points" payment method
|
39
|
+
|
40
|
+
Configuration -> Payment Methods -> New payment Method
|
41
|
+
|
42
|
+
You need to select "Spree::PaymentMethod::LoyaltyPoints" as a Provider
|
43
|
+
|
44
|
+
* Spree admin need to make following settings on Configuration page:
|
45
|
+
|
46
|
+
- **Set minimum amount of an order that should be spent by the customer to earn loyalty points** - On the basis of this, it will be decided by the system whether loyalty points should be awarded for the order or not, based on the order value.
|
47
|
+
|
48
|
+
- **Set number of loyalty points to be awarded per unit amount spent** - On the basis of this configuration, number of loyalty points to be awarded are calculated based on the order value and per unit value defined. The loyalty points get added to the customer's account which he can use in future purchases.
|
49
|
+
|
50
|
+
Ex. Suppose Admin set this value this value to 1, it means, Customer will receive 1 Loyalty Point on spending every $1 on this site.
|
51
|
+
|
52
|
+
In order to receive loyalty points the payment should be done through some other
|
53
|
+
mode like Credit card, Debit card, Cash, Check etc.
|
54
|
+
|
55
|
+
No loyalty points are awarded for purchase done through existing Loyalty point
|
56
|
+
balance
|
57
|
+
|
58
|
+
- **Set minimum loyalty points balance required for redeeming** - On the basis of this, Customer will be permitted to make a payment with Loyalty Points only if he/she has Loyalty Points balance more than the minimum loyalty points set by the Admin.
|
59
|
+
|
60
|
+
- **Set Loyalty Point to Amount conversion rate** - This conversion rate converts the loyalty points into amount. This amount is displayed on the checkout screen with Loyalty Points balance.
|
61
|
+
|
62
|
+
- **Set Time to award Loyalty Points after payment** - Loyalty Points will be credited to the Customer's account on the basis of this set time period. This time period will be considered only after Customer makes the payment and Admin marks this payment "Capture".
|
63
|
+
|
64
|
+
**This field is provided to curb the misuse of the Loyalty Points by the customers. So, we suggest to set this time on the basis of the merchant's "Return Policy".**
|
65
|
+
|
66
|
+
[Rest of the content remains the same until the Credits section...]
|
67
|
+
|
68
|
+
## License
|
69
|
+
|
70
|
+
This extension is available as open source under the terms of either:
|
71
|
+
* [GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
72
|
+
* [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause)
|
73
|
+
|
74
|
+
## Credits
|
75
|
+
|
76
|
+
Originally created by Vinsol.
|
77
|
+
Fork maintained by [be agile Co., Ltd.](https://be-agile.jp/).
|
@@ -0,0 +1,81 @@
|
|
1
|
+
function SelectOptionChange(selectBoxId, containerId, heading) {
|
2
|
+
var that = this;
|
3
|
+
this.selectBoxId = selectBoxId;
|
4
|
+
this.containerId = containerId;
|
5
|
+
$('#' + this.selectBoxId).on('change', function(event) {
|
6
|
+
var appendPath = event.target.value;
|
7
|
+
$.ajax({
|
8
|
+
url: $(this).attr('data-transactions-link') + "/order_transactions/" + appendPath,
|
9
|
+
dataType: "json",
|
10
|
+
success: function(responseData, returnStatus, xhr) {
|
11
|
+
var $mainContainer = $('#' + containerId);
|
12
|
+
// Remove table for last order transactions
|
13
|
+
$mainContainer.html("");
|
14
|
+
// Add heading
|
15
|
+
$mainContainer.append($('<h5>' + heading + '</h5>'));
|
16
|
+
// Parse response data from ajax
|
17
|
+
that.displayData(responseData, $mainContainer);
|
18
|
+
}
|
19
|
+
});
|
20
|
+
});
|
21
|
+
}
|
22
|
+
$(document).ready(function() {
|
23
|
+
var orderSelection = new SelectOptionChange('loyalty_points_transaction_source_id', 'loyalty-points-order-transactions', 'Loyalty Point Transactions')
|
24
|
+
orderSelection.displayData = function(responseData, container) {
|
25
|
+
var $table = this.appendTable(container);
|
26
|
+
this.appendHeaderRow($table);
|
27
|
+
var $tableBody = this.appendTableBody($table);
|
28
|
+
|
29
|
+
$.each(responseData, function(index, transaction) {
|
30
|
+
var $transactionRow = this.appendTableRow($tableBody);
|
31
|
+
transaction_date = new Date(transaction['updated_at']).toLocaleString();
|
32
|
+
this.appendTableRowData(transaction_date, $transactionRow);
|
33
|
+
this.appendSourceReference(transaction['source_type'], transaction['source']['number'], $transactionRow);
|
34
|
+
this.appendTableRowData(transaction['comment'], $transactionRow);
|
35
|
+
this.appendTableRowData(transaction['transaction_type'], $transactionRow);
|
36
|
+
this.appendTableRowData(transaction['loyalty_points'], $transactionRow);
|
37
|
+
this.appendTableRowData(transaction['balance'], $transactionRow);
|
38
|
+
}.bind(this));
|
39
|
+
}
|
40
|
+
orderSelection.appendTable = function(container) {
|
41
|
+
return $('<table></table>').addClass("table").appendTo(container);
|
42
|
+
}
|
43
|
+
orderSelection.appendHeaderRow = function(container) {
|
44
|
+
$tableHead = this.appendTableHeader(container);
|
45
|
+
$tableHeadRow = this.appendTableRow($tableHead);
|
46
|
+
this.appendHeadData('Date', $tableHeadRow);
|
47
|
+
this.appendHeadData('Source', $tableHeadRow);
|
48
|
+
this.appendHeadData('Source Reference', $tableHeadRow);
|
49
|
+
this.appendHeadData('Comment', $tableHeadRow);
|
50
|
+
this.appendHeadData('Transaction Type', $tableHeadRow);
|
51
|
+
this.appendHeadData('Loyalty Points', $tableHeadRow);
|
52
|
+
this.appendHeadData('Updated Balance', $tableHeadRow);
|
53
|
+
}
|
54
|
+
orderSelection.appendTableBody = function(container) {
|
55
|
+
return $('<tbody></tbody>').appendTo(container);
|
56
|
+
}
|
57
|
+
orderSelection.appendTableHeader = function(container) {
|
58
|
+
return $('<thead></thead>').appendTo(container);
|
59
|
+
}
|
60
|
+
orderSelection.appendTableRow = function(container) {
|
61
|
+
return $('<tr></tr>').appendTo(container);
|
62
|
+
}
|
63
|
+
orderSelection.appendTableRowData = function(content, container) {
|
64
|
+
if (!content) {
|
65
|
+
content = "";
|
66
|
+
}
|
67
|
+
return $('<td>' + content + '</td>').appendTo(container);
|
68
|
+
}
|
69
|
+
orderSelection.appendHeadData = function(content, container) {
|
70
|
+
return $('<th>' + content + '</th>').appendTo(container);
|
71
|
+
}
|
72
|
+
orderSelection.appendSourceReference = function(source_type, source_reference, container) {
|
73
|
+
this.appendTableRowData(source_type.replace('Spree::', ''), container);
|
74
|
+
if (source_type == "Spree::Order") {
|
75
|
+
$("<td><a href='/admin/orders/" + source_reference + "/edit'>" + source_reference + "</a></td>").appendTo(container);
|
76
|
+
}
|
77
|
+
else {
|
78
|
+
this.appendTableRowData(source_reference, container);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
});
|
File without changes
|
File without changes
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Spree
|
2
|
+
module Admin
|
3
|
+
class LoyaltyPointsTransactionsController < ResourceController
|
4
|
+
belongs_to Spree.user_class.to_s.underscore
|
5
|
+
before_action :set_user, only: :order_transactions
|
6
|
+
before_action :set_ordered_transactions, only: :index
|
7
|
+
|
8
|
+
def order_transactions
|
9
|
+
order = Spree::Order.find_by(id: params[:order_id])
|
10
|
+
@loyalty_points_transactions = @user.loyalty_points_transactions.for_order(order).includes(:source).order(updated_at: :desc)
|
11
|
+
respond_with @loyalty_points_transactions
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
invoke_callbacks(:create, :before)
|
16
|
+
@object.attributes = loyalty_points_transaction_params
|
17
|
+
if @object.save
|
18
|
+
invoke_callbacks(:create, :after)
|
19
|
+
respond_with(@object) do |format|
|
20
|
+
format.html { redirect_to location_after_save, success: flash_message_for(@object, :successfully_created) }
|
21
|
+
format.js { render layout: false }
|
22
|
+
end
|
23
|
+
else
|
24
|
+
invoke_callbacks(:create, :fails)
|
25
|
+
respond_with(@object) do |format|
|
26
|
+
format.html { render action: :new }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def set_user
|
34
|
+
unless @user = Spree.user_class.find_by(id: params[:user_id])
|
35
|
+
redirect_to spree.admin_users_path, notice: flash_message_for(Spree.user_class.new, :not_found)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def loyalty_points_transaction_params
|
40
|
+
params.require(:loyalty_points_transaction).permit(:loyalty_points, :type, :comment, :source_id, :source_type)
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_resource
|
44
|
+
if params[:loyalty_points_transaction].present? && params[:loyalty_points_transaction][:type].present?
|
45
|
+
parent.send(association_name(params[:loyalty_points_transaction][:type])).build
|
46
|
+
else
|
47
|
+
parent.send(controller_name).build
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def association_name(klass)
|
52
|
+
klass.gsub('Spree::', '').pluralize.underscore
|
53
|
+
end
|
54
|
+
|
55
|
+
def collection_url
|
56
|
+
if (parent_data.present? && @parent.nil?) || parent_data.blank?
|
57
|
+
spree.admin_users_url
|
58
|
+
else
|
59
|
+
spree.admin_user_loyalty_points_url(parent)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_ordered_transactions
|
64
|
+
@loyalty_points_transactions = @loyalty_points_transactions.order(updated_at: :desc).
|
65
|
+
page(params[:page]).
|
66
|
+
per(params[:per_page] || Spree::Backend::Config[:admin_orders_per_page])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Spree
|
2
|
+
module Admin
|
3
|
+
module ResourceControllerDecorator
|
4
|
+
protected
|
5
|
+
|
6
|
+
def parent
|
7
|
+
if parent_data.is_a?(Hash) && parent_data[:model_class].present?
|
8
|
+
@parent ||= parent_data[:model_class].send("find_by_#{parent_data[:find_by]}", params["#{resource.model_name}_id"])
|
9
|
+
raise ActiveRecord::RecordNotFound unless @parent
|
10
|
+
instance_variable_set("@#{resource.model_name}", @parent)
|
11
|
+
else
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def resource_not_found
|
17
|
+
flash[:error] = flash_message_for(model_class.new, :not_found)
|
18
|
+
flash[:error] = flash_message_for(parent_data[:model_class].new, :not_found) if parent_data.present? && @parent.nil?
|
19
|
+
redirect_to collection_url
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Spree::Admin::ResourceController.prepend(Spree::Admin::ResourceControllerDecorator)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Spree
|
2
|
+
module Admin
|
3
|
+
module ReturnAuthorizationsControllerDecorator
|
4
|
+
def self.prepended(base)
|
5
|
+
base.before_action :set_loyalty_points_transactions, only: [:new, :edit, :create, :update]
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def set_loyalty_points_transactions
|
11
|
+
@loyalty_points_transactions = @return_authorization.order.loyalty_points_transactions.
|
12
|
+
page(params[:page]).
|
13
|
+
per(params[:per_page] || Spree::Backend::Config[:admin_orders_per_page])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Spree::Admin::ReturnAuthorizationsController.prepend(Spree::Admin::ReturnAuthorizationsControllerDecorator)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Spree
|
2
|
+
module CheckoutControllerDecorator
|
3
|
+
def self.prepended(base)
|
4
|
+
base.before_action :sufficient_loyalty_points, only: [:update], if: -> { params[:state] == 'payment' }
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def sufficient_loyalty_points
|
10
|
+
payment_method_ids = params[:order][:payments_attributes].collect do |payment|
|
11
|
+
payment["payment_method_id"]
|
12
|
+
end
|
13
|
+
if Spree::PaymentMethod.loyalty_points_id_included?(payment_method_ids) && !@order.user.has_sufficient_loyalty_points?(@order)
|
14
|
+
flash[:error] = Spree.t(:insufficient_loyalty_points)
|
15
|
+
redirect_to spree.checkout_state_path(@order.state)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Spree::CheckoutController.prepend Spree::CheckoutControllerDecorator if Spree::CheckoutController.included_modules.exclude?(Spree::CheckoutControllerDecorator)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Spree
|
2
|
+
class LoyaltyPointsController < StoreController
|
3
|
+
|
4
|
+
def index
|
5
|
+
@loyalty_points_transactions = spree_current_user.loyalty_points_transactions.includes(:source).order(updated_at: :desc).
|
6
|
+
page(params[:page]).
|
7
|
+
per(params[:per_page] || Spree::Backend::Config[:admin_orders_per_page])
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
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::Store.default.loyalty_points_awarding_unit).floor
|
10
|
+
elsif purpose == 'redeem'
|
11
|
+
(amount / Spree::Store.default.loyalty_points_conversion_rate).ceil
|
12
|
+
else
|
13
|
+
0
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def eligible_for_loyalty_points?(amount)
|
18
|
+
amount >= Spree::Store.default.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
|
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::Store.default.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,48 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class Payment
|
5
|
+
module LoyaltyPoints
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
scope :by_loyalty_points, -> { joins(:payment_method).readonly(false).where(spree_payment_methods: { type: 'Spree::PaymentMethod::LoyaltyPoints'}) }
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
def any_with_loyalty_points?
|
15
|
+
by_loyalty_points.size != 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def redeem_loyalty_points
|
22
|
+
loyalty_points_redeemed = loyalty_points_for(amount, 'redeem')
|
23
|
+
order.create_debit_transaction(loyalty_points_redeemed)
|
24
|
+
end
|
25
|
+
|
26
|
+
def return_loyalty_points
|
27
|
+
loyalty_points_redeemed = loyalty_points_for(amount, 'redeem')
|
28
|
+
order.create_credit_transaction(loyalty_points_redeemed)
|
29
|
+
end
|
30
|
+
|
31
|
+
def by_loyalty_points?
|
32
|
+
payment_method.type == "Spree::PaymentMethod::LoyaltyPoints"
|
33
|
+
end
|
34
|
+
|
35
|
+
def redeemable_loyalty_points_balance?
|
36
|
+
order.user.loyalty_points_balance >= Spree::Store.default.loyalty_points_redeeming_balance
|
37
|
+
end
|
38
|
+
|
39
|
+
def redeemable_user_balance
|
40
|
+
unless redeemable_loyalty_points_balance?
|
41
|
+
min_balance = Spree::Store.default.loyalty_points_redeeming_balance
|
42
|
+
errors.add :loyalty_points_balance, "should be atleast #{ min_balance.to_s + " " + "point".pluralize(min_balance) } for redeeming Loyalty Points"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
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,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,42 @@
|
|
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, class_name: "::#{Spree.user_class}"
|
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
|
+
scope :for_order, ->(order) { where(source: order) }
|
16
|
+
|
17
|
+
before_create :generate_transaction_id
|
18
|
+
|
19
|
+
def transaction_type
|
20
|
+
CLASS_TO_TRANSACTION_TYPE[type]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def source_or_comment_present
|
26
|
+
unless source.present? || comment.present?
|
27
|
+
errors.add :base, 'Source or Comment should be present'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def generate_transaction_id
|
32
|
+
begin
|
33
|
+
self.transaction_id = (Time.current.strftime("%s") + rand(999999).to_s).to(15)
|
34
|
+
end while Spree::LoyaltyPointsTransaction.where(transaction_id: transaction_id).present?
|
35
|
+
end
|
36
|
+
|
37
|
+
def transactions_total_range
|
38
|
+
validate_transactions_total_range(transaction_type, source)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Spree
|
2
|
+
module OrderDecorator
|
3
|
+
def self.prepended(base)
|
4
|
+
base.include Spree::LoyaltyPoints
|
5
|
+
base.include Spree::Order::LoyaltyPoints
|
6
|
+
|
7
|
+
base.has_many :loyalty_points_transactions, as: :source
|
8
|
+
base.has_many :loyalty_points_credit_transactions, as: :source
|
9
|
+
base.has_many :loyalty_points_debit_transactions, as: :source
|
10
|
+
|
11
|
+
base.scope :loyalty_points_not_awarded, -> {
|
12
|
+
includes(:loyalty_points_credit_transactions).where(spree_loyalty_points_transactions: { source_id: nil })
|
13
|
+
}
|
14
|
+
|
15
|
+
base.scope :with_hours_since_payment, ->(num) {
|
16
|
+
where('`spree_orders`.`paid_at` < ? ', num.hours.ago)
|
17
|
+
}
|
18
|
+
|
19
|
+
base.scope :with_uncredited_loyalty_points, ->(num) {
|
20
|
+
with_hours_since_payment(num).loyalty_points_not_awarded
|
21
|
+
}
|
22
|
+
|
23
|
+
# FSM transitions
|
24
|
+
base.state_machines[:state].after_transition(
|
25
|
+
from: base.state_machines[:state].states.map(&:name) - [:complete],
|
26
|
+
to: [:complete],
|
27
|
+
do: :complete_loyalty_points_payments
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Spree::Order.prepend(Spree::OrderDecorator)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Spree
|
2
|
+
module PaymentDecorator
|
3
|
+
def self.prepended(base)
|
4
|
+
base.include Spree::LoyaltyPoints
|
5
|
+
base.include Spree::Payment::LoyaltyPoints
|
6
|
+
|
7
|
+
base.validates :amount, numericality: { greater_than: 0 }, if: :by_loyalty_points?
|
8
|
+
base.validate :redeemable_user_balance, if: :by_loyalty_points?
|
9
|
+
base.scope :state_not, ->(s) { where('state != ?', s) }
|
10
|
+
|
11
|
+
fsm = base.state_machines[:state]
|
12
|
+
fsm.after_transition from: fsm.states.map(&:name) - [:completed], to: [:completed], do: :notify_paid_order
|
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
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def invalidate_old_payments
|
20
|
+
return if store_credit? || by_loyalty_points?
|
21
|
+
order.payments.with_state('checkout').where("id != ?", self.id).each do |payment|
|
22
|
+
payment.invalidate! unless payment.store_credit?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def notify_paid_order
|
27
|
+
if all_payments_completed?
|
28
|
+
order.touch :paid_at
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def all_payments_completed?
|
33
|
+
order.payments.state_not('invalid').all? { |payment| payment.completed? }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Spree::Payment.prepend Spree::PaymentDecorator if Spree::Payment.included_modules.exclude?(Spree::PaymentDecorator)
|