Conversation
Introduce 7 lifecycle callbacks for credit events, enabling users to
hook into credit operations for analytics, notifications, audit logging,
and custom business logic.
New callbacks:
- on_credits_added: Fires after credits are deposited
- on_credits_deducted: Fires after credits are removed
- on_low_balance_reached: Fires when balance crosses below threshold
- on_balance_depleted: Fires when balance reaches zero
- on_insufficient_credits: Fires before raising InsufficientCredits
- on_credit_pack_purchased: Fires after credit pack fulfillment
- on_subscription_credits_awarded: Fires after subscription renewal
Key design decisions:
- Callbacks receive a CallbackContext struct with typed, consistent data
- Error isolation: callback failures never break credit operations
- Backward compatible: existing on_low_balance { |owner| } still works
- Deprecation warnings (warn-once) for legacy handle_event method
- Configuration reset in test teardown prevents callback pollution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pull Request Review: Lifecycle Callbacks SystemI've completed a thorough review of PR #25. Overall, this is an excellent implementation that adds significant value to the gem. The code quality is high, the design is thoughtful, and backward compatibility is well-preserved. Here's my detailed feedback: ✅ Strengths1. Excellent Architecture & Design
2. Backward Compatibility Done Right
3. Comprehensive Testing
4. Developer Experience
🔍 Detailed Code Review
|
There was a problem hiding this comment.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on February 6
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
The subscription_credits_awarded callback was being dispatched before fulfillment.update! completed. If that update failed, the callback would have already fired for credits that get rolled back. Fix: Wrap add_credits and fulfillment.update! in a transaction block, dispatch callback only after the transaction commits. Also adds regression tests: - Test that credits roll back when outer transaction fails - Test that subscription upgrade callback receives persisted transaction Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
lib/usage_credits/models/concerns/pay_subscription_extension.rb
Outdated
Show resolved
Hide resolved
Move subscription_credits_awarded callback dispatch outside the transaction block in handle_initial_award_and_fulfillment_setup, matching the pattern used in handle_plan_upgrade. Changes: - Callback now dispatched AFTER transaction commits (not inside) - Added transaction: parameter to match upgrade path signature - Removed rescue/rollback pattern (let errors propagate naturally) This ensures callbacks only fire for persisted data and maintains consistency across all subscription credit award scenarios. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
Adds a comprehensive callback system for credit lifecycle events, inspired by patterns from the
api_keysgem. This enables users to hook into credit operations for analytics, notifications, audit logging, and custom business logic.7 new callbacks available:
on_credits_added- After credits are deposited to a walleton_credits_deducted- After credits are removed from a walleton_low_balance_reached- When balance crosses below threshold (fires once per crossing)on_balance_depleted- When balance reaches exactly zeroon_insufficient_credits- Before raisingInsufficientCreditserroron_credit_pack_purchased- After credit pack purchase is fulfilledon_subscription_credits_awarded- After subscription credits are awardedWhy we built this
Users needed hooks into credit operations for common use cases:
The existing
on_low_balancecallback was too limited - it only provided the owner, not the context needed for rich integrations.How it works
All callbacks receive a
CallbackContextstruct with typed, consistent data:Design decisions & pitfalls avoided
1. Error isolation
Callback errors are caught and logged, never breaking credit operations. A failing analytics call shouldn't prevent a user from spending credits.
2. Backward compatibility preserved
The existing
on_low_balance { |owner| }syntax continues to work unchanged. Internally, it wraps the legacy callback to extractownerfrom the new context system. Zero migration required for existing users.3. Deprecation with warn-once pattern
Legacy methods (
handle_event,notify_low_balance) show deprecation warnings on first use only, avoiding log spam from repeated calls.4. Test isolation
Added
UsageCredits.reset!to test teardown to prevent callback pollution between tests. This was critical - without it, callbacks configured in one test would leak into others.5. Low balance fires once per threshold crossing
on_low_balance_reachedonly fires when crossing from above to below the threshold, not on every deduction while already below. This prevents notification spam.6. Balance depleted edge case
on_balance_depletedfires only when balance reaches exactly zero. If negative balances are enabled and balance goes from positive to negative (skipping zero), this callback won't fire - by design.Files changed
New files:
lib/usage_credits/callback_context.rb- Immutable struct for callback datalib/usage_credits/callbacks.rb- Centralized dispatch with error isolationtest/usage_credits/callbacks_test.rb- Unit teststest/integration/wallet_callbacks_test.rb- Integration teststest/usage_credits/backward_compat_test.rb- Backward compat testsModified:
lib/usage_credits/configuration.rb- 7 callback DSL methodslib/usage_credits/models/wallet.rb- Dispatch callbacks from credit operationslib/usage_credits/models/concerns/pay_charge_extension.rb-credit_pack_purchasedcallbacklib/usage_credits/models/concerns/pay_subscription_extension.rb-subscription_credits_awardedcallbacklib/usage_credits.rb- Requires + deprecation handlinglib/generators/usage_credits/templates/initializer.rb- Improved callback documentationREADME.md- New "Lifecycle callbacks" sectiontest/test_helper.rb- Reset configuration between testsTest plan
on_low_balanceworks🤖 Generated with Claude Code
Note
Adds a robust callback framework to hook into credit events with error isolation and backward compatibility.
Callbacksdispatcher andCallbackContextstruct; 7 events:credits_added,credits_deducted,low_balance_reached,balance_depleted,insufficient_credits,credit_pack_purchased,subscription_credits_awardedWallet(add_credits,deduct_credits,spend_credits_oninsufficient path),Pay::Charge(credit pack fulfillment), andPay::Subscription(initial award and upgrades)on_low_balanceusage in favor ofon_low_balance_reached(legacy still supported); deprecateUsageCredits.handle_eventandnotify_low_balancewith warn-once behavior; addreset!to clear warnings/configConfigurationwith DSL methods for all callbacks; update initializer template and README with usage and fieldsWritten by Cursor Bugbot for commit b75799c. This will update automatically on new commits. Configure here.