referral_box 0.1.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/CHANGELOG.md +27 -0
- data/LICENSE.txt +21 -0
- data/README.md +194 -0
- data/app/assets/javascripts/referral_box.js +138 -0
- data/app/controllers/referral_box/dashboard_controller.rb +91 -0
- data/app/controllers/referral_box/tracking_controller.rb +30 -0
- data/app/helpers/referral_box/application_helper.rb +56 -0
- data/app/helpers/referral_box/dashboard_helper.rb +41 -0
- data/app/views/layouts/referral_box/dashboard.html.erb +233 -0
- data/config/routes.rb +14 -0
- data/db/migrate/001_create_referral_box_transactions.rb +21 -0
- data/db/migrate/002_create_referral_box_referral_logs.rb +28 -0
- data/lib/generators/referral_box/install/install_generator.rb +45 -0
- data/lib/generators/referral_box/install/templates/initializer.rb +63 -0
- data/lib/generators/referral_box/install/templates/user_model.rb +45 -0
- data/lib/referral_box/configuration.rb +32 -0
- data/lib/referral_box/engine.rb +31 -0
- data/lib/referral_box/models/referral_log.rb +109 -0
- data/lib/referral_box/models/transaction.rb +39 -0
- data/lib/referral_box/version.rb +5 -0
- data/lib/referral_box.rb +191 -0
- metadata +233 -0
data/lib/referral_box.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
require "active_record"
|
5
|
+
require "action_view"
|
6
|
+
require "action_pack"
|
7
|
+
|
8
|
+
require_relative "referral_box/version"
|
9
|
+
require_relative "referral_box/engine"
|
10
|
+
require_relative "referral_box/configuration"
|
11
|
+
require_relative "referral_box/models/transaction"
|
12
|
+
require_relative "referral_box/models/referral_log"
|
13
|
+
|
14
|
+
module ReferralBox
|
15
|
+
class << self
|
16
|
+
def configure
|
17
|
+
yield configuration
|
18
|
+
end
|
19
|
+
|
20
|
+
def configuration
|
21
|
+
@configuration ||= Configuration.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def earn_points(user, amount, event: nil)
|
25
|
+
return false unless user && amount.positive?
|
26
|
+
|
27
|
+
# Apply reward modifier based on tier
|
28
|
+
modifier = configuration.reward_modifier&.call(user) || 1.0
|
29
|
+
final_amount = (amount * modifier).round
|
30
|
+
|
31
|
+
# Calculate expiration date
|
32
|
+
expiry_days = configuration.points_expiry_days
|
33
|
+
expires_at = expiry_days ? Time.current + expiry_days.days : nil
|
34
|
+
|
35
|
+
# Create transaction
|
36
|
+
transaction = Transaction.create!(
|
37
|
+
user: user,
|
38
|
+
points: final_amount,
|
39
|
+
transaction_type: "earn",
|
40
|
+
event_data: event&.as_json,
|
41
|
+
expires_at: expires_at,
|
42
|
+
description: "Earned #{final_amount} points"
|
43
|
+
)
|
44
|
+
|
45
|
+
# Update user tier if needed
|
46
|
+
update_user_tier(user)
|
47
|
+
|
48
|
+
transaction
|
49
|
+
rescue => e
|
50
|
+
Rails.logger.error "ReferralBox: Failed to earn points: #{e.message}"
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
def redeem_points(user, points, offer: nil)
|
55
|
+
return false unless user && points.positive?
|
56
|
+
|
57
|
+
current_balance = balance(user)
|
58
|
+
return false if current_balance < points
|
59
|
+
|
60
|
+
# Calculate cost using custom rule if provided
|
61
|
+
cost = if configuration.redeem_rule
|
62
|
+
configuration.redeem_rule.call(user, offer)
|
63
|
+
else
|
64
|
+
points
|
65
|
+
end
|
66
|
+
|
67
|
+
# Create transaction
|
68
|
+
transaction = Transaction.create!(
|
69
|
+
user: user,
|
70
|
+
points: -cost,
|
71
|
+
transaction_type: "redeem",
|
72
|
+
offer_data: offer&.as_json,
|
73
|
+
description: "Redeemed #{cost} points"
|
74
|
+
)
|
75
|
+
|
76
|
+
transaction
|
77
|
+
rescue => e
|
78
|
+
Rails.logger.error "ReferralBox: Failed to redeem points: #{e.message}"
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
def balance(user)
|
83
|
+
return 0 unless user
|
84
|
+
|
85
|
+
Transaction.where(user: user)
|
86
|
+
.where("expires_at IS NULL OR expires_at > ?", Time.current)
|
87
|
+
.sum(:points)
|
88
|
+
end
|
89
|
+
|
90
|
+
def tier(user)
|
91
|
+
return nil unless user && configuration.tier_thresholds.present?
|
92
|
+
|
93
|
+
balance = self.balance(user)
|
94
|
+
thresholds = configuration.tier_thresholds
|
95
|
+
|
96
|
+
# Find the highest tier the user qualifies for
|
97
|
+
thresholds.sort_by { |_, points| points }.reverse.each do |tier_name, required_points|
|
98
|
+
return tier_name if balance >= required_points
|
99
|
+
end
|
100
|
+
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def track_referral(ref_code:, user_agent: nil, ip_address: nil, referrer: nil, device_data: nil)
|
105
|
+
return false unless ref_code.present?
|
106
|
+
|
107
|
+
# Extract device and geo data from device_data parameter
|
108
|
+
device_type = device_data&.dig("device_type")
|
109
|
+
browser = device_data&.dig("browser")
|
110
|
+
geo_data = device_data&.dig("geo_data")
|
111
|
+
device_info = device_data&.except("device_type", "browser", "geo_data", "collected_at")
|
112
|
+
|
113
|
+
ReferralLog.create!(
|
114
|
+
referral_code: ref_code,
|
115
|
+
user_agent: user_agent,
|
116
|
+
ip_address: ip_address,
|
117
|
+
referrer: referrer,
|
118
|
+
clicked_at: Time.current,
|
119
|
+
device_type: device_type,
|
120
|
+
browser: browser,
|
121
|
+
geo_data: geo_data,
|
122
|
+
device_data: device_info
|
123
|
+
)
|
124
|
+
rescue => e
|
125
|
+
Rails.logger.error "ReferralBox: Failed to track referral: #{e.message}"
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
def process_referral_signup(referee, ref_code)
|
130
|
+
return false unless referee && ref_code.present?
|
131
|
+
|
132
|
+
# Find the referral log
|
133
|
+
referral_log = ReferralLog.where(referral_code: ref_code)
|
134
|
+
.where(referee_id: nil)
|
135
|
+
.order(clicked_at: :desc)
|
136
|
+
.first
|
137
|
+
|
138
|
+
return false unless referral_log
|
139
|
+
|
140
|
+
# Find the referrer
|
141
|
+
referrer = find_user_by_referral_code(ref_code)
|
142
|
+
return false unless referrer
|
143
|
+
|
144
|
+
# Update referral log
|
145
|
+
referral_log.update!(
|
146
|
+
referee: referee,
|
147
|
+
signed_up_at: Time.current
|
148
|
+
)
|
149
|
+
|
150
|
+
# Update referee's referrer
|
151
|
+
referee.update!(referrer: referrer)
|
152
|
+
|
153
|
+
# Process referral rewards
|
154
|
+
if configuration.referral_reward
|
155
|
+
configuration.referral_reward.call(referrer, referee)
|
156
|
+
end
|
157
|
+
|
158
|
+
true
|
159
|
+
rescue => e
|
160
|
+
Rails.logger.error "ReferralBox: Failed to process referral signup: #{e.message}"
|
161
|
+
false
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def update_user_tier(user)
|
167
|
+
old_tier = user.tier
|
168
|
+
new_tier = tier(user)
|
169
|
+
|
170
|
+
return if old_tier == new_tier
|
171
|
+
|
172
|
+
# Update user's tier in database
|
173
|
+
user.update!(tier: new_tier)
|
174
|
+
|
175
|
+
# Call tier change callback if configured
|
176
|
+
if configuration.on_tier_changed
|
177
|
+
configuration.on_tier_changed.call(user, old_tier, new_tier)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def find_user_by_referral_code(code)
|
182
|
+
return nil unless configuration.reference_class_name
|
183
|
+
|
184
|
+
user_class = configuration.reference_class_name.constantize
|
185
|
+
user_class.find_by(referral_code: code)
|
186
|
+
rescue NameError
|
187
|
+
Rails.logger.error "ReferralBox: User class '#{configuration.reference_class_name}' not found"
|
188
|
+
nil
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
metadata
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: referral_box
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kapil Dev Pal
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-07-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 6.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '6.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 6.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activerecord
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '6.0'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 6.0.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '6.0'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 6.0.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: actionview
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '6.0'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 6.0.0
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '6.0'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 6.0.0
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: actionpack
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '6.0'
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 6.0.0
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '6.0'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 6.0.0
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: kaminari
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '1.2'
|
100
|
+
type: :runtime
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - "~>"
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '1.2'
|
107
|
+
- !ruby/object:Gem::Dependency
|
108
|
+
name: rspec-rails
|
109
|
+
requirement: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '6.0'
|
114
|
+
type: :development
|
115
|
+
prerelease: false
|
116
|
+
version_requirements: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - "~>"
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '6.0'
|
121
|
+
- !ruby/object:Gem::Dependency
|
122
|
+
name: factory_bot_rails
|
123
|
+
requirement: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - "~>"
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '6.0'
|
128
|
+
type: :development
|
129
|
+
prerelease: false
|
130
|
+
version_requirements: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - "~>"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '6.0'
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: pry-byebug
|
137
|
+
requirement: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - "~>"
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '3.9'
|
142
|
+
type: :development
|
143
|
+
prerelease: false
|
144
|
+
version_requirements: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - "~>"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '3.9'
|
149
|
+
- !ruby/object:Gem::Dependency
|
150
|
+
name: rubocop
|
151
|
+
requirement: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - "~>"
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '1.0'
|
156
|
+
type: :development
|
157
|
+
prerelease: false
|
158
|
+
version_requirements: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - "~>"
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '1.0'
|
163
|
+
- !ruby/object:Gem::Dependency
|
164
|
+
name: rubocop-rails
|
165
|
+
requirement: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - "~>"
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '2.0'
|
170
|
+
type: :development
|
171
|
+
prerelease: false
|
172
|
+
version_requirements: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - "~>"
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '2.0'
|
177
|
+
description: Add loyalty points, dynamic tier levels, and referral rewards to any
|
178
|
+
Rails application with ease. Features include customizable earning rules, unique
|
179
|
+
referral codes, built-in analytics, and an admin dashboard.
|
180
|
+
email:
|
181
|
+
- dev.kapildevpal@gmail.com
|
182
|
+
executables: []
|
183
|
+
extensions: []
|
184
|
+
extra_rdoc_files: []
|
185
|
+
files:
|
186
|
+
- CHANGELOG.md
|
187
|
+
- LICENSE.txt
|
188
|
+
- README.md
|
189
|
+
- app/assets/javascripts/referral_box.js
|
190
|
+
- app/controllers/referral_box/dashboard_controller.rb
|
191
|
+
- app/controllers/referral_box/tracking_controller.rb
|
192
|
+
- app/helpers/referral_box/application_helper.rb
|
193
|
+
- app/helpers/referral_box/dashboard_helper.rb
|
194
|
+
- app/views/layouts/referral_box/dashboard.html.erb
|
195
|
+
- config/routes.rb
|
196
|
+
- db/migrate/001_create_referral_box_transactions.rb
|
197
|
+
- db/migrate/002_create_referral_box_referral_logs.rb
|
198
|
+
- lib/generators/referral_box/install/install_generator.rb
|
199
|
+
- lib/generators/referral_box/install/templates/initializer.rb
|
200
|
+
- lib/generators/referral_box/install/templates/user_model.rb
|
201
|
+
- lib/referral_box.rb
|
202
|
+
- lib/referral_box/configuration.rb
|
203
|
+
- lib/referral_box/engine.rb
|
204
|
+
- lib/referral_box/models/referral_log.rb
|
205
|
+
- lib/referral_box/models/transaction.rb
|
206
|
+
- lib/referral_box/version.rb
|
207
|
+
homepage: https://github.com/KapilDevPal/referral_box
|
208
|
+
licenses:
|
209
|
+
- MIT
|
210
|
+
metadata:
|
211
|
+
source_code_uri: https://github.com/KapilDevPal/referral_box
|
212
|
+
changelog_uri: https://github.com/KapilDevPal/referral_box/blob/main/CHANGELOG.md
|
213
|
+
rubygems_mfa_required: 'true'
|
214
|
+
post_install_message:
|
215
|
+
rdoc_options: []
|
216
|
+
require_paths:
|
217
|
+
- lib
|
218
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: 2.7.0
|
223
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
224
|
+
requirements:
|
225
|
+
- - ">="
|
226
|
+
- !ruby/object:Gem::Version
|
227
|
+
version: '0'
|
228
|
+
requirements: []
|
229
|
+
rubygems_version: 3.5.11
|
230
|
+
signing_key:
|
231
|
+
specification_version: 4
|
232
|
+
summary: A flexible Ruby gem for building loyalty and referral systems in Rails apps
|
233
|
+
test_files: []
|