refactor: improve logging and code formatting in billing and project services

This commit is contained in:
Nawaz Dhandala
2026-01-28 12:08:01 +00:00
parent 367a80c413
commit b06bc71a2c
7 changed files with 238 additions and 94 deletions

View File

@@ -2,14 +2,13 @@ import { BillingWebhookSecret, IsBillingEnabled } from "../EnvironmentConfig";
import UserMiddleware from "../Middleware/UserAuthorization";
import BillingService from "../Services/BillingService";
import ProjectService from "../Services/ProjectService";
import {
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
OneUptimeRequest,
} from "../Utils/Express";
import Express from "../Utils/Express";
import Response from "../Utils/Response";
import BadDataException from "../../Types/Exception/BadDataException";
import Permission, { UserPermission } from "../../Types/Permission";
@@ -30,41 +29,60 @@ export default class BillingAPI {
`/billing/webhook`,
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
logger.debug(`[Invoice Email] Webhook endpoint hit - /billing/webhook`);
logger.debug(
`[Invoice Email] Webhook endpoint hit - /billing/webhook`,
);
if (!IsBillingEnabled) {
logger.debug(`[Invoice Email] Billing not enabled, returning early`);
logger.debug(
`[Invoice Email] Billing not enabled, returning early`,
);
return Response.sendJsonObjectResponse(req, res, {
message: "Billing is not enabled",
});
}
if (!BillingWebhookSecret) {
logger.error(`[Invoice Email] Billing webhook secret is not configured`);
logger.error(
`[Invoice Email] Billing webhook secret is not configured`,
);
throw new BadDataException(
"Billing webhook secret is not configured",
);
}
const signature = req.headers["stripe-signature"] as string;
logger.debug(`[Invoice Email] Stripe signature header present: ${!!signature}`);
const signature: string = req.headers["stripe-signature"] as string;
logger.debug(
`[Invoice Email] Stripe signature header present: ${Boolean(signature)}`,
);
if (!signature) {
logger.error(`[Invoice Email] Missing Stripe signature header`);
throw new BadDataException("Missing Stripe signature header");
}
const rawBody = (req as OneUptimeRequest).rawBody;
logger.debug(`[Invoice Email] Raw body present: ${!!rawBody}, length: ${rawBody?.length || 0}`);
const rawBody: string | undefined = (req as OneUptimeRequest).rawBody;
logger.debug(
`[Invoice Email] Raw body present: ${Boolean(rawBody)}, length: ${rawBody?.length || 0}`,
);
if (!rawBody) {
logger.error(`[Invoice Email] Missing raw body for webhook verification`);
throw new BadDataException("Missing raw body for webhook verification");
logger.error(
`[Invoice Email] Missing raw body for webhook verification`,
);
throw new BadDataException(
"Missing raw body for webhook verification",
);
}
logger.debug(`[Invoice Email] Verifying webhook signature...`);
const event = BillingService.verifyWebhookSignature(rawBody, signature);
logger.debug(`[Invoice Email] Webhook signature verified successfully, event type: ${event.type}`);
const event: Stripe.Event = BillingService.verifyWebhookSignature(
rawBody,
signature,
);
logger.debug(
`[Invoice Email] Webhook signature verified successfully, event type: ${event.type}`,
);
// Handle the event asynchronously
logger.debug(`[Invoice Email] Handling webhook event...`);

View File

@@ -1,7 +1,8 @@
const IsBillingEnabled: boolean = process.env["BILLING_ENABLED"] === "true";
const BillingPublicKey: string = process.env["BILLING_PUBLIC_KEY"] || "";
const BillingPrivateKey: string = process.env["BILLING_PRIVATE_KEY"] || "";
const BillingWebhookSecret: string = process.env["BILLING_WEBHOOK_SECRET"] || "";
const BillingWebhookSecret: string =
process.env["BILLING_WEBHOOK_SECRET"] || "";
export default {
IsBillingEnabled,

View File

@@ -1,18 +1,29 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1769599843642 implements MigrationInterface {
public name = 'MigrationName1769599843642'
public name = "MigrationName1769599843642";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "Project" ADD "sendInvoicesByEmail" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`);
await queryRunner.query(`ALTER TABLE "Project" DROP COLUMN "sendInvoicesByEmail"`);
}
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "Project" ADD "sendInvoicesByEmail" boolean NOT NULL DEFAULT false`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
);
await queryRunner.query(
`ALTER TABLE "Project" DROP COLUMN "sendInvoicesByEmail"`,
);
}
}

