Skip to content

fix: redirect to existing payment session on paid booking retry#23096

Merged
anikdhabal merged 10 commits intomainfrom
devin/payment-rebooking-fix-1755182142
Aug 18, 2025
Merged

fix: redirect to existing payment session on paid booking retry#23096
anikdhabal merged 10 commits intomainfrom
devin/payment-rebooking-fix-1755182142

Conversation

@anikdhabal
Copy link
Contributor

@anikdhabal anikdhabal commented Aug 14, 2025

Summary

Fixes payment bypass issue where users who cancel payment and retry booking see success page instead of payment form. Now returns existing payment UID to redirect users to original payment session, preventing duplicate bookings while ensuring payment completion.

This PR also adds unit tests to verify the payment redirect fix that ensures users who cancel payment and retry booking get redirected to the existing payment session instead of creating duplicate bookings.

Fixes CAL-6247
Fixes #23065

devin-ai-integration bot and others added 2 commits August 14, 2025 15:37
- Extract existing payment UID from unpaid bookings
- Return paymentUid and paymentId when payment form should be shown
- Prevents payment form bypass by redirecting to original payment session
- Maintains payment continuity without creating duplicate bookings

Fixes issue where canceled payments showed success page on rebooking

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
- Test verifies existing payment UID is returned on booking retry
- Ensures no duplicate bookings are created in database
- Validates paymentRequired flag is set correctly for unpaid bookings
- Covers the payment redirect fix in handleNewBooking.ts

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 14, 2025

Walkthrough

The PR modifies handleNewBooking to treat existing bookings for paid events as payment-aware: it computes requiresPayment from paymentAppData.price, determines if an existing booking is paid, sets paymentRequired accordingly, and returns existing payment identifiers (paymentUid, paymentId) when appropriate. The booking repository's findOriginalRescheduledBooking include now fetches related payment records. Tests add a payment-retry scenario asserting idempotent booking and payment behavior for repeated paid-event booking attempts.

Assessment against linked issues

