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
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.1.29",
"version": "1.1.30",
"main": "index.ts",
"license": "UNLICENSED",
"scripts": {
Expand All @@ -21,6 +21,8 @@
"devDependencies": {
"@shelf/jest-mongodb": "^1.2.2",
"@types/jest": "^26.0.8",
"@types/lodash.clonedeep": "^4.5.9",
"@types/lodash.mergewith": "^4.6.9",
"eslint": "^6.7.2",
"eslint-config-codex": "1.2.4",
"eslint-plugin-import": "^2.19.1",
Expand All @@ -37,7 +39,8 @@
"@graphql-tools/schema": "^8.5.1",
"@graphql-tools/utils": "^8.9.0",
"@hawk.so/nodejs": "^3.1.1",
"@hawk.so/types": "^0.1.31",
"@hawk.so/types": "^0.1.33",
"@n1ru4l/json-patch-plus": "^0.2.0",
"@types/amqp-connection-manager": "^2.0.4",
"@types/bson": "^4.0.5",
"@types/debug": "^4.1.5",
Expand Down Expand Up @@ -70,6 +73,8 @@
"graphql-upload": "^13",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.15",
"lodash.clonedeep": "^4.5.0",
"lodash.mergewith": "^4.6.2",
"migrate-mongo": "^7.0.1",
"mime-types": "^2.1.25",
"mongodb": "^3.7.3",
Expand Down
121 changes: 0 additions & 121 deletions src/billing/cloudpayments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,9 @@
import cloudPaymentsApi from '../utils/cloudPaymentsApi';
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;
};

/**
* Class for describing the logic of payment routes
*/
Expand Down Expand Up @@ -86,7 +80,6 @@
public getRouter(): express.Router {
const router = express.Router();

router.get('/compose-payment', this.composePayment.bind(this));
router.all('/check', this.check.bind(this));
router.all('/pay', this.pay.bind(this));
router.all('/fail', this.fail.bind(this));
Expand All @@ -95,121 +88,7 @@
return router;
}

/**
* Prepares payment data before charge
*
* @param req — Express request object
* @param res - Express response object
*/
private async composePayment(req: ComposePaymentRequest, res: express.Response): Promise<void> {
const { workspaceId, tariffPlanId, shouldSaveCard } = req.query;
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
Details:
workspaceId: ${workspaceId}
tariffPlanId: ${tariffPlanId}
userId: ${userId}`
, req.query);

return;
}

let workspace;
let tariffPlan;

try {
workspace = await this.getWorkspace(req, workspaceId);
tariffPlan = await this.getPlan(req, tariffPlanId);
} catch (e) {
const error = e as Error;

this.sendError(res, 1, `[Billing / Compose payment] Can't get data from Database ${error.toString()}`, req.query);

return;
}

try {
await this.getMember(userId, workspace);
} catch (e) {
const error = e as Error;

this.sendError(res, 1, `[Billing / Compose payment] Can't compose payment due to error: ${error.toString()}`, req.query);

return;
}
const invoiceId = this.generateInvoiceId(tariffPlan, workspace);

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);
} catch (e) {
const error = e as Error;

this.sendError(res, 1, `[Billing / Compose payment] Can't generate checksum: ${error.toString()}`, req.query);

return;
}

this.handleSendingToTelegramError(telegram.sendMessage(`✅ [Billing / Compose payment]

card link operation: ${isCardLinkOperation}
amount: ${+tariffPlan.monthlyCharge} RUB
last charge date: ${workspace.lastChargeDate?.toISOString()}
next payment date: ${nextPaymentDate.toISOString()}
workspace id: ${workspace._id.toString()}
debug: ${Boolean(workspace.isDebug)}`
, TelegramBotURLs.Money));

res.send({
invoiceId,
plan: {
id: tariffPlan._id.toString(),
name: tariffPlan.name,
monthlyCharge: tariffPlan.monthlyCharge,
},
isCardLinkOperation,
currency: 'RUB',
checksum,
nextPaymentDate: nextPaymentDate.toISOString(),
});
}

