Skip to content

Commit 7d0819f

Browse files
Merge pull request #516 from codex-team/master
Update prod
2 parents 81dcb5f + 05b1b97 commit 7d0819f

File tree

8 files changed

+730
-145
lines changed

8 files changed

+730
-145
lines changed

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hawk.api",
3-
"version": "1.1.29",
3+
"version": "1.1.30",
44
"main": "index.ts",
55
"license": "UNLICENSED",
66
"scripts": {
@@ -21,6 +21,8 @@
2121
"devDependencies": {
2222
"@shelf/jest-mongodb": "^1.2.2",
2323
"@types/jest": "^26.0.8",
24+
"@types/lodash.clonedeep": "^4.5.9",
25+
"@types/lodash.mergewith": "^4.6.9",
2426
"eslint": "^6.7.2",
2527
"eslint-config-codex": "1.2.4",
2628
"eslint-plugin-import": "^2.19.1",
@@ -37,7 +39,8 @@
3739
"@graphql-tools/schema": "^8.5.1",
3840
"@graphql-tools/utils": "^8.9.0",
3941
"@hawk.so/nodejs": "^3.1.1",
40-
"@hawk.so/types": "^0.1.31",
42+
"@hawk.so/types": "^0.1.33",
43+
"@n1ru4l/json-patch-plus": "^0.2.0",
4144
"@types/amqp-connection-manager": "^2.0.4",
4245
"@types/bson": "^4.0.5",
4346
"@types/debug": "^4.1.5",
@@ -70,6 +73,8 @@
7073
"graphql-upload": "^13",
7174
"jsonwebtoken": "^8.5.1",
7275
"lodash": "^4.17.15",
76+
"lodash.clonedeep": "^4.5.0",
77+
"lodash.mergewith": "^4.6.2",
7378
"migrate-mongo": "^7.0.1",
7479
"mime-types": "^2.1.25",
7580
"mongodb": "^3.7.3",

src/billing/cloudpayments.ts

Lines changed: 0 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,9 @@ import { PaymentData } from './types/paymentData';
4242
import cloudPaymentsApi from '../utils/cloudPaymentsApi';
4343
import PlanModel from '../models/plan';
4444
import { ClientApi, ClientService, CustomerReceiptItem, ReceiptApi, ReceiptTypes, TaxationSystem } from 'cloudpayments';
45-
import { ComposePaymentPayload } from './types/composePaymentPayload';
4645

4746
const PENNY_MULTIPLIER = 100;
4847

49-
interface ComposePaymentRequest extends express.Request {
50-
query: ComposePaymentPayload & { [key: string]: any };
51-
context: import('../types/graphql').ResolverContextBase;
52-
};
53-
5448
/**
5549
* Class for describing the logic of payment routes
5650
*/
@@ -86,7 +80,6 @@ export default class CloudPaymentsWebhooks {
8680
public getRouter(): express.Router {
8781
const router = express.Router();
8882

89-
router.get('/compose-payment', this.composePayment.bind(this));
9083
router.all('/check', this.check.bind(this));
9184
router.all('/pay', this.pay.bind(this));
9285
router.all('/fail', this.fail.bind(this));
@@ -95,120 +88,6 @@ export default class CloudPaymentsWebhooks {
9588
return router;
9689
}
9790

98-
/**
99-
* Prepares payment data before charge
100-
*
101-
* @param req — Express request object
102-
* @param res - Express response object
103-
*/
104-
private async composePayment(req: ComposePaymentRequest, res: express.Response): Promise<void> {
105-
const { workspaceId, tariffPlanId, shouldSaveCard } = req.query;
106-
const userId = req.context.user.id;
107-
108-
if (!workspaceId || !tariffPlanId || !userId) {
109-
this.sendError(res, 1, `[Billing / Compose payment] No workspace, tariff plan or user id in request body
110-
Details:
111-
workspaceId: ${workspaceId}
112-
tariffPlanId: ${tariffPlanId}
113-
userId: ${userId}`
114-
, req.query);
115-
116-
return;
117-
}
118-
119-
let workspace;
120-
let tariffPlan;
121-
122-
try {
123-
workspace = await this.getWorkspace(req, workspaceId);
124-
tariffPlan = await this.getPlan(req, tariffPlanId);
125-
} catch (e) {
126-
const error = e as Error;
127-
128-
this.sendError(res, 1, `[Billing / Compose payment] Can't get data from Database ${error.toString()}`, req.query);
129-
130-
return;
131-
}
132-
133-
try {
134-
await this.getMember(userId, workspace);
135-
} catch (e) {
136-
const error = e as Error;
137-
138-
this.sendError(res, 1, `[Billing / Compose payment] Can't compose payment due to error: ${error.toString()}`, req.query);
139-
140-
return;
141-
}
142-
const invoiceId = this.generateInvoiceId(tariffPlan, workspace);
143-
144-
const isCardLinkOperation = workspace.tariffPlanId.toString() === tariffPlanId && !workspace.isTariffPlanExpired();
145-
146-
// Calculate next payment date
147-
const lastChargeDate = new Date(workspace.lastChargeDate);
148-
const now = new Date();
149-
let nextPaymentDate: Date;
150-
151-
if (isCardLinkOperation) {
152-
nextPaymentDate = new Date(lastChargeDate);
153-
} else {
154-
nextPaymentDate = new Date(now);
155-
}
156-
157-
if (workspace.isDebug) {
158-
nextPaymentDate.setDate(nextPaymentDate.getDate() + 1);
159-
} else {
160-
nextPaymentDate.setMonth(nextPaymentDate.getMonth() + 1);
161-
}
162-
163-
let checksum;
164-
165-
try {
166-
const checksumData = isCardLinkOperation ? {
167-
isCardLinkOperation: true,
168-
workspaceId: workspace._id.toString(),
169-
userId: userId,
170-
nextPaymentDate: nextPaymentDate.toISOString(),
171-
} : {
172-
workspaceId: workspace._id.toString(),
173-
userId: userId,
174-
tariffPlanId: tariffPlan._id.toString(),
175-
shouldSaveCard: shouldSaveCard === 'true',
176-
nextPaymentDate: nextPaymentDate.toISOString(),
177-
};
178-
179-
checksum = await checksumService.generateChecksum(checksumData);
180-
} catch (e) {
181-
const error = e as Error;
182-
183-
this.sendError(res, 1, `[Billing / Compose payment] Can't generate checksum: ${error.toString()}`, req.query);
184-
185-
return;
186-
}
187-
188-
this.handleSendingToTelegramError(telegram.sendMessage(`✅ [Billing / Compose payment]
189-
190-
card link operation: ${isCardLinkOperation}
191-
amount: ${+tariffPlan.monthlyCharge} RUB
192-
last charge date: ${workspace.lastChargeDate?.toISOString()}
193-
next payment date: ${nextPaymentDate.toISOString()}
194-
workspace id: ${workspace._id.toString()}
195-
debug: ${Boolean(workspace.isDebug)}`
196-
, TelegramBotURLs.Money));
197-
198-
res.send({
199-
invoiceId,
200-
plan: {
201-
id: tariffPlan._id.toString(),
202-
name: tariffPlan.name,
203-
monthlyCharge: tariffPlan.monthlyCharge,
204-
},
205-
isCardLinkOperation,
206-
currency: 'RUB',
207-
checksum,
208-
nextPaymentDate: nextPaymentDate.toISOString(),
209-
});
210-
}
211-
21291
/**
21392
* Generates invoice id for payment
21493
*

src/billing/types/composePaymentPayload.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/resolvers/billingNew.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@ import {
1010
import checksumService from '../utils/checksumService';
1111
import { UserInputError } from 'apollo-server-express';
1212
import cloudPaymentsApi, { CloudPaymentsJsonData } from '../utils/cloudPaymentsApi';
13+
import * as telegram from '../utils/telegram';
14+
import { TelegramBotURLs } from '../utils/telegram';
1315

1416
/**
1517
* The amount we will debit to confirm the subscription.
1618
* After confirmation, we will refund the user money.
1719
*/
1820
const AMOUNT_FOR_CARD_VALIDATION = 1;
1921

22+
/**
23+
* Input data for composePayment query
24+
*/
25+
interface ComposePaymentArgs {
26+
input: {
27+
workspaceId: string;
28+
tariffPlanId: string;
29+
shouldSaveCard?: boolean;
30+
};
31+
}
32+
2033
/**
2134
* Data for processing payment with saved card
2235
*/
@@ -58,6 +71,101 @@ export default {
5871
): Promise<BusinessOperationModel[]> {
5972
return factories.businessOperationsFactory.getWorkspacesBusinessOperations(ids);
6073
},
74+
75+
/**
76+
* GraphQL version of composePayment: prepares data before charge
77+
*/
78+
async composePayment(
79+
_obj: undefined,
80+
{ input }: ComposePaymentArgs,
81+
{ user, factories }: ResolverContextWithUser
82+
): Promise<{
83+
invoiceId: string;
84+
plan: { id: string; name: string; monthlyCharge: number };
85+
isCardLinkOperation: boolean;
86+
currency: string;
87+
checksum: string;
88+
nextPaymentDate: Date;
89+
}> {
90+
const { workspaceId, tariffPlanId, shouldSaveCard } = input;
91+
92+
if (!workspaceId || !tariffPlanId || !user?.id) {
93+
throw new UserInputError('No workspaceId, tariffPlanId or user id provided');
94+
}
95+
96+
const workspace = await factories.workspacesFactory.findById(workspaceId);
97+
const plan = await factories.plansFactory.findById(tariffPlanId);
98+
99+
if (!workspace || !plan) {
100+
throw new UserInputError("Can't get workspace or plan by provided ids");
101+
}
102+
103+
const member = await workspace.getMemberInfo(user.id);
104+
105+
if (!member) {
106+
throw new UserInputError('User is not a member of the workspace');
107+
}
108+
109+
const now = new Date();
110+
const invoiceId = `${workspace.name} ${now.getDate()}/${now.getMonth() + 1} ${plan.name}`;
111+
112+
const isCardLinkOperation = workspace.tariffPlanId.toString() === tariffPlanId && !workspace.isTariffPlanExpired();
113+
114+
// Calculate next payment date
115+
const lastChargeDate = workspace.lastChargeDate ? new Date(workspace.lastChargeDate) : now;
116+
const nextPaymentDate = isCardLinkOperation ? new Date(lastChargeDate) : new Date(now);
117+
118+
if (workspace.isDebug) {
119+
nextPaymentDate.setDate(nextPaymentDate.getDate() + 1);
120+
} else {
121+
nextPaymentDate.setMonth(nextPaymentDate.getMonth() + 1);
122+
}
123+
124+
const checksumData = isCardLinkOperation
125+
? {
126+
isCardLinkOperation: true as const,
127+
workspaceId: workspace._id.toString(),
128+
userId: user.id,
129+
nextPaymentDate: nextPaymentDate.toISOString(),
130+
}
131+
: {
132+
workspaceId: workspace._id.toString(),
133+
userId: user.id,
134+
tariffPlanId: plan._id.toString(),
135+
shouldSaveCard: Boolean(shouldSaveCard),
136+
nextPaymentDate: nextPaymentDate.toISOString(),
137+
};
138+
139+
const checksum = await checksumService.generateChecksum(checksumData);
140+
141+
/**
142+
* Send info to Telegram (non-blocking)
143+
*/
144+
telegram
145+
.sendMessage(`✅ [Billing / Compose payment]
146+
147+
card link operation: ${isCardLinkOperation}
148+
amount: ${+plan.monthlyCharge} RUB
149+
last charge date: ${workspace.lastChargeDate?.toISOString()}
150+
next payment date: ${nextPaymentDate.toISOString()}
151+
workspace id: ${workspace._id.toString()}
152+
debug: ${Boolean(workspace.isDebug)}`
153+
, TelegramBotURLs.Money)
154+
.catch(e => console.error('Error while sending message to Telegram: ' + e));
155+
156+
return {
157+
invoiceId,
158+
plan: {
159+
id: plan._id.toString(),
160+
name: plan.name,
161+
monthlyCharge: plan.monthlyCharge,
162+
},
163+
isCardLinkOperation,
164+
currency: 'RUB',
165+
checksum,
166+
nextPaymentDate,
167+
};
168+
},
61169
},
62170
/**
63171
* Resolver for Union Payload type.

0 commit comments

Comments
 (0)