"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const stringify = require("qs-stringify");
const z = require("zod");
const index$2 = require("./index-QSf6WJj0.cjs");
const getParsedAppKeysFromSlug = require("./getParsedAppKeysFromSlug-C26nE0Z-.cjs");
const indexBrowser = require("./index-browser-RtwB1hqI.cjs");
const querystring = require("querystring");
const getInstalledAppPath = require("./getInstalledAppPath-2GQAo7UC.cjs");
const createOAuthAppCredential = require("./createOAuthAppCredential--aER13Ig.cjs");
const decodeOAuthState = require("./decodeOAuthState--7fljpCf.cjs");
const stripeBilllingService = require("./stripe-billling-service-OFw1819B.cjs");
const getSafeRedirectUrl = require("./getSafeRedirectUrl-MkOChEjJ.cjs");
const httpError = require("./http-error-mkY8Ke7V.cjs");
const defaultHandler = require("./defaultHandler-MSjhitc-.cjs");
const defaultResponder = require("./defaultResponder-ZNP4idbH.cjs");
const Stripe = require("stripe");
const uuid = require("uuid");
const errors = require("./errors-fb9bpDY1.cjs");
require("@stripe/stripe-js/pure.js");
const index$3 = require("./email-manager-PJbiqghj.cjs");
const _metadata = require("./_metadata-7sljCkcz.cjs");
const getStripeAppKeys = () => getParsedAppKeysFromSlug.getParsedAppKeysFromSlug("stripe", indexBrowser.appKeysSchema$4);
async function handler$3(req, res) {
  const { client_id } = await getStripeAppKeys();
  if (req.method === "GET") {
    const user = await index$2.prisma.user.findUnique({
      where: {
        id: req.session?.user?.id
      },
      select: {
        email: true,
        name: true
      }
    });
    const redirect_uri = encodeURI(`${index$2.WEBAPP_URL}/api/integrations/stripepayment/callback`);
    const stripeConnectParams = {
      client_id,
      scope: "read_write",
      response_type: "code",
      stripe_user: {
        email: user?.email,
        first_name: user?.name || void 0,
        /** We need this so E2E don't fail for international users */
        country: process.env.NEXT_PUBLIC_IS_E2E ? "US" : void 0
      },
      redirect_uri,
      state: typeof req.query.state === "string" ? req.query.state : void 0
    };
    const params = z.z.record(z.z.any()).parse(stripeConnectParams);
    const query = stringify(params);
    const url = `https://connect.stripe.com/oauth/authorize?${query}`;
    res.status(200).json({ url });
  }
}
function getReturnToValueFromQueryState(req) {
  let returnTo = "";
  try {
    returnTo = JSON.parse(`${req.query.state}`).returnTo;
  } catch (error) {
    console.info("No 'returnTo' in req.query.state");
  }
  return returnTo;
}
async function handler$2(req, res) {
  const { code, error, error_description } = req.query;
  const state = decodeOAuthState.decodeOAuthState(req);
  if (error) {
    if (error === "access_denied") {
      state?.onErrorReturnTo ? res.redirect(state.onErrorReturnTo) : res.redirect("/apps/installed/payment");
    }
    const query = querystring.stringify({ error, error_description });
    res.redirect(`/apps/installed?${query}`);
    return;
  }
  if (!req.session?.user?.id) {
    return res.status(401).json({ message: "You must be logged in to do this" });
  }
  const response = await stripeBilllingService.stripe.oauth.token({
    grant_type: "authorization_code",
    code: code?.toString()
  });
  const data = { ...response, default_currency: "" };
  if (response["stripe_user_id"]) {
    const account = await stripeBilllingService.stripe.accounts.retrieve(response["stripe_user_id"]);
    data["default_currency"] = account.default_currency;
  }
  await createOAuthAppCredential.createOAuthAppCredential(
    { appId: "stripe", type: "stripe_payment" },
    data,
    req
  );
  const returnTo = getReturnToValueFromQueryState(req);
  res.redirect(returnTo || getInstalledAppPath.getInstalledAppPath({ variant: "payment", slug: "stripe" }));
}
async function handler$1(req, res) {
  if (req.method !== "POST" && req.method !== "GET")
    return res.status(405).json({ message: "Method not allowed" });
  if (!req.session?.user?.id)
    return res.status(401).json({ message: "Not authenticated" });
  const customerId = await stripeBilllingService.getStripeCustomerIdFromUserId(req.session.user.id);
  if (!customerId)
    return res.status(400).json({ message: "CustomerId not found in stripe" });
  let return_url = `${index$2.WEBAPP_URL}/settings/billing`;
  if (typeof req.query.returnTo === "string") {
    const safeRedirectUrl = getSafeRedirectUrl.getSafeRedirectUrl(req.query.returnTo);
    if (safeRedirectUrl)
      return_url = safeRedirectUrl;
  }
  const stripeSession = await stripeBilllingService.stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url
  });
  res.redirect(302, stripeSession.url);
}
const getPremiumMonthlyPlanPriceId = () => {
  return indexBrowser.PREMIUM_MONTHLY_PLAN_PRICE;
};
async function checkPremiumUsername(_username) {
  const username = indexBrowser.slugify(_username);
  const response = await fetch(`${index$2.WEBSITE_URL}/api/username`, {
    credentials: "include",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ username }),
    method: "POST",
    mode: "cors"
  });
  const json = await response.json();
  return json;
}
async function handler(req, res) {
  if (req.method === "GET") {
    const userId = req.session?.user.id;
    let { intentUsername = null } = req.query;
    const { callbackUrl } = req.query;
    if (!userId || !intentUsername) {
      res.status(404).end();
      return;
    }
    if (intentUsername && typeof intentUsername === "object") {
      intentUsername = intentUsername[0];
    }
    const customerId = await stripeBilllingService.getStripeCustomerIdFromUserId(userId);
    if (!customerId) {
      res.status(404).json({ message: "Missing customer id" });
      return;
    }
    const userData = await index$2.prisma.user.findFirst({
      where: { id: userId },
      select: { id: true, metadata: true }
    });
    if (!userData) {
      res.status(404).json({ message: "Missing user data" });
      return;
    }
    const return_url = `${index$2.WEBAPP_URL}/api/integrations/stripepayment/paymentCallback?checkoutSessionId={CHECKOUT_SESSION_ID}&callbackUrl=${callbackUrl}`;
    const createSessionParams = {
      mode: "subscription",
      line_items: [
        {
          quantity: 1,
          price: getPremiumMonthlyPlanPriceId()
        }
      ],
      allow_promotion_codes: true,
      customer: customerId,
      success_url: return_url,
      cancel_url: return_url
    };
    const checkPremiumResult = await checkPremiumUsername(intentUsername);
    if (!checkPremiumResult.available) {
      return res.status(404).json({ message: "Intent username not available" });
    }
    const stripeCustomer = await stripeBilllingService.stripe.customers.retrieve(customerId);
    if (!stripeCustomer || stripeCustomer.deleted) {
      return res.status(400).json({ message: "Stripe customer not found or deleted" });
    }
    await stripeBilllingService.stripe.customers.update(customerId, {
      metadata: {
        ...stripeCustomer.metadata,
        username: intentUsername
      }
    });
    if (userData) {
      await index$2.prisma.user.update({
        where: { id: userId },
        data: {
          metadata: {
            ...userData.metadata || {},
            isPremium: false
          }
        }
      });
    }
    const checkoutSession = await stripeBilllingService.stripe.checkout.sessions.create(createSessionParams);
    if (checkoutSession && checkoutSession.url) {
      return res.redirect(checkoutSession.url).end();
    }
    return res.status(404).json({ message: "Couldn't redirect to stripe checkout session" });
  }
}
async function getCustomerAndCheckoutSession(checkoutSessionId) {
  const billingService = new stripeBilllingService.StripeBillingService();
  const checkoutSession = await billingService.getCheckoutSession(checkoutSessionId);
  const customerOrCustomerId = checkoutSession.customer;
  let customerId = null;
  if (!customerOrCustomerId) {
    return { checkoutSession, stripeCustomer: null };
  }
  if (typeof customerOrCustomerId === "string") {
    customerId = customerOrCustomerId;
  } else if (customerOrCustomerId.deleted) {
    return { checkoutSession, stripeCustomer: null };
  } else {
    customerId = customerOrCustomerId.id;
  }
  const stripeCustomer = await billingService.getCustomer(customerId);
  if (stripeCustomer.deleted) {
    return { checkoutSession, stripeCustomer: null };
  }
  return { stripeCustomer, checkoutSession };
}
const querySchema = z.object({
  callbackUrl: z.string().transform((url) => {
    if (url.search(/^https?:\/\//) === -1) {
      url = `${index$2.WEBAPP_URL}${url}`;
    }
    return new URL(url);
  }),
  checkoutSessionId: z.string()
});
async function getHandler(req, res) {
  const { callbackUrl, checkoutSessionId } = querySchema.parse(req.query);
  const { stripeCustomer, checkoutSession } = await getCustomerAndCheckoutSession(checkoutSessionId);
  if (!stripeCustomer)
    throw new httpError.HttpError({
      statusCode: 404,
      message: "Stripe customer not found or deleted",
      url: req.url,
      method: req.method
    });
  let user = await index$2.prisma.user.findFirst({
    where: {
      metadata: {
        path: ["stripeCustomerId"],
        equals: stripeCustomer.id
      }
    }
  });
  if (!user && stripeCustomer.email) {
    user = await index$2.prisma.user.findFirst({
      where: {
        email: stripeCustomer.email
      }
    });
  }
  if (!user)
    throw new httpError.HttpError({ statusCode: 404, message: "User not found", url: req.url, method: req.method });
  if (checkoutSession.payment_status === "paid" && stripeCustomer.metadata.username) {
    try {
      await index$2.prisma.user.update({
        data: {
          username: stripeCustomer.metadata.username,
          metadata: {
            ...user.metadata,
            isPremium: true
          }
        },
        where: {
          id: user.id
        }
      });
    } catch (error) {
      console.error(error);
      throw new httpError.HttpError({
        statusCode: 400,
        url: req.url,
        method: req.method,
        message: "We have received your payment. Your premium username could still not be reserved. Please contact support@cal.com and mention your premium username"
      });
    }
  }
  callbackUrl.searchParams.set("paymentStatus", checkoutSession.payment_status);
  return res.redirect(callbackUrl.toString()).end();
}
const paymentCallback = defaultHandler.defaultHandler({
  GET: Promise.resolve({ default: defaultResponder.defaultResponder(getHandler) })
});
const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
  __proto__: null,
  add: handler$3,
  callback: handler$2,
  paymentCallback,
  portal: handler$1,
  subscription: handler
}, Symbol.toStringTag, { value: "Module" }));
function createPaymentLink(opts) {
  const { paymentUid, name, email, date, absolute = true } = opts;
  let link = "";
  if (absolute)
    link = index$2.WEBSITE_URL;
  const query = querystring.stringify({ date, name, email });
  return `${link}/payment/${paymentUid}?${query}`;
}
process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY || "";
const log = index$2.logger.getSubLogger({ prefix: ["payment-service:stripe"] });
const stripeCredentialKeysSchema = z.object({
  stripe_user_id: z.string(),
  default_currency: z.string(),
  stripe_publishable_key: z.string()
});
const stripeAppKeysSchema = z.object({
  client_id: z.string(),
  payment_fee_fixed: z.number(),
  payment_fee_percentage: z.number()
});
class PaymentService {
  constructor(credentials) {
    const keyParsing = stripeCredentialKeysSchema.safeParse(credentials.key);
    if (keyParsing.success) {
      this.credentials = keyParsing.data;
    } else {
      this.credentials = null;
    }
    this.stripe = new Stripe(process.env.STRIPE_PRIVATE_KEY || "", {
      apiVersion: "2020-08-27"
    });
  }
  async getPayment(where) {
    const payment = await index$2.prisma.payment.findFirst({ where });
    if (!payment)
      throw new Error("Payment not found");
    if (!payment.externalId)
      throw new Error("Payment externalId not found");
    return { ...payment, externalId: payment.externalId };
  }
  /* This method is for creating charges at the time of booking */
  async create(payment, bookingId, userId, username, bookerName, paymentOption, bookerEmail, bookerPhoneNumber, eventTitle, bookingTitle) {
    try {
      if (indexBrowser.paymentOptionEnum$1.parse(paymentOption) !== "ON_BOOKING") {
        throw new Error("Payment option is not compatible with create method");
      }
      if (!this.credentials) {
        throw new Error("Stripe credentials not found");
      }
      const customer = await stripeBilllingService.retrieveOrCreateStripeCustomerByEmail(
        this.credentials.stripe_user_id,
        bookerEmail,
        bookerPhoneNumber
      );
      const params = {
        amount: payment.amount,
        currency: payment.currency,
        customer: customer.id,
        automatic_payment_methods: {
          enabled: true
        },
        metadata: {
          identifier: "cal.com",
          bookingId,
          calAccountId: userId,
          calUsername: username,
          bookerName,
          bookerEmail,
          bookerPhoneNumber: bookerPhoneNumber ?? null,
          eventTitle: eventTitle || "",
          bookingTitle: bookingTitle || ""
        }
      };
      const paymentIntent = await this.stripe.paymentIntents.create(params, {
        stripeAccount: this.credentials.stripe_user_id
      });
      const paymentData = await index$2.prisma.payment.create({
        data: {
          uid: uuid.v4(),
          app: {
            connect: {
              slug: "stripe"
            }
          },
          booking: {
            connect: {
              id: bookingId
            }
          },
          amount: payment.amount,
          currency: payment.currency,
          externalId: paymentIntent.id,
          data: Object.assign({}, paymentIntent, {
            stripe_publishable_key: this.credentials.stripe_publishable_key,
            stripeAccount: this.credentials.stripe_user_id
          }),
          fee: 0,
          refunded: false,
          success: false,
          paymentOption: paymentOption || "ON_BOOKING"
        }
      });
      if (!paymentData) {
        throw new Error();
      }
      return paymentData;
    } catch (error) {
      log.error("Stripe: Payment could not be created", bookingId, indexBrowser.safeStringify(error));
      throw new Error("payment_not_created_error");
    }
  }
  async collectCard(payment, bookingId, paymentOption, bookerEmail, bookerPhoneNumber) {
    try {
      if (!this.credentials) {
        throw new Error("Stripe credentials not found");
      }
      if (indexBrowser.paymentOptionEnum$1.parse(paymentOption) !== "HOLD") {
        throw new Error("Payment option is not compatible with create method");
      }
      const customer = await stripeBilllingService.retrieveOrCreateStripeCustomerByEmail(
        this.credentials.stripe_user_id,
        bookerEmail,
        bookerPhoneNumber
      );
      const params = {
        customer: customer.id,
        payment_method_types: ["card"],
        metadata: {
          bookingId,
          bookerPhoneNumber: bookerPhoneNumber ?? null
        }
      };
      const setupIntent = await this.stripe.setupIntents.create(params, {
        stripeAccount: this.credentials.stripe_user_id
      });
      const paymentData = await index$2.prisma.payment.create({
        data: {
          uid: uuid.v4(),
          app: {
            connect: {
              slug: "stripe"
            }
          },
          booking: {
            connect: {
              id: bookingId
            }
          },
          amount: payment.amount,
          currency: payment.currency,
          externalId: setupIntent.id,
          data: Object.assign(
            {},
            {
              setupIntent,
              stripe_publishable_key: this.credentials.stripe_publishable_key,
              stripeAccount: this.credentials.stripe_user_id
            }
          ),
          fee: 0,
          refunded: false,
          success: false,
          paymentOption: paymentOption || "ON_BOOKING"
        }
      });
      return paymentData;
    } catch (error) {
      log.error(
        "Stripe: Payment method could not be collected for bookingId",
        bookingId,
        indexBrowser.safeStringify(error)
      );
      throw new Error("Stripe: Payment method could not be collected");
    }
  }
  async chargeCard(payment, _bookingId) {
    try {
      if (!this.credentials) {
        throw new Error("Stripe credentials not found");
      }
      const stripeAppKeys = await index$2.prisma.app.findFirst({
        select: {
          keys: true
        },
        where: {
          slug: "stripe"
        }
      });
      const paymentObject = payment.data;
      const setupIntent = paymentObject.setupIntent;
      const { payment_fee_fixed, payment_fee_percentage } = stripeAppKeysSchema.parse(stripeAppKeys?.keys);
      const paymentFee = Math.round(payment.amount * payment_fee_percentage + payment_fee_fixed);
      const customer = await this.stripe.customers.retrieve(setupIntent.customer, {
        stripeAccount: this.credentials.stripe_user_id
      });
      const paymentMethod = await this.stripe.paymentMethods.retrieve(setupIntent.payment_method, {
        stripeAccount: this.credentials.stripe_user_id
      });
      if (!customer) {
        throw new Error(`Stripe customer does not exist for setupIntent ${setupIntent.id}`);
      }
      if (!paymentMethod) {
        throw new Error(`Stripe paymentMethod does not exist for setupIntent ${setupIntent.id}`);
      }
      const params = {
        amount: payment.amount,
        currency: payment.currency,
        application_fee_amount: paymentFee,
        customer: setupIntent.customer,
        payment_method: setupIntent.payment_method,
        off_session: true,
        confirm: true
      };
      const paymentIntent = await this.stripe.paymentIntents.create(params, {
        stripeAccount: this.credentials.stripe_user_id
      });
      const paymentData = await index$2.prisma.payment.update({
        where: {
          id: payment.id
        },
        data: {
          success: true,
          data: {
            ...paymentObject,
            paymentIntent
          }
        }
      });
      if (!paymentData) {
        throw new Error();
      }
      return paymentData;
    } catch (error) {
      log.error("Stripe: Could not charge card for payment", _bookingId, indexBrowser.safeStringify(error));
      const errorMappings = {
        "your card was declined": "your_card_was_declined",
        "your card does not support this type of purchase": "your_card_does_not_support_this_type_of_purchase",
        "amount must convert to at least": "amount_must_convert_to_at_least"
      };
      let userMessage = "could_not_charge_card";
      if (error instanceof Error) {
        const errorMessage = error.message.toLowerCase();
        for (const [key, message] of Object.entries(errorMappings)) {
          if (errorMessage.includes(key)) {
            userMessage = message;
            break;
          }
        }
      }
      throw new errors.ErrorWithCode(errors.ErrorCode.ChargeCardFailure, userMessage);
    }
  }
  async update() {
    throw new Error("Method not implemented.");
  }
  async refund(paymentId) {
    try {
      const payment = await this.getPayment({
        id: paymentId,
        success: true,
        refunded: false
      });
      const refund = await this.stripe.refunds.create(
        {
          payment_intent: payment.externalId
        },
        { stripeAccount: payment.data["stripeAccount"] }
      );
      if (!refund || refund.status === "failed") {
        throw new Error("Refund failed");
      }
      const updatedPayment = await index$2.prisma.payment.update({
        where: {
          id: payment.id
        },
        data: {
          refunded: true
        }
      });
      return updatedPayment;
    } catch (e) {
      const err = errors.getErrorFromUnknown(e);
      throw err;
    }
  }
  async afterPayment(event, booking, paymentData, eventTypeMetadata) {
    await index$3.sendAwaitingPaymentEmailAndSMS(
      {
        ...event,
        paymentInfo: {
          link: createPaymentLink({
            paymentUid: paymentData.uid,
            name: booking.user?.name,
            email: booking.user?.email,
            date: booking.startTime.toISOString()
          }),
          paymentOption: paymentData.paymentOption || "ON_BOOKING",
          amount: paymentData.amount,
          currency: paymentData.currency
        }
      },
      eventTypeMetadata
    );
  }
  async deletePayment(paymentId) {
    try {
      const payment = await this.getPayment({
        id: paymentId
      });
      const stripeAccount = payment.data.stripeAccount;
      if (!stripeAccount) {
        throw new Error("Stripe account not found");
      }
      const sessions = await this.stripe.checkout.sessions.list(
        {
          payment_intent: payment.externalId
        },
        { stripeAccount }
      );
      for (const session of sessions.data) {
        await this.stripe.checkout.sessions.expire(session.id, { stripeAccount });
      }
      await this.stripe.paymentIntents.cancel(payment.externalId, { stripeAccount });
      return true;
    } catch (e) {
      log.error("Stripe: Unable to delete Payment in stripe of paymentId", paymentId, indexBrowser.safeStringify(e));
      return false;
    }
  }
  getPaymentPaidStatus() {
    throw new Error("Method not implemented.");
  }
  getPaymentDetails() {
    throw new Error("Method not implemented.");
  }
  isSetupAlready() {
    return !!this.credentials;
  }
}
const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
  __proto__: null,
  PREMIUM_MONTHLY_PLAN_PRICE: indexBrowser.PREMIUM_MONTHLY_PLAN_PRICE,
  PREMIUM_PLAN_PRODUCT_ID: indexBrowser.PREMIUM_PLAN_PRODUCT_ID,
  PaymentService,
  STRIPE_TEAM_MONTHLY_PRICE_ID: indexBrowser.STRIPE_TEAM_MONTHLY_PRICE_ID,
  paymentOptions: indexBrowser.paymentOptions,
  stripeCredentialKeysSchema
}, Symbol.toStringTag, { value: "Module" }));
exports.metadata = _metadata.metadata;
exports.api = index$1;
exports.lib = index;
