Squashed stripe 1st integration
MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
-import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingInvoiceStatus, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
+import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
import User, { UserStatus } from '../../types/User';
import BackendError from '../../exception/BackendError';
@@ -7,6 +7,7 @@ import BillingStorage from '../../storage/mongodb/BillingStorage';
import Constants from '../../utils/Constants';
import Logging from '../../utils/Logging';
import NotificationHandler from '../../notification/NotificationHandler';
+import { Request } from 'express';
import { ServerAction } from '../../types/Server';
import SettingStorage from '../../storage/mongodb/SettingStorage';
import TenantStorage from '../../storage/mongodb/TenantStorage';
@@ -18,6 +19,7 @@ import Utils from '../../utils/Utils';
const MODULE_NAME = 'BillingIntegration';
export default abstract class BillingIntegration<T extends BillingSetting> {
+
protected readonly tenantID: string; // Assuming UUID or other string format ID
protected settings: T;
@@ -517,9 +519,16 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
abstract createInvoiceItem(user: BillingUser, invoiceID: string, invoiceItem: BillingInvoiceItem, idempotencyKey?: string | number): Promise<BillingInvoiceItem>;
+ // eslint-disable-next-line max-len
abstract createInvoice(user: BillingUser, invoiceItem: BillingInvoiceItem, idempotencyKey?: string | number): Promise<{ invoice: BillingInvoice; invoiceItem: BillingInvoiceItem }>;
abstract downloadInvoiceDocument(invoice: BillingInvoice): Promise<BillingInvoiceDocument>;
abstract finalizeInvoice(invoice: BillingInvoice): Promise<string>;
+
+ // ##CR - TO BE CLARIFIED - clarify the return type!
+ abstract attachPaymentMethod(user: User, paymentMethodId: string): Promise<unknown>;
+ abstract chargeInvoice(invoice: BillingInvoice): Promise<unknown>;
+ abstract handleBillingEvent(req: Request): boolean;
+
}

