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.
@@ -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: []