init
This commit is contained in:
288
worklenz-backend/src/controllers/billing-controller.ts
Normal file
288
worklenz-backend/src/controllers/billing-controller.ts
Normal 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."));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user