MODIFY
src/server/rest/CentralRestServerService.ts
src/server/rest/CentralRestServerService.ts
@@ -84,6 +84,7 @@ class RequestMapper {
[ServerAction.BILLING_SYNCHRONIZE_INVOICES]: BillingService.handleSynchronizeInvoices.bind(this),
[ServerAction.BILLING_FORCE_SYNCHRONIZE_USER_INVOICES]: BillingService.handleForceSynchronizeUserInvoices.bind(this),
[ServerAction.BILLING_CREATE_TRANSACTION_INVOICE]: BillingService.handleCreateTransactionInvoice.bind(this),
+ [ServerAction.BILLING_ATTACH_PAYMENT_METHOD]: BillingService.handleBillingAttachPaymentMethod.bind(this),
[ServerAction.OCPI_ENPOINT_CREATE]: OCPIEndpointService.handleCreateOcpiEndpoint.bind(this),
[ServerAction.OCPI_ENPOINT_PING]: OCPIEndpointService.handlePingOcpiEndpoint.bind(this),
[ServerAction.OCPI_ENPOINT_CHECK_CDRS]: OCPIEndpointService.handleCheckCdrsEndpoint.bind(this),
@@ -227,6 +228,7 @@ class RequestMapper {
[ServerAction.SYNCHRONIZE_CAR_CATALOGS]: CarService.handleSynchronizeCarCatalogs.bind(this),
[ServerAction.CAR_UPDATE]: CarService.handleUpdateCar.bind(this),
[ServerAction.TAG_UPDATE]: TagService.handleUpdateTag.bind(this),
+ [ServerAction.BILLING_CHARGE_INVOICE]: BillingService.handleBillingChargeInvoice.bind(this),
});
break;
@@ -328,6 +330,20 @@ export default class CentralRestServerService {
UtilsService.handleUnknownAction(action, req, res, next);
}
break;
+
+ case 'POST':
+ // Check Context
+ switch (action) {
+ // Ping
+ case ServerAction.BILLING_WEB_HOOK:
+ await BillingService.handleBillingWebHook(action, req, res);
+ // Res.sendStatus(StatusCodes.OK);
+ break;
+ default:
+ // Delegate
+ UtilsService.handleUnknownAction(action, req, res, next);
+ }
+ break;
}
} catch (error) {
next(error);

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -110,7 +110,7 @@ export default class BillingService {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.GENERAL_ERROR,
- message: 'Cannot aquire lock',
+ message: 'Cannot acquire lock',
module: MODULE_NAME, method: 'handleSynchronizeUsers',
action: action,
user: req.user
@@ -163,7 +163,7 @@ export default class BillingService {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.GENERAL_ERROR,
- message: 'Cannot aquire lock',
+ message: 'Cannot acquire lock',
module: MODULE_NAME, method: 'handleSynchronizeUser',
action: action,
user: req.user
@@ -215,7 +215,7 @@ export default class BillingService {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.GENERAL_ERROR,
- message: 'Cannot aquire lock',
+ message: 'Cannot acquire lock',
module: MODULE_NAME, method: 'handleSynchronizeUser',
action: action,
user: req.user
@@ -234,7 +234,7 @@ export default class BillingService {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.GENERAL_ERROR,
- message: 'Cannot aquire lock',
+ message: 'Cannot acquire lock',
module: MODULE_NAME, method: 'handleSynchronizeUser',
action: action,
user: req.user
@@ -369,7 +369,7 @@ export default class BillingService {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.GENERAL_ERROR,
- message: 'Cannot aquire lock',
+ message: 'Cannot acquire lock',
module: MODULE_NAME, method: 'handleSynchronizeUserInvoices',
action: action,
user: req.user
@@ -426,7 +426,7 @@ export default class BillingService {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.GENERAL_ERROR,
- message: 'Cannot aquire lock',
+ message: 'Cannot acquire lock',
module: MODULE_NAME, method: 'handleForceSynchronizeUserInvoices',
action: action,
user: req.user
@@ -492,7 +492,38 @@ export default class BillingService {
next();
}
- public static async handleDownloadInvoice(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ public static async handleBillingAttachPaymentMethod(action: ServerAction, req: Request, res: Response): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
+ Action.BILLING_ATTACH_PAYMENT_METHOD, Entity.BILLING, MODULE_NAME, 'handleBillingAttachPaymentMethod');
+ // Filter
+ // TODO - const filteredRequest = BillingSecurity.filterSynchronizeUserRequest(req.body);
+ // Get the billing impl
+ const billingImpl = await BillingFactory.getBillingImpl(req.user.tenantID);
+ if (!billingImpl) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Billing service is not configured',
+ module: MODULE_NAME, method: 'handleBillingAttachPaymentMethod',
+ action: action,
+ user: req.user
+ });
+ }
+ // Get user
+ const user: User = await UserStorage.getUser(req.user.tenantID, req.user.id);
+ UtilsService.assertObjectExists(action, user, `User '${req.user.id}' does not exist`,
+ MODULE_NAME, 'handleAttachPaymentMethod', req.user);
+ // Invoke the billing implementation
+ const paymentMethodId: string = req.body.paymentMethodId;
+ const operationResult = await billingImpl.attachPaymentMethod(user, paymentMethodId);
+ if (operationResult) {
+ console.log(operationResult);
+ }
+ res.json(operationResult);
+ }
+
+ public static async handleDownloadInvoice(action: ServerAction, req: Request, res: Response): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
Action.DOWNLOAD, Entity.BILLING, MODULE_NAME, 'handleDownloadInvoice');
@@ -551,4 +582,80 @@ export default class BillingService {
});
}
}
+
+ public static async handleBillingChargeInvoice(action: ServerAction, req: Request, res: Response): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
+ Action.BILLING_CHARGE_INVOICE, Entity.BILLING, MODULE_NAME, 'handleBillingChargeInvoice');
+ // Filter
+ const filteredRequest = BillingSecurity.filterChargeInvoiceRequest(req.body);
+ // Get the Invoice
+ const invoice = await BillingStorage.getInvoice(req.user.tenantID, filteredRequest.invoiceID);
+ UtilsService.assertObjectExists(action, invoice, `Invoice ID '${filteredRequest.invoiceID}' does not exist`,
+ MODULE_NAME, 'handleDownloadInvoice', req.user);
+ // Check Auth
+ // if (!Authorizations.canChargeInvoice(req.user, invoice.userID)) {
+ // throw new AppAuthError({
+ // errorCode: HTTPAuthError.ERROR,
+ // user: req.user,
+ // entity: Entity.INVOICE, action: Action.CHARGE_INVOICE,
+ // module: MODULE_NAME, method: 'handleBillingChargeInvoice',
+ // });
+ // }
+ // Get the billing impl
+ const billingImpl = await BillingFactory.getBillingImpl(req.user.tenantID);
+ if (!billingImpl) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Billing service is not configured',
+ module: MODULE_NAME, method: 'handleBillingChargeInvoice',
+ action: action,
+ user: req.user
+ });
+ }
+ // Invoke the billing implementation (stripe)
+ const billingInvoice = await billingImpl.getInvoice(invoice.invoiceID);
+ const operationResult = await billingImpl.chargeInvoice(billingInvoice);
+ if (operationResult) {
+ console.log(operationResult);
+ }
+ res.json(operationResult);
+ }
+
+ public static async handleBillingWebHook(action: ServerAction, req: Request, res: Response): Promise<void> {
+ // Check if component is active
+ // ?? How to do it in this context
+ // Filter
+ const filteredRequest = BillingSecurity.filterBillingWebHookRequest(req.query);
+ // Check Auth
+ // How to check it - no JWT!
+ // Retrieve Tenant ID from the URL Query Parameters
+ if (!filteredRequest.tenantID) {
+
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Unexpected situation - TenantID is not set',
+ module: MODULE_NAME, method: 'handleBillingWebHook',
+ action: action,
+ // User: req.user
+ });
+ }
+ const billingImpl = await BillingFactory.getBillingImpl(filteredRequest.tenantID);
+ if (!billingImpl) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Billing service is not configured',
+ module: MODULE_NAME, method: 'handleBillingWebHook',
+ action: action,
+ // User: req.user
+ });
+ }
+ // ##CR - don't wait for the result!!!
+ const done = billingImpl.handleBillingEvent(req);
+ // Return a response to acknowledge receipt of the event
+ res.json({ received: done });
+ }
}

