billing - better handling of invoices with no metadata
MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -295,7 +295,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
return stripeInvoice;
}
- public async synchronizeAsBillingInvoice(stripeInvoice: Stripe.Invoice, checkUserExists:boolean): Promise<BillingInvoice> {
+ public async synchronizeAsBillingInvoice(stripeInvoice: Stripe.Invoice): Promise<BillingInvoice> {
if (!stripeInvoice) {
throw new BackendError({
message: 'Unexpected situation - invoice is not set',
@@ -320,16 +320,6 @@ export default class StripeBillingIntegration extends BillingIntegration {
module: MODULE_NAME, action: ServerAction.BILLING,
method: 'synchronizeAsBillingInvoice',
});
- } else if (checkUserExists) {
- // Let's make sure the userID is still valid
- const user = await UserStorage.getUser(this.tenant, userID);
- if (!user) {
- throw new BackendError({
- message: `Unexpected situation - the e-Mobility user does not exist - ${userID}`,
- module: MODULE_NAME, action: ServerAction.BILLING,
- method: 'synchronizeAsBillingInvoice',
- });
- }
}
// Get the corresponding BillingInvoice (if any)
const billingInvoice: BillingInvoice = await BillingStorage.getInvoiceByInvoiceID(this.tenant, stripeInvoice.id);
@@ -450,7 +440,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
stripeInvoice = await this.getStripeInvoice(billingInvoice.invoiceID);
}
// Let's replicate some information on our side
- billingInvoice = await this.synchronizeAsBillingInvoice(stripeInvoice, false);
+ billingInvoice = await this.synchronizeAsBillingInvoice(stripeInvoice);
if (!billingInvoice) {
throw new Error(`Unexpected situation - failed to synchronize ${stripeInvoice.id} - the invoice is null`);
}
@@ -1037,7 +1027,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
};
}
- private async getLatestDraftInvoiceOfTheMonth(customerID: string): Promise<Stripe.Invoice> {
+ private async getLatestDraftInvoiceOfTheMonth(tenantID:string, userID: string, customerID: string): Promise<Stripe.Invoice> {
// Fetch the invoice list - c.f.: https://stripe.com/docs/api/invoices/list
// The invoices are returned sorted by creation date, with the most recent ones appearing first.
const list = await this.stripe.invoices.list({
@@ -1046,11 +1036,21 @@ export default class StripeBillingIntegration extends BillingIntegration {
limit: 1
});
const latestDraftInvoice = !Utils.isEmptyArray((list.data)) ? list.data[0] : null;
- // Check for the date
- // We do not want to mix in the same invoice charging sessions from different months
- if (latestDraftInvoice && moment.unix(latestDraftInvoice.created).isSame(moment(), 'month')) {
+ // An invoice with no metadata should not be reused - it may have been created manually from the STRIPE dashboard
+ if (latestDraftInvoice?.metadata) {
+ // Check for the tenant
+ if (tenantID !== latestDraftInvoice.metadata.tenantID) {
+ return null;
+ }
+ // Check for the userID
+ if (userID !== latestDraftInvoice.metadata.userID) {
+ return null;
+ }
+ // Check for the date - We do not want to mix in the same invoice charging sessions from different months
+ if (moment.unix(latestDraftInvoice.created).isSame(moment(), 'month')) {
return latestDraftInvoice;
}
+ }
// The latest DRAFT invoice is too old - don't reuse it!
return null;
}
@@ -1156,7 +1156,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
stripeInvoice = null;
} else {
// immediateBillingAllowed is OFF - let's add to the latest DRAFT invoice (if any)
- stripeInvoice = await this.getLatestDraftInvoiceOfTheMonth(customerID);
+ stripeInvoice = await this.getLatestDraftInvoiceOfTheMonth(this.tenant.id, userID, customerID);
}
// Let's create an invoice item per dimension
// When the stripeInvoice is null a pending item is created
@@ -1195,7 +1195,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
stripeInvoice = await this.getStripeInvoice(stripeInvoice.id);
}
// Let's replicate some information on our side
- const billingInvoice = await this.synchronizeAsBillingInvoice(stripeInvoice, false);
+ const billingInvoice = await this.synchronizeAsBillingInvoice(stripeInvoice);
if (!billingInvoice) {
throw new Error(`Unexpected situation - failed to synchronize ${stripeInvoice.id} - the invoice is null`);
}

billing - better handling of invoices with no metadata