feat: wire CryptoCheckout 4-step flow into billing page#63
Conversation
Replaces flat token bar (BuyCryptoCreditPanel) with 4-step flow: amount → coin/chain picker → deposit address + QR → confirmation tracker. Fixes PaymentMethodPicker amountUsd prop and test ReactNode type. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reviewer's GuideReplaces the previous crypto credits purchase panel with the new 4-step CryptoCheckout flow on the billing credits page and tidies up related types, tests, and UI wiring. Sequence diagram for CryptoCheckout polling and status transitionssequenceDiagram
actor User
participant BillingCreditsPage
participant CryptoCheckout
participant PaymentMethodPicker
participant BackendAPI
User->>BillingCreditsPage: Open billing credits page
BillingCreditsPage->>CryptoCheckout: Render CryptoCheckout
CryptoCheckout->>BackendAPI: getSupportedPaymentMethods
BackendAPI-->>CryptoCheckout: methods
User->>CryptoCheckout: Enter amount and continue
CryptoCheckout->>PaymentMethodPicker: Render with methods and callbacks
User->>PaymentMethodPicker: Select payment method
PaymentMethodPicker-->>CryptoCheckout: onSelect(method)
CryptoCheckout->>BackendAPI: Create checkout for method and amount
BackendAPI-->>CryptoCheckout: checkoutId and initial status pending
loop Poll until terminal status
CryptoCheckout->>BackendAPI: getPaymentStatus(checkoutId)
BackendAPI-->>CryptoCheckout: status and amounts
alt status expired or failed
CryptoCheckout->>CryptoCheckout: setStatus(expired or failed)
CryptoCheckout->>CryptoCheckout: clear polling interval
else full amount received and >= expected
CryptoCheckout->>CryptoCheckout: setStatus(confirming)
CryptoCheckout->>CryptoCheckout: setStep(confirming)
else partial amount received
CryptoCheckout->>CryptoCheckout: setStatus(partial)
else still pending
CryptoCheckout->>CryptoCheckout: keep polling
end
end
alt status credited
CryptoCheckout->>User: Show credited state and Done button
User->>CryptoCheckout: Click Done buy more credits
CryptoCheckout->>CryptoCheckout: handleReset and go back to amount step
end
Class diagram for updated billing credits page and CryptoCheckout componentsclassDiagram
class BillingCreditsPage {
+render()
}
class BuyCreditsPanel {
+render()
}
class CouponInput {
+render()
}
class CryptoCheckout {
-methods PaymentMethod[]
-status PaymentStatus
-step CryptoCheckoutStep
-loading boolean
+useEffect_loadMethods()
+useEffect_pollStatus()
+handleMethod(method PaymentMethod)
+handleReset()
+render()
}
class PaymentMethodPicker {
+methods PaymentMethod[]
+onSelect(method PaymentMethod)
+onBack()
+render()
}
class AutoTopupCard {
+render()
}
class TransactionHistory {
+render()
}
BillingCreditsPage --> BuyCreditsPanel : renders
BillingCreditsPage --> CouponInput : renders
BillingCreditsPage --> CryptoCheckout : renders
BillingCreditsPage --> AutoTopupCard : renders
BillingCreditsPage --> TransactionHistory : renders
CryptoCheckout --> PaymentMethodPicker : renders
CryptoCheckout --> PaymentMethod : uses
class PaymentMethod {
+id string
+name string
+coin string
+chain string
}
class PaymentStatus {
<<enumeration>>
pending
partial
confirming
credited
expired
failed
}
class CryptoCheckoutStep {
<<enumeration>>
amount
method
deposit
confirming
}
CryptoCheckout --> PaymentStatus : uses
CryptoCheckout --> CryptoCheckoutStep : uses
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
Disabled knowledge base sources:
📝 WalkthroughWalkthroughThree files are modified: test mocks are reformatted for clarity, a crypto payment component is swapped on the credits page, and the checkout component is refactored with improved formatting and a prop removal from a child component. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
✨ 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 |
|
🎉 This PR is included in version 1.26.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The
getSupportedPaymentMethodscall is swallowed on error andmethods.length === 0returnsnull, which means the crypto checkout silently disappears on failure; consider surfacing a minimal error or fallback state so the user understands why the option isn’t available.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `getSupportedPaymentMethods` call is swallowed on error and `methods.length === 0` returns `null`, which means the crypto checkout silently disappears on failure; consider surfacing a minimal error or fallback state so the user understands why the option isn’t available.
## Individual Comments
### Comment 1
<location path="src/components/billing/crypto-checkout.tsx" line_range="35" />
<code_context>
- getSupportedPaymentMethods().then(setMethods).catch(() => {});
+ getSupportedPaymentMethods()
+ .then(setMethods)
+ .catch(() => {});
}, []);
</code_context>
<issue_to_address>
**issue (bug_risk):** Swallowing errors in the payment methods fetch makes debugging and UX issues harder to detect.
This empty catch means network or backend failures when loading payment methods are ignored, potentially resulting in an empty UI (`methods.length === 0`) with no signal of the underlying issue. Please at least log the error (e.g., Sentry/console) or show a simple fallback state so production issues are detectable.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| getSupportedPaymentMethods().then(setMethods).catch(() => {}); | ||
| getSupportedPaymentMethods() | ||
| .then(setMethods) | ||
| .catch(() => {}); |
There was a problem hiding this comment.
issue (bug_risk): Swallowing errors in the payment methods fetch makes debugging and UX issues harder to detect.
This empty catch means network or backend failures when loading payment methods are ignored, potentially resulting in an empty UI (methods.length === 0) with no signal of the underlying issue. Please at least log the error (e.g., Sentry/console) or show a simple fallback state so production issues are detectable.
Greptile SummaryThis PR wires the existing Key findings:
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| src/components/billing/crypto-checkout.tsx | Core 4-step checkout component. Two logic issues: silent failure on createCheckout error with no user feedback, and confirmationsRequired not cleared on handleReset. |
| src/app/(dashboard)/billing/credits/page.tsx | Straightforward swap of BuyCryptoCreditPanel import for CryptoCheckout; no logic changes, clean integration. |
| src/tests/crypto-checkout.test.tsx | Type fix for framer-motion mock and reformatting of mock data; two existing tests cover happy-path render and amount-to-picker navigation. |
Sequence Diagram
sequenceDiagram
participant U as User
participant CC as CryptoCheckout
participant API as @/lib/api (tRPC)
U->>CC: Mount (billing page)
CC->>API: getSupportedPaymentMethods()
API-->>CC: SupportedPaymentMethod[]
U->>CC: Select amount (AmountSelector)
CC->>CC: setStep("method")
U->>CC: Select payment method (PaymentMethodPicker)
CC->>API: createCheckout(methodId, amountUsd)
API-->>CC: CheckoutResult { depositAddress, referenceId, ... }
CC->>CC: setStep("deposit")
loop Poll every 5s while checkout active
CC->>API: getChargeStatus(referenceId)
API-->>CC: ChargeStatusResult
alt credited === true
CC->>CC: setStatus("credited"), clearInterval
else status expired/failed
CC->>CC: setStatus(status), clearInterval
else amountReceived >= amountExpected
CC->>CC: setStatus("confirming"), setStep("confirming")
else amountReceived > 0
CC->>CC: setStatus("partial")
end
end
U->>CC: Click "Done — buy more credits"
CC->>CC: handleReset() → setStep("amount")
Comments Outside Diff (2)
-
src/components/billing/crypto-checkout.tsx, line 73-88 (link)Silent checkout failure leaves user without feedback
When
createCheckoutthrows (network error, server validation error, rate limit, etc.), thecatchblock silently stays on the method step. In a billing payment flow, this is a meaningful UX issue — the loading spinner disappears and nothing indicates to the user that something went wrong. They may repeatedly tap the same payment method assuming it's loading, or assume the flow is broken.At minimum, a local error state should be surfaced:
const handleMethod = useCallback( async (method: SupportedPaymentMethod) => { setLoading(true); setCheckoutError(null); try { const result = await createCheckout(method.id, amountUsd); setCheckout(result); setStatus("waiting"); setStep("deposit"); } catch { setCheckoutError("Failed to create checkout. Please try again."); } finally { setLoading(false); } }, [amountUsd], );And render
checkoutErrorbelow thePaymentMethodPickersimilarly to howloadingis rendered.Prompt To Fix With AI
This is a comment left during a code review. Path: src/components/billing/crypto-checkout.tsx Line: 73-88 Comment: **Silent checkout failure leaves user without feedback** When `createCheckout` throws (network error, server validation error, rate limit, etc.), the `catch` block silently stays on the method step. In a billing payment flow, this is a meaningful UX issue — the loading spinner disappears and nothing indicates to the user that something went wrong. They may repeatedly tap the same payment method assuming it's loading, or assume the flow is broken. At minimum, a local error state should be surfaced: ``` const handleMethod = useCallback( async (method: SupportedPaymentMethod) => { setLoading(true); setCheckoutError(null); try { const result = await createCheckout(method.id, amountUsd); setCheckout(result); setStatus("waiting"); setStep("deposit"); } catch { setCheckoutError("Failed to create checkout. Please try again."); } finally { setLoading(false); } }, [amountUsd], ); ``` And render `checkoutError` below the `PaymentMethodPicker` similarly to how `loading` is rendered. How can I resolve this? If you propose a fix, please make it concise.
-
src/components/billing/crypto-checkout.tsx, line 90-96 (link)confirmationsRequirednot reset inhandleResethandleResetresetsconfirmationsto0but omitsconfirmationsRequired. If a user completes a Bitcoin purchase (e.g. 3 confirmations required) and then immediately starts a new Ethereum checkout, theConfirmationTrackerwill display the staleconfirmationsRequiredvalue until the first poll fires (up to 5 seconds after the deposit step is reached). This produces a misleading progress indicator.Prompt To Fix With AI
This is a comment left during a code review. Path: src/components/billing/crypto-checkout.tsx Line: 90-96 Comment: **`confirmationsRequired` not reset in `handleReset`** `handleReset` resets `confirmations` to `0` but omits `confirmationsRequired`. If a user completes a Bitcoin purchase (e.g. 3 confirmations required) and then immediately starts a new Ethereum checkout, the `ConfirmationTracker` will display the stale `confirmationsRequired` value until the first poll fires (up to 5 seconds after the deposit step is reached). This produces a misleading progress indicator. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/components/billing/crypto-checkout.tsx
Line: 73-88
Comment:
**Silent checkout failure leaves user without feedback**
When `createCheckout` throws (network error, server validation error, rate limit, etc.), the `catch` block silently stays on the method step. In a billing payment flow, this is a meaningful UX issue — the loading spinner disappears and nothing indicates to the user that something went wrong. They may repeatedly tap the same payment method assuming it's loading, or assume the flow is broken.
At minimum, a local error state should be surfaced:
```
const handleMethod = useCallback(
async (method: SupportedPaymentMethod) => {
setLoading(true);
setCheckoutError(null);
try {
const result = await createCheckout(method.id, amountUsd);
setCheckout(result);
setStatus("waiting");
setStep("deposit");
} catch {
setCheckoutError("Failed to create checkout. Please try again.");
} finally {
setLoading(false);
}
},
[amountUsd],
);
```
And render `checkoutError` below the `PaymentMethodPicker` similarly to how `loading` is rendered.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/components/billing/crypto-checkout.tsx
Line: 90-96
Comment:
**`confirmationsRequired` not reset in `handleReset`**
`handleReset` resets `confirmations` to `0` but omits `confirmationsRequired`. If a user completes a Bitcoin purchase (e.g. 3 confirmations required) and then immediately starts a new Ethereum checkout, the `ConfirmationTracker` will display the stale `confirmationsRequired` value until the first poll fires (up to 5 seconds after the deposit step is reached). This produces a misleading progress indicator.
```suggestion
const handleReset = useCallback(() => {
setStep("amount");
setCheckout(null);
setStatus("waiting");
setAmountUsd(0);
setConfirmations(0);
setConfirmationsRequired(0);
}, []);
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/components/billing/crypto-checkout.tsx
Line: 52-58
Comment:
**Redundant `setStep("confirming")` on every poll**
Once the full amount is received but not yet credited, the condition `amountReceivedCents >= amountExpectedCents` remains true on every subsequent poll tick. This causes `setStep("confirming")` and `setStatus("confirming")` to be called redundantly on every 5-second interval, generating unnecessary re-renders.
```suggestion
} else if (
res.amountReceivedCents > 0 &&
res.amountReceivedCents >= res.amountExpectedCents
) {
setStatus((prev) => (prev !== "confirming" ? "confirming" : prev));
setStep((prev) => (prev !== "confirming" ? "confirming" : prev));
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat: wire CryptoCheckout 4-step flow in..." | Re-trigger Greptile
| } else if ( | ||
| res.amountReceivedCents > 0 && | ||
| res.amountReceivedCents >= res.amountExpectedCents | ||
| ) { | ||
| setStatus("confirming"); | ||
| setStep("confirming"); | ||
| } else if (res.amountReceivedCents > 0) { |
There was a problem hiding this comment.
Redundant
setStep("confirming") on every poll
Once the full amount is received but not yet credited, the condition amountReceivedCents >= amountExpectedCents remains true on every subsequent poll tick. This causes setStep("confirming") and setStatus("confirming") to be called redundantly on every 5-second interval, generating unnecessary re-renders.
| } else if ( | |
| res.amountReceivedCents > 0 && | |
| res.amountReceivedCents >= res.amountExpectedCents | |
| ) { | |
| setStatus("confirming"); | |
| setStep("confirming"); | |
| } else if (res.amountReceivedCents > 0) { | |
| } else if ( | |
| res.amountReceivedCents > 0 && | |
| res.amountReceivedCents >= res.amountExpectedCents | |
| ) { | |
| setStatus((prev) => (prev !== "confirming" ? "confirming" : prev)); | |
| setStep((prev) => (prev !== "confirming" ? "confirming" : prev)); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/billing/crypto-checkout.tsx
Line: 52-58
Comment:
**Redundant `setStep("confirming")` on every poll**
Once the full amount is received but not yet credited, the condition `amountReceivedCents >= amountExpectedCents` remains true on every subsequent poll tick. This causes `setStep("confirming")` and `setStatus("confirming")` to be called redundantly on every 5-second interval, generating unnecessary re-renders.
```suggestion
} else if (
res.amountReceivedCents > 0 &&
res.amountReceivedCents >= res.amountExpectedCents
) {
setStatus((prev) => (prev !== "confirming" ? "confirming" : prev));
setStep((prev) => (prev !== "confirming" ? "confirming" : prev));
```
How can I resolve this? If you propose a fix, please make it concise.|
@claude Please fix all issues raised by the reviewers on this PR. Feedback available from: Steps:
Do not close or delete the PR. Do not skip issues. If a reviewer flags something, fix it or leave a clear explanation as a PR comment. |
Replaces flat token bar with 4-step crypto checkout: amount → coin/chain → deposit QR → confirmations. Fixes two pre-existing type errors from PR #62.
Summary by Sourcery
Integrate the multi-step CryptoCheckout flow into the billing credits page and tidy up supporting mocks and layout.
New Features:
Bug Fixes:
Enhancements:
Note
Wire CryptoCheckout 4-step flow into the billing credits page
Replaces
BuyCryptoCreditPanelwithCryptoCheckoutin credits/page.tsx. Also removes theamountUsdprop from thePaymentMethodPickercall insideCryptoCheckout.Macroscope summarized 6c329dd.
Summary by CodeRabbit
Tests
Refactor