Skip to content
Merged
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.1.19",
"version": "1.1.20",
"main": "index.ts",
"license": "UNLICENSED",
"scripts": {
Expand Down
64 changes: 53 additions & 11 deletions src/billing/cloudpayments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
PlanDBScheme,
PlanProlongationPayload
} from '@hawk.so/types';
import { PENNY_MULTIPLIER } from 'codex-accounting-sdk';
import WorkspaceModel from '../models/workspace';
import HawkCatcher from '@hawk.so/nodejs';
import { publish } from '../rabbitmq';
Expand All @@ -45,8 +44,10 @@
import { ClientApi, ClientService, CustomerReceiptItem, ReceiptApi, ReceiptTypes, TaxationSystem } from 'cloudpayments';
import { ComposePaymentPayload } from './types/composePaymentPayload';

const PENNY_MULTIPLIER = 100;

interface ComposePaymentRequest extends express.Request {
query: ComposePaymentPayload & { [key: string]: any };

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

View workflow job for this annotation

GitHub Actions / ESlint

Unexpected any. Specify a different type
context: import('../types/graphql').ResolverContextBase;
};

Expand Down Expand Up @@ -105,7 +106,12 @@
const userId = req.context.user.id;

if (!workspaceId || !tariffPlanId || !userId) {
this.sendError(res, 1, `[Billing / Compose payment] No workspace, tariff plan or user id in request body`, req.query);
this.sendError(res, 1, `[Billing / Compose payment] No workspace, tariff plan or user id in request body
Details:
workspaceId: ${workspaceId}
tariffPlanId: ${tariffPlanId}
userId: ${userId}`
, req.query);

return;
}
Expand Down Expand Up @@ -137,18 +143,37 @@

const isCardLinkOperation = workspace.tariffPlanId.toString() === tariffPlanId && !workspace.isTariffPlanExpired();

// Calculate next payment date
const lastChargeDate = new Date(workspace.lastChargeDate);
const now = new Date();
let nextPaymentDate: Date;

if (isCardLinkOperation) {
nextPaymentDate = new Date(lastChargeDate);
} else {
nextPaymentDate = new Date(now);
}

if (workspace.isDebug) {
nextPaymentDate.setDate(nextPaymentDate.getDate() + 1);
} else {
nextPaymentDate.setMonth(nextPaymentDate.getMonth() + 1);
}

let checksum;

try {
const checksumData = isCardLinkOperation ? {
isCardLinkOperation: true,
workspaceId: workspace._id.toString(),
userId: userId,
nextPaymentDate: nextPaymentDate.toISOString(),
} : {
workspaceId: workspace._id.toString(),
userId: userId,
tariffPlanId: tariffPlan._id.toString(),
shouldSaveCard: shouldSaveCard === 'true',
nextPaymentDate: nextPaymentDate.toISOString(),
};

checksum = await checksumService.generateChecksum(checksumData);
Expand All @@ -170,10 +195,11 @@
isCardLinkOperation,
currency: 'RUB',
checksum,
nextPaymentDate: nextPaymentDate.toISOString(),
});
}

/**

Check warning on line 202 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 @@ -268,7 +294,7 @@
status: BusinessOperationStatus.Pending,
payload: {
workspaceId: workspace._id,
amount: +body.Amount * PENNY_MULTIPLIER,
amount: +body.Amount,
currency: body.Currency,
userId: member._id,
tariffPlanId: plan._id,
Expand All @@ -290,7 +316,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 319 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 @@ -357,7 +383,6 @@

try {
await businessOperation.setStatus(BusinessOperationStatus.Confirmed);
await workspace.resetBillingPeriod();
await workspace.changePlan(tariffPlan._id);

const subscriptionId = body.SubscriptionId;
Expand Down Expand Up @@ -427,7 +452,7 @@

try {
await publish('cron-tasks', 'cron-tasks/limiter', JSON.stringify({
type: 'check-single-workspace',
type: 'unblock-workspace',
workspaceId: data.workspaceId,
}));
} catch (e) {
Expand Down Expand Up @@ -479,8 +504,6 @@
* Refund the money that were charged to link a card
*/
if (data.isCardLinkOperation) {
this.handleSendingToTelegramError(telegram.sendMessage(`✅ [Billing / Pay] Recurrent payments activated for «${workspace.name}». 1 RUB charged`, TelegramBotURLs.Money));

await cloudPaymentsApi.cancelPayment(body.TransactionId);

const member = await this.getMember(data.userId, workspace);
Expand All @@ -503,7 +526,13 @@
dtCreated: new Date(),
});

