Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.4.12",
"version": "1.4.13",
"main": "index.ts",
"license": "BUSL-1.1",
"scripts": {
Expand Down
26 changes: 21 additions & 5 deletions src/billing/cloudpayments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
BusinessOperationType,
ConfirmedMemberDBScheme,
PayloadOfWorkspacePlanPurchase,
PlanDBScheme,
PlanProlongationPayload
PlanDBScheme
} from '@hawk.so/types';
import WorkspaceModel from '../models/workspace';
import HawkCatcher from '@hawk.so/nodejs';
Expand Down Expand Up @@ -88,7 +87,7 @@
return router;
}

/**

Check warning on line 90 in src/billing/cloudpayments.ts

View workflow job for this annotation

GitHub Actions / ESlint

Missing JSDoc @returns for function
* Generates invoice id for payment
*
* @param tariffPlan - tariff plan to generate invoice id
Expand Down Expand Up @@ -205,7 +204,7 @@
telegram.sendMessage(`🤗 [Billing / Check] All checks passed successfully «${workspace.name}»`, TelegramBotURLs.Money)
.catch(e => console.error('Error while sending message to Telegram: ' + e));

HawkCatcher.send(new Error('[Billing / Check] All checks passed successfully'), body as any);

Check warning on line 207 in src/billing/cloudpayments.ts

View workflow job for this annotation

GitHub Actions / ESlint

Unexpected any. Specify a different type

res.json({
code: CheckCodes.SUCCESS,
Expand Down Expand Up @@ -487,7 +486,7 @@
*/
private async fail(req: express.Request, res: express.Response): Promise<void> {
const body: FailRequest = req.body;
let data: PlanProlongationPayload;
let data: PaymentData;

console.log('💎 CloudPayments /fail request', body);

Expand All @@ -507,12 +506,29 @@
* @todo handle card linking and update business operation status
*/

if (!data.workspaceId || !data.userId || !data.tariffPlanId) {
this.sendError(res, FailCodes.SUCCESS, `[Billing / Fail] No workspace or user id or plan id in request body`, body);
if (!data.workspaceId) {
this.sendError(res, FailCodes.SUCCESS, `[Billing / Fail] No workspace id in request body`, body);

return;
}

if (!data.userId) {
this.sendError(res, FailCodes.SUCCESS, `[Billing / Fail] No user id in request body`, body);

return;
}

/**
* In card linking mode tariff plan id is taken from workspace.
*/
if (!data.isCardLinkOperation) {
if (!data.tariffPlanId) {
this.sendError(res, FailCodes.SUCCESS, `[Billing / Fail] No tariffPlanId in request body`, body);

return;
}
}

try {
businessOperation = await this.getBusinessOperation(req, body.TransactionId.toString());
workspace = await this.getWorkspace(req, data.workspaceId);
Expand Down Expand Up @@ -555,7 +571,7 @@

this.handleSendingToTelegramError(telegram.sendMessage(`❌ [Billing / Fail] Transaction failed for «${workspace.name}»`, TelegramBotURLs.Money));

HawkCatcher.send(new Error('[Billing / Fail] Transaction failed'), body as any);

Check warning on line 574 in src/billing/cloudpayments.ts

View workflow job for this annotation

GitHub Actions / ESlint

Unexpected any. Specify a different type

res.json({
code: FailCodes.SUCCESS,
Expand Down Expand Up @@ -737,7 +753,7 @@
* @param errorText - error description
* @param backtrace - request data and error data
*/
private sendError(res: express.Response, errorCode: CheckCodes | PayCodes | FailCodes | RecurrentCodes, errorText: string, backtrace: { [key: string]: any }): void {

Check warning on line 756 in src/billing/cloudpayments.ts

View workflow job for this annotation

GitHub Actions / ESlint

Unexpected any. Specify a different type
res.json({
code: errorCode,
});
Expand Down Expand Up @@ -802,7 +818,7 @@
promise.catch(e => console.error('Error while sending message to Telegram: ' + e));
}

/**

Check warning on line 821 in src/billing/cloudpayments.ts

View workflow job for this annotation

GitHub Actions / ESlint

Missing JSDoc @returns for function
* Parses body and returns card data
* @param request - request body to parse
*/
Expand Down
60 changes: 59 additions & 1 deletion test/integration/cases/billing/fail.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { apiInstance } from '../../utils';
import { FailCodes, FailRequest } from '../../../../src/billing/types';
import { CardType, Currency, OperationType, ReasonCode, ReasonCodesTranscript } from '../../../../src/billing/types/enums';
import { Collection, ObjectId, Db } from 'mongodb';
import { BusinessOperationDBScheme, BusinessOperationStatus, PlanDBScheme, BusinessOperationType, UserDBScheme, WorkspaceDBScheme, UserNotificationType, PlanProlongationPayload } from '@hawk.so/types';
import { BusinessOperationDBScheme, BusinessOperationStatus, PlanDBScheme, BusinessOperationType, UserDBScheme, WorkspaceDBScheme, UserNotificationType } from '@hawk.so/types';
import { WorkerPaths } from '../../../../src/rabbitmq';
import { PaymentFailedNotificationTask, SenderWorkerTaskType } from '../../../../src/types/personalNotifications';
import checksumService from '../../../../src/utils/checksumService';
import jwt, { Secret } from 'jsonwebtoken';
import type { Global } from '@jest/types';

declare var global: Global.Global;
Expand Down Expand Up @@ -215,6 +216,35 @@ describe('Fail webhook', () => {
expect(message && JSON.parse(message.content.toString())).toStrictEqual(expectedLimiterTask);
expect(apiResponse.data.code).toBe(FailCodes.SUCCESS);
});

test('Should change business operation status to rejected for card linking payload without tariff plan id', async () => {
const apiResponse = await apiInstance.post('/billing/fail', {
...validRequest,
Data: JSON.stringify({
checksum: await checksumService.generateChecksum({
isCardLinkOperation: true,
userId: user._id.toString(),
workspaceId: workspace._id.toString(),
nextPaymentDate: new Date().toString(),
}),
cloudPayments: {
recurrent: {
interval: 'Month',
period: 1,
amount: 100,
startDate: new Date().toISOString(),
},
},
}),
});

const updatedBusinessOperation = await businessOperationsCollection.findOne({
transactionId: transactionId.toString(),
});

expect(apiResponse.data.code).toBe(FailCodes.SUCCESS);
expect(updatedBusinessOperation?.status).toBe(BusinessOperationStatus.Rejected);
});
});

describe('With invalid request', () => {
Expand Down Expand Up @@ -252,5 +282,33 @@ describe('Fail webhook', () => {
expect(apiResponse.data.code).toBe(FailCodes.SUCCESS);
expect(updatedBusinessOperation?.status).toBe(BusinessOperationStatus.Pending);
});

test('Should not change business operation status for non-card-link payload without tariff plan id', async () => {
const invalidChecksum = jwt.sign({
isCardLinkOperation: false,
userId: user._id.toString(),
workspaceId: workspace._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}, process.env.JWT_SECRET_BILLING_CHECKSUM as Secret, { expiresIn: '30m' });

const apiResponse = await apiInstance.post('/billing/fail', {
...validRequest,
Data: JSON.stringify({
checksum: invalidChecksum,
}),
});

const updatedBusinessOperation = await businessOperationsCollection.findOne({
transactionId: transactionId.toString(),
});
const message = await global.rabbitChannel.get(WorkerPaths.Email.queue, {
noAck: true,
});

expect(apiResponse.data.code).toBe(FailCodes.SUCCESS);
expect(message).toBeFalsy();
expect(updatedBusinessOperation?.status).toBe(BusinessOperationStatus.Pending);
});
});
});
Loading