MODIFY
src/server/rest/v1/service/security/BillingSecurity.ts
src/server/rest/v1/service/security/BillingSecurity.ts
-import { HttpCreateTransactionInvoiceRequest, HttpForceSynchronizeUserInvoicesRequest, HttpSynchronizeUserRequest } from '../../../../../types/requests/HttpUserRequest';
+import { HttpBillingInvoiceRequest, HttpBillingWebHookRequest } from '../../../../../types/requests/HttpBillingRequest';
+import { HttpChargeInvoiceRequest, HttpCreateTransactionInvoiceRequest, HttpForceSynchronizeUserInvoicesRequest, HttpSynchronizeUserRequest } from '../../../../../types/requests/HttpUserRequest';
-import { HttpBillingInvoiceRequest } from '../../../../../types/requests/HttpBillingRequest';
import HttpByIDRequest from '../../../../../types/requests/HttpByIDRequest';
import UtilsSecurity from './UtilsSecurity';
import sanitize from 'mongo-sanitize';
@@ -56,4 +56,18 @@ export default class BillingSecurity {
ID: sanitize(request.ID)
};
}
+
+ static filterChargeInvoiceRequest(request: any): HttpChargeInvoiceRequest {
+ return {
+ invoiceID: sanitize(request.invoiceID)
+ };
+ }
+
+ static filterBillingWebHookRequest(requestQuery: any): HttpBillingWebHookRequest {
+
+ return {
+ tenantID: sanitize(requestQuery.TenantID)
+ };
+ }
+
}

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -114,6 +114,8 @@ export enum Action {
REFUND_TRANSACTION = 'RefundTransaction',
SYNCHRONIZE_BILLING_USERS = 'SynchronizeBillingUsers',
SYNCHRONIZE_BILLING_USER = 'SynchronizeBillingUser',
+ BILLING_ATTACH_PAYMENT_METHOD = 'BillingAttachPaymentMethod',
+ BILLING_CHARGE_INVOICE = 'BillingChargeInvoice',
CHECK_CONNECTION = 'CheckConnection',
RETRIEVE_CONSUMPTION = 'RetrieveConsumption',
PING = 'Ping',

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -373,6 +373,9 @@ export enum ServerAction {
BILLING_DOWNLOAD_INVOICE = 'BillingDownloadInvoice',
BILLING_CREATE_TRANSACTION_INVOICE = 'BillingCreateTransactionInvoice',
BILLING_NEW_INVOICE = 'BillingNewInvoice',
+ BILLING_ATTACH_PAYMENT_METHOD = 'BillingAttachPaymentMethod',
+ BILLING_CHARGE_INVOICE = 'BillingChargeInvoice',
+ BILLING_WEB_HOOK = 'BillingWebHook',
MONGO_DB = 'MongoDB',

MODIFY
src/types/requests/HttpBillingRequest.ts
src/types/requests/HttpBillingRequest.ts
@@ -12,3 +12,7 @@ export interface HttpBillingInvoiceRequest extends HttpDatabaseRequest {
EndDateTime?: Date;
Search?: string;
}
+
+export interface HttpBillingWebHookRequest {
+ tenantID?: string
+}

MODIFY
src/types/requests/HttpUserRequest.ts
src/types/requests/HttpUserRequest.ts
@@ -18,6 +18,10 @@ export interface HttpCreateTransactionInvoiceRequest {
transactionID?: string;
}
+export interface HttpChargeInvoiceRequest {
+ invoiceID?: string;
+}
+
export interface HttpUserMobileTokenRequest {
id: string;
mobileToken: string;

Squashed stripe 1st integration