usage_credits 0.2.0 → 0.3.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 +4 -4
- data/Appraisals +22 -4
- data/CHANGELOG.md +9 -0
- data/README.md +74 -15
- data/gemfiles/pay_10.0.gemfile +34 -22
- data/gemfiles/pay_11.0.gemfile +34 -22
- data/gemfiles/pay_8.3.gemfile +34 -22
- data/gemfiles/pay_9.0.gemfile +34 -22
- data/gemfiles/rails_7.2.gemfile +41 -0
- data/gemfiles/rails_8.1.gemfile +41 -0
- data/lib/generators/usage_credits/templates/initializer.rb +49 -9
- data/lib/usage_credits/callback_context.rb +28 -0
- data/lib/usage_credits/callbacks.rb +67 -0
- data/lib/usage_credits/configuration.rb +67 -1
- data/lib/usage_credits/models/concerns/pay_charge_extension.rb +40 -3
- data/lib/usage_credits/models/concerns/pay_subscription_extension.rb +89 -48
- data/lib/usage_credits/models/credit_pack.rb +51 -4
- data/lib/usage_credits/models/wallet.rb +55 -21
- data/lib/usage_credits/version.rb +1 -1
- data/lib/usage_credits.rb +19 -2
- metadata +6 -100
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e1f36f964eea89e835d789f34b6b2ce8994940896d21652af2400282c0e24a0
|
|
4
|
+
data.tar.gz: ad7c6be299aee7f89929841bfe18a6f44e2dd91361afe30ff238980bdc75544b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4047f5a333b5e1359468468927100ea71a5c5b7117d8eec9e73a81ad8b40c243d796dd3d0e37604c9fd46b9b44239612d5bc1b30176e4322364312cc1c900017
|
|
7
|
+
data.tar.gz: b21b55c0b524b18fcf47339b926a425f53fcca5a3148f221bc8e86e4976f1281425041edd1d9cc903b68ab6cf82dd38a1a97df84373d1658d021cc43b3308de0
|
data/Appraisals
CHANGED
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Test
|
|
3
|
+
# Test minimum supported Rails version (with latest Pay)
|
|
4
|
+
appraise "rails-7.2" do
|
|
5
|
+
gem "rails", "~> 7.2.0"
|
|
6
|
+
gem "pay", "~> 11.0"
|
|
7
|
+
gem "stripe", "~> 18.0"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Test latest Rails version (with latest Pay) - this is the default/main Gemfile anyway
|
|
11
|
+
appraise "rails-8.1" do
|
|
12
|
+
gem "rails", "~> 8.1.0"
|
|
13
|
+
gem "pay", "~> 11.0"
|
|
14
|
+
gem "stripe", "~> 18.0"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Test minimum supported Pay version (with latest Rails)
|
|
4
18
|
appraise "pay-8.3" do
|
|
5
19
|
gem "pay", "~> 8.3.0"
|
|
6
20
|
gem "stripe", "~> 13.0"
|
|
21
|
+
gem "rails", "~> 8.1.0"
|
|
7
22
|
end
|
|
8
23
|
|
|
9
|
-
# Test
|
|
24
|
+
# Test Pay 9.0 (popular stable version with latest Rails)
|
|
10
25
|
appraise "pay-9.0" do
|
|
11
26
|
gem "pay", "~> 9.0.0"
|
|
12
27
|
gem "stripe", "~> 13.0"
|
|
28
|
+
gem "rails", "~> 8.1.0"
|
|
13
29
|
end
|
|
14
30
|
|
|
15
|
-
# Test
|
|
31
|
+
# Test Pay 10.0 (with latest Rails)
|
|
16
32
|
appraise "pay-10.0" do
|
|
17
33
|
gem "pay", "~> 10.0.0"
|
|
18
34
|
gem "stripe", "~> 15.0"
|
|
35
|
+
gem "rails", "~> 8.1.0"
|
|
19
36
|
end
|
|
20
37
|
|
|
21
|
-
# Test
|
|
38
|
+
# Test latest Pay version (with latest Rails)
|
|
22
39
|
appraise "pay-11.0" do
|
|
23
40
|
gem "pay", "~> 11.0"
|
|
24
41
|
gem "stripe", "~> 18.0"
|
|
42
|
+
gem "rails", "~> 8.1.0"
|
|
25
43
|
end
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [0.3.0] - 2026-01-15
|
|
2
|
+
|
|
3
|
+
- Add lifecycle callbacks by @rameerez in https://github.com/rameerez/usage_credits/pull/25
|
|
4
|
+
- Fix credit pack fulfillment not working with Pay 10+ (Stripe data in `object` vs `data` in `Pay::Charge`) by @rameerez in https://github.com/rameerez/usage_credits/pull/26
|
|
5
|
+
|
|
6
|
+
## [0.2.1] - 2026-01-15
|
|
7
|
+
|
|
8
|
+
- Add custom `create_checkout_session` options (like `success_url`) to credit pack purchases by @yshmarov in https://github.com/rameerez/usage_credits/pull/5
|
|
9
|
+
|
|
1
10
|
## [0.2.0] - 2025-12-29
|
|
2
11
|
|
|
3
12
|
- Add Claude Code GitHub Workflow by @rameerez in https://github.com/rameerez/usage_credits/pull/14
|
data/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# 💳✨ `usage_credits` - Add usage-based credits to your Rails app
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/usage_credits) [](https://github.com/rameerez/usage_credits/actions)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> [!TIP]
|
|
6
|
+
> **🚀 Ship your next Rails app 10x faster!** I've built **[RailsFast](https://railsfast.com)**, a production-ready Rails boilerplate template that comes with everything you need to launch a software business in days, not weeks.
|
|
7
|
+
|
|
8
|
+
`usage_credits` allows your users to have in-app credits / tokens they can use to perform operations.
|
|
6
9
|
|
|
7
10
|
✨ Perfect for SaaS, AI apps, games, and API products that want to implement usage-based pricing.
|
|
8
11
|
|
|
@@ -262,27 +265,83 @@ process_image(params) # If this fails, credits are already spent!
|
|
|
262
265
|
> If validation fails (e.g., file too large), both methods will raise `InvalidOperation`.
|
|
263
266
|
> Perform your operation inside the `spend_credits_on` block OR make the credit spend conditional to the actual operation, so users are not charged if the operation fails.
|
|
264
267
|
|
|
265
|
-
## Low balance alerts
|
|
268
|
+
## Low balance alerts & other lifecycle callbacks
|
|
269
|
+
|
|
270
|
+
`usage_credits` makes it easy to add callbacks for important events.
|
|
271
|
+
|
|
272
|
+
You can hook into credit events, for example, to upsell credit packs to users when they reach a low balance:
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
config.on_low_balance_reached do |ctx|
|
|
276
|
+
# Sell your users credits here
|
|
277
|
+
# Example: LowCreditsMailer.buy_more(ctx.owner, remaining: ctx.new_balance).deliver_later
|
|
278
|
+
end
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
> [!TIP]
|
|
282
|
+
> For `on_low_balance_reached`, define what a low balance is by setting the `low_balance_threshold` like: `config.low_balance_threshold = 100.credits`
|
|
283
|
+
|
|
284
|
+
You configure callbacks in your `usage_credits.rb` initializer file.
|
|
285
|
+
|
|
286
|
+
Here are all the available callbacks you can hook into:
|
|
287
|
+
|
|
288
|
+
- `on_credits_added`: After credits are added to a wallet
|
|
289
|
+
- `on_credits_deducted`: After credits are deducted from a wallet
|
|
290
|
+
- `on_low_balance_reached`: When balance drops below threshold (fires once per crossing)
|
|
291
|
+
- `on_balance_depleted`: When balance reaches exactly zero
|
|
292
|
+
- `on_insufficient_credits`: When an operation fails due to insufficient credits
|
|
293
|
+
- `on_credit_pack_purchased`: After a credit pack purchase is fulfilled
|
|
294
|
+
- `on_subscription_credits_awarded`: After subscription credits are awarded
|
|
266
295
|
|
|
267
|
-
|
|
296
|
+
They're useful for analytics, audit logging, notifications, or custom business logic.
|
|
297
|
+
|
|
298
|
+
Callbacks are isolated, so errors in callbacks won't break credit operations.
|
|
299
|
+
|
|
300
|
+
> [!IMPORTANT]
|
|
301
|
+
> Callbacks get executed every single time the action happens (duh) so you don't want to do heavy operations there, or else your app could become extremely slow. Keep callbacks fast: one good option is using the callback to enqueue background jobs (`deliver_later`, `perform_later`) to avoid blocking credit operations.
|
|
302
|
+
|
|
303
|
+
### The `ctx` object in callbacks
|
|
304
|
+
|
|
305
|
+
All callbacks receive a context object (`ctx`). The `ctx` objects contains useful fields like `ctx.owner`, `ctx.amount`, etc. Some examples:
|
|
268
306
|
|
|
269
307
|
```ruby
|
|
270
308
|
UsageCredits.configure do |config|
|
|
271
|
-
#
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
309
|
+
# Prompt users to buy more credits when they run out
|
|
310
|
+
config.on_balance_depleted do |ctx|
|
|
311
|
+
# Send a mail here like "You've ran out of credits! Purchase more here!"
|
|
312
|
+
OutOfCreditsMailer.buy_more(ctx.owner).deliver_later
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Log failed operations (useful for debugging)
|
|
316
|
+
config.on_insufficient_credits do |ctx|
|
|
317
|
+
Rails.logger.info "[Credits] User #{ctx.owner.id} needs #{ctx.amount}, has #{ctx.metadata[:available]}"
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Track purchases in your analytics (Mixpanel, Amplitude, Segment, etc.)
|
|
321
|
+
config.on_credit_pack_purchased do |ctx|
|
|
322
|
+
# Replace with your analytics service
|
|
323
|
+
# Log something like: "User #{ctx.owner.id} purchased #{ctx.amount} credits"
|
|
282
324
|
end
|
|
283
325
|
end
|
|
284
326
|
```
|
|
285
327
|
|
|
328
|
+
Available `ctx` fields vary by event:
|
|
329
|
+
|
|
330
|
+
- `credits_added`: `ctx.owner`, `ctx.wallet`, `ctx.amount`, `ctx.previous_balance`, `ctx.new_balance`, `ctx.transaction`, `ctx.category`, `ctx.metadata`
|
|
331
|
+
- `credits_deducted`: `ctx.owner`, `ctx.wallet`, `ctx.amount`, `ctx.previous_balance`, `ctx.new_balance`, `ctx.transaction`, `ctx.category`, `ctx.metadata`
|
|
332
|
+
- `low_balance_reached`: `ctx.owner`, `ctx.wallet`, `ctx.previous_balance`, `ctx.new_balance`, `ctx.threshold`
|
|
333
|
+
- `balance_depleted`: `ctx.owner`, `ctx.wallet`, `ctx.previous_balance`, `ctx.new_balance`
|
|
334
|
+
- `insufficient_credits`: `ctx.owner`, `ctx.wallet`, `ctx.amount`, `ctx.operation_name`, `ctx.metadata`
|
|
335
|
+
- `credit_pack_purchased`: `ctx.owner`, `ctx.wallet`, `ctx.amount`, `ctx.transaction`, `ctx.metadata`
|
|
336
|
+
- `subscription_credits_awarded`: `ctx.owner`, `ctx.wallet`, `ctx.amount`, `ctx.transaction`, `ctx.metadata`
|
|
337
|
+
|
|
338
|
+
All contexts support `ctx.to_h` to convert to a hash (excludes nil values).
|
|
339
|
+
|
|
340
|
+
**Metadata contents:**
|
|
341
|
+
- `insufficient_credits`: `{ available:, required:, params: }`
|
|
342
|
+
- `credit_pack_purchased`: `{ credit_pack_name:, credit_pack:, pay_charge:, price_cents: }`
|
|
343
|
+
- `subscription_credits_awarded`: `{ subscription_plan_name:, subscription:, pay_subscription:, fulfillment_period: }`
|
|
344
|
+
|
|
286
345
|
## Award bonus credits
|
|
287
346
|
|
|
288
347
|
You might want to award bonus credits to your users for arbitrary actions at any point, like referring a friend, completing signup, or any other reason.
|
data/gemfiles/pay_10.0.gemfile
CHANGED
|
@@ -2,28 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "
|
|
6
|
-
gem "mocha"
|
|
7
|
-
gem "simplecov", require: false
|
|
8
|
-
gem "standard"
|
|
9
|
-
gem "vcr"
|
|
10
|
-
gem "webmock"
|
|
11
|
-
gem "braintree", ">= 2.92.0"
|
|
12
|
-
gem "lemonsqueezy", "~> 1.0"
|
|
13
|
-
gem "paddle", "~> 2.6"
|
|
14
|
-
gem "stripe", "~> 15.0"
|
|
15
|
-
gem "prawn"
|
|
16
|
-
gem "receipts"
|
|
17
|
-
gem "sqlite3"
|
|
18
|
-
gem "pg"
|
|
19
|
-
gem "bootsnap", require: false
|
|
20
|
-
gem "puma"
|
|
21
|
-
gem "web-console", group: :development
|
|
22
|
-
gem "importmap-rails"
|
|
23
|
-
gem "sprockets-rails"
|
|
24
|
-
gem "stimulus-rails"
|
|
25
|
-
gem "turbo-rails"
|
|
26
|
-
gem "rdoc", ">= 7.0"
|
|
5
|
+
gem "rake", "~> 13.0"
|
|
27
6
|
gem "pay", "~> 10.0.0"
|
|
7
|
+
gem "stripe", "~> 15.0"
|
|
8
|
+
gem "rails", "~> 8.1.0"
|
|
9
|
+
|
|
10
|
+
group :development do
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "web-console"
|
|
13
|
+
gem "standard"
|
|
14
|
+
gem "rubocop", "~> 1.0"
|
|
15
|
+
gem "rubocop-minitest", "~> 0.35"
|
|
16
|
+
gem "rubocop-performance", "~> 1.0"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
group :test do
|
|
20
|
+
gem "minitest", "~> 5.0"
|
|
21
|
+
gem "mocha"
|
|
22
|
+
gem "simplecov", require: false
|
|
23
|
+
gem "vcr"
|
|
24
|
+
gem "webmock"
|
|
25
|
+
gem "braintree", ">= 2.92.0"
|
|
26
|
+
gem "lemonsqueezy", "~> 1.0"
|
|
27
|
+
gem "paddle", "~> 2.6"
|
|
28
|
+
gem "prawn"
|
|
29
|
+
gem "receipts"
|
|
30
|
+
gem "sqlite3"
|
|
31
|
+
gem "pg"
|
|
32
|
+
gem "bootsnap", require: false
|
|
33
|
+
gem "puma"
|
|
34
|
+
gem "importmap-rails"
|
|
35
|
+
gem "sprockets-rails"
|
|
36
|
+
gem "stimulus-rails"
|
|
37
|
+
gem "turbo-rails"
|
|
38
|
+
gem "rdoc", ">= 7.0"
|
|
39
|
+
end
|
|
28
40
|
|
|
29
41
|
gemspec path: "../"
|
data/gemfiles/pay_11.0.gemfile
CHANGED
|
@@ -2,28 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "
|
|
6
|
-
gem "mocha"
|
|
7
|
-
gem "simplecov", require: false
|
|
8
|
-
gem "standard"
|
|
9
|
-
gem "vcr"
|
|
10
|
-
gem "webmock"
|
|
11
|
-
gem "braintree", ">= 2.92.0"
|
|
12
|
-
gem "lemonsqueezy", "~> 1.0"
|
|
13
|
-
gem "paddle", "~> 2.6"
|
|
14
|
-
gem "stripe", "~> 18.0"
|
|
15
|
-
gem "prawn"
|
|
16
|
-
gem "receipts"
|
|
17
|
-
gem "sqlite3"
|
|
18
|
-
gem "pg"
|
|
19
|
-
gem "bootsnap", require: false
|
|
20
|
-
gem "puma"
|
|
21
|
-
gem "web-console", group: :development
|
|
22
|
-
gem "importmap-rails"
|
|
23
|
-
gem "sprockets-rails"
|
|
24
|
-
gem "stimulus-rails"
|
|
25
|
-
gem "turbo-rails"
|
|
26
|
-
gem "rdoc", ">= 7.0"
|
|
5
|
+
gem "rake", "~> 13.0"
|
|
27
6
|
gem "pay", "~> 11.0"
|
|
7
|
+
gem "stripe", "~> 18.0"
|
|
8
|
+
gem "rails", "~> 8.1.0"
|
|
9
|
+
|
|
10
|
+
group :development do
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "web-console"
|
|
13
|
+
gem "standard"
|
|
14
|
+
gem "rubocop", "~> 1.0"
|
|
15
|
+
gem "rubocop-minitest", "~> 0.35"
|
|
16
|
+
gem "rubocop-performance", "~> 1.0"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
group :test do
|
|
20
|
+
gem "minitest", "~> 5.0"
|
|
21
|
+
gem "mocha"
|
|
22
|
+
gem "simplecov", require: false
|
|
23
|
+
gem "vcr"
|
|
24
|
+
gem "webmock"
|
|
25
|
+
gem "braintree", ">= 2.92.0"
|
|
26
|
+
gem "lemonsqueezy", "~> 1.0"
|
|
27
|
+
gem "paddle", "~> 2.6"
|
|
28
|
+
gem "prawn"
|
|
29
|
+
gem "receipts"
|
|
30
|
+
gem "sqlite3"
|
|
31
|
+
gem "pg"
|
|
32
|
+
gem "bootsnap", require: false
|
|
33
|
+
gem "puma"
|
|
34
|
+
gem "importmap-rails"
|
|
35
|
+
gem "sprockets-rails"
|
|
36
|
+
gem "stimulus-rails"
|
|
37
|
+
gem "turbo-rails"
|
|
38
|
+
gem "rdoc", ">= 7.0"
|
|
39
|
+
end
|
|
28
40
|
|
|
29
41
|
gemspec path: "../"
|
data/gemfiles/pay_8.3.gemfile
CHANGED
|
@@ -2,28 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "
|
|
6
|
-
gem "mocha"
|
|
7
|
-
gem "simplecov", require: false
|
|
8
|
-
gem "standard"
|
|
9
|
-
gem "vcr"
|
|
10
|
-
gem "webmock"
|
|
11
|
-
gem "braintree", ">= 2.92.0"
|
|
12
|
-
gem "lemonsqueezy", "~> 1.0"
|
|
13
|
-
gem "paddle", "~> 2.6"
|
|
14
|
-
gem "stripe", "~> 13.0"
|
|
15
|
-
gem "prawn"
|
|
16
|
-
gem "receipts"
|
|
17
|
-
gem "sqlite3"
|
|
18
|
-
gem "pg"
|
|
19
|
-
gem "bootsnap", require: false
|
|
20
|
-
gem "puma"
|
|
21
|
-
gem "web-console", group: :development
|
|
22
|
-
gem "importmap-rails"
|
|
23
|
-
gem "sprockets-rails"
|
|
24
|
-
gem "stimulus-rails"
|
|
25
|
-
gem "turbo-rails"
|
|
26
|
-
gem "rdoc", ">= 7.0"
|
|
5
|
+
gem "rake", "~> 13.0"
|
|
27
6
|
gem "pay", "~> 8.3.0"
|
|
7
|
+
gem "stripe", "~> 13.0"
|
|
8
|
+
gem "rails", "~> 8.1.0"
|
|
9
|
+
|
|
10
|
+
group :development do
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "web-console"
|
|
13
|
+
gem "standard"
|
|
14
|
+
gem "rubocop", "~> 1.0"
|
|
15
|
+
gem "rubocop-minitest", "~> 0.35"
|
|
16
|
+
gem "rubocop-performance", "~> 1.0"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
group :test do
|
|
20
|
+
gem "minitest", "~> 5.0"
|
|
21
|
+
gem "mocha"
|
|
22
|
+
gem "simplecov", require: false
|
|
23
|
+
gem "vcr"
|
|
24
|
+
gem "webmock"
|
|
25
|
+
gem "braintree", ">= 2.92.0"
|
|
26
|
+
gem "lemonsqueezy", "~> 1.0"
|
|
27
|
+
gem "paddle", "~> 2.6"
|
|
28
|
+
gem "prawn"
|
|
29
|
+
gem "receipts"
|
|
30
|
+
gem "sqlite3"
|
|
31
|
+
gem "pg"
|
|
32
|
+
gem "bootsnap", require: false
|
|
33
|
+
gem "puma"
|
|
34
|
+
gem "importmap-rails"
|
|
35
|
+
gem "sprockets-rails"
|
|
36
|
+
gem "stimulus-rails"
|
|
37
|
+
gem "turbo-rails"
|
|
38
|
+
gem "rdoc", ">= 7.0"
|
|
39
|
+
end
|
|
28
40
|
|
|
29
41
|
gemspec path: "../"
|
data/gemfiles/pay_9.0.gemfile
CHANGED
|
@@ -2,28 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "
|
|
6
|
-
gem "mocha"
|
|
7
|
-
gem "simplecov", require: false
|
|
8
|
-
gem "standard"
|
|
9
|
-
gem "vcr"
|
|
10
|
-
gem "webmock"
|
|
11
|
-
gem "braintree", ">= 2.92.0"
|
|
12
|
-
gem "lemonsqueezy", "~> 1.0"
|
|
13
|
-
gem "paddle", "~> 2.6"
|
|
14
|
-
gem "stripe", "~> 13.0"
|
|
15
|
-
gem "prawn"
|
|
16
|
-
gem "receipts"
|
|
17
|
-
gem "sqlite3"
|
|
18
|
-
gem "pg"
|
|
19
|
-
gem "bootsnap", require: false
|
|
20
|
-
gem "puma"
|
|
21
|
-
gem "web-console", group: :development
|
|
22
|
-
gem "importmap-rails"
|
|
23
|
-
gem "sprockets-rails"
|
|
24
|
-
gem "stimulus-rails"
|
|
25
|
-
gem "turbo-rails"
|
|
26
|
-
gem "rdoc", ">= 7.0"
|
|
5
|
+
gem "rake", "~> 13.0"
|
|
27
6
|
gem "pay", "~> 9.0.0"
|
|
7
|
+
gem "stripe", "~> 13.0"
|
|
8
|
+
gem "rails", "~> 8.1.0"
|
|
9
|
+
|
|
10
|
+
group :development do
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "web-console"
|
|
13
|
+
gem "standard"
|
|
14
|
+
gem "rubocop", "~> 1.0"
|
|
15
|
+
gem "rubocop-minitest", "~> 0.35"
|
|
16
|
+
gem "rubocop-performance", "~> 1.0"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
group :test do
|
|
20
|
+
gem "minitest", "~> 5.0"
|
|
21
|
+
gem "mocha"
|
|
22
|
+
gem "simplecov", require: false
|
|
23
|
+
gem "vcr"
|
|
24
|
+
gem "webmock"
|
|
25
|
+
gem "braintree", ">= 2.92.0"
|
|
26
|
+
gem "lemonsqueezy", "~> 1.0"
|
|
27
|
+
gem "paddle", "~> 2.6"
|
|
28
|
+
gem "prawn"
|
|
29
|
+
gem "receipts"
|
|
30
|
+
gem "sqlite3"
|
|
31
|
+
gem "pg"
|
|
32
|
+
gem "bootsnap", require: false
|
|
33
|
+
gem "puma"
|
|
34
|
+
gem "importmap-rails"
|
|
35
|
+
gem "sprockets-rails"
|
|
36
|
+
gem "stimulus-rails"
|
|
37
|
+
gem "turbo-rails"
|
|
38
|
+
gem "rdoc", ">= 7.0"
|
|
39
|
+
end
|
|
28
40
|
|
|
29
41
|
gemspec path: "../"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "rake", "~> 13.0"
|
|
6
|
+
gem "rails", "~> 7.2.0"
|
|
7
|
+
gem "pay", "~> 11.0"
|
|
8
|
+
gem "stripe", "~> 18.0"
|
|
9
|
+
|
|
10
|
+
group :development do
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "web-console"
|
|
13
|
+
gem "standard"
|
|
14
|
+
gem "rubocop", "~> 1.0"
|
|
15
|
+
gem "rubocop-minitest", "~> 0.35"
|
|
16
|
+
gem "rubocop-performance", "~> 1.0"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
group :test do
|
|
20
|
+
gem "minitest", "~> 5.0"
|
|
21
|
+
gem "mocha"
|
|
22
|
+
gem "simplecov", require: false
|
|
23
|
+
gem "vcr"
|
|
24
|
+
gem "webmock"
|
|
25
|
+
gem "braintree", ">= 2.92.0"
|
|
26
|
+
gem "lemonsqueezy", "~> 1.0"
|
|
27
|
+
gem "paddle", "~> 2.6"
|
|
28
|
+
gem "prawn"
|
|
29
|
+
gem "receipts"
|
|
30
|
+
gem "sqlite3"
|
|
31
|
+
gem "pg"
|
|
32
|
+
gem "bootsnap", require: false
|
|
33
|
+
gem "puma"
|
|
34
|
+
gem "importmap-rails"
|
|
35
|
+
gem "sprockets-rails"
|
|
36
|
+
gem "stimulus-rails"
|
|
37
|
+
gem "turbo-rails"
|
|
38
|
+
gem "rdoc", ">= 7.0"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
gemspec path: "../"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "rake", "~> 13.0"
|
|
6
|
+
gem "rails", "~> 8.1.0"
|
|
7
|
+
gem "pay", "~> 11.0"
|
|
8
|
+
gem "stripe", "~> 18.0"
|
|
9
|
+
|
|
10
|
+
group :development do
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "web-console"
|
|
13
|
+
gem "standard"
|
|
14
|
+
gem "rubocop", "~> 1.0"
|
|
15
|
+
gem "rubocop-minitest", "~> 0.35"
|
|
16
|
+
gem "rubocop-performance", "~> 1.0"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
group :test do
|
|
20
|
+
gem "minitest", "~> 5.0"
|
|
21
|
+
gem "mocha"
|
|
22
|
+
gem "simplecov", require: false
|
|
23
|
+
gem "vcr"
|
|
24
|
+
gem "webmock"
|
|
25
|
+
gem "braintree", ">= 2.92.0"
|
|
26
|
+
gem "lemonsqueezy", "~> 1.0"
|
|
27
|
+
gem "paddle", "~> 2.6"
|
|
28
|
+
gem "prawn"
|
|
29
|
+
gem "receipts"
|
|
30
|
+
gem "sqlite3"
|
|
31
|
+
gem "pg"
|
|
32
|
+
gem "bootsnap", require: false
|
|
33
|
+
gem "puma"
|
|
34
|
+
gem "importmap-rails"
|
|
35
|
+
gem "sprockets-rails"
|
|
36
|
+
gem "stimulus-rails"
|
|
37
|
+
gem "turbo-rails"
|
|
38
|
+
gem "rdoc", ">= 7.0"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
gemspec path: "../"
|
|
@@ -82,18 +82,58 @@ UsageCredits.configure do |config|
|
|
|
82
82
|
# end
|
|
83
83
|
#
|
|
84
84
|
#
|
|
85
|
+
# === Lifecycle Callbacks ===
|
|
86
|
+
#
|
|
87
|
+
# Hook into credit events for analytics, notifications, and custom logic.
|
|
88
|
+
# All callbacks receive a context object with event-specific data.
|
|
89
|
+
#
|
|
90
|
+
# Available callbacks:
|
|
91
|
+
# on_credits_added - After credits are added to a wallet
|
|
92
|
+
# on_credits_deducted - After credits are deducted from a wallet
|
|
93
|
+
# on_low_balance_reached - When balance drops below threshold (fires once per crossing)
|
|
94
|
+
# on_balance_depleted - When balance reaches exactly zero
|
|
95
|
+
# on_insufficient_credits - When an operation fails due to insufficient credits
|
|
96
|
+
# on_credit_pack_purchased - After a credit pack purchase is fulfilled
|
|
97
|
+
# on_subscription_credits_awarded - After subscription credits are awarded
|
|
98
|
+
#
|
|
99
|
+
# Context object properties (available depending on event):
|
|
100
|
+
# ctx.event # Symbol - the event name
|
|
101
|
+
# ctx.owner # The wallet owner (User, Team, etc.)
|
|
102
|
+
# ctx.wallet # The UsageCredits::Wallet instance
|
|
103
|
+
# ctx.amount # Credits involved
|
|
104
|
+
# ctx.previous_balance # Balance before the operation
|
|
105
|
+
# ctx.new_balance # Balance after the operation
|
|
106
|
+
# ctx.transaction # The UsageCredits::Transaction record
|
|
107
|
+
# ctx.category # Transaction category (:manual_adjustment, :operation_charge, etc.)
|
|
108
|
+
# ctx.threshold # Low balance threshold (for low_balance_reached)
|
|
109
|
+
# ctx.operation_name # Operation name (for insufficient_credits)
|
|
110
|
+
# ctx.metadata # Additional context-specific data
|
|
111
|
+
# ctx.to_h # Convert to hash (excludes nil values)
|
|
112
|
+
#
|
|
113
|
+
# IMPORTANT: Keep callbacks fast! Use background jobs (deliver_later, perform_later) to avoid blocking credit operations.
|
|
114
|
+
#
|
|
115
|
+
# Example: Prompt user to buy more credits when running low
|
|
116
|
+
#
|
|
117
|
+
# config.low_balance_threshold = 100.credits # Set to nil to disable (default: 100)
|
|
118
|
+
#
|
|
119
|
+
# config.on_low_balance_reached do |ctx|
|
|
120
|
+
# LowCreditsMailer.buy_more(ctx.owner, remaining: ctx.new_balance).deliver_later
|
|
121
|
+
# end
|
|
85
122
|
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
123
|
+
# Example: Prompt user to buy credits when they run out:
|
|
124
|
+
# config.on_balance_depleted do |ctx|
|
|
125
|
+
# OutOfCreditsMailer.buy_more(ctx.owner).deliver_later
|
|
126
|
+
# end
|
|
91
127
|
#
|
|
92
|
-
#
|
|
128
|
+
# Example: Log when users hit credit limits (useful for debugging)
|
|
129
|
+
# config.on_insufficient_credits do |ctx|
|
|
130
|
+
# Rails.logger.info "[Credits] User #{ctx.owner.id} needs #{ctx.amount}, has #{ctx.metadata[:available]}"
|
|
131
|
+
# end
|
|
93
132
|
#
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
133
|
+
# Example: Track credit purchases (replace with your analytics service)
|
|
134
|
+
# config.on_credit_pack_purchased do |ctx|
|
|
135
|
+
# # e.g., Mixpanel, Amplitude, Segment, PostHog, etc.
|
|
136
|
+
# YourAnalyticsService.track(ctx.owner.id, "credits_purchased", amount: ctx.amount)
|
|
97
137
|
# end
|
|
98
138
|
#
|
|
99
139
|
#
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module UsageCredits
|
|
4
|
+
# Immutable context object passed to all callbacks
|
|
5
|
+
# Provides consistent, typed access to event data
|
|
6
|
+
CallbackContext = Struct.new(
|
|
7
|
+
:event, # Symbol - the event type
|
|
8
|
+
:wallet, # UsageCredits::Wallet instance
|
|
9
|
+
:amount, # Integer - credits involved (if applicable)
|
|
10
|
+
:previous_balance, # Integer - balance before operation
|
|
11
|
+
:new_balance, # Integer - balance after operation
|
|
12
|
+
:threshold, # Integer - low balance threshold (for low_balance events)
|
|
13
|
+
:category, # Symbol - transaction category
|
|
14
|
+
:operation_name, # Symbol - name of the operation
|
|
15
|
+
:transaction, # UsageCredits::Transaction - the transaction created
|
|
16
|
+
:metadata, # Hash - additional contextual data
|
|
17
|
+
keyword_init: true
|
|
18
|
+
) do
|
|
19
|
+
def to_h
|
|
20
|
+
super.compact
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Convenience: get owner from wallet
|
|
24
|
+
def owner
|
|
25
|
+
wallet&.owner
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|