this.handleSendingToTelegramError(telegram.sendMessage(`✅ [Billing / Pay] Recurrent payments activated for «${workspace.name}». 1 RUB returned`, TelegramBotURLs.Money));
this.handleSendingToTelegramError(telegram.sendMessage(`✅ [Billing / Pay] Card linked

workspace id: ${workspace._id}
date of operation: ${body.DateTime}
first payment date: ${data.cloudPayments?.recurrent.startDate}
sum: ${data.cloudPayments?.recurrent.amount}${body.Currency}`
, TelegramBotURLs.Money));
} else {
/**
* Russia code from ISO 3166-1
Expand All @@ -517,7 +546,14 @@

await this.sendReceipt(workspace, tariffPlan, userEmail);

this.handleSendingToTelegramError(telegram.sendMessage(`✅ [Billing / Pay] Payment passed successfully for «${workspace.name}»`, TelegramBotURLs.Money));
this.handleSendingToTelegramError(telegram.sendMessage(`✅ [Billing / Pay] New payment

amount: ${+body.Amount} ${body.Currency}
next payment date: ${data.cloudPayments?.recurrent.startDate}
workspace id: ${workspace._id}
date of operation: ${body.DateTime}
subscription id: ${body.SubscriptionId}`
, TelegramBotURLs.Money));
}
} catch (e) {
const error = e as Error;
Expand Down Expand Up @@ -607,9 +643,9 @@
return;
}

this.handleSendingToTelegramError(telegram.sendMessage(` [Billing / Fail] Transaction failed for «${workspace.name}»`, TelegramBotURLs.Money));
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 648 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 All @@ -629,7 +665,13 @@

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

this.handleSendingToTelegramError(telegram.sendMessage(`[Billing / Recurrent] New recurrent event with ${body.Status} status`, TelegramBotURLs.Money));
this.handleSendingToTelegramError(telegram.sendMessage(`✅ [Billing / Recurrent] New recurrent transaction

amount: ${+body.Amount} ${body.Currency}
next payment date: ${body.NextTransactionDate}
workspace id: ${body.AccountId}
subscription id: ${body.Id}`
, TelegramBotURLs.Money));
HawkCatcher.send(new Error(`[Billing / Recurrent] New recurrent event with ${body.Status} status`), req.body);

switch (body.Status) {
Expand Down Expand Up @@ -772,7 +814,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 817 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 @@ -837,7 +879,7 @@
promise.catch(e => console.error('Error while sending message to Telegram: ' + e));
}

/**

Check warning on line 882 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
1 change: 0 additions & 1 deletion src/models/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,6 @@ export default class WorkspaceModel extends AbstractModel<WorkspaceDBScheme> imp
$set: {
tariffPlanId: this.tariffPlanId,
billingPeriodEventsCount: 0,
isBlocked: false,
lastChargeDate: new Date(),
},
}
Expand Down
10 changes: 10 additions & 0 deletions src/utils/checksumService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ interface PlanPurchaseChecksumData {
* If true, we will save user card
*/
shouldSaveCard: boolean;
/**
* Next payment date
*/
nextPaymentDate: string;
}

