"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; import QRCode from "react-qr-code"; import z from "zod"; import type { PaymentPageProps } from "@calcom/features/ee/payments/pages/payment"; import { useBookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useCopy } from "@calcom/lib/hooks/useCopy"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc"; import { Button } from "@calcom/ui/components/button"; import { Spinner } from "@calcom/ui/components/icon"; import { showToast } from "@calcom/ui/components/toast"; interface IAlbyPaymentComponentProps { payment: { // Will be parsed on render data: unknown; }; paymentPageProps: PaymentPageProps; } // Create zod schema for data const PaymentAlbyDataSchema = z.object({ invoice: z .object({ paymentRequest: z.string(), }) .required(), }); export const AlbyPaymentComponent = (props: IAlbyPaymentComponentProps) => { const { payment } = props; const { data } = payment; const [showQRCode, setShowQRCode] = useState(window.webln === undefined); const [isPaying, setPaying] = useState(false); const { copyToClipboard, isCopied } = useCopy(); const wrongUrl = ( <>

Couldn't obtain payment URL

); const parsedData = PaymentAlbyDataSchema.safeParse(data); if (!parsedData.success || !parsedData.data?.invoice?.paymentRequest) { return wrongUrl; } const paymentRequest = parsedData.data.invoice.paymentRequest; return (
{isPaying && } {!isPaying && ( <> {!showQRCode && (
{window.webln && ( )}
)} {showQRCode && ( <>

Waiting for payment

Click or scan the invoice below to pay

Don't have a lightning wallet? )} )}
Powered by  Alby Alby
); }; type PaymentCheckerProps = PaymentPageProps; function PaymentChecker(props: PaymentCheckerProps) { // TODO: move booking success code to a common lib function // TODO: subscribe rather than polling const searchParams = useCompatSearchParams(); const bookingSuccessRedirect = useBookingSuccessRedirect(); const utils = trpc.useUtils(); const { t } = useLocale(); useEffect(() => { if (searchParams === null) { return; } // use closure to ensure non-nullability const sp = searchParams; const interval = setInterval(() => { (async () => { if (props.booking.status === "ACCEPTED") { return; } const { booking: bookingResult } = await utils.viewer.bookings.find.fetch({ bookingUid: props.booking.uid, }); if (bookingResult?.paid) { showToast("Payment successful", "success"); const params: { uid: string; email: string | null; location: string; } = { uid: props.booking.uid, email: sp.get("email"), location: t("web_conferencing_details_to_follow"), }; bookingSuccessRedirect({ successRedirectUrl: props.eventType.successRedirectUrl, query: params, booking: props.booking, forwardParamsSuccessRedirect: props.eventType.forwardParamsSuccessRedirect, }); } })(); }, 1000); return () => clearInterval(interval); }, [ bookingSuccessRedirect, props.booking, props.booking.id, props.booking.status, props.eventType.id, props.eventType.successRedirectUrl, props.eventType.forwardParamsSuccessRedirect, props.payment.success, searchParams, t, utils.viewer.bookings, ]); return null; }