Objective Addressed Explanation
Prevent clients from bypassing immediate payment on booking; enforce payment flow for paid events and avoid duplicate bookings/payments on retries (#23065, CAL-6247)

Out-of-scope changes

Code Change (file_path) Explanation
Added prismaMock import (packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts) Test helper import; not directly required by the production fix—affects test implementation only.
Duplicate test block inserted (packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts) Duplicate test appears twice in the patch; this is a test maintenance issue unrelated to the production objective.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c81a909 and 4ac0f9e.

📒 Files selected for processing (1)
  • packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/payment-rebooking-fix-1755182142

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@keithwillcode keithwillcode added the core area: core, team members only label Aug 14, 2025
@anikdhabal anikdhabal changed the title test: add unit tests for payment retry scenario fix: redirect to existing payment session on paid booking retry Aug 14, 2025
@vercel
Copy link

vercel bot commented Aug 14, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Aug 18, 2025 11:21am
cal-eu Ignored Ignored Aug 18, 2025 11:21am

@anikdhabal anikdhabal marked this pull request as ready for review August 14, 2025 16:46
@anikdhabal anikdhabal requested a review from a team as a code owner August 14, 2025 16:46
@graphite-app graphite-app bot requested a review from a team August 14, 2025 16:46
@dosubot dosubot bot added bookings area: bookings, availability, timezones, double booking 🐛 bug Something isn't working labels Aug 14, 2025
@linear
Copy link

linear bot commented Aug 14, 2025

@anikdhabal anikdhabal requested a review from joeauyeung August 14, 2025 16:49
@graphite-app
Copy link

graphite-app bot commented Aug 14, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (08/14/25)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add ready-for-e2e label" took an action on this PR • (08/18/25)

1 label was added to this PR based on Keith Williams's automation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (2)
packages/lib/server/repository/booking.ts (2)

827-833: Limit payment include to id/uid, order desc and take 1 — safe to apply

Verified this repo call site: getValidBookingFromEventTypeForAttendee is only used by packages/features/bookings/lib/handleNewBooking.ts, and that caller only reads payment[0].id and payment[0].uid — changing the include to a single, ordered slim payment is safe.

  • Files to change:
    • packages/lib/server/repository/booking.ts (getValidBookingFromEventTypeForAttendee include block, ~lines 827–833)

Apply this diff:

       include: {
         attendees: true,
         references: true,
         user: true,
-        payment: true,
+        payment: {
+          select: { id: true, uid: true },
+          orderBy: { id: "desc" },
+          take: 1,
+        },
       },

684-688: Limit payment payload and return latest successful payment (deterministic ordering)

The current include (payment: true) returns all payment columns in unspecified order. Downstream code expects to find a successful payment and the id/uid — returning a single, deterministic, small object is safer and smaller.

Files to change:

  • packages/lib/server/repository/booking.ts — findOriginalRescheduledBooking include block (around the current lines ~684–688).

Apply this diff:

         destinationCalendar: true,
-        payment: true,
+        payment: {
+          where: { success: true },
+          select: { id: true, uid: true, success: true },
+          orderBy: { id: "desc" },
+          take: 1,
+        },
         references: true,
         workflowReminders: true,

Notes:

  • We filter to successful payments, select only id/uid (and success so existing .find(payment => payment.success) still works), order by id desc and take 1 to get the newest successful payment deterministically.
  • There are many other occurrences of include: { payment: true } across the repo — consider a follow-up PR to convert them to explicit select-based nested relations per the “select-only” guideline to avoid large payloads and brittle order-dependent logic.
🧹 Nitpick comments (2)
packages/features/bookings/lib/handleNewBooking.ts (1)

565-571: Edge cases and ordering for firstPayment selection.

  • firstPayment = existingBooking.payment[0] assumes payments are ordered and that the first one is the correct (active) session to reuse. Without explicit ordering, Prisma won’t guarantee this.
  • If ever a booking exists with requiresPayment === true, existingBooking.paid === false, but existingBooking.payment.length === 0 (e.g., a previous session failed before a Payment row was created), current logic would treat it as “paid” and suppress the payment form.

Recommendations:

  • Pair this with the repository change to order and take: 1 (newest first) so [0] is deterministic.
  • Consider treating “no payments on unpaid booking” as “needs payment”:
    • const isPaidBooking = existingBooking.paid;
    • const firstPayment = (requiresPayment && !isPaidBooking && existingBooking.payment.length) ? existingBooking.payment[0] : undefined;

If you want, I can provide a precise diff for this block after you confirm expected behavior for the “unpaid but no payments” scenario.

packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts (1)

3283-3402: Solid coverage for “payment retry” behavior; minor nits.

This test validates the intended regression fix:

  • Reuses the existing booking UID on retry.
  • Returns the original payment UID instead of creating a new session.
  • Asserts no duplicate Booking rows and exactly one Payment row.

Minor suggestions:

  • The test mocks Daily Video and Google Calendar but seeds only the Stripe app in apps. They’re not exercised here (location is a plain string), so you can remove those extra mocks to reduce noise.
  • To align with repo guidelines on Prisma usage, optionally replace include: { payment: true } in the test queries with a minimal select (though this is less critical for tests).

If helpful, I can send an updated test snippet removing unused mocks.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e237749 and ced4a36.

📒 Files selected for processing (3)
  • packages/features/bookings/lib/handleNewBooking.ts (3 hunks)
  • packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts (2 hunks)
  • packages/lib/server/repository/booking.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/server/repository/booking.ts
  • packages/features/bookings/lib/handleNewBooking.ts
  • packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/lib/server/repository/booking.ts
  • packages/features/bookings/lib/handleNewBooking.ts
  • packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts
🧬 Code Graph Analysis (2)
packages/features/bookings/lib/handleNewBooking.ts (1)
packages/features/bookings/lib/getBookingResponsesSchema.ts (1)
  • bookingResponse (14-14)
packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts (3)
packages/platform/libraries/index.ts (1)
  • handleNewBooking (35-35)
apps/web/test/utils/bookingScenario/bookingScenario.ts (10)
  • getBooker (2178-2192)
  • getOrganizer (1496-1552)
  • TestData (1215-1487)
  • getGoogleCalendarCredential (1168-1176)
  • getStripeAppCredential (1206-1213)
  • getScenarioData (1554-1640)
  • createBookingScenario (954-985)
  • mockSuccessfulVideoMeetingCreation (2022-2040)
  • mockPaymentApp (2056-2081)
  • mockCalendarToHaveNoBusySlots (1901-1914)
apps/web/test/utils/bookingScenario/getMockRequestDataForBooking.ts (1)
  • getMockRequestDataForBooking (39-53)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: required
🔇 Additional comments (3)
packages/features/bookings/lib/handleNewBooking.ts (2)

554-554: Good: localizes “requires payment” decision next to existing-booking path.

requiresPayment derived from paymentAppData.price > 0 is concise and consistent with the rest of the handler.


578-589: Surfacing paymentRequired/paymentUid/paymentId for retry looks correct.

  • paymentRequired maps to shouldShowPaymentForm, which is the right flag to signal the client to redirect back to the payment flow.
  • Returning paymentUid/paymentId enables seamless redirection to the same payment session and avoids duplicate bookings.

This should address the “success page on retry” bug.

packages/features/bookings/lib/handleNewBooking/test/fresh-booking.test.ts (1)

10-11: Test infra import LGTM.

Importing prismaMock allows stable assertions against the in-memory DB state.

- Add deleteEventsAndMeetings call back to EventManager.reschedule() when changedOrganizer is true
- Add unit test to verify calendar deletion during round robin reassignment
- Fixes regression where calendar events weren't deleted from original host's calendar

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
@github-actions github-actions bot added billing area: billing, stripe, payments, paypal, get paid Private Created by Linear-GitHub Sync 🧹 Improvements Improvements to existing features. Mostly UX/UI labels Aug 14, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/features/ee/round-robin/roundRobinReassignment.test.ts (2)

298-404: Add delegated-credential scenario to harden deletion path

To prevent regressions for delegated credentials, add a variant where the booking reference has delegationCredentialId set (and possibly credentialId null/0). Assert deleteEventsAndMeetings receives that reference so delegated deletions are exercised.

I can draft the additional test block mirroring this one with a delegated reference if helpful.


298-404: Optional: reset spies between tests

Minor isolation improvement: clear/restore spies/mocks after each test to avoid cross-test leakage. setupAndTeardown may cover some of this, but being explicit helps if another test modifies the same spies.

For example:

import { afterEach } from "vitest";
afterEach(() => {
  vi.clearAllMocks();
  vi.restoreAllMocks();
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ced4a36 and 3c9f8c4.

📒 Files selected for processing (2)
  • packages/features/ee/round-robin/roundRobinReassignment.test.ts (1 hunks)
  • packages/lib/EventManager.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/EventManager.ts
  • packages/features/ee/round-robin/roundRobinReassignment.test.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/lib/EventManager.ts
  • packages/features/ee/round-robin/roundRobinReassignment.test.ts
🧠 Learnings (1)
📚 Learning: 2025-07-22T11:42:47.623Z
Learnt from: CarinaWolli
PR: calcom/cal.com#22296
File: packages/lib/bookings/filterHostsBySameRoundRobinHost.ts:41-42
Timestamp: 2025-07-22T11:42:47.623Z
Learning: The filterHostsBySameRoundRobinHost function in packages/lib/bookings/filterHostsBySameRoundRobinHost.ts has a known limitation where it doesn't work correctly with fixed hosts or round robin groups. This is pre-existing technical debt that was already broken before the round robin groups feature. CarinaWolli has documented this in Linear issue CAL-6134 for future fix.

Applied to files:

  • packages/features/ee/round-robin/roundRobinReassignment.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (1)
packages/features/ee/round-robin/roundRobinReassignment.test.ts (1)

298-404: Great regression test covering organizer change pre-delete — LGTM

This test asserts the right behaviors (reschedule with changedOrganizer + prior destination calendar, and deleteEventsAndMeetings called with the original host’s calendar reference). It directly guards the new flow.

joeauyeung
joeauyeung previously approved these changes Aug 15, 2025
Copy link
Contributor

@joeauyeung joeauyeung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@volnei volnei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small comment

@github-actions github-actions bot marked this pull request as draft August 18, 2025 11:05
auto-merge was automatically disabled August 18, 2025 11:05

Pull request was converted to draft

anikdhabal and others added 2 commits August 18, 2025 16:42
- Changed from numbered list format to concise single line description
- Test functionality remains unchanged and passes successfully

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
@anikdhabal anikdhabal requested a review from volnei August 18, 2025 11:22
@anikdhabal anikdhabal marked this pull request as ready for review August 18, 2025 11:22
@anikdhabal anikdhabal enabled auto-merge (squash) August 18, 2025 11:25
@anikdhabal anikdhabal merged commit a8a3a93 into main Aug 18, 2025
57 of 60 checks passed
@anikdhabal anikdhabal deleted the devin/payment-rebooking-fix-1755182142 branch August 18, 2025 11:44
@github-actions
Copy link
Contributor

E2E results are ready!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

billing area: billing, stripe, payments, paypal, get paid bookings area: bookings, availability, timezones, double booking 🐛 bug Something isn't working core area: core, team members only 🧹 Improvements Improvements to existing features. Mostly UX/UI Private Created by Linear-GitHub Sync ready-for-e2e

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clients bypass immediate payment when booking appointments

4 participants