fix(web): only grant developer credits on paid checkout#1931
Conversation
|
Paragon Review Skipped Hi @richiemcilroy! Your Polarity credit balance is insufficient to complete this review. Please visit https://app.paragon.run to finish your review. |
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
| await addCreditsToAccount({ | ||
| accountId, | ||
| amountCents: Number(amountCents), | ||
| referenceId: paymentIntentId, | ||
| referenceType: "stripe_payment_intent", | ||
| metadata: { | ||
| amountCents: Number(amountCents), | ||
| stripeSessionId: session.id, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Worth validating amountCents is numeric before granting credits; Number("abc") becomes NaN and would currently flow into addCreditsToAccount.
| await addCreditsToAccount({ | |
| accountId, | |
| amountCents: Number(amountCents), | |
| referenceId: paymentIntentId, | |
| referenceType: "stripe_payment_intent", | |
| metadata: { | |
| amountCents: Number(amountCents), | |
| stripeSessionId: session.id, | |
| }, | |
| }); | |
| const amountCentsNumber = Number(amountCents); | |
| if (!Number.isFinite(amountCentsNumber) || amountCentsNumber <= 0) { | |
| console.error("Invalid amountCents for developer credits:", { | |
| accountId, | |
| amountCents, | |
| paymentIntentId, | |
| }); | |
| return new Response("Invalid amountCents", { status: 400 }); | |
| } | |
| await addCreditsToAccount({ | |
| accountId, | |
| amountCents: amountCentsNumber, | |
| referenceId: paymentIntentId, | |
| referenceType: "stripe_payment_intent", | |
| metadata: { | |
| amountCents: amountCentsNumber, | |
| stripeSessionId: session.id, | |
| }, | |
| }); |
| if ( | ||
| !userIsPro(user) && | ||
| typeof duration === "number" && | ||
| Number.isFinite(duration) && | ||
| duration > 300 | ||
| ) { |
There was a problem hiding this comment.
Number.isFinite(duration) silently lets Infinity bypass this early cap — the old condition evaluated Infinity > 300 as true and threw upgrade_required, but Number.isFinite(Infinity) is false, so the check is skipped. The comment says finalize is the authoritative enforcement point, but if a client deliberately passes duration: Infinity the pre-upload rejection no longer fires. Dropping Number.isFinite while keeping typeof duration === "number" restores the original behaviour since NaN > 300 is already false.
| if ( | |
| !userIsPro(user) && | |
| typeof duration === "number" && | |
| Number.isFinite(duration) && | |
| duration > 300 | |
| ) { | |
| if ( | |
| !userIsPro(user) && | |
| typeof duration === "number" && | |
| duration > 300 | |
| ) { |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/video/create-for-processing.ts
Line: 54-59
Comment:
`Number.isFinite(duration)` silently lets `Infinity` bypass this early cap — the old condition evaluated `Infinity > 300` as `true` and threw `upgrade_required`, but `Number.isFinite(Infinity)` is `false`, so the check is skipped. The comment says finalize is the authoritative enforcement point, but if a client deliberately passes `duration: Infinity` the pre-upload rejection no longer fires. Dropping `Number.isFinite` while keeping `typeof duration === "number"` restores the original behaviour since `NaN > 300` is already `false`.
```suggestion
if (
!userIsPro(user) &&
typeof duration === "number" &&
duration > 300
) {
```
How can I resolve this? If you propose a fix, please make it concise.|
@greptileai please review the PR |
Only grants developer credits when the Stripe checkout session is actually paid, and handles the async-payment-succeeded event so delayed payment methods still grant credits once they settle.
Greptile Summary
Guards developer-credit grants behind
payment_status === "paid"and adds acheckout.session.async_payment_succeededhandler so delayed payment methods (e.g. SEPA, ACH) still receive credits once the payment settles.route.ts): The credit-granting logic is extracted intograntDeveloperCredits, which now checkspayment_statusbefore writing to the DB and is called from bothcheckout.session.completedand the newcheckout.session.async_payment_succeededbranch. The existingpaymentIntentId-based idempotency check prevents double-grants if both events fire for the same session.payment_status: "paid"; two new cases cover the unpaid-skip path and the async-payment-succeeded path.create-for-processing.ts: Addstypeof/Number.isFiniteguards to the pre-upload duration check, with a comment clarifying that finalize is the authoritative enforcement point.Confidence Score: 5/5
Safe to merge — the payment_status guard and async event handler are both correct, idempotency is preserved, and the new paths have unit-test coverage.
The core webhook change is straightforward: it extracts existing logic, adds one guard, and adds one event branch. Stripe's event semantics ensure checkout.session.async_payment_succeeded only fires after payment settles, and the paymentIntentId dedup check prevents double-grants regardless. No pre-existing paths are altered.
No files require special attention.
Important Files Changed
Reviews (2): Last reviewed commit: "fix(web): only grant developer credits o..." | Re-trigger Greptile