stripe - nbrOfItems - new approach
MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -237,11 +237,11 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
return stripeInvoice;
}
- private getNumberOfItems(stripeInvoice: Stripe.Invoice): number {
- // STRIPE version 8.137.0 - total_count property is deprecated - TODO - find another way to get it!
- const nbrOfItems: number = stripeInvoice.lines['total_count'];
- return nbrOfItems;
- }
+ // private getNumberOfItems(stripeInvoice: Stripe.Invoice): number {
+ // // STRIPE version 8.137.0 - total_count property is deprecated - TODO - find another way to get it!
+ // const nbrOfItems: number = stripeInvoice.lines['total_count'];
+ // return nbrOfItems;
+ // }
// ----------------------------------------------------------
// TODO - get rid of this logic and use webhooks instead!
@@ -344,10 +344,9 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
}
// Get the corresponding BillingInvoice (if any)
const billingInvoice: BillingInvoice = await BillingStorage.getInvoiceByInvoiceID(this.tenantID, stripeInvoice.id);
- const nbrOfItems: number = this.getNumberOfItems(stripeInvoice);
const invoiceToSave: BillingInvoice = {
id: billingInvoice?.id, // ACHTUNG: billingInvoice is null when creating the Billing Invoice
- userID, invoiceID, customerID, number, amount, amountPaid, currency, createdOn, nbrOfItems, downloadUrl, downloadable: !!downloadUrl,
+ userID, invoiceID, customerID, number, amount, amountPaid, currency, createdOn, downloadUrl, downloadable: !!downloadUrl,
status: status as BillingInvoiceStatus,
};
// Let's persist the up-to-date data
@@ -450,7 +449,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
}
billingInvoice = await this.synchronizeAsBillingInvoice(billingInvoice.invoiceID, false);
- await StripeHelpers.handleStripeOperationResult(this.tenantID, billingInvoice, operationResult);
+ await StripeHelpers.updateInvoiceAdditionalData(this.tenantID, billingInvoice, operationResult);
return billingInvoice;
}
@@ -868,8 +867,19 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
status: BillingStatus.BILLED,
invoiceID: billingInvoice.id,
invoiceStatus: billingInvoice.status,
- invoiceItem
+ invoiceItem: this.shrinkInvoiceItem(invoiceItem),
+ };
+ }
+
+ private shrinkInvoiceItem(fatInvoiceItem: BillingInvoiceItem): BillingInvoiceItem {
+ // The initial invoice item includes redundant transaction data
+ const { description, pricingData } = fatInvoiceItem;
+ // Let's return only essential information
+ const lightInvoiceItem: BillingInvoiceItem = {
+ description,
+ pricingData
};
+ return lightInvoiceItem;
}
private convertToBillingInvoiceItem(transaction: Transaction) : BillingInvoiceItem {
@@ -896,6 +906,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
metadata: {
// Let's keep track of the initial data for troubleshooting purposes
tenantID: this.tenantID,
+ transactionID: transaction.id,
userID: transaction.userID,
price,
roundedPrice,
@@ -914,6 +925,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
const customerID: string = user.billingData?.customerID;
// Check whether a DRAFT invoice can be used
let stripeInvoice = await this._getLatestDraftInvoice(customerID);
+ // Let's create an invoice item (could be a pending one when the stripeInvoice does not yet exist)
const invoiceItemParameters: Stripe.InvoiceItemCreateParams = this._buildInvoiceItemParameters(customerID, billingInvoiceItem, stripeInvoice?.id);
const stripeInvoiceItem = await this._createStripeInvoiceItem(invoiceItemParameters, this.buildIdemPotencyKey(idemPotencyKey, true));
if (!stripeInvoiceItem) {
@@ -926,7 +938,6 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
message: `Unexpected situation - stripe invoice item is null - stripe invoice id: '${stripeInvoice?.id }'`
});
}
- // Stripe invoice ID is not yet known - Let's create a pending invoice item
if (!stripeInvoice) {
// Let's create a new draft invoice (if none has been found)
stripeInvoice = await this._createStripeInvoice(customerID, userID, this.buildIdemPotencyKey(idemPotencyKey));
@@ -950,7 +961,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
// Let's replicate some information on our side
const billingInvoice = await this.synchronizeAsBillingInvoice(stripeInvoice.id, false);
// We have now a Billing Invoice - Let's update it with details about the last operation result
- await StripeHelpers.handleStripeOperationResult(this.tenantID, billingInvoice, operationResult);
+ await StripeHelpers.updateInvoiceAdditionalData(this.tenantID, billingInvoice, operationResult, billingInvoiceItem);
// Return the billing invoice
return billingInvoice;
}

