Skip to content

Commit 0f574bf

Browse files
committed
Generate "isCardLinkOperation" on backend
1 parent 68ae23f commit 0f574bf

File tree

4 files changed

+66
-24
lines changed

4 files changed

+66
-24
lines changed

src/billing/cloudpayments.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export default class CloudPaymentsWebhooks {
102102
* @param res - Express response object
103103
*/
104104
private async composePayment(req: ComposePaymentRequest, res: express.Response): Promise<void> {
105-
const { workspaceId, tariffPlanId, shouldSaveCard, isCardLinkOperation } = req.query;
105+
const { workspaceId, tariffPlanId, shouldSaveCard } = req.query;
106106
const userId = req.context.user.id;
107107

108108
if (!workspaceId || !tariffPlanId || !userId) {
@@ -129,23 +129,30 @@ export default class CloudPaymentsWebhooks {
129129
await this.getMember(userId, workspace);
130130
} catch (e) {
131131
const error = e as Error;
132-
132+
133133
this.sendError(res, 1, `[Billing / Compose payment] Can't compose payment due to error: ${error.toString()}`, req.query);
134-
134+
135135
return;
136136
}
137137
const invoiceId = this.generateInvoiceId(tariffPlan, workspace);
138+
139+
const isCardLinkOperation = workspace.tariffPlanId.toString() === tariffPlanId && !this.isPlanExpired(workspace);
138140

139141
let checksum;
140142

141143
try {
142-
checksum = await checksumService.generateChecksum({
144+
const checksumData = isCardLinkOperation ? {
145+
isCardLinkOperation: true,
146+
workspaceId: workspace._id.toString(),
147+
userId: userId,
148+
} : {
143149
workspaceId: workspace._id.toString(),
144150
userId: userId,
145151
tariffPlanId: tariffPlan._id.toString(),
146152
shouldSaveCard: shouldSaveCard === 'true',
147-
isCardLinkOperation: isCardLinkOperation === 'true',
148-
});
153+
}
154+
155+
checksum = await checksumService.generateChecksum(checksumData);
149156
} catch (e) {
150157
const error = e as Error;
151158

@@ -161,11 +168,24 @@ export default class CloudPaymentsWebhooks {
161168
name: tariffPlan.name,
162169
monthlyCharge: tariffPlan.monthlyCharge,
163170
},
171+
isCardLinkOperation,
164172
currency: 'RUB',
165173
checksum,
166174
});
167175
}
168176

177+
/**
178+
* Returns true if workspace's plan is expired
179+
* @param workspace - workspace to check
180+
*/
181+
private isPlanExpired(workspace: WorkspaceModel): boolean {
182+
const lastChargeDate = new Date(workspace.lastChargeDate);
183+
const planExpiracyDate = lastChargeDate.setMonth(lastChargeDate.getMonth() + 1);
184+
const isPlanExpired = planExpiracyDate < Date.now();
185+
186+
return isPlanExpired;
187+
}
188+
169189
/**
170190
* Generates invoice id for payment
171191
*
@@ -204,7 +224,13 @@ export default class CloudPaymentsWebhooks {
204224
let member: ConfirmedMemberDBScheme;
205225
let plan: PlanDBScheme;
206226

207-
if (!data.workspaceId || !data.tariffPlanId || !data.userId || data.isCardLinkOperation === undefined) {
227+
if (data.isCardLinkOperation && (!data.userId || !data.workspaceId)) {
228+
this.sendError(res, CheckCodes.PAYMENT_COULD_NOT_BE_ACCEPTED, '[Billing / Check] Card linking – invalid data', body);
229+
230+
return;
231+
}
232+
233+
if (!data.isCardLinkOperation && (!data.userId || !data.workspaceId || !data.tariffPlanId)) {
208234
this.sendError(res, CheckCodes.PAYMENT_COULD_NOT_BE_ACCEPTED, '[Billing / Check] There is no necessary data in the request', body);
209235

210236
return;

src/billing/types/composePaymentPayload.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,4 @@ export interface ComposePaymentPayload {
1515
* If true, we will save user card
1616
*/
1717
shouldSaveCard: 'true' | 'false';
18-
/**
19-
* True if this is card linking operation – charging minimal amount of money to validate card info
20-
*/
21-
isCardLinkOperation: 'true' | 'false';
2218
}

src/resolvers/billingNew.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ export default {
138138
*/
139139
async payWithCard(_obj: undefined, args: PayWithCardArgs, { factories, user }: ResolverContextWithUser): Promise<any> {
140140
const paymentData = checksumService.parseAndVerifyChecksum(args.input.checksum);
141+
142+
if (!('tariffPlanId' in paymentData)) {
143+
throw new UserInputError('Invalid checksum');
144+
}
145+
141146
const fullUserInfo = await factories.usersFactory.findById(user.id);
142147

143148
const workspace = await factories.workspacesFactory.findById(paymentData.workspaceId);

src/utils/checksumService.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { PlanProlongationPayload } from '@hawk.so/types';
22
import jwt, { Secret } from 'jsonwebtoken';
33

4-
interface ChecksumData {
4+
export type ChecksumData = PlanPurchaseChecksumData | CardLinkChecksumData;
5+
6+
interface PlanPurchaseChecksumData {
57
/**
68
* Workspace Identifier
79
*/
@@ -18,6 +20,17 @@ interface ChecksumData {
1820
* If true, we will save user card
1921
*/
2022
shouldSaveCard: boolean;
23+
}
24+
25+
interface CardLinkChecksumData {
26+
/**
27+
* Workspace Identifier
28+
*/
29+
workspaceId: string;
30+
/**
31+
* Id of the user making the payment
32+
*/
33+
userId: string;
2134
/**
2235
* True if this is card linking operation – charging minimal amount of money to validate card info
2336
*/
@@ -49,18 +62,20 @@ class ChecksumService {
4962
public parseAndVerifyChecksum(checksum: string): ChecksumData {
5063
const payload = jwt.verify(checksum, process.env.JWT_SECRET_BILLING_CHECKSUM as Secret) as ChecksumData;
5164

52-
/**
53-
* Filter unnecessary fields from JWT payload (e.g. "iat")
54-
*/
55-
const { tariffPlanId, workspaceId, userId, shouldSaveCard, isCardLinkOperation } = payload;
56-
57-
return {
58-
tariffPlanId,
59-
workspaceId,
60-
userId,
61-
shouldSaveCard,
62-
isCardLinkOperation
63-
};
65+
if ('isCardLinkOperation' in payload) {
66+
return {
67+
workspaceId: payload.workspaceId,
68+
userId: payload.userId,
69+
isCardLinkOperation: payload.isCardLinkOperation
70+
}
71+
} else {
72+
return {
73+
workspaceId: payload.workspaceId,
74+
userId: payload.userId,
75+
tariffPlanId: payload.tariffPlanId,
76+
shouldSaveCard: payload.shouldSaveCard
77+
}
78+
}
6479
}
6580
}
6681

0 commit comments

Comments
 (0)