View File

@@ -477,5 +477,5 @@ export default [
MigrationName1769428821686,
MigrationName1769469813786,
RenameNotificationRuleTypes1769517677937,
MigrationName1769599843642
MigrationName1769599843642,
];

View File

@@ -1,4 +1,9 @@
import { BillingPrivateKey, BillingWebhookSecret, IsBillingEnabled, DashboardClientUrl } from "../EnvironmentConfig";
import {
BillingPrivateKey,
BillingWebhookSecret,
IsBillingEnabled,
DashboardClientUrl,
} from "../EnvironmentConfig";
import Project from "../../Models/DatabaseModels/Project";
import ServerMeteredPlan from "../Types/Billing/MeteredPlan/ServerMeteredPlan";
import Errors from "../Utils/Errors";
@@ -91,10 +96,14 @@ export class BillingService extends BaseService {
financeAccountingEmail?: string | null,
sendInvoicesByEmail?: boolean | null,
): Promise<void> {
logger.debug(`[Invoice Email] updateCustomerBusinessDetails called - customerId: ${id}, sendInvoicesByEmail: ${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}`);
logger.debug(
`[Invoice Email] Billing not enabled, skipping updateCustomerBusinessDetails for customer ${id}`,
);
throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED);
}
/*
@@ -141,8 +150,12 @@ export class BillingService extends BaseService {
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}`);
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: Stripe.CustomerUpdateParams = {
@@ -180,7 +193,9 @@ export class BillingService extends BaseService {
} as any;
}
logger.debug(`[Invoice Email] Updating Stripe customer ${id} with metadata: ${JSON.stringify(metadata)}`);
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}`);
}
@@ -943,20 +958,28 @@ export class BillingService extends BaseService {
recipientEmail?: Email,
projectId?: ObjectID,
): Promise<void> {
logger.debug(`[Invoice Email] sendInvoiceByEmail called for invoice: ${invoiceId}, recipientEmail: ${recipientEmail?.toString()}`);
logger.debug(
`[Invoice Email] sendInvoiceByEmail called for invoice: ${invoiceId}, recipientEmail: ${recipientEmail?.toString()}`,
);
if (!this.isBillingEnabled()) {
logger.debug(`[Invoice Email] Billing not enabled, skipping send for invoice: ${invoiceId}`);
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`);
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`);
logger.error(
`[Invoice Email] Invoice ${invoiceId} not found in Stripe`,
);
return;
}
@@ -967,16 +990,22 @@ export class BillingService extends BaseService {
}
if (!toEmail) {
logger.error(`[Invoice Email] No recipient email found for invoice ${invoiceId}`);
logger.error(
`[Invoice Email] No recipient email found for invoice ${invoiceId}`,
);
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)} ${stripeInvoice.currency?.toUpperCase() || 'USD'}`;
? OneUptimeDate.getDateAsFormattedString(
new Date(stripeInvoice.created * 1000),
)
: OneUptimeDate.getDateAsFormattedString(
OneUptimeDate.getCurrentDate(),
);
const amount = `${(stripeInvoice.amount_due / 100).toFixed(2)} ${stripeInvoice.currency?.toUpperCase() || "USD"}`;
const invoicePdfUrl = stripeInvoice.invoice_pdf || undefined;
const description = stripeInvoice.description || undefined;
@@ -986,7 +1015,9 @@ export class BillingService extends BaseService {
dashboardLink = `${DashboardClientUrl.toString()}/dashboard/${projectId.toString()}/settings/billing`;
}
logger.debug(`[Invoice Email] Sending invoice email to ${toEmail.toString()} - Invoice #${invoiceNumber}, Amount: ${amount}`);
logger.debug(
`[Invoice Email] Sending invoice email to ${toEmail.toString()} - Invoice #${invoiceNumber}, Amount: ${amount}`,
);
// Send email via OneUptime MailService
await MailService.sendMail(
@@ -997,9 +1028,9 @@ export class BillingService extends BaseService {
invoiceNumber: invoiceNumber,
invoiceDate: invoiceDate,
amount: amount,
description: description || '',
invoicePdfUrl: invoicePdfUrl || '',
dashboardLink: dashboardLink || '',
description: description || "",
invoicePdfUrl: invoicePdfUrl || "",
dashboardLink: dashboardLink || "",
},
subject: `Invoice #${invoiceNumber} from OneUptime`,
},
@@ -1008,38 +1039,56 @@ export class BillingService extends BaseService {
},
);
logger.debug(`[Invoice Email] Successfully sent invoice ${invoiceId} email to ${toEmail.toString()}`);
logger.debug(
`[Invoice Email] Successfully sent invoice ${invoiceId} email to ${toEmail.toString()}`,
);
} catch (err) {
logger.error(`[Invoice Email] Failed to send invoice ${invoiceId} by email: ${err}`);
logger.error(
`[Invoice Email] Failed to send invoice ${invoiceId} by email: ${err}`,
);
// Don't throw - sending email is not critical
}
}
@CaptureSpan()
public async shouldSendInvoicesByEmail(customerId: string): Promise<boolean> {
logger.debug(`[Invoice Email] shouldSendInvoicesByEmail called for customer: ${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}`);
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: Stripe.Response<Stripe.Customer | Stripe.DeletedCustomer> =
await this.stripe.customers.retrieve(customerId);
logger.debug(
`[Invoice Email] Retrieving customer ${customerId} from Stripe to check preference`,
);
const customer: Stripe.Response<
Stripe.Customer | Stripe.DeletedCustomer
> = await this.stripe.customers.retrieve(customerId);
if (!customer || customer.deleted) {
logger.debug(`[Invoice Email] Customer ${customerId} not found or deleted, returning false`);
logger.debug(
`[Invoice Email] Customer ${customerId} not found or deleted, returning false`,
);
return false;
}
const metadata = (customer as Stripe.Customer).metadata;
const sendInvoicesByEmail = metadata?.["send_invoices_by_email"] === "true";
logger.debug(`[Invoice Email] Customer ${customerId} metadata.send_invoices_by_email = "${metadata?.["send_invoices_by_email"]}", result: ${sendInvoicesByEmail}`);
const sendInvoicesByEmail =
metadata?.["send_invoices_by_email"] === "true";
logger.debug(
`[Invoice Email] Customer ${customerId} metadata.send_invoices_by_email = "${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}`);
logger.error(
`[Invoice Email] Failed to check invoice email preference for customer ${customerId}: ${err}`,
);
return false;
}
}
@@ -1059,7 +1108,9 @@ export class BillingService extends BaseService {
const recipientEmail = options?.recipientEmail;
const projectId = options?.projectId;
logger.debug(`[Invoice Email] generateInvoiceAndChargeCustomer called - customer: ${customerId}, amount: $${amountInUsd}, sendInvoiceByEmail: ${sendInvoiceByEmail}, recipientEmail: ${recipientEmail?.toString()}, projectId: ${projectId?.toString()}`);
logger.debug(
`[Invoice Email] generateInvoiceAndChargeCustomer called - customer: ${customerId}, amount: $${amountInUsd}, sendInvoiceByEmail: ${sendInvoiceByEmail}, recipientEmail: ${recipientEmail?.toString()}, projectId: ${projectId?.toString()}`,
);
const invoice: Stripe.Invoice = await this.stripe.invoices.create({
customer: customerId,
@@ -1068,11 +1119,15 @@ export class BillingService extends BaseService {
});
if (!invoice || !invoice.id) {
logger.error(`[Invoice Email] Failed to create invoice for customer ${customerId}`);
logger.error(
`[Invoice Email] Failed to create invoice for customer ${customerId}`,
);
throw new APIException(Errors.BillingService.INVOICE_NOT_GENERATED);
}
logger.debug(`[Invoice Email] Created invoice ${invoice.id} for customer ${customerId}`);
logger.debug(
`[Invoice Email] Created invoice ${invoice.id} for customer ${customerId}`,
);
await this.stripe.invoiceItems.create({
invoice: invoice.id,
@@ -1081,7 +1136,9 @@ export class BillingService extends BaseService {
customer: customerId,
});
logger.debug(`[Invoice Email] Added invoice item to invoice ${invoice.id}: ${itemText}, $${amountInUsd}`);
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}`);
@@ -1092,13 +1149,19 @@ export class BillingService extends BaseService {
// Send invoice by email if requested
if (sendInvoiceByEmail) {
logger.debug(`[Invoice Email] sendInvoiceByEmail is true, sending invoice ${invoice.id} by email`);
logger.debug(
`[Invoice Email] sendInvoiceByEmail is true, sending invoice ${invoice.id} by email`,
);
await this.sendInvoiceByEmail(invoice.id!, recipientEmail, projectId);
} else {
logger.debug(`[Invoice Email] sendInvoiceByEmail is false, skipping email for invoice ${invoice.id}`);
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}`);
logger.error(
`[Invoice Email] Failed to pay invoice ${invoice.id}, voiding: ${err}`,
);
// mark invoice as failed and do not collect payment.
await this.voidInvoice(invoice.id!);
throw err;
@@ -1195,49 +1258,70 @@ export class BillingService extends BaseService {
throw new BadDataException("Billing webhook secret is not configured");
}
logger.debug(`[Invoice Email] Verifying webhook signature with secret (length: ${BillingWebhookSecret.length})`);
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}`);
logger.debug(
`[Invoice Email] Webhook signature verified, event type: ${event.type}, event id: ${event.id}`,
);
return event;
}
@CaptureSpan()
public async handleWebhookEvent(event: Stripe.Event): Promise<void> {
logger.debug(`[Invoice Email] handleWebhookEvent called - event type: ${event.type}, event id: ${event.id}`);
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}`);
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}`);
logger.debug(
`[Invoice Email] Processing invoice.finalized event ${event.id}`,
);
const invoice = event.data.object as Stripe.Invoice;
logger.debug(`[Invoice Email] Invoice details - id: ${invoice.id}, number: ${invoice.number}, customer: ${invoice.customer}, status: ${invoice.status}`);
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`);
logger.debug(
`[Invoice Email] No customer on invoice ${invoice.id}, skipping`,
);
return;
}
const customerId = typeof invoice.customer === "string"
? invoice.customer
: invoice.customer.id;
const customerId =
typeof invoice.customer === "string"
? invoice.customer
: invoice.customer.id;
logger.debug(`[Invoice Email] Extracted customer ID: ${customerId} from invoice ${invoice.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`);
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`);
logger.debug(
`[Invoice Email] Customer ${customerId} has invoice emails enabled, looking up project`,
);
// Lazy import to avoid circular dependency
const { default: ProjectService } = await import("./ProjectService");
@@ -1264,23 +1348,37 @@ export class BillingService extends BaseService {
if (project.financeAccountingEmail) {
recipientEmail = new Email(project.financeAccountingEmail);
}
logger.debug(`[Invoice Email] Found project ${projectId?.toString()}, financeAccountingEmail: ${recipientEmail?.toString()}`);
logger.debug(
`[Invoice Email] Found project ${projectId?.toString()}, financeAccountingEmail: ${recipientEmail?.toString()}`,
);
} else {
logger.debug(`[Invoice Email] No project found for customer ${customerId}, will use Stripe customer email`);
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`);
logger.debug(
`[Invoice Email] Sending invoice ${invoice.id} by email`,
);
await this.sendInvoiceByEmail(invoice.id, recipientEmail, projectId);
logger.debug(`[Invoice Email] Successfully processed invoice.finalized - sent invoice ${invoice.id} by email`);
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}`);
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}`);
logger.error(
`[Invoice Email] Failed to send invoice by email for invoice ${invoice.id}: ${err}`,
);
// Don't throw - webhook should still return success
}
} else {
logger.debug(`[Invoice Email] Ignoring event type ${event.type}, not invoice.finalized`);
logger.debug(
`[Invoice Email] Ignoring event type ${event.type}, not invoice.finalized`,
);
}
}
}

View File

@@ -283,8 +283,12 @@ export class ProjectService extends DatabaseService<Model> {
updateBy.data.financeAccountingEmail ||
updateBy.data.sendInvoicesByEmail !== undefined
) {
logger.debug(`[Invoice Email] ProjectService.onBeforeUpdate - syncing billing details to Stripe`);
logger.debug(`[Invoice Email] Fields being updated - businessDetails: ${!!updateBy.data.businessDetails}, businessDetailsCountry: ${!!updateBy.data.businessDetailsCountry}, financeAccountingEmail: ${!!updateBy.data.financeAccountingEmail}, sendInvoicesByEmail: ${updateBy.data.sendInvoicesByEmail}`);
logger.debug(
`[Invoice Email] ProjectService.onBeforeUpdate - syncing billing details to Stripe`,
);
logger.debug(
`[Invoice Email] Fields being updated - businessDetails: ${Boolean(updateBy.data.businessDetails)}, businessDetailsCountry: ${Boolean(updateBy.data.businessDetailsCountry)}, financeAccountingEmail: ${Boolean(updateBy.data.financeAccountingEmail)}, sendInvoicesByEmail: ${updateBy.data.sendInvoicesByEmail}`,
);
// Sync to Stripe.
const project: Model | null = await this.findOneById({
@@ -297,15 +301,20 @@ export class ProjectService extends DatabaseService<Model> {
props: { isRoot: true },
});
logger.debug(`[Invoice Email] Project found - paymentProviderCustomerId: ${project?.paymentProviderCustomerId}, existing sendInvoicesByEmail: ${(project as any)?.sendInvoicesByEmail}`);
logger.debug(
`[Invoice Email] Project found - paymentProviderCustomerId: ${project?.paymentProviderCustomerId}, existing sendInvoicesByEmail: ${(project as any)?.sendInvoicesByEmail}`,
);
if (project?.paymentProviderCustomerId) {
try {
const sendInvoicesByEmailValue = updateBy.data.sendInvoicesByEmail !== undefined
? (updateBy.data.sendInvoicesByEmail as boolean)
: (project as any).sendInvoicesByEmail || null;
const sendInvoicesByEmailValue =
updateBy.data.sendInvoicesByEmail !== undefined
? (updateBy.data.sendInvoicesByEmail as boolean)
: (project as any).sendInvoicesByEmail || null;
logger.debug(`[Invoice Email] Calling BillingService.updateCustomerBusinessDetails with sendInvoicesByEmail: ${sendInvoicesByEmailValue}`);
logger.debug(
`[Invoice Email] Calling BillingService.updateCustomerBusinessDetails with sendInvoicesByEmail: ${sendInvoicesByEmailValue}`,
);
await BillingService.updateCustomerBusinessDetails(
project.paymentProviderCustomerId,
@@ -317,14 +326,18 @@ export class ProjectService extends DatabaseService<Model> {
sendInvoicesByEmailValue,
);
logger.debug(`[Invoice Email] Successfully synced billing details to Stripe for customer ${project.paymentProviderCustomerId}`);
logger.debug(
`[Invoice Email] Successfully synced billing details to Stripe for customer ${project.paymentProviderCustomerId}`,
);
} catch (err) {
logger.error(
`[Invoice Email] Failed to update Stripe customer business details: ${err}`,
);
}
} else {
logger.debug(`[Invoice Email] No paymentProviderCustomerId found, skipping Stripe sync`);
logger.debug(
`[Invoice Email] No paymentProviderCustomerId found, skipping Stripe sync`,
);
}
}
if (updateBy.data.enableAutoRechargeSmsOrCallBalance) {

View File

@@ -22,14 +22,17 @@ import Project from "Common/Models/DatabaseModels/Project";
import React, { FunctionComponent, ReactElement, useState } from "react";
import useAsyncEffect from "use-async-effect";
const DeleteAccount: FunctionComponent<PageComponentProps> = (): ReactElement => {
const DeleteAccount: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
const [projects, setProjects] = useState<Array<Project>>([]);
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
const [isDeleting, setIsDeleting] = useState<boolean>(false);
const [deleteError, setDeleteError] = useState<string>("");
const [showDeleteErrorModal, setShowDeleteErrorModal] = useState<boolean>(false);
const [showDeleteErrorModal, setShowDeleteErrorModal] =
useState<boolean>(false);
const userId: ObjectID = UserUtil.getUserId();