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
61 changes: 52 additions & 9 deletions src/billing/cloudpayments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
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,6 +44,8 @@ import PlanModel from '../models/plan';
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 };
context: import('../types/graphql').ResolverContextBase;
Expand Down Expand Up @@ -105,7 +106,12 @@ export default class CloudPaymentsWebhooks {
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 @@ export default class CloudPaymentsWebhooks {

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,6 +195,7 @@ export default class CloudPaymentsWebhooks {
isCardLinkOperation,
currency: 'RUB',
checksum,
nextPaymentDate: nextPaymentDate.toISOString(),
});
}

Expand Down Expand Up @@ -268,7 +294,7 @@ export default class CloudPaymentsWebhooks {
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 Down Expand Up @@ -479,8 +505,6 @@ export default class CloudPaymentsWebhooks {
* 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 +527,13 @@ export default class CloudPaymentsWebhooks {
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 +547,14 @@ export default class CloudPaymentsWebhooks {

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,7 +644,7 @@ export default class CloudPaymentsWebhooks {
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);

Expand All @@ -629,7 +666,13 @@ export default class CloudPaymentsWebhooks {

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
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