MODIFY
src/integration/billing/stripe/StripeHelpers.ts
src/integration/billing/stripe/StripeHelpers.ts
-import { BillingError, BillingErrorCode, BillingErrorType, BillingInvoice, BillingOperationResult } from '../../../types/Billing';
+import { BillingAdditionalData, BillingError, BillingErrorCode, BillingErrorType, BillingInvoice, BillingInvoiceItem, BillingOperationResult } from '../../../types/Billing';
import BillingStorage from '../../../storage/mongodb/BillingStorage';
import Stripe from 'stripe';
export default class StripeHelpers {
- public static async handleStripeOperationResult(tenantID: string, billingInvoice: BillingInvoice, operationResult: BillingOperationResult): Promise<void> {
- if (!billingInvoice || !operationResult) {
+
+ public static async updateInvoiceAdditionalData(tenantID: string, billingInvoice: BillingInvoice,
+ operationResult: BillingOperationResult, billingInvoiceItem?: BillingInvoiceItem): Promise<void> {
+ if (!billingInvoice) {
return;
}
- if (!operationResult.succeeded) {
+ let billingError = null;
+ if (operationResult && !operationResult.succeeded) {
// The operation failed
- const billingError: BillingError = StripeHelpers.convertToBillingError(operationResult.error);
- await BillingStorage.saveLastBillingError(tenantID, billingInvoice.id, billingError);
+ billingError = StripeHelpers.convertToBillingError(operationResult.error);
+ }
+ if (billingInvoiceItem || billingError) {
+ const additionalData: BillingAdditionalData = {
+ item: billingInvoiceItem,
+ lastError: billingError
+ };
+ await BillingStorage.updateInvoiceAdditionalData(tenantID, billingInvoice, additionalData);
}
}
@@ -22,6 +31,7 @@ export default class StripeHelpers {
// Wrap it in a format that we can consume!
return {
message: error.message,
+ when: new Date(),
errorType,
errorCode,
rootCause,

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
-import { BillingError, BillingInvoice, BillingInvoiceStatus } from '../../types/Billing';
+import { BillingAdditionalData, BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus } from '../../types/Billing';
import global, { FilterParams } from '../../types/GlobalType';
import Constants from '../../utils/Constants';
@@ -173,7 +173,7 @@ export default class BillingStorage {
status: invoiceToSave.status,
currency: invoiceToSave.currency,
createdOn: Utils.convertToDate(invoiceToSave.createdOn),
- nbrOfItems: Utils.convertToInt(invoiceToSave.nbrOfItems),
+ // nbrOfItems: Utils.convertToInt(invoiceToSave.nbrOfItems),
downloadable: Utils.convertToBoolean(invoiceToSave.downloadable),
downloadUrl: invoiceToSave.downloadUrl
};
@@ -188,24 +188,27 @@ export default class BillingStorage {
return invoiceMDB._id.toHexString();
}
- public static async saveLastBillingError(tenantID: string, invoiceID: string, error: BillingError): Promise<void> {
+ public static async updateInvoiceAdditionalData(tenantID: string, invoiceToUpdate: BillingInvoice, additionalData: BillingAdditionalData): Promise<void> {
// Debug
- const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'saveLastBillingError');
+ const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'saveInvoiceAdditionalData');
// Check Tenant
await DatabaseUtils.checkTenant(tenantID);
- // Set data
- const updatedInvoiceMDB = {
- lastBillingError: {
- when: new Date(),
- error
+ // Preserve the previous list of items
+ const items: BillingInvoiceItem[] = invoiceToUpdate.items || [];
+ if (additionalData.item) {
+ items.push(additionalData.item);
}
+ // Set data
+ const updatedInvoiceMDB: any = {
+ nbrOfItems: items.length,
+ items,
+ lastError: additionalData.lastError
};
- // Modify and return the modified document
await global.database.getCollection(tenantID, 'invoices').findOneAndUpdate(
- { '_id': Utils.convertToObjectID(invoiceID) },
+ { '_id': Utils.convertToObjectID(invoiceToUpdate.id) },
{ $set: updatedInvoiceMDB });
// Debug
- await Logging.traceEnd(tenantID, MODULE_NAME, 'saveLastBillingError', uniqueTimerID, updatedInvoiceMDB);
+ await Logging.traceEnd(tenantID, MODULE_NAME, 'saveInvoiceAdditionalData', uniqueTimerID, updatedInvoiceMDB);
}
public static async deleteInvoice(tenantID: string, id: string): Promise<void> {

MODIFY
src/types/Billing.ts
src/types/Billing.ts
@@ -81,6 +81,8 @@ export interface BillingInvoice {
nbrOfItems?: number;
downloadable?: boolean
downloadUrl?: string;
+ items?: BillingInvoiceItem[];
+ lastError?: BillingError;
}
export interface BillingInvoiceItem {
@@ -130,6 +132,7 @@ export interface BillingPaymentMethod {
export interface BillingError {
// Billing Error should expose the information which is common to all payment platforms
message: string
+ when: Date
errorType: BillingErrorType, // SERVER or APPLICATION errors
errorCode: BillingErrorCode, // More information about the root cause
rootCause?: unknown; // The original error from the payment platform
@@ -148,3 +151,8 @@ export enum BillingErrorCode {
NO_PAYMENT_METHOD = 'no_payment_method',
CARD_ERROR = 'card_error',
}
+
+export interface BillingAdditionalData {
+ item?: BillingInvoiceItem,
+ lastError?: BillingError,
+}

stripe - nbrOfItems - new approach