bp - prepare transfer
MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
@@ -292,6 +292,10 @@ export default abstract class BillingIntegration {
}
protected async triggerTransferPreparation(billingInvoice: BillingInvoice): Promise<void> {
+ if (!billingInvoice.sessions) {
+ // This should not happen!
+ return;
+ }
// The invoice may include several sessions - let's check if at least one of these needs a transfer
const sessions = billingInvoice.sessions.filter((session) => session?.accountData?.withTransferActive);
if (sessions.length > 0) {

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): Promise<BillingInvoice> {
+ public async convertToBillingInvoice(stripeInvoice: Stripe.Invoice): Promise<BillingInvoice> {
if (!stripeInvoice) {
throw new BackendError({
message: 'Unexpected situation - invoice is not set',
@@ -323,22 +323,26 @@ export default class StripeBillingIntegration extends BillingIntegration {
}
// Get the corresponding BillingInvoice (if any)
const billingInvoice: BillingInvoice = await BillingStorage.getInvoiceByInvoiceID(this.tenant, stripeInvoice.id);
- const invoiceToSave: BillingInvoice = {
- id: billingInvoice?.id, // ACHTUNG: billingInvoice is null when creating the Billing Invoice
+ let invoiceToSave: BillingInvoice;
+ if (billingInvoice) {
+ // Update existing invoice
+ invoiceToSave = {
+ ...billingInvoice, // includes the id
// eslint-disable-next-line id-blacklist
- userID, invoiceID, customerID, liveMode, number, amount, amountPaid, currency, createdOn, downloadUrl, downloadable: !!downloadUrl,
- status: status as BillingInvoiceStatus, payInvoiceUrl,
+ invoiceID, liveMode, userID, number, status: status as BillingInvoiceStatus,
+ amount, amountPaid, currency, customerID, createdOn, downloadable: !!downloadUrl, downloadUrl, payInvoiceUrl,
+ };
+ } else {
+ // Create new invoice
+ invoiceToSave = {
+ id: null,
+ // eslint-disable-next-line id-blacklist
+ invoiceID, liveMode, userID, number, status: status as BillingInvoiceStatus,
+ amount, amountPaid, currency, customerID, createdOn, downloadable: !!downloadUrl, downloadUrl, payInvoiceUrl,
+ sessions: []
};
- // Let's persist the invoice with up-to-date data
- const freshInvoiceId = await BillingStorage.saveInvoice(this.tenant, invoiceToSave);
- // Let's get a clean invoice instance
- let freshBillingInvoice = await BillingStorage.getInvoice(this.tenant, freshInvoiceId);
- if (!freshBillingInvoice) {
- // This should not happen - but it happened several times - so let's wait a bit and try again
- await Utils.sleep(2000);
- freshBillingInvoice = await BillingStorage.getInvoice(this.tenant, freshInvoiceId);
}
- return freshBillingInvoice;
+ return invoiceToSave;
}
private async createStripeInvoiceItem(parameters: Stripe.InvoiceItemCreateParams, idempotencyKey: string | number): Promise<Stripe.InvoiceItem> {
@@ -420,7 +424,8 @@ export default class StripeBillingIntegration extends BillingIntegration {
const operationResult = await this.chargeStripeInvoice(billingInvoice.invoiceID);
if (!operationResult?.succeeded && operationResult?.error) {
if (StripeHelpers.isResourceMissingError(operationResult.error)) {
- await StripeHelpers.updateInvoiceAdditionalData(this.tenant, billingInvoice, operationResult);
+ StripeHelpers.enrichInvoiceWithAdditionalData(billingInvoice, operationResult);
+ await BillingStorage.saveInvoice(this.tenant, billingInvoice);
throw operationResult.error;
} else {
await Logging.logError({
@@ -440,13 +445,14 @@ 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);
- if (!billingInvoice) {
- throw new Error(`Unexpected situation - failed to synchronize ${stripeInvoice.id} - the invoice is null`);
- }
- await StripeHelpers.updateInvoiceAdditionalData(this.tenant, billingInvoice, operationResult);
- // Send a notification to the user
+ billingInvoice = await this.convertToBillingInvoice(stripeInvoice);
+ StripeHelpers.enrichInvoiceWithAdditionalData(billingInvoice, operationResult);
+ // Save invoice
+ const invoiceID = await BillingStorage.saveInvoice(this.tenant, billingInvoice);
+ billingInvoice.id = invoiceID;
+ // Notification the user about the new invoice
void this.sendInvoiceNotification(billingInvoice);
+ // Update transactions with invoice data
await this.updateTransactionsBillingData(billingInvoice);
// Trigger transfer preparation task
if (billingInvoice.status === BillingInvoiceStatus.PAID) {
@@ -1199,12 +1205,15 @@ 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);
+ const billingInvoice = await this.convertToBillingInvoice(stripeInvoice);
if (!billingInvoice) {
throw new Error(`Unexpected situation - failed to synchronize ${stripeInvoice.id} - the invoice is null`);
}
// We have now a Billing Invoice - Let's update it with details about the last operation result
- await StripeHelpers.updateInvoiceAdditionalData(this.tenant, billingInvoice, operationResult, billingInvoiceItem);
+ StripeHelpers.enrichInvoiceWithAdditionalData(billingInvoice, operationResult, billingInvoiceItem);
+ // Save invoice
+ const invoiceID = await BillingStorage.saveInvoice(this.tenant, billingInvoice);
+ billingInvoice.id = invoiceID;
// Trigger transfer preparation task
if (billingInvoice.status === BillingInvoiceStatus.PAID) {
await this.triggerTransferPreparation(billingInvoice);

MODIFY
src/integration/billing/stripe/StripeHelpers.ts
src/integration/billing/stripe/StripeHelpers.ts
@@ -17,32 +17,28 @@ export interface StripeChargeOperationResult {
export default class StripeHelpers {
- public static async updateInvoiceAdditionalData(tenant: Tenant,
- billingInvoice: BillingInvoice,
- operationResult: StripeChargeOperationResult,
- billingInvoiceItem?: BillingInvoiceItem): Promise<void> {
+ public static enrichInvoiceWithAdditionalData(billingInvoice: BillingInvoice, operationResult: StripeChargeOperationResult, billingInvoiceItem?: BillingInvoiceItem): void {
// Do we have an error to preserve
let billingError: BillingError;
if (operationResult && !operationResult.succeeded) {
// The operation failed
billingError = StripeHelpers.convertToBillingError(operationResult.error);
}
- // Do we have a new charging session?
- let session: BillingSessionData;
if (billingInvoiceItem) {
- session = {
+ const session: BillingSessionData = {
transactionID: billingInvoiceItem.transactionID,
pricingData: billingInvoiceItem.pricingData,
accountData: billingInvoiceItem.accountData,
};
+ if (billingInvoice.sessions) {
+ billingInvoice.sessions.push(session);
+ } else {
+ billingInvoice.sessions = [ session ];
+ }
}
// Is there anything to update?
- if (session || billingError) {
- const additionalData: BillingAdditionalData = {
- session,
- lastError: billingError
- };
- await BillingStorage.updateInvoiceAdditionalData(tenant, billingInvoice, additionalData);
+ if (billingError) {
+ billingInvoice.lastError = billingError;
}
}

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
@@ -170,6 +170,12 @@ export default class BillingStorage {
downloadUrl: invoiceToSave.downloadUrl,
payInvoiceUrl: invoiceToSave.payInvoiceUrl
};
+ if (invoiceToSave.sessions) {
+ invoiceMDB.sessions = invoiceToSave.sessions;
+ }
+ if (invoiceToSave.lastError) {
+ invoiceMDB.lastError = invoiceToSave.lastError;
+ }
// Modify and return the modified document
await global.database.getCollection<any>(tenant.id, 'invoices').findOneAndUpdate(
{ _id: invoiceMDB._id },
@@ -180,25 +186,6 @@ export default class BillingStorage {
return invoiceMDB._id.toString();
}
- public static async updateInvoiceAdditionalData(tenant: Tenant, invoiceToUpdate: BillingInvoice, additionalData: BillingAdditionalData): Promise<void> {
- const startTime = Logging.traceDatabaseRequestStart();
- DatabaseUtils.checkTenantObject(tenant);
- // Preserve the previous list of sessions
- const sessions: BillingSessionData[] = invoiceToUpdate.sessions || [];
- if (additionalData.session) {
- sessions.push(additionalData.session);
- }
- // Set data
- const updatedInvoiceMDB: any = {
- sessions,
- lastError: additionalData.lastError
- };
- await global.database.getCollection(tenant.id, 'invoices').findOneAndUpdate(
- { '_id': DatabaseUtils.convertToObjectID(invoiceToUpdate.id) },
- { $set: updatedInvoiceMDB });
- await Logging.traceDatabaseRequestEnd(tenant, MODULE_NAME, 'saveInvoiceAdditionalData', startTime, updatedInvoiceMDB);
- }
-
public static async deleteInvoice(tenant: Tenant, id: string): Promise<void> {
const startTime = Logging.traceDatabaseRequestStart();
DatabaseUtils.checkTenantObject(tenant);

bp - prepare transfer