billing service - api changes + cleanup
MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
-import { BillingChargeInvoiceAction, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethodResult, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
+import { BillingChargeInvoiceAction, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
/* eslint-disable @typescript-eslint/member-ordering */
import User, { UserStatus } from '../../types/User';
@@ -504,7 +504,7 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
abstract setupPaymentMethod(user: User, paymentMethodId: string): Promise<BillingOperationResult>;
- abstract getPaymentMethods(user: User): Promise<BillingPaymentMethodResult>;
+ abstract getPaymentMethods(user: User): Promise<BillingPaymentMethod[]>;
- abstract deletePaymentMethod(user: User, paymentMethodId: string): Promise<BillingPaymentMethodResult>;
+ abstract deletePaymentMethod(user: User, paymentMethodId: string): Promise<BillingOperationResult>;
}

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
/* eslint-disable @typescript-eslint/member-ordering */
-import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingPaymentMethodResult, BillingStatus, BillingTax, BillingUser, BillingUserData } from '../../../types/Billing';
+import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingStatus, BillingTax, BillingUser, BillingUserData } from '../../../types/Billing';
import { DocumentEncoding, DocumentType } from '../../../types/GlobalType';
import AxiosFactory from '../../../utils/AxiosFactory';
@@ -522,7 +522,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
return billingOperationResult;
}
- public async getPaymentMethods(user: User): Promise<DataResult<BillingPaymentMethod>> {
+ public async getPaymentMethods(user: User): Promise<BillingPaymentMethod[]> {
// Check Stripe
await this.checkConnection();
// Check billing data consistency
@@ -537,11 +537,11 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
});
}
// Let's do it!
- const paymentMethodResult: BillingPaymentMethodResult = await this._getPaymentMethods(user, customerID);
- return paymentMethodResult;
+ const paymentMethods: BillingPaymentMethod[] = await this._getPaymentMethods(user, customerID);
+ return paymentMethods;
}
- public async deletePaymentMethod(user: User, paymentMethodId: string): Promise<BillingPaymentMethodResult> {
+ public async deletePaymentMethod(user: User, paymentMethodId: string): Promise<BillingOperationResult> {
// Check Stripe
await this.checkConnection();
// Check billing data consistency
@@ -556,8 +556,8 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
});
}
// Let's do it!
- const paymentMethodResult: BillingPaymentMethodResult = await this._detachPaymentMethod(paymentMethodId, customerID);
- return paymentMethodResult;
+ const billingOperationResult: BillingOperationResult = await this._detachPaymentMethod(paymentMethodId, customerID);
+ return billingOperationResult;
}
private async _createSetupIntent(user: User, customerID: string): Promise<BillingOperationResult> {
@@ -626,26 +626,26 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
succeeded: true,
internalData: paymentMethod
};
- } catch (e) {
+ } catch (error) {
// catch stripe errors and send the information back to the client
await Logging.logError({
tenantID: this.tenantID,
action: ServerAction.BILLING_SETUP_PAYMENT_METHOD,
actionOnUser: user,
module: MODULE_NAME, method: '_attachPaymentMethod',
- message: `Stripe operation failed - ${e?.message as string}`
+ message: `Stripe operation failed - ${error?.message as string}`
});
const billingOperationResult: BillingOperationResult = {
succeeded: false,
error: {
- message: e?.message
+ message: error?.message
}
};
return billingOperationResult;
}
}
- private async _getPaymentMethods(user: User, customerID: string): Promise<DataResult<BillingPaymentMethod>> {
+ private async _getPaymentMethods(user: User, customerID: string): Promise<BillingPaymentMethod[]> {
const paymentMethods: BillingPaymentMethod[] = [];
try {
let request;
@@ -672,13 +672,6 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
requestParams.starting_after = paymentMethods[paymentMethods.length - 1].id;
}
} while (request.has_more);
- await Logging.logInfo({
- tenantID: this.tenantID,
- source: Constants.CENTRAL_SERVER,
- action: ServerAction.BILLING_PAYMENT_METHODS,
- module: MODULE_NAME, method: '_getPaymentMethods',
- message: `Payment method has been listed for customer '${customerID}' - (${user.email})`
- });
} catch (e) {
// catch stripe errors and send the information back to the client
await Logging.logError({
@@ -689,14 +682,10 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
message: `Stripe operation failed - ${e?.message as string}`
});
}
- return {
- count: paymentMethods.length,
- result: paymentMethods
- };
+ return paymentMethods;
}
- private async _detachPaymentMethod(paymentMethodId: string, customerID: string): Promise<BillingPaymentMethodResult> {
- const detachedPaymentMethod: BillingPaymentMethod[] = [];
+ private async _detachPaymentMethod(paymentMethodId: string, customerID: string): Promise<BillingOperationResult> {
try {
// Verify payment method to be deleted is not the default one
const customer = await this.getStripeCustomer(customerID);
@@ -711,15 +700,6 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
}
// Detach payment method from the stripe customer
const paymentMethod: Stripe.PaymentMethod = await this.stripe.paymentMethods.detach(paymentMethodId);
- detachedPaymentMethod.push({
- id: paymentMethod.id,
- brand: paymentMethod.card.brand,
- expiringOn: new Date(paymentMethod.card.exp_year, paymentMethod.card.exp_month, 0),
- last4: paymentMethod.card.last4,
- type: paymentMethod.type,
- createdOn: moment.unix(paymentMethod.created).toDate(),
- isDefault: paymentMethod.id === customer.invoice_settings.default_payment_method
- });
await Logging.logInfo({
tenantID: this.tenantID,
source: Constants.CENTRAL_SERVER,
@@ -727,6 +707,11 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
module: MODULE_NAME, method: '_detachPaymentMethod',
message: `Payment method ${paymentMethodId} has been detached - customer '${customerID}'`
});
+ // Send some feedback
+ return {
+ succeeded: true,
+ internalData: paymentMethod
+ };
} catch (error) {
// catch stripe errors and send the information back to the client
await Logging.logError({
@@ -736,12 +721,15 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
message: `Failed to detach payment method - customer '${customerID}'`,
detailedMessages: { error: error.message, stack: error.stack }
});
- }
+ // Send some feedback
return {
- count: detachedPaymentMethod.length,
- result: detachedPaymentMethod
+ succeeded: false,
+ error: {
+ message: error?.message
+ }
};
}
+ }
public async startTransaction(transaction: Transaction): Promise<BillingDataTransactionStart> {
// Check Stripe

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
import { Action, Entity } from '../../../../types/Authorization';
-import { BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethodResult, BillingUserSynchronizeAction } from '../../../../types/Billing';
+import { BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingUserSynchronizeAction } from '../../../../types/Billing';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
@@ -10,6 +10,7 @@ import BillingFactory from '../../../../integration/billing/BillingFactory';
import BillingSecurity from './security/BillingSecurity';
import BillingStorage from '../../../../storage/mongodb/BillingStorage';
import Constants from '../../../../utils/Constants';
+import { DataResult } from '../../../../types/DataResult';
import LockingHelper from '../../../../locking/LockingHelper';
import LockingManager from '../../../../locking/LockingManager';
import Logging from '../../../../utils/Logging';
@@ -543,11 +544,12 @@ export default class BillingService {
UtilsService.assertObjectExists(action, user, `User '${filteredRequest.userID}' does not exist`,
MODULE_NAME, 'handleBillingGetPaymentMethods', req.user);
// Invoke the billing implementation
- const paymentMethodResult: BillingPaymentMethodResult = await billingImpl.getPaymentMethods(user);
- if (paymentMethodResult) {
- console.log(paymentMethodResult);
+ const paymentMethods: BillingPaymentMethod[] = await billingImpl.getPaymentMethods(user);
+ const dataResult: DataResult<BillingPaymentMethod> = {
+ count: paymentMethods.length,
+ result: paymentMethods
}
- res.json(paymentMethodResult);
+ res.json(dataResult);
next();
}
@@ -578,14 +580,12 @@ export default class BillingService {
});
}
// Get user - ACHTUNG user !== req.user
- const user: User = await UserStorage.getUser(req.user.tenantID, filteredRequest.userID);
- UtilsService.assertObjectExists(action, user, `User '${filteredRequest.userID}' does not exist`,
+ const userID = req.user.id;
+ const user: User = await UserStorage.getUser(req.user.tenantID, userID);
+ UtilsService.assertObjectExists(action, user, `User '${userID}' does not exist`,
MODULE_NAME, 'handleBillingDeletePaymentMethod', req.user);
// Invoke the billing implementation
- const paymentMethodResult: BillingPaymentMethodResult = await billingImpl.deletePaymentMethod(user, filteredRequest.paymentMethodId);
- if (paymentMethodResult) {
- console.log(paymentMethodResult);
- }
+ await billingImpl.deletePaymentMethod(user, filteredRequest.paymentMethodId);
res.json(Constants.REST_RESPONSE_SUCCESS);
next();
}

