Skip to content

Commit 377fa6f

Browse files
committed
feat(billing): refactor promo code handling and update payment data structure
1 parent 72a6e60 commit 377fa6f

9 files changed

Lines changed: 250 additions & 132 deletions

File tree

src/billing/cloudpayments.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -162,16 +162,21 @@ export default class CloudPaymentsWebhooks {
162162

163163
const recurrentPaymentSettings = data.cloudPayments?.recurrent;
164164

165-
if (data.promoCodeValue && !data.isCardLinkOperation) {
165+
if (data.promo && !data.isCardLinkOperation) {
166166
try {
167167
const promoCodeService = new PromoCodeService(context.factories);
168-
const promoPricing = await promoCodeService.getPricingForPlan(data.promoCodeValue, data.userId, data.workspaceId, plan);
168+
const promoPricing = await promoCodeService.getPricingForPromoCodeId(
169+
data.promo.id,
170+
data.userId,
171+
data.workspaceId,
172+
plan
173+
);
169174

170175
if (
171-
promoPricing.promoCode._id.toString() !== data.promoCodeId ||
172-
promoPricing.finalAmount !== data.finalAmount ||
173-
promoPricing.originalAmount !== data.originalAmount ||
174-
promoPricing.discountAmount !== data.discountAmount
176+
promoPricing.benefitType !== data.promo.benefitType ||
177+
promoPricing.finalAmount !== data.promo.finalAmount ||
178+
promoPricing.originalAmount !== data.promo.originalAmount ||
179+
promoPricing.discountAmount !== data.promo.discountAmount
175180
) {
176181
this.sendError(res, CheckCodes.WRONG_AMOUNT, '[Billing / Check] Promo code payment data does not match current promo calculation', body);
177182

@@ -190,8 +195,8 @@ export default class CloudPaymentsWebhooks {
190195
* The amount will be considered correct if it is equal to the cost of the tariff plan.
191196
* Also, the cost will be correct if it is a payment to activate the subscription.
192197
*/
193-
const expectedAmount = data.finalAmount ?? plan.monthlyCharge;
194-
const isRightAmount = +body.Amount === expectedAmount || (!data.finalAmount && recurrentPaymentSettings?.startDate);
198+
const expectedAmount = data.promo?.finalAmount ?? plan.monthlyCharge;
199+
const isRightAmount = +body.Amount === expectedAmount || (!data.promo?.finalAmount && recurrentPaymentSettings?.startDate);
195200

196201
if (!isRightAmount) {
197202
this.sendError(res, CheckCodes.WRONG_AMOUNT, `[Billing / Check] Amount does not equal to plan monthly charge`, body);
@@ -322,20 +327,25 @@ export default class CloudPaymentsWebhooks {
322327
await workspace.setSubscriptionId(subscriptionId);
323328
}
324329

325-
if (data.promoCodeValue && !data.isCardLinkOperation && data.benefitType) {
330+
if (data.promo && !data.isCardLinkOperation) {
326331
const promoCodeService = new PromoCodeService(req.context.factories);
327-
const promoCode = await promoCodeService.getValidPromoCode(data.promoCodeValue, data.userId, data.workspaceId);
332+
const promoPricing = await promoCodeService.getPricingForPromoCodeId(
333+
data.promo.id,
334+
data.userId,
335+
data.workspaceId,
336+
tariffPlan
337+
);
328338

329339
await promoCodeService.createUsage({
330-
promoCode,
340+
promoCode: promoPricing.promoCode,
331341
userId: data.userId,
332342
workspaceId: workspace._id,
333343
planId: tariffPlan._id,
334-
benefitType: data.benefitType,
335-
originalAmount: data.originalAmount,
336-
finalAmount: data.finalAmount,
337-
discountAmount: data.discountAmount,
338-
utm: data.promoUtm,
344+
benefitType: data.promo.benefitType,
345+
originalAmount: data.promo.originalAmount,
346+
finalAmount: data.promo.finalAmount,
347+
discountAmount: data.promo.discountAmount,
348+
utm: data.promo.utm,
339349
});
340350
}
341351
} catch (e) {
@@ -485,7 +495,7 @@ plan monthly charge: ${data.cloudPayments?.recurrent.amount} ${body.Currency}`
485495
*/
486496
const userEmail = body.IssuerBankCountry === RUSSIA_ISO_CODE ? user.email : undefined;
487497

488-
await this.sendReceipt(workspace, tariffPlan, userEmail, data.finalAmount ?? tariffPlan.monthlyCharge);
498+
await this.sendReceipt(workspace, tariffPlan, userEmail, data.promo?.finalAmount ?? tariffPlan.monthlyCharge);
489499

490500
let messageText = '';
491501

src/billing/types/paymentData.ts

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,55 +37,66 @@ interface CloudPaymentsSettings {
3737
recurrent: RecurrentPaymentSettings;
3838
}
3939

40-
export interface PaymentData {
40+
/**
41+
* Promo data attached to payment request
42+
*/
43+
export interface PaymentPromoData {
4144
/**
42-
* Data for Cloudpayments needs
45+
* Applied promo code id
4346
*/
44-
cloudPayments?: CloudPaymentsSettings;
47+
id: string;
48+
4549
/**
46-
* Workspace Identifier
50+
* Promo benefit type
4751
*/
48-
workspaceId: string;
52+
benefitType: 'percent_discount' | 'amount_discount' | 'fixed_price';
53+
4954
/**
50-
* Id of the user making the payment
55+
* Plan price before promo
5156
*/
52-
userId: string;
57+
originalAmount: number;
58+
5359
/**
54-
* Workspace current plan id or plan id to change
60+
* Final price after promo
5561
*/
56-
tariffPlanId: string;
62+
finalAmount: number;
63+
5764
/**
58-
* If true, we will save user card
65+
* Actual discount amount
5966
*/
60-
shouldSaveCard: boolean;
67+
discountAmount: number;
68+
6169
/**
62-
* Applied promo code id
70+
* UTM parameters captured when promo was applied
6371
*/
64-
promoCodeId?: string;
72+
utm?: Utm;
73+
}
74+
75+
export interface PaymentData {
6576
/**
66-
* Applied promo code value
77+
* Data for Cloudpayments needs
6778
*/
68-
promoCodeValue?: string;
79+
cloudPayments?: CloudPaymentsSettings;
6980
/**
70-
* Promo benefit type
81+
* Workspace Identifier
7182
*/
72-
benefitType?: 'grant_plan' | 'percent_discount' | 'amount_discount' | 'fixed_price';
83+
workspaceId: string;
7384
/**
74-
* Plan price before promo
85+
* Id of the user making the payment
7586
*/
76-
originalAmount?: number;
87+
userId: string;
7788
/**
78-
* Final price after promo
89+
* Workspace current plan id or plan id to change
7990
*/
80-
finalAmount?: number;
91+
tariffPlanId: string;
8192
/**
82-
* Actual discount amount
93+
* If true, we will save user card
8394
*/
84-
discountAmount?: number;
95+
shouldSaveCard: boolean;
8596
/**
86-
* UTM parameters captured when promo was applied
97+
* Applied promo code data
8798
*/
88-
promoUtm?: Utm;
99+
promo?: PaymentPromoData;
89100
/**
90101
* True if this is card linking operation – charging minimal amount of money to validate card info
91102
*/

src/resolvers/billingNew.ts

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { UserInputError } from 'apollo-server-express';
1212
import cloudPaymentsApi, { CloudPaymentsJsonData } from '../utils/cloudPaymentsApi';
1313
import * as telegram from '../utils/telegram';
1414
import { TelegramBotURLs } from '../utils/telegram';
15-
import PromoCodeService, { PromoCodeError, PromoCodeErrorCode, PromoCodePreviewResult } from '../utils/promoCodeService';
15+
import PromoCodeService, { PromoCodeError, PromoCodeErrorCode, PromoCodePreviewResult, buildPaymentPromoData } from '../utils/promoCodeService';
1616
import { publish } from '../rabbitmq';
1717
import type { Utm } from '@hawk.so/types';
1818
import { validateUtmParams } from '../utils/utm/utm';
@@ -117,10 +117,13 @@ export default {
117117
checksum: string;
118118
nextPaymentDate: Date;
119119
cloudPaymentsPublicId: string;
120-
promoCode?: string;
121-
originalAmount?: number;
122-
finalAmount?: number;
123-
discountAmount?: number;
120+
promo?: {
121+
id: string;
122+
benefitType: 'percent_discount' | 'amount_discount' | 'fixed_price';
123+
originalAmount: number;
124+
finalAmount: number;
125+
discountAmount: number;
126+
};
124127
}> {
125128
const { workspaceId, tariffPlanId, shouldSaveCard, promoCode } = input;
126129
const promoUtm = validateUtmParams(input.promoUtm);
@@ -162,23 +165,15 @@ export default {
162165
}
163166

164167
let paymentAmount = plan.monthlyCharge;
165-
let promoPaymentData;
168+
let paymentPromo;
166169

167170
if (promoCode && !isCardLinkOperation) {
168171
try {
169172
const promoCodeService = new PromoCodeService(factories);
170173
const pricing = await promoCodeService.getPricingForPlan(promoCode, user.id, workspace._id.toString(), plan);
171174

172175
paymentAmount = pricing.finalAmount;
173-
promoPaymentData = {
174-
promoCodeId: pricing.promoCode._id.toString(),
175-
promoCodeValue: pricing.promoCode.value,
176-
benefitType: pricing.benefitType,
177-
originalAmount: pricing.originalAmount,
178-
finalAmount: pricing.finalAmount,
179-
discountAmount: pricing.discountAmount,
180-
...(promoUtm && Object.keys(promoUtm).length > 0 ? { promoUtm } : {}),
181-
};
176+
paymentPromo = buildPaymentPromoData(pricing, promoUtm);
182177
} catch (error) {
183178
throwPromoCodeGraphQLError(error);
184179
}
@@ -207,7 +202,7 @@ export default {
207202
tariffPlanId: plan._id.toString(),
208203
shouldSaveCard: Boolean(shouldSaveCard),
209204
nextPaymentDate: nextPaymentDate.toISOString(),
210-
...promoPaymentData,
205+
...(paymentPromo ? { promo: paymentPromo } : {}),
211206
};
212207

213208
const checksum = await checksumService.generateChecksum(checksumData);
@@ -239,10 +234,7 @@ debug: ${Boolean(workspace.isDebug)}`
239234
checksum,
240235
nextPaymentDate,
241236
cloudPaymentsPublicId: process.env.CLOUDPAYMENTS_PUBLIC_ID || '',
242-
promoCode: promoPaymentData?.promoCodeValue,
243-
originalAmount: promoPaymentData?.originalAmount,
244-
finalAmount: promoPaymentData?.finalAmount,
245-
discountAmount: promoPaymentData?.discountAmount,
237+
promo: paymentPromo,
246238
};
247239
},
248240
},
@@ -394,7 +386,7 @@ debug: ${Boolean(workspace.isDebug)}`
394386
throw new UserInputError('Wrong checksum data');
395387
}
396388

397-
const planPaymentAmount = paymentData.finalAmount ?? plan.monthlyCharge;
389+
const planPaymentAmount = paymentData.promo?.finalAmount ?? plan.monthlyCharge;
398390

399391
const token = fullUserInfo.bankCards?.find(card => card.id === args.input.cardId)?.token;
400392

src/typeDefs/billing.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,36 @@ type PreviewPromoCodeResponse {
342342
plans: [PromoCodePlanPrice!]!
343343
}
344344
345+
"""
346+
Promo data returned with composePayment
347+
"""
348+
type ComposePaymentPromo {
349+
"""
350+
Applied promo code id
351+
"""
352+
id: ID!
353+
354+
"""
355+
Promo benefit type
356+
"""
357+
benefitType: PromoCodeBenefitType!
358+
359+
"""
360+
Plan price before promo
361+
"""
362+
originalAmount: Int!
363+
364+
"""
365+
Plan price after promo
366+
"""
367+
finalAmount: Int!
368+
369+
"""
370+
Actual discount amount in money
371+
"""
372+
discountAmount: Int!
373+
}
374+
345375
"""
346376
Response of composePayment query
347377
"""
@@ -382,24 +412,9 @@ type ComposePaymentResponse {
382412
cloudPaymentsPublicId: String!
383413
384414
"""
385-
Applied promo code value
386-
"""
387-
promoCode: String
388-
389-
"""
390-
Plan price before promo
391-
"""
392-
originalAmount: Int
393-
394-
"""
395-
Plan price after promo
396-
"""
397-
finalAmount: Int
398-
399-
"""
400-
Actual discount amount in money
415+
Applied promo code data
401416
"""
402-
discountAmount: Int
417+
promo: ComposePaymentPromo
403418
}
404419
405420

src/utils/checksumService.ts

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import jwt, { Secret } from 'jsonwebtoken';
2-
import type { Utm } from '@hawk.so/types';
2+
import type { PaymentPromoData } from '../billing/types/paymentData';
33

44
export type ChecksumData = PlanPurchaseChecksumData | CardLinkChecksumData;
55

@@ -25,33 +25,9 @@ interface PlanPurchaseChecksumData {
2525
*/
2626
nextPaymentDate: string;
2727
/**
28-
* Applied promo code id
28+
* Applied promo code data
2929
*/
30-
promoCodeId?: string;
31-
/**
32-
* Applied promo code value
33-
*/
34-
promoCodeValue?: string;
35-
/**
36-
* Promo benefit type
37-
*/
38-
benefitType?: 'grant_plan' | 'percent_discount' | 'amount_discount' | 'fixed_price';
39-
/**
40-
* Plan price before promo
41-
*/
42-
originalAmount?: number;
43-
/**
44-
* Final price after promo
45-
*/
46-
finalAmount?: number;
47-
/**
48-
* Actual discount amount
49-
*/
50-
discountAmount?: number;
51-
/**
52-
* UTM parameters captured when promo was applied
53-
*/
54-
promoUtm?: Utm;
30+
promo?: PaymentPromoData;
5531
}
5632

5733
interface CardLinkChecksumData {
@@ -112,13 +88,7 @@ class ChecksumService {
11288
tariffPlanId: payload.tariffPlanId,
11389
shouldSaveCard: payload.shouldSaveCard,
11490
nextPaymentDate: payload.nextPaymentDate,
115-
promoCodeId: payload.promoCodeId,
116-
promoCodeValue: payload.promoCodeValue,
117-
benefitType: payload.benefitType,
118-
originalAmount: payload.originalAmount,
119-
finalAmount: payload.finalAmount,
120-
discountAmount: payload.discountAmount,
121-
promoUtm: payload.promoUtm,
91+
promo: payload.promo,
12292
};
12393
}
12494
}

0 commit comments

Comments
 (0)