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 { 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 { 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 { 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 { 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 { 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 { const { contactNo } = req.query; if (!contactNo) { return res.status(200).send(new ServerResponse(false, null, "Contact number is required!")); } const html = ` Worklenz Local Billing - Contact Information

Worklenz Local Billing - Contact Information

Name: ${req.user?.name}

Contact No: ${contactNo as string}

Email: ${req.user?.email}

`; 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.")); } }