288 lines
10 KiB
TypeScript
288 lines
10 KiB
TypeScript
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."));
|
|
}
|
|
|
|
} |