added tests assign/detach/verify
added missing next()s
repaired getTaxes()
MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -187,10 +187,13 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
return this.convertToBillingUser(customer);
}
+ // TODO : check we retrieve only the active ones
public async getTaxes(): Promise<BillingTax[]> {
+ await this.checkConnection();
const taxes = [] as BillingTax[];
+ try {
let request;
- const requestParams : Stripe.TaxRateListParams = { limit: StripeBillingIntegration.STRIPE_MAX_LIST };
+ const requestParams : Stripe.TaxRateListParams = { limit: StripeBillingIntegration.STRIPE_MAX_LIST, active: true };
do {
request = await this.stripe.taxRates.list(requestParams);
for (const tax of request.data) {
@@ -205,6 +208,23 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
requestParams.starting_after = taxes[taxes.length - 1].id;
}
} while (request.has_more);
+ await Logging.logInfo({
+ tenantID: this.tenantID,
+ source: Constants.CENTRAL_SERVER,
+ action: ServerAction.BILLING_TAXES,
+ module: MODULE_NAME, method: 'getTaxes',
+ message: `Retrieved tax list (${taxes.length} taxes)`
+ });
+ } catch (e) {
+ // catch stripe errors and send the information back to the client
+ await Logging.logError({
+ tenantID: this.tenantID,
+ action: ServerAction.BILLING_TAXES,
+ actionOnUser: 'user',
+ module: MODULE_NAME, method: 'getTaxes',
+ message: `Stripe operation failed - ${e?.message as string}`
+ });
+ }
return taxes;
}

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -472,7 +472,7 @@ export default class BillingService {
next();
}
- public static async handleBillingSetupPaymentMethod(action: ServerAction, req: Request, res: Response): Promise<void> {
+ public static async handleBillingSetupPaymentMethod(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
const filteredRequest = BillingSecurity.filterSetupPaymentMethodRequest(req);
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(filteredRequest.user, TenantComponents.BILLING,
@@ -510,9 +510,10 @@ export default class BillingService {
console.log(operationResult);
}
res.json(operationResult);
+ next();
}
- public static async handleBillingGetPaymentMethods(action: ServerAction, req: Request, res: Response): Promise<void> {
+ public static async handleBillingGetPaymentMethods(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
const filteredRequest = BillingSecurity.filterPaymentMethodsRequest(req);
// Check if component is active
@@ -548,6 +549,7 @@ export default class BillingService {
console.log(paymentMethodResult);
}
res.json(paymentMethodResult);
+ next();
}
public static async handleBillingDeletePaymentMethod(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
@@ -586,6 +588,7 @@ export default class BillingService {
console.log(paymentMethodResult);
}
res.json(Constants.REST_RESPONSE_SUCCESS);
+ next();
}
public static async handleDownloadInvoice(action: ServerAction, req: Request, res: Response): Promise<void> {
@@ -692,7 +695,7 @@ export default class BillingService {
// });
}
- public static async handleBillingWebHook(action: ServerAction, req: Request, res: Response): Promise<void> {
+ public static async handleBillingWebHook(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
// ?? How to do it in this context
// Filter
@@ -726,5 +729,6 @@ export default class BillingService {
const done = await billingImpl.consumeBillingEvent(req);
// Return a response to acknowledge receipt of the event
res.json({ received: done });
+ next();
}
}

MODIFY
test/api/BillingStripeIntegrationTest.ts
test/api/BillingStripeIntegrationTest.ts
@@ -54,7 +54,7 @@ describe('Billing Stripe Service', function() {
await testData.forceBillingSettings(immediateBilling);
});
- it('Should add a different payment method to BILLING-TEST user', async () => {
+ it('Should add a different source to BILLING-TEST user', async () => {
await testData.assignPaymentMethod('tok_fr');
});
@@ -62,8 +62,13 @@ describe('Billing Stripe Service', function() {
await testData.checkImmediateBillingWithTaxes();
});
+ // TODO : change this as soon as we can test pm - now it's sources, not pm
+ it('Should detach newly added source to BILLING-TEST user', async () => {
+ await testData.assignPaymentMethod('tok_fr');
+ await testData.detachPaymentMethod();
+ await testData.retrieveDeletedPaymentMethod();
+ });
});
-
});
});

MODIFY
test/api/BillingStripeTestData.ts
test/api/BillingStripeTestData.ts
-import { BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingUser } from '../../src/types/Billing';
+import { BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingPaymentMethodResult, BillingUser } from '../../src/types/Billing';
import { BillingSettingsType, SettingDB, StripeBillingSetting } from '../../src/types/Setting';
import chai, { assert, expect } from 'chai';
@@ -12,11 +12,13 @@ import Stripe from 'stripe';
import StripeBillingIntegration from '../../src/integration/billing/stripe/StripeBillingIntegration';
import TenantContext from './context/TenantContext';
import TestConstants from './client/utils/TestConstants';
+import TestData from './client/utils/TestData';
import User from '../../src/types/User';
import UserStorage from '../../src/storage/mongodb/UserStorage';
import chaiSubset from 'chai-subset';
import config from '../config';
import responseHelper from '../helpers/responseHelper';
+import { result } from 'lodash';
chai.use(chaiSubset);
chai.use(responseHelper);
@@ -33,6 +35,8 @@ export default class StripeIntegrationTestData {
// Billing Implementation - STRIPE
public billingImpl: StripeBillingIntegration;
public billingUser: BillingUser; // DO NOT CONFUSE - BillingUser is not a User!
+ public source: Stripe.Response<Stripe.CustomerSource>;
+ public deletedSourceId: string;
public async initialize(): Promise<void> {
@@ -126,7 +130,7 @@ export default class StripeIntegrationTestData {
}
public async assignPaymentMethod(stripe_test_token: string) : Promise<Stripe.CustomerSource> {
- // Assign a payment method using test tokens (instead of test card numbers)
+ // Assign a source using test tokens (instead of test card numbers)
// c.f.: https://stripe.com/docs/testing#cards
const concreteImplementation : StripeBillingIntegration = this.billingImpl ;
const stripeInstance = await concreteImplementation.getStripeInstance();
@@ -134,9 +138,31 @@ export default class StripeIntegrationTestData {
source: stripe_test_token // e.g.: tok_visa, tok_amex, tok_fr
});
expect(source).to.not.be.null;
+ this.source = source;
return source;
}
+ // Detach the last assigned source
+ public async detachPaymentMethod() : Promise<BillingPaymentMethodResult> {
+ 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, this.source.id);
+ expect(deletedSource).to.not.be.null;
+ this.deletedSourceId = deletedSource.result[0].id;
+ return deletedSource;
+ }
+
+ // TODO : modify this test with conrete implementation when we have implemented getPaymentMethod(pmID)
+ public async retrieveDeletedPaymentMethod() : Promise<void> {
+ const concreteImplementation : StripeBillingIntegration = this.billingImpl;
+ const stripeInstance = await concreteImplementation.getStripeInstance();
+ try {
+ await stripeInstance.paymentMethods.retrieve(this.deletedSourceId);
+ } catch (error) {
+ expect(error.code).to.equal('resource_missing');
+ }
+ }
+
public async assignTaxRate(rate: number) : Promise<Stripe.TaxRate> {
// Let's create a tax rate
const concreteImplementation : StripeBillingIntegration = this.billingImpl ;

added tests assign/detach/verify
added missing next()s
repaired getTaxes()