'the the event type' (slots API property description), 'for for new
booking' (EventManager debug log), and 'one-one one template'
(en locale).
Co-authored-by: Sahitya Chandra <sahityajb@gmail.com>
The first member of a dynamic group decides which conferencing app the group
booking uses. getPublicEvent read users[0] straight from findUsersByUsername,
which does not guarantee the order given in the URL, so /alice+bob could end up
using bob's preference — or fall back to Cal Video when only alice has one set.
handleNewBooking already sorts by the dynamic user list for this exact reason
(sortUsersByDynamicList in getLocationValuesForDb.ts). Applying the same order
here keeps the advertised location and the booked location in sync.
PayPal order creation divided every amount by 100 before sending it,
regardless of currency. JPY has no minor unit, so `convertToSmallestCurrencyUnit`
stores it unscaled and dividing corrupted it: a JPY 10000 event asked PayPal
to collect 100.
Use the same zero-decimal-aware conversion that the price input and
`formatPrice` already use, so the amount charged matches the amount stored
and displayed. Currencies with minor units are unaffected — among PayPal's
supported currencies only JPY is both zero-decimal and supported, and the
added tests pin USD, EUR, HUF and TWD to their existing behaviour.
HitPay's payment-requests API takes `amount` as a decimal string in major
units, so the integration divided by 100 on the way out and multiplied by 100
on the way back in. Both conversions were unconditional, but the price input
writes the amount through `convertToSmallestCurrencyUnit`, which stores
zero-decimal currencies unscaled.
For the three zero-decimal currencies HitPay offers — JPY, KRW and VND — a
¥10,000 event therefore asked HitPay to collect ¥100. The two unconditional
conversions cancelled out in the database, so the Payment row and the booker's
page both still showed ¥10,000 while only 1% was charged, and `refund()` is
not implemented for this app.
Route both directions through the shared helpers so the amount charged matches
the amount stored and displayed. Currencies with a minor unit are unaffected;
the added tests pin USD and SGD to their existing behaviour.
* Add Clara app (link-as-an-app, AI & Automation)
Clara is a website conversion agent: it answers website visitors in text and
voice and books meetings into their Cal.com. This is a link-as-an-app listing
(same pattern as the existing Chatbase app) — it opens a setup guide at
claraconverts.com/guide/cal-com; nothing installs into the user's account.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Point Clara listing CTA to the Cal.com setup guide
Addresses CodeRabbit review: the "Get started" link in DESCRIPTION.md pointed
to the homepage; point it to claraconverts.com/guide/cal-com so the CTA leads
straight to Cal.com onboarding (matching the app's externalLink).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: trim whitespace in parseIpFromHeaders to prevent IP banlist bypass
Fixes#29851
parseIpFromHeaders did not trim whitespace after splitting on comma
(or from array elements), so a padded x-forwarded-for header like
' 1.2.3.4 , 5.6.7.8' would fail an exact-match lookup against
IP_BANLIST, allowing a banned IP to bypass the check.
Added regression tests covering padded string, array, and an
end-to-end isIpInBanlist test reproducing the reported bypass.
* fix: address CodeRabbit review feedback
- Guard against undefined rawIp when parsing an empty header array
- Delete IP_BANLIST env var in test cleanup instead of assigning undefined
- Add regression test for empty array input to parseIpFromHeaders
* test: add regression test for tab/whitespace trimming in parseIpFromHeaders
The Hebrew locale was missing a space between the number and the minute/hour
short form abbreviation (e.g. "15ד׳" instead of "15 ד׳"). This caused
screen readers to pronounce the duration incorrectly ("15 metres") and
made the booking page duration text difficult to read.
Also fixes the same issue for hour_one_short.
* fix(bookings): refund all seat payments when cancelling a paid seated booking
A paid seated booking creates one Payment row per seat on the same bookingId (each seat's payment_intent.succeeded webhook flips only its own payment via handlePaymentSuccess). processPaymentRefund only refunded payment.find((p) => p.success) — the first successful payment — so fully cancelling a multi-seat paid booking with an auto-refund policy refunded a single seat and left the other attendees charged with no error surfaced.
Refund every successful, not-yet-refunded payment instead.
Tests: unit regression in processPaymentRefund.test.ts (all payments refunded, verified red before the fix); integration test in handleCancelBooking.test.ts proving a full cancel of a seated multi-payment booking hands all payments to the refund path.
* fix(bookings): make seat refunds resilient and drop dayjs in test
Address CodeRabbit review on #29685:
- processPaymentRefund now attempts every seat's refund independently and throws an aggregate error, so one provider failure no longer skips the remaining seats.
- Add a regression test for that resilience, and use native Date instead of dayjs for fixed-offset test timestamps.
When a booking question is optional and left unanswered, the field value
stored in customInputs can be null. The previous guard only checked
for empty strings, so null (and undefined) would coerce to the literal
strings "null"/"undefined" inside the template literal, polluting calendar
event descriptions.
Fixes#29688
Fixes an issue where cancellation emails were sending ICS files with
METHOD:REQUEST instead of METHOD:CANCEL. As per RFC 5546, calendar
clients only process cancellations correctly when METHOD:CANCEL is used,
otherwise cancelled events remain in attendee calendars with active
reminders.
- Use METHOD:CANCEL for cancelled booking ICS files
- Keep METHOD:REQUEST for non-cancelled booking invitations
- Preserve STATUS:CANCELLED and sequence handling
- Add tests to verify the generated ICS method for cancellations
* refactor: derive category metadata from single Record source of truth
* test: add test for category order and entries sync
* fix: check for duplicates
generateUsernameSuggestion built its numeric suffix with
String(rand).padStart(4 - rand.toString().length, "0"). padStart's first
argument is the target total length, not the number of pad characters, so the
expression only padded single-digit numbers: rand=1 became "001" but rand=42
became "42" and rand=999 stayed "999". Suggested usernames therefore had
inconsistent suffix widths, and the magic 4 didn't match the 3-wide output the
existing test pins ("johnny001").
Pull the suffix into a small helper that always pads to 3 digits, so collisions
and the returned value share one definition and every suggestion is uniformly
zero-padded. Add unit tests for the first-attempt path and for two- and
three-digit suffixes; the two-digit case fails on the old code.
Co-authored-by: Bandhan Majumder <133476557+bandhan-majumder@users.noreply.github.com>
- Remove two console.log statements dumping private hashed link data
in update.handler.ts (fixes production data leakage)
- Replace console.log(e) with structured logger.error in
getScheduleByEventTypeSlug.handler.ts
- Fix @ts-expect-error by typing reduce accumulator as
Record<string, unknown> in update.handler.ts
- Narrow Prisma include to select in duplicate.handler.ts:
team and webhooks now fetch only required fields
Fixes#29500
Co-authored-by: lucadapice <dapice.lc@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Bandhan Majumder <133476557+bandhan-majumder@users.noreply.github.com>
* fix: load more users on table scroll
Signed-off-by: Chayan Das <daschayan8837@gmail.com>
* refactor: move user listing logic to UserRepository
Add a dedicated listUsers method with search and cursor pagination support.
Add repository tests covering filtering and pagination.
Ensure user listing metadata remains consistent with the returned results.
Signed-off-by: Chayan Das <daschayan8837@gmail.com>
---------
Signed-off-by: Chayan Das <daschayan8837@gmail.com>
* refactor: use userRepository class methods for signup handlers
* feat: add user repo modules for DI
* feat: build user repo container
* test: integration test for user repository
* fix: use DI container in selfhosted
* refactor: tests, module, container and handlers
---------
Co-authored-by: Bandhan Majumder <bandhanmajumder16@gmail.com>
- disable update button until form is valid
- add required field validation
- add translation key for password complexity message
Signed-off-by: Chayan Das <daschayan8837@gmail.com>
* cleanup(webhooks): remove instant meeting trigger support
* remove dead code
* remove instant meeting from prisma enums and related cleanup
* fix(webhooks): make instant meeting enum migration atomic
- Fixes search mismatch for apps like 'Zoho CRM' vs 'zohocrm'
- Normalizes input by removing spaces, dashes, and underscores
- Updates ZohoCRM display name to 'Zoho CRM' for consistency
- Keeps change minimal without expanding search scope