transfers - bill platform fee - review
MODIFY
src/assets/server/rest/v1/docs/e-mobility-oas.json
src/assets/server/rest/v1/docs/e-mobility-oas.json
"bearerAuth": []
}
],
- "description": "Send a Transfer invoice to the sub-account owner",
+ "description": "Triggers the transfer of funds to the sub-account",
"parameters": [
{
"in": "path",

MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
/* eslint-disable @typescript-eslint/member-ordering */
import { AsyncTaskType, AsyncTasks } from '../../types/AsyncTask';
-import { BillingAccount, BillingChargeInvoiceAction, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingSessionAccountData, BillingSessionData, BillingStatus, BillingTax, BillingTransfer, BillingTransferSession, BillingTransferStatus, BillingUser } from '../../types/Billing';
+import { BillingAccount, BillingChargeInvoiceAction, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingPlatformInvoice, BillingSessionAccountData, BillingSessionData, BillingStatus, BillingTax, BillingTransfer, BillingTransferSession, BillingTransferStatus, BillingUser } from '../../types/Billing';
import Tenant, { TenantComponents } from '../../types/Tenant';
import Transaction, { StartTransactionErrorCode } from '../../types/Transaction';
import User, { UserStatus } from '../../types/User';
@@ -710,7 +710,7 @@ export default abstract class BillingIntegration {
abstract createSubAccount(): Promise<BillingAccount>;
- abstract generateTransferInvoice(transfer: BillingTransfer, user: User): Promise<BillingInvoice>;
+ abstract billPlatformFee(transfer: BillingTransfer, user: User): Promise<BillingPlatformInvoice>;
protected async triggerTransferPreparation(billingInvoice: BillingInvoice): Promise<void> {
if (!billingInvoice.sessions) {

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
import { AsyncTaskType, AsyncTasks } from '../../../types/AsyncTask';
/* eslint-disable @typescript-eslint/member-ordering */
-import { BillingAccount, BillingAccountStatus, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingSessionAccountData, BillingStatus, BillingTax, BillingTransfer, BillingUser, BillingUserData } from '../../../types/Billing';
+import { BillingAccount, BillingAccountStatus, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingPlatformInvoice, BillingSessionAccountData, BillingStatus, BillingTax, BillingTransfer, BillingUser, BillingUserData } from '../../../types/Billing';
import { DimensionType, PricedConsumptionData, PricedDimensionData } from '../../../types/Pricing';
import FeatureToggles, { Feature } from '../../../utils/FeatureToggles';
import StripeHelpers, { StripeChargeOperationResult } from './StripeHelpers';
@@ -1742,40 +1742,47 @@ export default class StripeBillingIntegration extends BillingIntegration {
};
}
- public async generateTransferInvoice(billingTransfer: BillingTransfer, user: User): Promise<BillingInvoice> {
+ public async billPlatformFee(billingTransfer: BillingTransfer, user: User): Promise<BillingPlatformInvoice> {
await this.checkConnection();
// Create invoice items
try {
await Promise.all(billingTransfer.sessions.map(async (session) =>
this.createStripeInvoiceItem({
- amount: session.amount * 100,
+ amount: session.amount * 100, // TODO - bill the platform fee (not the session amount)
customer: user.billingData.customerID,
currency: billingTransfer.currency
- }, this.buildIdemPotencyKey(session.transactionID, 'invoice'))
+ }, this.buildIdemPotencyKey(session.transactionID, 'invoice', 'platformFee'))
));
} catch (e) {
throw new BackendError({
message: 'Unexpected situation - unable to create invoice item',
detailedMessages: { e },
module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
- method: 'generateTransferInvoice',
+ method: 'billPlatformFee',
});
}
// Create invoice
let stripeInvoice: Stripe.Invoice;
try {
- stripeInvoice = await this.createStripeInvoice(user.billingData.customerID, user.id, this.buildIdemPotencyKey(billingTransfer.id, 'invoice'));
+ stripeInvoice = await this.createStripeInvoice(user.billingData.customerID, user.id, this.buildIdemPotencyKey(billingTransfer.id, 'invoice', 'platformFee'));
} catch (e) {
throw new BackendError({
message: 'Unexpected situation - unable to create transfer invoice',
detailedMessages: { e },
module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
- method: 'generateTransferInvoice',
+ method: 'billPlatformFee',
+ });
+ }
+ if (!stripeInvoice) {
+ throw new BackendError({
+ message: 'Unexpected situation - platform invoice is not set',
+ module: MODULE_NAME, action: ServerAction.BILLING,
+ method: 'convertToBillingPlatformInvoice',
});
}
- // Do not charge the user
try {
stripeInvoice = await this.stripe.invoices.pay(stripeInvoice.id, {
+ // Do not charge the customer
paid_out_of_band: true
});
} catch (e) {
@@ -1783,10 +1790,29 @@ export default class StripeBillingIntegration extends BillingIntegration {
message: 'Unexpected situation - unable to flag the invoice as paid out of band',
detailedMessages: { e },
module: MODULE_NAME, action: ServerAction.BILLING_TRANSFER_FINALIZE,
- method: 'generateTransferInvoice',
+ method: 'billPlatformFee',
});
}
- const invoice = this.convertToBillingInvoice(stripeInvoice);
+ const invoice = this.convertToBillingPlatformInvoice(stripeInvoice);
+ return invoice;
+ }
+
+ public convertToBillingPlatformInvoice(stripeInvoice: Stripe.Invoice): BillingPlatformInvoice {
+ // eslint-disable-next-line id-blacklist, max-len
+ const { id: invoiceID, customer, number: invoiceNumber, livemode: liveMode, amount_due: amount, status, currency: invoiceCurrency, metadata } = stripeInvoice;
+ const customerID = customer as string;
+ const currency = invoiceCurrency?.toUpperCase();
+ const epoch = stripeInvoice.status_transitions?.finalized_at || stripeInvoice.created;
+ const createdOn = moment.unix(epoch).toDate(); // epoch to Date!
+ const userID = metadata.userID;
+ // Amount is in cents!
+ const totalAmount = amount * 100;
+ const invoice: BillingPlatformInvoice = {
+ invoiceID, invoiceNumber, liveMode, userID,
+ status: status as BillingInvoiceStatus,
+ amount, totalAmount, currency,
+ customerID, createdOn
+ };
return invoice;
}
}

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -675,9 +675,10 @@ export default class BillingService {
user.billingData = (await billingImpl.forceSynchronizeUser(user)).billingData;
await UserStorage.saveUser(req.tenant, user);
}
- await billingImpl.generateTransferInvoice(transfer, user);
+ const invoice = await billingImpl.billPlatformFee(transfer, user);
// Update the transfer status
transfer.status = BillingTransferStatus.FINALIZED;
+ transfer.invoice = invoice;
await BillingStorage.saveTransfer(req.tenant, transfer);
res.json(Constants.REST_RESPONSE_SUCCESS);
next();

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
@@ -365,6 +365,20 @@ export default class BillingStorage {
invoiceExternalID: transfer.platformFeeData.invoiceExternalID
};
}
+ if (transfer.invoice) {
+ transferMDB.invoice = {
+ invoiceID: transfer.invoice.invoiceID,
+ liveMode: transfer.invoice.liveMode,
+ userID: transfer.invoice.userID,
+ invoiceNumber: transfer.invoice.invoiceNumber,
+ status: transfer.invoice.status,
+ amount: transfer.invoice.amount,
+ totalAmount: transfer.invoice.totalAmount,
+ currency: transfer.invoice.currency,
+ customerID: transfer.invoice.customerID,
+ createdOn: transfer.invoice.createdOn,
+ };
+ }
// Check Created/Last Changed By
DatabaseUtils.addLastChangedCreatedProps(transferMDB, transfer);
// Save

MODIFY
src/types/Billing.ts
src/types/Billing.ts
@@ -205,6 +205,7 @@ export interface BillingTransfer extends CreatedUpdatedProps, BillingTransferAut
platformFeeData: BillingPlatformFeeData;
transferExternalID: string; // Transfer sent to the CPO
currency: string;
+ invoice?: BillingPlatformInvoice; // The invoice generated to bill the platform fee
}
// Very important - preserve maximal precision - Decimal type is persisted as an object in the DB
@@ -220,3 +221,16 @@ export interface BillingTransferSession {
platformFeeStrategy: BillingPlatformFeeStrategy;
}
+export interface BillingPlatformInvoice {
+ invoiceID: string;
+ liveMode: boolean;
+ userID: string;
+ invoiceNumber: string;
+ status: BillingInvoiceStatus;
+ amount: number;
+ totalAmount: number;
+ currency: string;
+ customerID: string;
+ createdOn: Date;
+}
+

transfers - bill platform fee - review