release/v0.1.17-phase17-credit-packs#243
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR adds a complete credit pack purchasing feature: environment configuration for three Stripe price IDs, a new ChangesCredit pack checkout and webhook handling
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ck-enum CONV-257: Create CreditPack enum
…cks-config CONV-258: Create credit packs config
…ck-dto CONV-259: Create CreditPack DTO
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ck-repository CONV-260: Create CreditPackRepository
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-checkout-creation CONV-261: Test credit pack checkout creation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ck-webhook-handler-skeleton CONV-265: Create CreditPackWebhookHandler skeleton
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-purchase-grants-credits CONV-266: Test credit pack purchase grants credits
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-pack-purchase-grant CONV-267: Implement credit pack purchase grant
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…redit-pack-purchase-is-ignored CONV-268: Test duplicate credit pack purchase is ignored
There was a problem hiding this comment.
3 issues found across 34 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="routes/web.php">
<violation number="1" location="routes/web.php:10">
P0: Stripe webhook POST route not excluded from CSRF protection. Stripe sends webhooks without CSRF tokens, so every incoming event will be rejected with HTTP 419 before reaching the handler. Credit pack purchases via Stripe Checkout will never trigger credit grants in production.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| use App\Http\Controllers\ProfileController; | ||
| use Illuminate\Support\Facades\Route; | ||
|
|
||
| Route::post('/stripe/webhook', [CashierWebhookController::class, 'handleWebhook']) |
There was a problem hiding this comment.
P0: Stripe webhook POST route not excluded from CSRF protection. Stripe sends webhooks without CSRF tokens, so every incoming event will be rejected with HTTP 419 before reaching the handler. Credit pack purchases via Stripe Checkout will never trigger credit grants in production.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At routes/web.php, line 10:
<comment>Stripe webhook POST route not excluded from CSRF protection. Stripe sends webhooks without CSRF tokens, so every incoming event will be rejected with HTTP 419 before reaching the handler. Credit pack purchases via Stripe Checkout will never trigger credit grants in production.</comment>
<file context>
@@ -1,10 +1,15 @@
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
+Route::post('/stripe/webhook', [CashierWebhookController::class, 'handleWebhook'])
+ ->name('cashier.webhook');
+
</file context>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-pack-purchase-idempotency CONV-269: Implement credit pack purchase idempotency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…transaction-metadata CONV-270: Add credit pack transaction metadata
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cta-component CONV-271: Add buy credits CTA component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-cta-in-user-dropdown CONV-272: Show buy credits CTA in user dropdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-cta-on-insufficient-credits CONV-273: Show buy credits CTA on insufficient credits
- Add CreditAccount::lockForUpdate() before idempotency check in CreditPackWebhookHandler to prevent double-grant on concurrent Stripe webhook retries (same pattern as SubscriptionWebhookHandler) - Exclude stripe/webhook route from CSRF protection so Stripe can deliver webhooks without a session token in production - Point Buy Credits CTA to /billing instead of dashboard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix Phase 17 code review issues
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
resources/views/components/billing/buy-credits-cta.blade.php (1)
10-10: ⚡ Quick winReplace the hardcoded button text color with a RateGuru token.
text-whiteintroduces a raw Tailwind color utility in a reusable component. Please switch this to the design-token-backed text color used for primary CTAs instead of hardcoding white here. As per coding guidelines, "Reusable UI components must use RateGuru tokens and must not introduce random Tailwind color utilities unless the deviation is documented in docs/design/design-contract.md".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@resources/views/components/billing/buy-credits-cta.blade.php` at line 10, The anchor in the buy-credits-cta component currently uses the raw Tailwind utility text-white; remove that utility and replace it with the RateGuru design-token for primary CTA text (e.g., the token-backed class used across the codebase such as text-rg-on-primary or the equivalent token variable like color:var(--rg-on-primary)), updating the class list on the <a> element and/or the inline style accordingly; if your codebase does not yet expose a token class, use the CSS variable (var(--rg-on-primary)) and add a TODO to replace with the canonical token class, and document any deviation in docs/design/design-contract.md.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/Billing/Webhooks/CreditPackWebhookHandler.php`:
- Around line 61-68: In the meta array inside CreditPackWebhookHandler (where
'stripe_price_id' is set), read the price id from the webhook payload (e.g.
$event['data']['object']['metadata']['price_id'] or similar on the fake event)
and use that value for 'stripe_price_id', falling back to $pack->stripePriceId
only when the payload value is null/empty; update the assignment so it prefers
the payload price id but retains the existing config value as a fallback.
In `@app/Livewire/Dashboard/DashboardConverter.php`:
- Line 67: Reset the insufficient-credit state whenever the user leaves or
re-enters the convert step and when the flow is restarted by clearing
$hasInsufficientCredits alongside convertError; locate the DashboardConverter
Livewire component and update the step-change handler(s) and the flow reset
method (the same places that currently reset convertError—refer to the convert()
method, the step navigation logic, and the flow restart/reset method) to set
$hasInsufficientCredits = false whenever convertError is cleared or the active
step changes away from or into the convert step.
---
Nitpick comments:
In `@resources/views/components/billing/buy-credits-cta.blade.php`:
- Line 10: The anchor in the buy-credits-cta component currently uses the raw
Tailwind utility text-white; remove that utility and replace it with the
RateGuru design-token for primary CTA text (e.g., the token-backed class used
across the codebase such as text-rg-on-primary or the equivalent token variable
like color:var(--rg-on-primary)), updating the class list on the <a> element
and/or the inline style accordingly; if your codebase does not yet expose a
token class, use the CSS variable (var(--rg-on-primary)) and add a TODO to
replace with the canonical token class, and document any deviation in
docs/design/design-contract.md.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 44631c54-a5e6-4177-bc63-d37508f6bc5d
📒 Files selected for processing (36)
.env.exampleapp/Billing/BillingPaymentService.phpapp/Billing/Gateway/CashierCreditPackCheckoutGateway.phpapp/Billing/Gateway/CreditPackCheckoutGateway.phpapp/Billing/Gateway/FakeCreditPackCheckoutGateway.phpapp/Billing/Webhooks/CreditPackWebhookHandler.phpapp/Data/Billing/CreditPackDto.phpapp/Enums/Billing/CreditPack.phpapp/Http/Controllers/Billing/CashierWebhookController.phpapp/Http/Controllers/Billing/CreditPackCheckoutController.phpapp/Livewire/Dashboard/DashboardConverter.phpapp/Providers/AppServiceProvider.phpapp/Services/Billing/CreditPackRepository.phpbootstrap/app.phpconfig/billing.phpresources/views/billing/credits-cancel.blade.phpresources/views/billing/credits-success.blade.phpresources/views/components/billing/buy-credits-cta.blade.phpresources/views/components/user-dropdown.blade.phpresources/views/livewire/dashboard/dashboard-converter.blade.phproutes/web.phptests/Feature/Billing/BillingPaymentServiceCreditPackTest.phptests/Feature/Billing/CashierWebhookRouteTest.phptests/Feature/Billing/CreditPackCheckoutRouteTest.phptests/Feature/Billing/CreditPackRedirectPagesTest.phptests/Feature/Billing/CreditPackWebhookHandlerTest.phptests/Feature/Billing/CreditPacksConfigTest.phptests/Feature/Dashboard/UserDropdownBuyCreditsTest.phptests/Feature/Livewire/DashboardConverterCreditsTest.phptests/Feature/ViewComponents/BuyCreditsCtaTest.phptests/Pest.phptests/Support/FakeStripeEvents.phptests/Unit/Billing/CreditPackDtoTest.phptests/Unit/Billing/CreditPackEnumTest.phptests/Unit/Billing/CreditPackRepositoryTest.phptests/Unit/Billing/CreditPackWebhookHandlerSkeletonTest.php
- Prefer payload price_id over config fallback in credit pack transaction metadata (stripe_price_id) - Reset hasInsufficientCredits in resetTargetSelection(), backToFormatStep(), goToSettingsStep(), and convertAnother() so stale error state cannot bleed into subsequent convert attempts - Replace text-white with color:var(--ca-on-primary) in buy-credits-cta component to use design token instead of hardcoded utility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix Phase 17 inline review issues
Phase 17 — Credit Packs
This release adds one-time credit pack purchases on top of the existing Cashier/CreditLedger architecture.
What's included (CONV-257 → CONV-273)
Foundation
CreditPackenum (small,medium,large)config/billing.phpwith credit_packs (label, credits, stripe_price_id, description)CreditPackDto— immutable DTO withfromConfig()factoryCreditPackRepository—all(),find(),findOrFail(),findByStripePriceId()Checkout
CreditPackCheckoutGatewayinterface +CashierCreditPackCheckoutGateway+FakeCreditPackCheckoutGatewayBillingPaymentService::createCreditPackCheckout()— validates price ID, delegates to gateway, no immediate credit grantPOST /billing/credits/{pack}route →CreditPackCheckoutController/billing/credits/successand/billing/credits/cancelredirect pagesWebhook Grant
CreditPackWebhookHandler::handleCheckoutSessionCompleted()— extracts user/pack from metadata, grants credits viaCreditLedgermetadata_json->stripe_checkout_session_idcheck — same session never grants twicepack_key,pack_credits,stripe_event_id,stripe_checkout_session_id,stripe_payment_intent_id,stripe_price_idCashierWebhookControllerextends Cashier's controller withhandleCheckoutSessionCompletedUI
<x-billing.buy-credits-cta>component (full and compact variants)What's NOT included (intentionally)
Test results
composer lintpassesnpm run buildpasses🤖 Generated with Claude Code
Summary by cubic
Adds one-time credit pack purchases with Stripe via
laravel/cashier, including checkout, webhook-based credit grants, and “Buy credits” CTAs. Implements Phase 17 — Credit Packs (CONV-257 → CONV-273) and hardens webhook delivery with CSRF exemption and row-level locking.New Features
config/billing.php(small,medium,large) with label, credits,stripe_price_id, and description;CreditPackDtoandCreditPackRepository.BillingPaymentService::createCreditPackCheckout,CreditPackCheckoutGatewaywithCashierCreditPackCheckoutGateway, POST/billing/credits/{pack}, and success/cancel pages./stripe/webhookviaCashierWebhookController;CreditPackWebhookHandlergrants credits viaCreditLedger, ensures idempotency bymetadata_json->stripe_checkout_session_id, locksCreditAccountbefore the check to prevent double-grants on concurrent Stripe retries, and stores Stripe metadata (stripe_event_id,stripe_checkout_session_id,stripe_payment_intent_id,stripe_customer_id,stripe_price_id, plus pack info). Prefers payloadprice_idover config fallback. CSRF is disabled for/stripe/webhook.<x-billing.buy-credits-cta>component; shown in the user dropdown and when a conversion fails due to low credits; error shows required credits and current balance. Resets the insufficient-credits state between steps to avoid stale errors. CTA links to/billingand usesvar(--ca-on-primary)text color token.Cashier::ignoreRoutes()with our own webhook route.Migration
STRIPE_CREDIT_PACK_SMALL_PRICE_ID,STRIPE_CREDIT_PACK_MEDIUM_PRICE_ID,STRIPE_CREDIT_PACK_LARGE_PRICE_ID.checkout.session.completedto/stripe/webhook.Written for commit 1a013b6. Summary will update on new commits.
Summary by CodeRabbit
Release Notes