interface CardLinkChecksumData {
Expand All @@ -35,6 +39,10 @@ interface CardLinkChecksumData {
* True if this is card linking operation – charging minimal amount of money to validate card info
*/
isCardLinkOperation: boolean;
/**
* Next payment date
*/
nextPaymentDate: string;
}

/**
Expand Down Expand Up @@ -67,13 +75,15 @@ class ChecksumService {
workspaceId: payload.workspaceId,
userId: payload.userId,
isCardLinkOperation: payload.isCardLinkOperation,
nextPaymentDate: payload.nextPaymentDate,
};
} else {
return {
workspaceId: payload.workspaceId,
userId: payload.userId,
tariffPlanId: payload.tariffPlanId,
shouldSaveCard: payload.shouldSaveCard,
nextPaymentDate: payload.nextPaymentDate,
};
}
}
Expand Down
7 changes: 7 additions & 0 deletions test/integration/cases/billing/check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ describe('Check webhook', () => {
userId: admin._id.toString(),
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
};
Expand All @@ -172,6 +173,7 @@ describe('Check webhook', () => {
userId: externalUser._id.toString(),
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
};
Expand All @@ -193,6 +195,7 @@ describe('Check webhook', () => {
userId: member._id.toString(),
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
};
Expand All @@ -214,6 +217,7 @@ describe('Check webhook', () => {
userId: admin._id.toString(),
tariffPlanId: '5fe383b0126d28007780641b',
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
};
Expand All @@ -236,6 +240,7 @@ describe('Check webhook', () => {
userId: admin._id.toString(),
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
};
Expand All @@ -257,6 +262,7 @@ describe('Check webhook', () => {
userId: admin._id.toString(),
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
};
Expand All @@ -283,6 +289,7 @@ describe('Check webhook', () => {
userId: admin._id.toString(),
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
cloudPayments: {
recurrent: {
Expand Down
4 changes: 3 additions & 1 deletion test/integration/cases/billing/fail.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ const tariffPlan: PlanDBScheme = {
name: 'Test plan',
};

const planProlongationPayload: PlanProlongationPayload = {
const planProlongationPayload = {
userId: user._id.toString(),
workspaceId: workspace._id.toString(),
tariffPlanId: tariffPlan._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
};

const validRequest: FailRequest = {
Expand Down Expand Up @@ -239,6 +240,7 @@ describe('Fail webhook', () => {
workspaceId: workspace._id.toString(),
tariffPlanId: tariffPlan._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
});
Expand Down
8 changes: 8 additions & 0 deletions test/integration/cases/billing/pay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ describe('Pay webhook', () => {
checksum: await checksumService.generateChecksum({
...paymentSuccessPayload,
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
};
Expand Down Expand Up @@ -457,6 +458,7 @@ describe('Pay webhook', () => {
checksum: await checksumService.generateChecksum({
...paymentSuccessPayload,
shouldSaveCard: true,
nextPaymentDate: new Date().toString(),
}),
}),
...cardDetails,
Expand Down Expand Up @@ -502,6 +504,7 @@ describe('Pay webhook', () => {
workspaceId: '',
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
});
Expand All @@ -523,6 +526,7 @@ describe('Pay webhook', () => {
workspaceId: workspace._id.toString(),
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
});
Expand All @@ -544,6 +548,7 @@ describe('Pay webhook', () => {
workspaceId: workspace._id.toString(),
tariffPlanId: '',
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
});
Expand All @@ -565,6 +570,7 @@ describe('Pay webhook', () => {
workspaceId: new ObjectId().toString(),
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
});
Expand All @@ -586,6 +592,7 @@ describe('Pay webhook', () => {
workspaceId: workspace._id.toString(),
tariffPlanId: new ObjectId().toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
});
Expand All @@ -607,6 +614,7 @@ describe('Pay webhook', () => {
workspaceId: workspace._id.toString(),
tariffPlanId: planToChange._id.toString(),
shouldSaveCard: false,
nextPaymentDate: new Date().toString(),
}),
}),
});
Expand Down
Loading