/**

Check warning on line 91 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 @@ -326,7 +205,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 208 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 @@ -668,7 +547,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 550 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 @@ -850,7 +729,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 732 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 @@ -915,7 +794,7 @@
promise.catch(e => console.error('Error while sending message to Telegram: ' + e));
}

/**

Check warning on line 797 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
18 changes: 0 additions & 18 deletions src/billing/types/composePaymentPayload.ts

This file was deleted.

108 changes: 108 additions & 0 deletions src/resolvers/billingNew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,26 @@ import {
import checksumService from '../utils/checksumService';
import { UserInputError } from 'apollo-server-express';
import cloudPaymentsApi, { CloudPaymentsJsonData } from '../utils/cloudPaymentsApi';
import * as telegram from '../utils/telegram';
import { TelegramBotURLs } from '../utils/telegram';

/**
* The amount we will debit to confirm the subscription.
* After confirmation, we will refund the user money.
*/
const AMOUNT_FOR_CARD_VALIDATION = 1;

/**
* Input data for composePayment query
*/
interface ComposePaymentArgs {
input: {
workspaceId: string;
tariffPlanId: string;
shouldSaveCard?: boolean;
};
}

/**
* Data for processing payment with saved card
*/
Expand Down Expand Up @@ -58,6 +71,101 @@ export default {
): Promise<BusinessOperationModel[]> {
return factories.businessOperationsFactory.getWorkspacesBusinessOperations(ids);
},

/**
* GraphQL version of composePayment: prepares data before charge
*/
async composePayment(
_obj: undefined,
{ input }: ComposePaymentArgs,
{ user, factories }: ResolverContextWithUser
): Promise<{
invoiceId: string;
plan: { id: string; name: string; monthlyCharge: number };
isCardLinkOperation: boolean;
currency: string;
checksum: string;
nextPaymentDate: Date;
}> {
const { workspaceId, tariffPlanId, shouldSaveCard } = input;

if (!workspaceId || !tariffPlanId || !user?.id) {
throw new UserInputError('No workspaceId, tariffPlanId or user id provided');
}

const workspace = await factories.workspacesFactory.findById(workspaceId);
const plan = await factories.plansFactory.findById(tariffPlanId);

if (!workspace || !plan) {
throw new UserInputError("Can't get workspace or plan by provided ids");
}

const member = await workspace.getMemberInfo(user.id);

if (!member) {
throw new UserInputError('User is not a member of the workspace');
}

const now = new Date();
const invoiceId = `${workspace.name} ${now.getDate()}/${now.getMonth() + 1} ${plan.name}`;

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

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

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

const checksumData = isCardLinkOperation
? {
isCardLinkOperation: true as const,
workspaceId: workspace._id.toString(),
userId: user.id,
nextPaymentDate: nextPaymentDate.toISOString(),
}
: {
workspaceId: workspace._id.toString(),
userId: user.id,
tariffPlanId: plan._id.toString(),
shouldSaveCard: Boolean(shouldSaveCard),
nextPaymentDate: nextPaymentDate.toISOString(),
};

const checksum = await checksumService.generateChecksum(checksumData);

/**
* Send info to Telegram (non-blocking)
*/
telegram
.sendMessage(`✅ [Billing / Compose payment]

card link operation: ${isCardLinkOperation}
amount: ${+plan.monthlyCharge} RUB
last charge date: ${workspace.lastChargeDate?.toISOString()}
next payment date: ${nextPaymentDate.toISOString()}
workspace id: ${workspace._id.toString()}
debug: ${Boolean(workspace.isDebug)}`
, TelegramBotURLs.Money)
.catch(e => console.error('Error while sending message to Telegram: ' + e));

return {
invoiceId,
plan: {
id: plan._id.toString(),
name: plan.name,
monthlyCharge: plan.monthlyCharge,
},
isCardLinkOperation,
currency: 'RUB',
checksum,
nextPaymentDate,
};
},
},
/**
* Resolver for Union Payload type.
Expand Down
Loading
Loading