MODIFY
src/server/rest/v1/service/security/BillingSecurity.ts
src/server/rest/v1/service/security/BillingSecurity.ts
@@ -81,7 +81,8 @@ export default class BillingSecurity {
static filterDeletePaymentMethodRequest(requestBody: any): HttpDeletePaymentMethod {
return {
- userID: sanitize(requestBody.userID),
+ // TODO - The UI cannot pass the userID so far - so we can only delete payment method for the user being logged in!
+ // userID: sanitize(requestBody.userID),
paymentMethodId: sanitize(requestBody.paymentMethodId),
};
}

MODIFY
src/types/Billing.ts
src/types/Billing.ts
@@ -127,10 +127,6 @@ export interface BillingPaymentMethod {
isDefault: boolean;
}
-export interface BillingPaymentMethodResult {
- result: BillingPaymentMethod[];
- count: number;
-}
export interface BillingError {
message: string
context?: unknown; // e.g.: payment ==> last_payment_error

MODIFY
src/types/requests/HttpBillingRequest.ts
src/types/requests/HttpBillingRequest.ts
import { BillingInvoiceStatus } from '../Billing';
import HttpDatabaseRequest from './HttpDatabaseRequest';
-import UserToken from '../UserToken';
export interface HttpBillingRequest {
}
-
export interface HttpBillingInvoiceRequest extends HttpDatabaseRequest {
UserID?: string;
Status?: BillingInvoiceStatus;
@@ -28,6 +26,6 @@ export interface HttpPaymentMethods {
}
export interface HttpDeletePaymentMethod {
- userID: string;
+ // userID: string;
paymentMethodId: string;
}

MODIFY
test/api/BillingStripeTestData.ts
test/api/BillingStripeTestData.ts
-import { BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingPaymentMethodResult, BillingUser, BillingUserData } from '../../src/types/Billing';
+import { BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingUser, BillingUserData } from '../../src/types/Billing';
import { BillingSettingsType, SettingDB, StripeBillingSetting } from '../../src/types/Setting';
import chai, { assert, expect } from 'chai';
@@ -141,9 +141,10 @@ export default class StripeIntegrationTestData {
public async checkDetachPaymentMethod(newSourceId: string) : Promise<void> {
const concreteImplementation : StripeBillingIntegration = this.billingImpl;
// TODO: check this is not the default pm as here we are dealing with source and not pm
- const deletedSource = await concreteImplementation.deletePaymentMethod(this.dynamicUser, newSourceId);
- expect(deletedSource).to.not.be.null;
- await this.retrievePaymentMethod(deletedSource.result[0].id);
+ const operationResult: BillingOperationResult = await concreteImplementation.deletePaymentMethod(this.dynamicUser, newSourceId);
+ expect(operationResult.internalData).to.not.be.null;
+ const paymentMethod = operationResult.internalData as any;
+ await this.retrievePaymentMethod(paymentMethod.id);
}
// TODO : modify this test with concrete implementation when we have implemented getPaymentMethod(pmID)

billing service - api changes + cleanup