diff --git a/package.json b/package.json index d2cf072c..78cbc0e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.1.19", + "version": "1.1.20", "main": "index.ts", "license": "UNLICENSED", "scripts": { diff --git a/src/billing/cloudpayments.ts b/src/billing/cloudpayments.ts index 98e11342..47a6916f 100644 --- a/src/billing/cloudpayments.ts +++ b/src/billing/cloudpayments.ts @@ -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'; @@ -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; @@ -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; } @@ -137,6 +143,23 @@ 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 { @@ -144,11 +167,13 @@ export default class CloudPaymentsWebhooks { 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); @@ -170,6 +195,7 @@ export default class CloudPaymentsWebhooks { isCardLinkOperation, currency: 'RUB', checksum, + nextPaymentDate: nextPaymentDate.toISOString(), }); } @@ -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, @@ -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); @@ -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 @@ -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; @@ -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); @@ -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) { diff --git a/src/utils/checksumService.ts b/src/utils/checksumService.ts index 666ed0e7..59601fb1 100644 --- a/src/utils/checksumService.ts +++ b/src/utils/checksumService.ts @@ -20,6 +20,10 @@ interface PlanPurchaseChecksumData { * If true, we will save user card */ shouldSaveCard: boolean; + /** + * Next payment date + */ + nextPaymentDate: string; } interface CardLinkChecksumData { @@ -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; } /** @@ -67,6 +75,7 @@ class ChecksumService { workspaceId: payload.workspaceId, userId: payload.userId, isCardLinkOperation: payload.isCardLinkOperation, + nextPaymentDate: payload.nextPaymentDate, }; } else { return { @@ -74,6 +83,7 @@ class ChecksumService { userId: payload.userId, tariffPlanId: payload.tariffPlanId, shouldSaveCard: payload.shouldSaveCard, + nextPaymentDate: payload.nextPaymentDate, }; } } diff --git a/test/integration/cases/billing/check.test.ts b/test/integration/cases/billing/check.test.ts index a700d792..2af26fcd 100644 --- a/test/integration/cases/billing/check.test.ts +++ b/test/integration/cases/billing/check.test.ts @@ -151,6 +151,7 @@ describe('Check webhook', () => { userId: admin._id.toString(), tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }; @@ -172,6 +173,7 @@ describe('Check webhook', () => { userId: externalUser._id.toString(), tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }; @@ -193,6 +195,7 @@ describe('Check webhook', () => { userId: member._id.toString(), tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }; @@ -214,6 +217,7 @@ describe('Check webhook', () => { userId: admin._id.toString(), tariffPlanId: '5fe383b0126d28007780641b', shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }; @@ -236,6 +240,7 @@ describe('Check webhook', () => { userId: admin._id.toString(), tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }; @@ -257,6 +262,7 @@ describe('Check webhook', () => { userId: admin._id.toString(), tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }; @@ -283,6 +289,7 @@ describe('Check webhook', () => { userId: admin._id.toString(), tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), cloudPayments: { recurrent: { diff --git a/test/integration/cases/billing/fail.test.ts b/test/integration/cases/billing/fail.test.ts index 02c6618f..be1fd4f2 100644 --- a/test/integration/cases/billing/fail.test.ts +++ b/test/integration/cases/billing/fail.test.ts @@ -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 = { @@ -239,6 +240,7 @@ describe('Fail webhook', () => { workspaceId: workspace._id.toString(), tariffPlanId: tariffPlan._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }); diff --git a/test/integration/cases/billing/pay.test.ts b/test/integration/cases/billing/pay.test.ts index 65695589..0fb4cfef 100644 --- a/test/integration/cases/billing/pay.test.ts +++ b/test/integration/cases/billing/pay.test.ts @@ -127,6 +127,7 @@ describe('Pay webhook', () => { checksum: await checksumService.generateChecksum({ ...paymentSuccessPayload, shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }; @@ -457,6 +458,7 @@ describe('Pay webhook', () => { checksum: await checksumService.generateChecksum({ ...paymentSuccessPayload, shouldSaveCard: true, + nextPaymentDate: new Date().toString(), }), }), ...cardDetails, @@ -502,6 +504,7 @@ describe('Pay webhook', () => { workspaceId: '', tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }); @@ -523,6 +526,7 @@ describe('Pay webhook', () => { workspaceId: workspace._id.toString(), tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }); @@ -544,6 +548,7 @@ describe('Pay webhook', () => { workspaceId: workspace._id.toString(), tariffPlanId: '', shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }); @@ -565,6 +570,7 @@ describe('Pay webhook', () => { workspaceId: new ObjectId().toString(), tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }); @@ -586,6 +592,7 @@ describe('Pay webhook', () => { workspaceId: workspace._id.toString(), tariffPlanId: new ObjectId().toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), }); @@ -607,6 +614,7 @@ describe('Pay webhook', () => { workspaceId: workspace._id.toString(), tariffPlanId: planToChange._id.toString(), shouldSaveCard: false, + nextPaymentDate: new Date().toString(), }), }), });