Skip to content

Comments

feat: Write same metadata to Stripe for booking payment and no show fee payment#23634

Merged
hariombalhara merged 12 commits intomainfrom
stripe-no-show-fee-write-metadata
Sep 15, 2025
Merged

feat: Write same metadata to Stripe for booking payment and no show fee payment#23634
hariombalhara merged 12 commits intomainfrom
stripe-no-show-fee-write-metadata

Conversation

@joeauyeung
Copy link
Contributor

@joeauyeung joeauyeung commented Sep 5, 2025

What does this PR do?

This PR ensures the same booking metadata is written to the Stripe payment for paid bookings and when charging a no show fee

  • Creates a shared this.generateMetadata in the Stripe PaymentService

Booking payment metadata

Screenshot From 2025-09-05 15-44-16

No show fee payment metadata

Screenshot From 2025-09-05 15-45-06 015-45-06.png

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Are there environment variables that should be set?
  • What are the minimal test data to have?
  • What is expected (happy path) to have (input and output)?
  • Any other important info that could help to test that PR

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 5, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

  • PaymentService.ts: Introduces a private generateMetadata helper used in both create and chargeCard to build PaymentIntent metadata. chargeCard signature changed to require bookingId (removed optional parameter). chargeCard now fetches booking via BookingRepository.findBookingById, constructs metadata from booking fields (user, attendees, event title, booking title), and updates error logs to reference bookingId. AfterPayment attendee filtering is reformatted with an explicit arrow function.
  • booking.ts: Adds BookingRepository.findBookingById using Prisma with bookingMinimalSelect plus eventType.title, user.id/username, and attendees’ fields.
  • payments.tsx: Removes the TRPC paymentsRouter exposing the chargeCard mutation and its associated processing.

Possibly related PRs

✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stripe-no-show-fee-write-metadata

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@graphite-app graphite-app bot requested a review from a team September 5, 2025 19:42
@keithwillcode keithwillcode added core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO labels Sep 5, 2025
@dosubot dosubot bot added the billing area: billing, stripe, payments, paypal, get paid label Sep 5, 2025
throw new Error("Stripe credentials not found");
}

const stripeAppKeys = await prisma.app.findFirst({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This variable is unused

slug: "stripe",
},
});
const bookingRepository = new BookingRepository(prisma);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We now need to get the booking to generate the metadata to send to Stripe

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: 4

🧹 Nitpick comments (1)
packages/app-store/stripepayment/lib/PaymentService.ts (1)

226-231: Good: fetch booking via repository

Using BookingRepository centralizes selection and avoids ad-hoc Prisma in the service. If this path is hot or tested often, consider injecting the repository (constructor param) for easier mocking.

📜 Review details

Configuration used: Path: .coderabbit.yaml

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 71a2b69 and 6789b20.

