var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { BillingPrivateKey, BillingWebhookSecret, IsBillingEnabled, DashboardClientUrl, } from "../EnvironmentConfig";
import ServerMeteredPlan from "../Types/Billing/MeteredPlan/ServerMeteredPlan";
import Errors from "../Utils/Errors";
import logger from "../Utils/Logger";
import BaseService from "./BaseService";
import MailService from "./MailService";
import ProjectService from "./ProjectService";
import { SubscriptionStatusUtil, } from "../../Types/Billing/SubscriptionStatus";
import OneUptimeDate from "../../Types/Date";
import Email from "../../Types/Email";
import EmailTemplateType from "../../Types/Email/EmailTemplateType";
import APIException from "../../Types/Exception/ApiException";
import BadDataException from "../../Types/Exception/BadDataException";
import ProductType from "../../Types/MeteredPlan/ProductType";
import ObjectID from "../../Types/ObjectID";
import Stripe from "stripe";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
export class BillingService extends BaseService {
    constructor() {
        super();
        this.stripe = new Stripe(BillingPrivateKey, {
            apiVersion: "2022-08-01",
        });
    }
    // returns billing id of the customer.
    async createCustomer(data) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        const customer = await this.stripe.customers.create({
            name: data.name,
            email: data.email.toString(),
            metadata: {
                id: data.id.toString(),
            },
        });
        return customer.id;
    }
    async updateCustomerName(id, newName) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        await this.stripe.customers.update(id, { name: newName });
    }
    async updateCustomerBusinessDetails(id, businessDetails, countryCode, financeAccountingEmail, sendInvoicesByEmail) {
        logger.debug(`[Invoice Email] updateCustomerBusinessDetails called - customerId: ${id}, sendInvoicesByEmail: ${sendInvoicesByEmail}`);
        if (!this.isBillingEnabled()) {
            logger.debug(`[Invoice Email] Billing not enabled, skipping updateCustomerBusinessDetails for customer ${id}`);
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        /*
         * Goal: Update Stripe Customer "Billing details" (address fields) rather than invoice footer.
         * We only have a single free-form textarea. We'll map:
         *   First non-empty line -> address.line1
         *   Second non-empty line (if any) and remaining (joined, truncated) -> address.line2
         * We also persist full text in metadata so we can reconstruct or improve parsing later.
         * NOTE: Because Stripe requires structured address, any city/state/postal/country detection
         * would be heuristic; we keep it simple unless we later add structured fields.
         */
        const lines = businessDetails
            .split(/\r?\n/)
            .map((l) => {
            return l.trim();
        })
            .filter((l) => {
            return l.length > 0;
        });
        let line1 = undefined;
        let line2 = undefined;
        if (lines && lines.length > 0) {
            const first = lines[0]; // non-null
            line1 = first.substring(0, 200); // Stripe typical limit safeguard.
        }
        if (lines && lines.length > 1) {
            const rest = lines.slice(1).join(", ");
            line2 = rest.substring(0, 200);
        }
        /*
         * financeAccountingEmail may be a comma/semicolon-separated list. Parse the first
         * valid address for Stripe (which stores a single email), and keep the full list
         * in metadata so OneUptime can fan out invoice emails to every recipient.
         */
        const parsedEmails = financeAccountingEmail
            ? (() => {
                try {
                    return Email.parseList(financeAccountingEmail);
                }
                catch (err) {
                    logger.error(`[Invoice Email] Failed to parse financeAccountingEmail for customer ${id}: ${err}`);
                    return [];
                }
            })()
            : [];
        const normalizedEmailList = parsedEmails
            .map((e) => {
            return e.toString();
        })
            .join(", ");
        const metadata = {
            business_details_full: businessDetails.substring(0, 5000),
        };
        if (normalizedEmailList) {
            metadata["finance_accounting_email"] = normalizedEmailList.substring(0, 500);
        }
        else {
            // Remove if cleared
            metadata["finance_accounting_email"] = "";
        }
        if (sendInvoicesByEmail !== undefined && sendInvoicesByEmail !== null) {
            metadata["send_invoices_by_email"] = sendInvoicesByEmail
                ? "true"
                : "false";
            logger.debug(`[Invoice Email] Setting send_invoices_by_email metadata to "${metadata["send_invoices_by_email"]}" for customer ${id}`);
        }
        const updateParams = {
            metadata,
            address: {},
        };
        /*
         * Stripe's customer.email only accepts one address. Use the first parsed email;
         * the rest are delivered by OneUptime's own webhook-driven invoice email path.
         */
        if (parsedEmails.length > 0) {
            updateParams.email = parsedEmails[0].toString();
        }
        if (line1) {
            updateParams.address = updateParams.address || {};
            updateParams.address.line1 = line1;
        }
        if (line2) {
            updateParams.address = updateParams.address || {};
            updateParams.address.line2 = line2;
        }
        if (countryCode) {
            updateParams.address = updateParams.address || {};
            // Stripe expects uppercase 2-letter ISO code
            updateParams.address.country = countryCode.toUpperCase();
        }
        if (!line1 && !line2 && !countryCode) {
            // Clear address if empty details submitted.
            updateParams.address = {
                line1: "",
                line2: "",
            };
        }
        logger.debug(`[Invoice Email] Updating Stripe customer ${id} with metadata: ${JSON.stringify(metadata)}`);
        await this.stripe.customers.update(id, updateParams);
        logger.debug(`[Invoice Email] Successfully updated Stripe customer ${id}`);
    }
    async deleteCustomer(id) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        await this.stripe.customers.del(id);
    }
    isBillingEnabled() {
        return IsBillingEnabled;
    }
    isSubscriptionActive(status) {
        return SubscriptionStatusUtil.isSubscriptionActive(status);
    }
    async subscribeToMeteredPlan(data) {
        const meteredPlanSubscriptionParams = {
            customer: data.customerId,
            proration_behavior: "always_invoice",
            items: data.serverMeteredPlans.map((item) => {
                return {
                    price: item.getPriceId(),
                };
            }),
            trial_end: data.trialDate && OneUptimeDate.isInTheFuture(data.trialDate)
                ? OneUptimeDate.toUnixTimestamp(data.trialDate)
                : "now",
        };
        if (data.promoCode) {
            meteredPlanSubscriptionParams.coupon = data.promoCode;
        }
        if (data.defaultPaymentMethodId) {
            meteredPlanSubscriptionParams.default_payment_method =
                data.defaultPaymentMethodId;
        }
        // Create metered subscriptions
        const meteredSubscription = await this.stripe.subscriptions.create(meteredPlanSubscriptionParams);
        for (const serverMeteredPlan of data.serverMeteredPlans) {
            await serverMeteredPlan.reportQuantityToBillingProvider(data.projectId, {
                meteredPlanSubscriptionId: meteredSubscription.id,
            });
        }
        return {
            meteredSubscriptionId: meteredSubscription.id,
            trialEndsAt: data.trialDate,
        };
    }
    isTestEnvironment() {
        return BillingPrivateKey.startsWith("sk_test");
    }
    async generateCouponCode(data) {
        const coupon = await this.stripe.coupons.create({
            name: data.name,
            percent_off: data.percentOff,
            duration: "repeating",
            duration_in_months: data.durationInMonths,
            max_redemptions: data.maxRedemptions,
            metadata: data.metadata || null,
        });
        return coupon.id;
    }
    async subscribeToPlan(data) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        let trialDate = null;
        if (typeof data.trial === "boolean") {
            if (data.trial) {
                trialDate = OneUptimeDate.getSomeDaysAfter(data.plan.getTrialPeriod());
            }
            else {
                trialDate = null;
            }
        }
        else if (data.trial instanceof Date) {
            trialDate = data.trial;
        }
        const subscriptionParams = {
            customer: data.customerId,
            items: [
                {
                    price: data.isYearly
                        ? data.plan.getYearlyPlanId()
                        : data.plan.getMonthlyPlanId(),
                    quantity: data.quantity,
                },
            ],
            proration_behavior: "always_invoice",
            trial_end: trialDate && data.plan.getTrialPeriod() > 0
                ? OneUptimeDate.toUnixTimestamp(trialDate)
                : "now",
        };
        if (data.promoCode) {
            subscriptionParams.coupon = data.promoCode;
        }
        if (data.defaultPaymentMethodId) {
            subscriptionParams.default_payment_method = data.defaultPaymentMethodId;
        }
        const subscription = await this.stripe.subscriptions.create(subscriptionParams);
        // Create metered subscriptions
        const meteredSubscription = await this.subscribeToMeteredPlan(Object.assign(Object.assign({}, data), { trialDate }));
        return {
            subscriptionId: subscription.id,
            meteredSubscriptionId: meteredSubscription.meteredSubscriptionId,
            trialEndsAt: trialDate && data.plan.getTrialPeriod() > 0 ? trialDate : null,
        };
    }
    async changeQuantity(subscriptionId, quantity) {
        var _a;
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
        if (!subscription) {
            throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND);
        }
        if (subscription.status === "canceled") {
            // subscription is canceled.
            return;
        }
        const subscriptionItemId = (_a = subscription.items.data[0]) === null || _a === void 0 ? void 0 : _a.id;
        if (!subscriptionItemId) {
            throw new BadDataException(Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND);
        }
        await this.stripe.subscriptionItems.update(subscriptionItemId, {
            quantity: quantity,
        });
        // add billing anchor, so that the billing cycle starts now. New quantity will be charged from now. https://stackoverflow.com/questions/44417047/immediately-charge-for-subscription-changes
        await this.stripe.subscriptions.update(subscriptionId, {
            proration_behavior: "always_invoice",
        });
    }
    async addOrUpdateMeteredPricingOnSubscription(subscriptionId, serverMeteredPlan, quantity) {
        var _a;
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        // get subscription.
        const subscription = await this.getSubscription(subscriptionId.toString());
        if (!subscription) {
            throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND);
        }
        // check if this pricing exists
        const pricingExists = subscription.items.data.some((item) => {
            var _a;
            return ((_a = item.price) === null || _a === void 0 ? void 0 : _a.id) === serverMeteredPlan.getPriceId();
        });
        if (pricingExists) {
            // update the quantity.
            const subscriptionItemId = (_a = subscription.items.data.find((item) => {
                var _a;
                return ((_a = item.price) === null || _a === void 0 ? void 0 : _a.id) === serverMeteredPlan.getPriceId();
            })) === null || _a === void 0 ? void 0 : _a.id;
            if (!subscriptionItemId) {
                throw new BadDataException(Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND);
            }
            // use stripe usage based api to update the quantity.
            await this.stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
                quantity: quantity,
            });
        }
        else {
            // add the pricing.
            const subscriptionItem = await this.stripe.subscriptionItems.create({
                subscription: subscriptionId,
                price: serverMeteredPlan.getPriceId(),
            });
            // use stripe usage based api to update the quantity.
            await this.stripe.subscriptionItems.createUsageRecord(subscriptionItem.id, {
                quantity: quantity,
            });
        }
        // complete.
    }
    async isPromoCodeValid(promoCode) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        try {
            const promoCodeResponse = await this.stripe.coupons.retrieve(promoCode);
            if (!promoCodeResponse) {
                throw new BadDataException(Errors.BillingService.PROMO_CODE_NOT_FOUND);
            }
            return promoCodeResponse.valid;
        }
        catch (err) {
            throw new BadDataException(err.message || Errors.BillingService.PROMO_CODE_INVALID);
        }
    }
    async removeSubscriptionItem(subscriptionId, subscriptionItemId, isMeteredSubscriptionItem) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
        if (!subscription) {
            throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND);
        }
        if (subscription.status === "canceled") {
            // subscription is canceled.
            return;
        }
        const subscriptionItemOptions = isMeteredSubscriptionItem
            ? {
                proration_behavior: "create_prorations",
                clear_usage: true,
            }
            : {};
        await this.stripe.subscriptionItems.del(subscriptionItemId, subscriptionItemOptions);
    }
    async getSubscriptionItems(subscriptionId) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
        if (!subscription) {
            throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND);
        }
        return subscription.items.data;
    }
    async changePlan(data) {
        var _a;
        logger.debug("Changing plan");
        logger.debug(data);
        if (!this.isBillingEnabled()) {
            logger.debug(Errors.BillingService.BILLING_NOT_ENABLED);
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        const subscription = await this.stripe.subscriptions.retrieve(data.subscriptionId);
        logger.debug("Subscription");
        logger.debug(subscription);
        if (!subscription) {
            logger.debug(Errors.BillingService.SUBSCRIPTION_NOT_FOUND);
            throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND);
        }
        logger.debug("Subscription status");
        logger.debug(subscription.status);
        const paymentMethods = await this.getPaymentMethods(subscription.customer.toString());
        logger.debug("Payment methods");
        logger.debug(paymentMethods);
        if (paymentMethods.length === 0) {
            logger.debug("No payment methods");
            throw new BadDataException(Errors.BillingService.NO_PAYMENTS_METHODS);
        }
        logger.debug("Cancelling subscriptions");
        logger.debug(data.subscriptionId);
        await this.cancelSubscription(data.subscriptionId);
        logger.debug("Cancelling metered subscriptions");
        logger.debug(data.meteredSubscriptionId);
        await this.cancelSubscription(data.meteredSubscriptionId);
        if (data.endTrialAt && !OneUptimeDate.isInTheFuture(data.endTrialAt)) {
            data.endTrialAt = undefined;
        }
        logger.debug("Subscribing to plan");
        const subscribeToPlan = await this.subscribeToPlan({
            projectId: data.projectId,
            customerId: subscription.customer.toString(),
            serverMeteredPlans: data.serverMeteredPlans,
            plan: data.newPlan,
            quantity: data.quantity,
            isYearly: data.isYearly,
            trial: data.endTrialAt,
            defaultPaymentMethodId: (_a = paymentMethods[0]) === null || _a === void 0 ? void 0 : _a.id,
            promoCode: undefined,
        });
        logger.debug("Subscribed to plan");
        const value = {
            subscriptionId: subscribeToPlan.subscriptionId,
            meteredSubscriptionId: subscribeToPlan.meteredSubscriptionId,
            trialEndsAt: subscribeToPlan.trialEndsAt || undefined,
        };
        return value;
    }
    async deletePaymentMethod(customerId, paymentMethodId) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        const paymentMethods = await this.getPaymentMethods(customerId);
        if (paymentMethods.length === 1) {
            throw new BadDataException(Errors.BillingService.MIN_REQUIRED_PAYMENT_METHOD_NOT_MET);
        }
        await this.stripe.paymentMethods.detach(paymentMethodId);
    }
    async hasPaymentMethods(customerId) {
        if ((await this.getPaymentMethods(customerId)).length > 0) {
            return true;
        }
        return false;
    }
    async setDefaultPaymentMethod(customerId, paymentMethodId) {
        await this.stripe.customers.update(customerId, {
            invoice_settings: {
                default_payment_method: paymentMethodId,
            },
        });
    }
    async getPaymentMethods(customerId) {
        var _a, _b, _c;
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        const paymentMethods = [];
        const cardPaymentMethods = await this.stripe.paymentMethods.list({
            customer: customerId,
            type: "card",
        });
        const sepaPaymentMethods = await this.stripe.paymentMethods.list({
            customer: customerId,
            type: "sepa_debit",
        });
        const usBankPaymentMethods = await this.stripe.paymentMethods.list({
            customer: customerId,
            type: "us_bank_account",
        });
        const bacsPaymentMethods = await this.stripe.paymentMethods.list({
            customer: customerId,
            type: "bacs_debit",
        });
        cardPaymentMethods.data.forEach((item) => {
            var _a, _b;
            paymentMethods.push({
                type: ((_a = item.card) === null || _a === void 0 ? void 0 : _a.brand) || "Card",
                last4Digits: ((_b = item.card) === null || _b === void 0 ? void 0 : _b.last4) || "xxxx",
                isDefault: false,
                id: item.id,
            });
        });
        bacsPaymentMethods.data.forEach((item) => {
            var _a;
            paymentMethods.push({
                type: "UK Bank Account",
                last4Digits: ((_a = item.bacs_debit) === null || _a === void 0 ? void 0 : _a.last4) || "xxxx",
                isDefault: false,
                id: item.id,
            });
        });
        usBankPaymentMethods.data.forEach((item) => {
            var _a;
            paymentMethods.push({
                type: "US Bank Account",
                last4Digits: ((_a = item.us_bank_account) === null || _a === void 0 ? void 0 : _a.last4) || "xxxx",
                isDefault: false,
                id: item.id,
            });
        });
        sepaPaymentMethods.data.forEach((item) => {
            var _a;
            paymentMethods.push({
                type: "EU Bank Account",
                last4Digits: ((_a = item.sepa_debit) === null || _a === void 0 ? void 0 : _a.last4) || "xxxx",
                isDefault: false,
                id: item.id,
            });
        });
        // check if there's a default payment method.
        const customer = await this.stripe.customers.retrieve(customerId);
        if (customer.invoice_settings &&
            !((_a = customer.invoice_settings) === null || _a === void 0 ? void 0 : _a.default_payment_method)) {
            // set the first payment method as default.
            if (paymentMethods.length > 0 && ((_b = paymentMethods[0]) === null || _b === void 0 ? void 0 : _b.id)) {
                await this.setDefaultPaymentMethod(customerId, (_c = paymentMethods[0]) === null || _c === void 0 ? void 0 : _c.id);
            }
        }
        return paymentMethods;
    }
    async getSetupIntentSecret(customerId) {
        const setupIntent = await this.stripe.setupIntents.create({
            customer: customerId,
        });
        if (!setupIntent.client_secret) {
            throw new APIException(Errors.BillingService.CLIENT_SECRET_MISSING);
        }
        return setupIntent.client_secret;
    }
    async getCustomerBalance(customerId) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        const customer = await this.stripe.customers.retrieve(customerId);
        if (!customer || customer.deleted) {
            throw new BadDataException(Errors.BillingService.CUSTOMER_NOT_FOUND);
        }
        // Balance is in cents, convert to dollars
        return (customer.balance || 0) / 100;
    }
    async cancelSubscription(subscriptionId) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        try {
            await this.stripe.subscriptions.del(subscriptionId);
        }
        catch (err) {
            logger.error(err, { subscriptionId });
        }
    }
    async getSubscriptionStatus(subscriptionId) {
        const subscription = await this.getSubscription(subscriptionId);
        return subscription.status;
    }
    async getSubscription(subscriptionId) {
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
        return subscription;
    }
    async getInvoice(customerId, invoiceId) {
        var _a, _b, _c, _d, _e;
        if (!this.isBillingEnabled()) {
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        logger.debug("Getting invoice");
        logger.debug(invoiceId);
        logger.debug(customerId);
        const invoice = await this.stripe.invoices.retrieve(invoiceId);
        if (!invoice) {
            throw new BadDataException(Errors.BillingService.INVOICE_NOT_FOUND);
        }
        return {
            id: invoice.id,
            amount: invoice.amount_due,
            currencyCode: invoice.currency,
            subscriptionId: ((_a = invoice.subscription) === null || _a === void 0 ? void 0 : _a.toString()) || undefined,
            status: ((_b = invoice.status) === null || _b === void 0 ? void 0 : _b.toString()) || "Unknown",
            downloadableLink: ((_c = invoice.invoice_pdf) === null || _c === void 0 ? void 0 : _c.toString()) || "",
            customerId: ((_d = invoice.customer) === null || _d === void 0 ? void 0 : _d.toString()) || "",
            invoiceDate: invoice.created
                ? new Date(invoice.created * 1000)
                : OneUptimeDate.getCurrentDate(),
            invoiceNumber: invoice.number || undefined,
            paymentIntentId: ((_e = invoice.payment_intent) === null || _e === void 0 ? void 0 : _e.toString()) || undefined,
        };
    }
    async getPaymentIntentClientSecret(paymentIntentId) {
        const paymentIntent = await this.stripe.paymentIntents.retrieve(paymentIntentId);
        if (!paymentIntent.client_secret) {
            throw new APIException(Errors.BillingService.CLIENT_SECRET_MISSING);
        }
        return paymentIntent.client_secret;
    }
    async getPaymentIntentIdFromInvoice(invoiceId) {
        const invoice = await this.stripe.invoices.retrieve(invoiceId);
        if (!invoice) {
            throw new BadDataException(Errors.BillingService.INVOICE_NOT_FOUND);
        }
        if (!invoice.payment_intent) {
            throw new BadDataException(Errors.BillingService.PAYMENT_INTENT_NOT_FOUND);
        }
        return invoice.payment_intent.toString();
    }
    async getInvoices(customerId) {
        const invoices = await this.stripe.invoices.list({
            customer: customerId,
            limit: 100,
        });
        let billingInvoices = invoices.data.map((invoice) => {
            var _a, _b, _c, _d, _e;
            return {
                id: invoice.id,
                amount: invoice.amount_due,
                currencyCode: invoice.currency,
                subscriptionId: ((_a = invoice.subscription) === null || _a === void 0 ? void 0 : _a.toString()) || undefined,
                status: ((_b = invoice.status) === null || _b === void 0 ? void 0 : _b.toString()) || "Unknown",
                downloadableLink: ((_c = invoice.invoice_pdf) === null || _c === void 0 ? void 0 : _c.toString()) || "",
                customerId: ((_d = invoice.customer) === null || _d === void 0 ? void 0 : _d.toString()) || "",
                invoiceDate: invoice.created
                    ? new Date(invoice.created * 1000)
                    : OneUptimeDate.getCurrentDate(),
                invoiceNumber: invoice.number || undefined,
                paymentIntent: ((_e = invoice.payment_intent) === null || _e === void 0 ? void 0 : _e.toString()) || undefined,
            };
        });
        // sort by date in descending order.
        billingInvoices = billingInvoices.sort((a, b) => {
            return a.invoiceDate.getTime() - b.invoiceDate.getTime();
        });
        return billingInvoices;
    }
    async sendInvoiceByEmail(invoiceId, recipientEmails, projectId) {
        var _a;
        const recipientsForLog = (recipientEmails || [])
            .map((e) => {
            return e.toString();
        })
            .join(", ");
        logger.debug(`[Invoice Email] sendInvoiceByEmail called for invoice: ${invoiceId}, recipients: ${recipientsForLog}`);
        if (!this.isBillingEnabled()) {
            logger.debug(`[Invoice Email] Billing not enabled, skipping send for invoice: ${invoiceId}`);
            throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
        }
        try {
            // Fetch invoice details from Stripe
            logger.debug(`[Invoice Email] Fetching invoice ${invoiceId} details from Stripe`);
            const stripeInvoice = await this.stripe.invoices.retrieve(invoiceId);
            if (!stripeInvoice) {
                logger.error(`[Invoice Email] Invoice ${invoiceId} not found in Stripe`, { projectId: projectId === null || projectId === void 0 ? void 0 : projectId.toString() });
                return;
            }
            /*
             * Determine recipient list. Fall back to the invoice's customer email when no
             * explicit recipients were supplied by the caller.
             */
            let toEmails = recipientEmails || [];
            if (toEmails.length === 0 && stripeInvoice.customer_email) {
                try {
                    toEmails = Email.parseList(stripeInvoice.customer_email);
                }
                catch (err) {
                    logger.error(`[Invoice Email] Failed to parse Stripe customer_email "${stripeInvoice.customer_email}" for invoice ${invoiceId}: ${err}`);
                }
            }
            if (toEmails.length === 0) {
                logger.error(`[Invoice Email] No recipient email found for invoice ${invoiceId}`, { projectId: projectId === null || projectId === void 0 ? void 0 : projectId.toString() });
                return;
            }
            // Format invoice data for email
            const invoiceNumber = stripeInvoice.number || invoiceId;
            const invoiceDate = stripeInvoice.created
                ? OneUptimeDate.getDateAsFormattedString(new Date(stripeInvoice.created * 1000))
                : OneUptimeDate.getDateAsFormattedString(OneUptimeDate.getCurrentDate());
            const amount = `${(stripeInvoice.amount_due / 100).toFixed(2)} ${((_a = stripeInvoice.currency) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || "USD"}`;
            const invoicePdfUrl = stripeInvoice.invoice_pdf || undefined;
            const description = stripeInvoice.description || undefined;
            // Build dashboard link
            let dashboardLink = undefined;
            if (projectId && DashboardClientUrl) {
                dashboardLink = `${DashboardClientUrl.toString()}/dashboard/${projectId.toString()}/settings/billing`;
            }
            for (const toEmail of toEmails) {
                logger.debug(`[Invoice Email] Sending invoice email to ${toEmail.toString()} - Invoice #${invoiceNumber}, Amount: ${amount}`);
                try {
                    await MailService.sendMail({
                        toEmail: toEmail,
                        templateType: EmailTemplateType.Invoice,
                        vars: {
                            invoiceNumber: invoiceNumber,
                            invoiceDate: invoiceDate,
                            amount: amount,
                            description: description || "",
                            invoicePdfUrl: invoicePdfUrl || "",
                            dashboardLink: dashboardLink || "",
                        },
                        subject: `Invoice #${invoiceNumber} from OneUptime`,
                    }, {
                        projectId: projectId,
                    });
                    logger.debug(`[Invoice Email] Successfully sent invoice ${invoiceId} email to ${toEmail.toString()}`);
                }
                catch (perRecipientErr) {
                    // Keep going for the remaining recipients if one fails.
                    logger.error(`[Invoice Email] Failed to send invoice ${invoiceId} to ${toEmail.toString()}: ${perRecipientErr}`, { projectId: projectId === null || projectId === void 0 ? void 0 : projectId.toString() });
                }
            }
        }
        catch (err) {
            logger.error(`[Invoice Email] Failed to send invoice ${invoiceId} by email: ${err}`, { projectId: projectId === null || projectId === void 0 ? void 0 : projectId.toString() });
            // Don't throw - sending email is not critical
        }
    }
    async shouldSendInvoicesByEmail(customerId) {
        logger.debug(`[Invoice Email] shouldSendInvoicesByEmail called for customer: ${customerId}`);
        if (!this.isBillingEnabled()) {
            logger.debug(`[Invoice Email] Billing not enabled, returning false for customer: ${customerId}`);
            return false;
        }
        try {
            logger.debug(`[Invoice Email] Retrieving customer ${customerId} from Stripe to check preference`);
            const customer = await this.stripe.customers.retrieve(customerId);
            if (!customer || customer.deleted) {
                logger.debug(`[Invoice Email] Customer ${customerId} not found or deleted, returning false`);
                return false;
            }
            const metadata = customer.metadata;
            const sendInvoicesByEmail = (metadata === null || metadata === void 0 ? void 0 : metadata["send_invoices_by_email"]) === "true";
            logger.debug(`[Invoice Email] Customer ${customerId} metadata.send_invoices_by_email = "${metadata === null || metadata === void 0 ? void 0 : metadata["send_invoices_by_email"]}", result: ${sendInvoicesByEmail}`);
            return sendInvoicesByEmail;
        }
        catch (err) {
            logger.error(`[Invoice Email] Failed to check invoice email preference for customer ${customerId}: ${err}`, { customerId });
            return false;
        }
    }
    async generateInvoiceAndChargeCustomer(customerId, itemText, amountInUsd, options) {
        const sendInvoiceByEmail = (options === null || options === void 0 ? void 0 : options.sendInvoiceByEmail) || false;
        const recipientEmails = options === null || options === void 0 ? void 0 : options.recipientEmails;
        const projectId = options === null || options === void 0 ? void 0 : options.projectId;
        const recipientsForLog = (recipientEmails || [])
            .map((e) => {
            return e.toString();
        })
            .join(", ");
        logger.debug(`[Invoice Email] generateInvoiceAndChargeCustomer called - customer: ${customerId}, amount: $${amountInUsd}, sendInvoiceByEmail: ${sendInvoiceByEmail}, recipients: ${recipientsForLog}, projectId: ${projectId === null || projectId === void 0 ? void 0 : projectId.toString()}`);
        const invoice = await this.stripe.invoices.create({
            customer: customerId,
            auto_advance: true, // do not automatically charge.
            collection_method: "charge_automatically",
        });
        if (!invoice || !invoice.id) {
            logger.error(`[Invoice Email] Failed to create invoice for customer ${customerId}`, { projectId: projectId === null || projectId === void 0 ? void 0 : projectId.toString() });
            throw new APIException(Errors.BillingService.INVOICE_NOT_GENERATED);
        }
        logger.debug(`[Invoice Email] Created invoice ${invoice.id} for customer ${customerId}`);
        await this.stripe.invoiceItems.create({
            invoice: invoice.id,
            amount: amountInUsd * 100,
            description: itemText,
            customer: customerId,
        });
        logger.debug(`[Invoice Email] Added invoice item to invoice ${invoice.id}: ${itemText}, $${amountInUsd}`);
        await this.stripe.invoices.finalizeInvoice(invoice.id);
        logger.debug(`[Invoice Email] Finalized invoice ${invoice.id}`);
        try {
            await this.payInvoice(customerId, invoice.id);
            logger.debug(`[Invoice Email] Paid invoice ${invoice.id}`);
            // Send invoice by email if requested
            if (sendInvoiceByEmail) {
                logger.debug(`[Invoice Email] sendInvoiceByEmail is true, sending invoice ${invoice.id} by email`);
                await this.sendInvoiceByEmail(invoice.id, recipientEmails, projectId);
            }
            else {
                logger.debug(`[Invoice Email] sendInvoiceByEmail is false, skipping email for invoice ${invoice.id}`);
            }
        }
        catch (err) {
            logger.error(`[Invoice Email] Failed to pay invoice ${invoice.id}, voiding: ${err}`, { projectId: projectId === null || projectId === void 0 ? void 0 : projectId.toString() });
            // mark invoice as failed and do not collect payment.
            await this.voidInvoice(invoice.id);
            throw err;
        }
    }
    async voidInvoice(invoiceId) {
        const invoice = await this.stripe.invoices.voidInvoice(invoiceId);
        return invoice;
    }
    async payInvoice(customerId, invoiceId) {
        var _a, _b, _c, _d, _e;
        // after the invoice is paid, // please fetch subscription and check the status.
        const paymentMethods = await this.getPaymentMethods(customerId);
        if (paymentMethods.length === 0) {
            throw new BadDataException(Errors.BillingService.NO_PAYMENTS_METHODS);
        }
        const invoice = await this.stripe.invoices.pay(invoiceId, {
            payment_method: ((_a = paymentMethods[0]) === null || _a === void 0 ? void 0 : _a.id) || "",
        });
        return {
            id: invoice.id,
            amount: invoice.amount_due,
            currencyCode: invoice.currency,
            subscriptionId: ((_b = invoice.subscription) === null || _b === void 0 ? void 0 : _b.toString()) || undefined,
            status: ((_c = invoice.status) === null || _c === void 0 ? void 0 : _c.toString()) || "Unknown",
            downloadableLink: ((_d = invoice.invoice_pdf) === null || _d === void 0 ? void 0 : _d.toString()) || "",
            customerId: ((_e = invoice.customer) === null || _e === void 0 ? void 0 : _e.toString()) || "",
            invoiceDate: invoice.created
                ? new Date(invoice.created * 1000)
                : OneUptimeDate.getCurrentDate(),
            invoiceNumber: invoice.number || undefined,
        };
    }
    getMeteredPlanPriceId(productType) {
        if (productType === ProductType.ActiveMonitoring) {
            if (this.isTestEnvironment()) {
                return "price_1N6CHFANuQdJ93r7qDaLmb7S";
            }
            return "price_1N6B9EANuQdJ93r7fj3bhcWP";
        }
        if (productType === ProductType.Logs) {
            if (this.isTestEnvironment()) {
                return "price_1OPnB5ANuQdJ93r7jG4NLCJG";
            }
            return "price_1OQ8gwANuQdJ93r74Pi85UQq";
        }
        if (productType === ProductType.Traces) {
            if (this.isTestEnvironment()) {
                return "price_1OQ8i9ANuQdJ93r75J3wr0PX";
            }
            return "price_1OQ8ivANuQdJ93r7NAR8KbH3";
        }
        if (productType === ProductType.Metrics) {
            if (this.isTestEnvironment()) {
                return "price_1OQ8iqANuQdJ93r7wZ7gJ7Gb";
            }
            return "price_1OQ8j0ANuQdJ93r7WGzR0p6j";
        }
        if (productType === ProductType.Profiles) {
            if (this.isTestEnvironment()) {
                return "price_1TGwUpANuQdJ93r7b9qxa5Se";
            }
            return "price_1TGwTDANuQdJ93r7s0jKRxaT";
        }
        throw new BadDataException("Plan with productType " + productType + " not found");
    }
    verifyWebhookSignature(payload, signature) {
        logger.debug(`[Invoice Email] verifyWebhookSignature called`);
        if (!BillingWebhookSecret) {
            logger.error(`[Invoice Email] Billing webhook secret is not configured`);
            throw new BadDataException("Billing webhook secret is not configured");
        }
        logger.debug(`[Invoice Email] Verifying webhook signature with secret (length: ${BillingWebhookSecret.length})`);
        const event = this.stripe.webhooks.constructEvent(payload, signature, BillingWebhookSecret);
        logger.debug(`[Invoice Email] Webhook signature verified, event type: ${event.type}, event id: ${event.id}`);
        return event;
    }
    async handleWebhookEvent(event) {
        logger.debug(`[Invoice Email] handleWebhookEvent called - event type: ${event.type}, event id: ${event.id}`);
        if (!this.isBillingEnabled()) {
            logger.debug(`[Invoice Email] Billing not enabled, ignoring webhook event ${event.id}`);
            return;
        }
        // Handle invoice.finalized event to send invoice by email if customer has opted in
        if (event.type === "invoice.finalized") {
            logger.debug(`[Invoice Email] Processing invoice.finalized event ${event.id}`);
            const invoice = event.data.object;
            logger.debug(`[Invoice Email] Invoice details - id: ${invoice.id}, number: ${invoice.number}, customer: ${invoice.customer}, status: ${invoice.status}`);
            if (!invoice.customer) {
                logger.debug(`[Invoice Email] No customer on invoice ${invoice.id}, skipping`);
                return;
            }
            const customerId = typeof invoice.customer === "string"
                ? invoice.customer
                : invoice.customer.id;
            logger.debug(`[Invoice Email] Extracted customer ID: ${customerId} from invoice ${invoice.id}`);
            try {
                logger.debug(`[Invoice Email] Checking if customer ${customerId} has invoice emails enabled`);
                const shouldSend = await this.shouldSendInvoicesByEmail(customerId);
                if (shouldSend && invoice.id) {
                    logger.debug(`[Invoice Email] Customer ${customerId} has invoice emails enabled, looking up project`);
                    // Find the project by Stripe customer ID
                    const project = await ProjectService.findOneBy({
                        query: {
                            paymentProviderCustomerId: customerId,
                        },
                        select: {
                            _id: true,
                            financeAccountingEmail: true,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    let recipientEmails = undefined;
                    let projectId = undefined;
                    if (project) {
                        projectId = project.id || undefined;
                        if (project.financeAccountingEmail) {
                            try {
                                recipientEmails = Email.parseList(project.financeAccountingEmail);
                            }
                            catch (parseErr) {
                                /*
                                 * Log and fall back to Stripe customer email rather than dropping
                                 * the invoice delivery entirely due to a malformed address.
                                 */
                                logger.error(`[Invoice Email] Failed to parse financeAccountingEmail for project ${projectId === null || projectId === void 0 ? void 0 : projectId.toString()}: ${parseErr}`);
                            }
                        }
                        const parsedForLog = (recipientEmails || [])
                            .map((e) => {
                            return e.toString();
                        })
                            .join(", ");
                        logger.debug(`[Invoice Email] Found project ${projectId === null || projectId === void 0 ? void 0 : projectId.toString()}, financeAccountingEmail recipients: ${parsedForLog}`);
                    }
                    else {
                        logger.debug(`[Invoice Email] No project found for customer ${customerId}, will use Stripe customer email`);
                    }
                    logger.debug(`[Invoice Email] Sending invoice ${invoice.id} by email`);
                    await this.sendInvoiceByEmail(invoice.id, recipientEmails, projectId);
                    logger.debug(`[Invoice Email] Successfully processed invoice.finalized - sent invoice ${invoice.id} by email`);
                }
                else {
                    logger.debug(`[Invoice Email] Customer ${customerId} has invoice emails disabled (shouldSend: ${shouldSend}), skipping email for invoice ${invoice.id}`);
                }
            }
            catch (err) {
                logger.error(`[Invoice Email] Failed to send invoice by email for invoice ${invoice.id}: ${err}`, { customerId });
                // Don't throw - webhook should still return success
            }
        }
        else {
            logger.debug(`[Invoice Email] Ignoring event type ${event.type}, not invoice.finalized`);
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "createCustomer", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "updateCustomerName", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Object, Object, Object]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "updateCustomerBusinessDetails", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "deleteCustomer", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "subscribeToMeteredPlan", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "generateCouponCode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "subscribeToPlan", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Number]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "changeQuantity", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, ServerMeteredPlan, Number]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "addOrUpdateMeteredPricingOnSubscription", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "isPromoCodeValid", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Boolean]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "removeSubscriptionItem", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getSubscriptionItems", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "changePlan", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "deletePaymentMethod", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "hasPaymentMethods", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "setDefaultPaymentMethod", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getPaymentMethods", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getSetupIntentSecret", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getCustomerBalance", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "cancelSubscription", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getSubscriptionStatus", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getSubscription", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getInvoice", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getPaymentIntentClientSecret", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getPaymentIntentIdFromInvoice", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "getInvoices", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Array,
        ObjectID]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "sendInvoiceByEmail", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "shouldSendInvoicesByEmail", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Number, Object]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "generateInvoiceAndChargeCustomer", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "voidInvoice", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "payInvoice", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, String]),
    __metadata("design:returntype", Object)
], BillingService.prototype, "verifyWebhookSignature", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], BillingService.prototype, "handleWebhookEvent", null);
export default new BillingService();
//# sourceMappingURL=BillingService.js.map