This commit is contained in:
chamikaJ
2025-04-17 18:28:54 +05:30
parent f583291d8a
commit 8825b0410a
2837 changed files with 241385 additions and 127578 deletions

View File

@@ -0,0 +1,288 @@
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
import db from "../config/db";
import { ServerResponse } from "../models/server-response";
import WorklenzControllerBase from "./worklenz-controller-base";
import HandleExceptions from "../decorators/handle-exceptions";
import { getTeamMemberCount } from "../shared/paddle-utils";
import { generatePayLinkRequest, updateUsers } from "../shared/paddle-requests";
import CryptoJS from "crypto-js";
import moment from "moment";
import axios from "axios";
import crypto from "crypto";
import fs from "fs";
import path from "path";
import { log_error } from "../shared/utils";
import { sendEmail } from "../shared/email";
export default class BillingController extends WorklenzControllerBase {
public static async getInitialCharge(count: number) {
if (!count) throw new Error("No selected plan detected.");
const baseRate = 4990;
const firstTier = 15;
const secondTierEnd = 200;
if (count <= firstTier) {
return baseRate;
} else if (count <= secondTierEnd) {
return baseRate + (count - firstTier) * 300;
}
return baseRate + (secondTierEnd - firstTier) * 300 + (count - secondTierEnd) * 200;
}
public static async getBillingMonth() {
const startDate = moment().format("YYYYMMDD");
const endDate = moment().add(1, "month").subtract(1, "day").format("YYYYMMDD");
return `${startDate} - ${endDate}`;
}
public static async chargeInitialPayment(signature: string, data: any) {
const config = {
method: "post",
maxBodyLength: Infinity,
url: process.env.DP_URL,
headers: {
"Content-Type": "application/json",
"Signature": signature,
"x-api-key": process.env.DP_API_KEY
},
data
};
axios.request(config)
.then((response) => {
console.log(JSON.stringify(response.data));
})
.catch((error) => {
console.log(error);
});
}
public static async saveLocalTransaction(signature: string, data: any) {
try {
const q = `INSERT INTO transactions (status, transaction_id, transaction_status, description, date_time, reference, amount, card_number)
VALUES ($1, $2, $3);`;
const result = await db.query(q, []);
} catch (error) {
log_error(error);
}
}
@HandleExceptions()
public static async upgradeToPaidPlan(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { plan, seatCount } = req.query;
const teamMemberData = await getTeamMemberCount(req.user?.owner_id ?? "");
teamMemberData.user_count = seatCount as string;
const axiosResponse = await generatePayLinkRequest(teamMemberData, plan as string, req.user?.owner_id, req.user?.id);
return res.status(200).send(new ServerResponse(true, axiosResponse.body));
}
@HandleExceptions()
public static async addMoreSeats(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { seatCount } = req.body;
const q = `SELECT subscription_id
FROM licensing_user_subscriptions lus
WHERE user_id = $1;`;
const result = await db.query(q, [req.user?.owner_id]);
const [data] = result.rows;
const response = await updateUsers(data.subscription_id, seatCount);
if (!response.body.subscription_id) {
return res.status(200).send(new ServerResponse(false, null, response.message || "Please check your subscription."));
}
return res.status(200).send(new ServerResponse(true, null, "Your purchase has been successfully completed!").withTitle("Done"));
}
@HandleExceptions()
public static async getDirectPayObject(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { seatCount } = req.query;
if (!seatCount) return res.status(200).send(new ServerResponse(false, null));
const email = req.user?.email;
const name = req.user?.name;
const amount = await this.getInitialCharge(parseInt(seatCount as string));
const uniqueTimestamp = moment().format("YYYYMMDDHHmmss");
const billingMonth = await this.getBillingMonth();
const { DP_MERCHANT_ID, DP_SECRET_KEY, DP_STAGE } = process.env;
const payload = {
merchant_id: DP_MERCHANT_ID,
amount: 10,
type: "RECURRING",
order_id: `WORKLENZ_${email}_${uniqueTimestamp}`,
currency: "LKR",
return_url: null,
response_url: null,
first_name: name,
last_name: null,
phone: null,
email,
description: `${name} (${email})`,
page_type: "IN_APP",
logo: "https://app.worklenz.com/assets/icons/icon-96x96.png",
start_date: moment().format("YYYY-MM-DD"),
do_initial_payment: 1,
interval: 1,
};
const encodePayload = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(payload)));
const signature = CryptoJS.HmacSHA256(encodePayload, DP_SECRET_KEY as string);
return res.status(200).send(new ServerResponse(true, { signature: signature.toString(CryptoJS.enc.Hex), dataString: encodePayload, stage: DP_STAGE }));
}
@HandleExceptions()
public static async saveTransactionData(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { status, card, transaction, seatCount } = req.body;
const { DP_MERCHANT_ID, DP_STAGE } = process.env;
const email = req.user?.email;
const amount = await this.getInitialCharge(parseInt(seatCount as string));
const uniqueTimestamp = moment().format("YYYYMMDDHHmmss");
const billingMonth = await this.getBillingMonth();
const values = [
status,
card?.id,
card?.number,
card?.brand,
card?.type,
card?.issuer,
card?.expiry?.year,
card?.expiry?.month,
card?.walletId,
transaction?.id,
transaction?.status,
transaction?.amount || 0,
transaction?.currency || null,
transaction?.channel || null,
transaction?.dateTime || null,
transaction?.message || null,
transaction?.description || null,
req.user?.id,
req.user?.owner_id,
];
const q = `INSERT INTO licensing_lkr_payments (
status, card_id, card_number, card_brand, card_type, card_issuer,
card_expiry_year, card_expiry_month, wallet_id,
transaction_id, transaction_status, transaction_amount,
transaction_currency, transaction_channel, transaction_datetime,
transaction_message, transaction_description, user_id, owner_id
)
VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19
);`;
await db.query(q, values);
if (transaction.status === "SUCCESS") {
const payload = {
"merchantId": DP_MERCHANT_ID,
"reference": `WORKLENZ_${email}_${uniqueTimestamp}`,
"type": "CARD_PAY",
"cardId": card.id,
"refCode": req.user?.id,
amount,
"currency": "LKR"
};
const dataString = Object.values(payload).join("");
const { DP_STAGE } = process.env;
const pemFile = DP_STAGE === "PROD" ? "src/keys/PRIVATE_KEY_PROD.pem" : `src/keys/PRIVATE_KEY_DEV.pem`;
const privateKeyTest = fs.readFileSync(path.resolve(pemFile), "utf8");
const sign = crypto.createSign("SHA256");
sign.update(dataString);
sign.end();
const signature = sign.sign(privateKeyTest);
const byteArray = new Uint8Array(signature);
let byteString = "";
for (let i = 0; i < byteArray.byteLength; i++) {
byteString += String.fromCharCode(byteArray[i]);
}
const base64Signature = btoa(byteString);
this.chargeInitialPayment(base64Signature, payload);
}
return res.status(200).send(new ServerResponse(true, null, "Your purchase has been successfully completed!").withTitle("Done"));
}
@HandleExceptions()
public static async getCardList(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const payload = {
"merchantId": "RT02300",
"reference": "1234",
"type": "LIST_CARD"
};
const { DP_STAGE } = process.env;
const dataString = `RT023001234LIST_CARD`;
const pemFile = DP_STAGE === "PROD" ? "src/keys/PRIVATE_KEY_PROD.pem" : `src/keys/PRIVATE_KEY_DEV.pem`;
const privateKeyTest = fs.readFileSync(path.resolve(pemFile), "utf8");
const sign = crypto.createSign("SHA256");
sign.update(dataString);
sign.end();
const signature = sign.sign(privateKeyTest);
const byteArray = new Uint8Array(signature);
let byteString = "";
for (let i = 0; i < byteArray.byteLength; i++) {
byteString += String.fromCharCode(byteArray[i]);
}
const base64Signature = btoa(byteString);
// const signature = CryptoJS.HmacSHA256(dataString, DP_SECRET_KEY as string).toString(CryptoJS.enc.Hex);
return res.status(200).send(new ServerResponse(true, { signature: base64Signature, dataString }));
}
@HandleExceptions()
public static async contactUs(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { contactNo } = req.query;
if (!contactNo) {
return res.status(200).send(new ServerResponse(false, null, "Contact number is required!"));
}
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worklenz Local Billing - Contact Information</title>
</head>
<body>
<div>
<h1 style="text-align: center; margin-bottom: 20px;">Worklenz Local Billing - Contact Information</h1>
<p><strong>Name:</strong> ${req.user?.name}</p>
<p><strong>Contact No:</strong> ${contactNo as string}</p>
<p><strong>Email:</strong> ${req.user?.email}</p>
</div>
</body>
</html>`;
const to = [process.env.CONTACT_US_EMAIL || "chamika@ceydigital.com"];
sendEmail({
to,
subject: "Worklenz - Local billing contact.",
html
});
return res.status(200).send(new ServerResponse(true, null, "Your contact information has been sent successfully."));
}
}