📒 Files selected for processing (3)
  • packages/app-store/stripepayment/lib/PaymentService.ts (7 hunks)
  • packages/lib/server/repository/booking.ts (1 hunks)
  • packages/trpc/server/routers/viewer/payments.tsx (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/trpc/server/routers/viewer/payments.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.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/app-store/stripepayment/lib/PaymentService.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/app-store/stripepayment/lib/PaymentService.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/lib/server/repository/booking.ts
  • packages/app-store/stripepayment/lib/PaymentService.ts
**/*Service.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Service files must include Service suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts)

Files:

  • packages/app-store/stripepayment/lib/PaymentService.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.018Z
Learning: In BookingPaymentInitiatedDTO and other webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking field is a restricted structure containing only specific fields (id, eventTypeId, userId) rather than the full database booking object, so there are no security or PII leakage concerns when passing the booking object to buildEventPayload.
📚 Learning: 2025-08-21T13:44:06.805Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.805Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • packages/lib/server/repository/booking.ts
📚 Learning: 2025-08-19T09:47:49.478Z
Learnt from: eunjae-lee
PR: calcom/cal.com#23166
File: packages/prisma/migrations/20250818151914_routing_form_response_denormalized_backfill2/migration.sql:65-66
Timestamp: 2025-08-19T09:47:49.478Z
Learning: The Booking table has a unique constraint and index on the uid column (defined as `uid String unique` in schema.prisma), so JOINs on Booking.uid have optimal performance and don't require additional indexing.

Applied to files:

  • packages/lib/server/repository/booking.ts
🧬 Code graph analysis (2)
packages/lib/server/repository/booking.ts (1)
packages/prisma/selects/booking.ts (1)
  • bookingMinimalSelect (3-13)
packages/app-store/stripepayment/lib/PaymentService.ts (1)
apps/api/v2/src/lib/logger.bridge.ts (1)
  • error (77-79)
⏰ Context from checks skipped due to timeout of 180000ms. 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 (2)
packages/app-store/stripepayment/lib/PaymentService.ts (2)

90-100: Good: unified metadata construction via helper

Switching create() to use a shared generator keeps booking and no-show flows consistent.


369-371: LGTM: clearer attendee filtering

Explicit arrow function improves readability without changing behavior.

}

async chargeCard(payment: Payment, _bookingId?: Booking["id"]): Promise<Payment> {
async chargeCard(payment: Payment, bookingId: Booking["id"]): Promise<Payment> {
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Signature change: ensure all call sites updated to required bookingId

chargeCard now requires bookingId (no optional param). Verify interface/type and usages are updated accordingly to avoid compile/runtime breaks.

Run:


🏁 Script executed:

#!/bin/bash
# 1) Check IAbstractPaymentService signature
rg -nP -C3 'IAbstractPaymentService' --type=ts --type=tsx

# 2) Find all call sites of chargeCard
rg -nP -C2 '\bchargeCard\s*\(' --type=ts --type=tsx

# 3) Sanity: ensure two args are passed (payment, bookingId)
rg -nP -C2 '\bchargeCard\s*\(\s*[^,]+,\s*[^)]+\)' --type=ts --type=tsx

Length of output: 268


🏁 Script executed:

#!/bin/bash
# 1) Check IAbstractPaymentService signature in TS files
rg -nP -C3 'interface\s+IAbstractPaymentService' -g '*.ts'

# 2) Find all call sites of chargeCard in TS files
rg -nP -C2 '\bchargeCard\s*\(' -g '*.ts'

# 3) Sanity: ensure two args are passed (payment, bookingId)
rg -nP -C2 '\bchargeCard\s*\(\s*[^,]+,\s*[^)]+\)' -g '*.ts'

Length of output: 5205


Require bookingId parameter across interface and all implementations

  • In packages/types/PaymentService.d.ts, change bookingId?: Booking["id"] to bookingId: Booking["id"]
  • Update stub in setupVitest.ts to async chargeCard(payment, bookingId)
  • In packages/app-store/paypal/lib/PaymentService.ts and packages/app-store/mock-payment-app/lib/PaymentService.ts, add (payment, bookingId) to chargeCard signature
  • Verify other store providers (hitpay, btcpayserver, alby) conform to the updated signature
🤖 Prompt for AI Agents
In packages/app-store/stripepayment/lib/PaymentService.ts around line 220, the
chargeCard method currently declares async chargeCard(payment: Payment,
bookingId is missing) — update the signature to async chargeCard(payment:
Payment, bookingId: Booking["id"]): Promise<Payment> to match the new required
bookingId in the interface; then update all call sites in this file to pass
bookingId. Also modify packages/types/PaymentService.d.ts to make bookingId
non-optional, change the stub in setupVitest.ts to async chargeCard(payment,
bookingId), and update chargeCard signatures in
packages/app-store/paypal/lib/PaymentService.ts and
packages/app-store/mock-payment-app/lib/PaymentService.ts; finally scan and
adjust other store providers (hitpay, btcpayserver, alby) and any tests or
usages to accept and forward the bookingId parameter.

Comment on lines +260 to +269
metadata: this.generateMetadata({
bookingId,
userId: booking.user?.id,
username: booking.user?.username,
bookerName: booking.attendees[0].name,
bookerEmail: booking.attendees[0].email,
bookerPhoneNumber: booking.attendees[0].phoneNumber ?? null,
eventTitle: booking.eventType?.title || null,
bookingTitle: booking.title,
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against empty/missing attendees to avoid runtime crash

booking.attendees[0] can be undefined (e.g., malformed data, edge imports). Add optional chaining and safe fallbacks.

Apply this diff:

-        metadata: this.generateMetadata({
-          bookingId,
-          userId: booking.user?.id,
-          username: booking.user?.username,
-          bookerName: booking.attendees[0].name,
-          bookerEmail: booking.attendees[0].email,
-          bookerPhoneNumber: booking.attendees[0].phoneNumber ?? null,
-          eventTitle: booking.eventType?.title || null,
-          bookingTitle: booking.title,
-        }),
+        metadata: this.generateMetadata({
+          bookingId,
+          userId: booking.user?.id ?? null,
+          username: booking.user?.username ?? null,
+          bookerName: booking.attendees?.[0]?.name ?? "",
+          bookerEmail: booking.attendees?.[0]?.email ?? "",
+          bookerPhoneNumber: booking.attendees?.[0]?.phoneNumber ?? null,
+          eventTitle: booking.eventType?.title ?? null,
+          bookingTitle: booking.title ?? "",
+        }),
🤖 Prompt for AI Agents
In packages/app-store/stripepayment/lib/PaymentService.ts around lines 260 to
269, metadata accesses booking.attendees[0] directly which can be undefined and
cause a runtime crash; change those accesses to use optional chaining and safe
fallbacks (e.g., booking.attendees?.[0]?.name ?? null,
booking.attendees?.[0]?.email ?? null, booking.attendees?.[0]?.phoneNumber ??
null) so bookerName, bookerEmail and bookerPhoneNumber always have defined safe
values before calling this.generateMetadata.

Comment on lines 383 to 410
async findBookingById(bookingId: number) {
return await this.prismaClient.booking.findUnique({
where: {
id: bookingId,
},
select: {
...bookingMinimalSelect,
eventType: {
select: {
title: true,
},
},
user: {
select: {
id: true,
username: true,
},
},
attendees: {
select: {
name: true,
email: true,
phoneNumber: true,
},
},
},
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Select only fields needed for Stripe metadata; drop bookingMinimalSelect here

This repository method is used to build Stripe metadata (event title, booking title, user id/username, and primary attendee info). Spreading bookingMinimalSelect pulls extra fields (customInputs, description, metadata, start/endTime, etc.) that aren’t needed. Tighten the select to minimize data surface and match our Prisma guideline.

Apply this diff:

-  async findBookingById(bookingId: number) {
-    return await this.prismaClient.booking.findUnique({
-      where: {
-        id: bookingId,
-      },
-      select: {
-        ...bookingMinimalSelect,
-        eventType: {
-          select: {
-            title: true,
-          },
-        },
-        user: {
-          select: {
-            id: true,
-            username: true,
-          },
-        },
-        attendees: {
-          select: {
-            name: true,
-            email: true,
-            phoneNumber: true,
-          },
-        },
-      },
-    });
-  }
+  async findBookingById(bookingId: number) {
+    return await this.prismaClient.booking.findUnique({
+      where: { id: bookingId },
+      select: {
+        id: true,
+        title: true,
+        eventType: { select: { title: true } },
+        user: { select: { id: true, username: true } },
+        attendees: { select: { name: true, email: true, phoneNumber: true } },
+      },
+    });
+  }

Optional: align with repo style by accepting a param object, e.g., findBookingById({ bookingId }: { bookingId: number }).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async findBookingById(bookingId: number) {
return await this.prismaClient.booking.findUnique({
where: {
id: bookingId,
},
select: {
...bookingMinimalSelect,
eventType: {
select: {
title: true,
},
},
user: {
select: {
id: true,
username: true,
},
},
attendees: {
select: {
name: true,
email: true,
phoneNumber: true,
},
},
},
});
}
async findBookingById(bookingId: number) {
return await this.prismaClient.booking.findUnique({
where: { id: bookingId },
select: {
id: true,
title: true,
eventType: { select: { title: true } },
user: { select: { id: true, username: true } },
attendees: { select: { name: true, email: true, phoneNumber: true } },
},
});
}

Copy link
Member

@hariombalhara hariombalhara left a comment

Choose a reason for hiding this comment

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

Left some comments

@github-actions github-actions bot marked this pull request as draft September 8, 2025 11:56
@vercel
Copy link

vercel bot commented Sep 8, 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 Sep 15, 2025 7:54am
cal-eu Ignored Ignored Sep 15, 2025 7:54am

@joeauyeung joeauyeung marked this pull request as ready for review September 13, 2025 02:01
@dosubot dosubot bot added the ✨ feature New feature or request label Sep 13, 2025
Comment on lines +414 to +416
// Ascending order ensures that the first attendee in the list is the booker and others are guests
// See why it is important https://github.com/calcom/cal.com/pull/20935
// TODO: Ideally we should return `booker` property directly from the booking
Copy link
Member

Choose a reason for hiding this comment

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

cc @joeauyeung made this change here

@github-actions
Copy link
Contributor

E2E results are ready!

@hariombalhara hariombalhara merged commit 31270ca into main Sep 15, 2025
42 checks passed
@hariombalhara hariombalhara deleted the stripe-no-show-fee-write-metadata branch September 15, 2025 08:50
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 core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO ✨ feature New feature or request ready-for-e2e size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants