Feat: Add auto replenish bundles#1298
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds per-user bundle auto-replenishment: DB model and indexes, service APIs to read/update status and trigger off‑session Stripe payments when bundle usage hits a threshold, webhook handlers to fulfill or disable on outcomes, payment-method persistence for reuse, controller endpoints, and integration tests. ChangesBundle Auto-Replenishment
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
desci-server/src/controllers/stripe/subscription.ts (1)
464-478: 💤 Low valueCatch-all 400 may mask unexpected server errors.
The catch block returns 400 for all non-ZodError exceptions. While business rule violations from the service (e.g., "no prior bundle purchase") should return 400, unexpected errors (database failures, etc.) should return 500 to aid debugging.
Consider checking for known business error types or messages to differentiate.
♻️ Suggested approach
} catch (error: any) { if (error instanceof ZodError) { return res.status(400).json({ error: 'Invalid request body', details: formatZodIssues(error), }); } logger.error({ err: error, userId: req.user?.id }, 'Failed to update bundle auto-replenishment'); - return res.status(400).json({ - error: error instanceof Error ? error.message : 'Failed to update bundle auto-replenishment', - }); + // Known business rule violations from service + const knownClientErrors = [ + 'No prior bundle purchase found', + 'Active paid subscription exists', + 'No Stripe customer found', + 'No reusable payment method found', + ]; + const isClientError = error instanceof Error && knownClientErrors.some((msg) => error.message.includes(msg)); + + return res.status(isClientError ? 400 : 500).json({ + error: error instanceof Error ? error.message : 'Failed to update bundle auto-replenishment', + }); }🤖 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 `@desci-server/src/controllers/stripe/subscription.ts` around lines 464 - 478, The catch-all currently returns 400 for every non-ZodError in the update bundle auto-replenishment handler; change it to distinguish expected business errors from unexpected server errors by checking for known business error types/messages (e.g., a custom BusinessError/SubscriptionError thrown by the service, or specific error codes/text) and return res.status(400) with that error message for those cases, but for all other errors log them via logger.error({ err: error, userId: req.user?.id }, 'Failed to update bundle auto-replenishment') and return res.status(500). Ensure you still handle ZodError with formatZodIssues and preserve the existing logging; reference the existing ZodError check, formatZodIssues, logger.error and the final res.status response when implementing the branching.desci-server/test/integration/bundleAutoReplenishment.test.ts (1)
184-229: ⚡ Quick winAdd a failure-path integration test for auto-replenishment disablement.
The suite validates enable/trigger/success, but it misses the payment-failure branch. Add one test that exercises the failure handler and asserts
isEnabledis turned off,replenishmentInProgressis cleared, and failure metadata is persisted.🤖 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 `@desci-server/test/integration/bundleAutoReplenishment.test.ts` around lines 184 - 229, Add an integration test mirroring the success test that calls SubscriptionService.handleBundleAutoReplenishmentFailed with a failed payment intent payload (e.g., id 'pi_auto_fail_123', matching metadata type 'bundle_auto_replenishment' and userId), then assert the bundleAutoReplenishment row for that user has isEnabled === false and replenishmentInProgress === false, assert a failure timestamp/field was set (e.g., lastFailedAt is not null) on the bundleAutoReplenishment record, and assert a related stripe record was created (e.g., prisma.stripeCheckoutFulfillment found with stripeSessionId 'auto_replenishment:pi_auto_fail_123') to confirm failure metadata was persisted.
🤖 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.
Nitpick comments:
In `@desci-server/src/controllers/stripe/subscription.ts`:
- Around line 464-478: The catch-all currently returns 400 for every
non-ZodError in the update bundle auto-replenishment handler; change it to
distinguish expected business errors from unexpected server errors by checking
for known business error types/messages (e.g., a custom
BusinessError/SubscriptionError thrown by the service, or specific error
codes/text) and return res.status(400) with that error message for those cases,
but for all other errors log them via logger.error({ err: error, userId:
req.user?.id }, 'Failed to update bundle auto-replenishment') and return
res.status(500). Ensure you still handle ZodError with formatZodIssues and
preserve the existing logging; reference the existing ZodError check,
formatZodIssues, logger.error and the final res.status response when
implementing the branching.
In `@desci-server/test/integration/bundleAutoReplenishment.test.ts`:
- Around line 184-229: Add an integration test mirroring the success test that
calls SubscriptionService.handleBundleAutoReplenishmentFailed with a failed
payment intent payload (e.g., id 'pi_auto_fail_123', matching metadata type
'bundle_auto_replenishment' and userId), then assert the bundleAutoReplenishment
row for that user has isEnabled === false and replenishmentInProgress === false,
assert a failure timestamp/field was set (e.g., lastFailedAt is not null) on the
bundleAutoReplenishment record, and assert a related stripe record was created
(e.g., prisma.stripeCheckoutFulfillment found with stripeSessionId
'auto_replenishment:pi_auto_fail_123') to confirm failure metadata was
persisted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3f41010f-e62f-4d6b-8261-327b2a7c460b
📒 Files selected for processing (8)
desci-server/prisma/migrations/20260518120000_add_bundle_auto_replenishment/migration.sqldesci-server/prisma/schema.prismadesci-server/src/controllers/stripe/subscription.tsdesci-server/src/controllers/stripe/webhook.tsdesci-server/src/routes/v1/stripe.tsdesci-server/src/services/FeatureLimits/FeatureUsageService.tsdesci-server/src/services/SubscriptionService.tsdesci-server/test/integration/bundleAutoReplenishment.test.ts
Closes #<GH_issue_number>
Description of the Problem / Feature
Explanation of the solution
Instructions on making this work
UI changes for review
When major UI changes will happen with this PR, please include links to URLS to compare or screenshots demonstrating the difference and notify design
Summary by CodeRabbit
New Features
Billing & Webhooks
Tests