From 882db6acc9c3de6358d81aed9556594d1d42d26f Mon Sep 17 00:00:00 2001 From: prafull-opensignlabs Date: Mon, 19 Aug 2024 11:28:22 +0530 Subject: [PATCH 1/5] fix: validation of generate token --- .../public/locales/en/translation.json | 6 +- .../public/locales/fr/translation.json | 6 +- apps/OpenSign/src/pages/GenerateToken.js | 146 ++++- .../v1/routes/CreateDocumentWithTemplate.js | 556 ++++++++-------- .../v1/routes/createDocumentwithCoordinate.js | 594 +++++++++--------- apps/OpenSignServer/cloud/main.js | 4 + .../cloud/parsefunction/AllowedApis.js | 35 ++ .../cloud/parsefunction/BuyApis.js | 87 +++ .../cloud/parsefunction/getapitoken.js | 21 +- 9 files changed, 867 insertions(+), 588 deletions(-) create mode 100644 apps/OpenSignServer/cloud/parsefunction/AllowedApis.js create mode 100644 apps/OpenSignServer/cloud/parsefunction/BuyApis.js diff --git a/apps/OpenSign/public/locales/en/translation.json b/apps/OpenSign/public/locales/en/translation.json index 9d9a53375..67fac42b1 100644 --- a/apps/OpenSign/public/locales/en/translation.json +++ b/apps/OpenSign/public/locales/en/translation.json @@ -602,6 +602,10 @@ "Proceed":"Proceed", "Select":"Select", "Add-seats":"Add Seats", - "user-name-exist":"user name already exist" + "user-name-exist":"user name already exist", + "buyapiaddon":"Buy APIs", + "quantityofapis" :"Quantity of APIs", + "additionalapis":"Please purchase add-on APIs", + "remainingapis":"Remaining APIs:" } \ No newline at end of file diff --git a/apps/OpenSign/public/locales/fr/translation.json b/apps/OpenSign/public/locales/fr/translation.json index fa0df737a..c36e0382d 100644 --- a/apps/OpenSign/public/locales/fr/translation.json +++ b/apps/OpenSign/public/locales/fr/translation.json @@ -602,7 +602,11 @@ "Total-price-for-next-time":"Prix ​​total pour la prochaine fois", "Proceed":"Procéder", "Select":"sélectionner", - "Add-seats":"Ajouter des sièges" + "Add-seats":"Ajouter des sièges", + "buyapiaddon":"Acheter des API", + "quantityofapis":"Quantité d'API", + "additionalapis":"Veuillez acheter des API complémentaires", + "remainingapis":"API restantes :" } \ No newline at end of file diff --git a/apps/OpenSign/src/pages/GenerateToken.js b/apps/OpenSign/src/pages/GenerateToken.js index 1d2b73312..5504965e2 100644 --- a/apps/OpenSign/src/pages/GenerateToken.js +++ b/apps/OpenSign/src/pages/GenerateToken.js @@ -11,6 +11,7 @@ import SubscribeCard from "../primitives/SubscribeCard"; import Tour from "reactour"; import { validplan } from "../json/plansArr"; import { useTranslation } from "react-i18next"; +import Parse from "parse"; function GenerateToken() { const { t } = useTranslation(); @@ -18,10 +19,20 @@ function GenerateToken() { const [parseAppId] = useState(localStorage.getItem("parseAppId")); const [apiToken, SetApiToken] = useState(""); const [isLoader, setIsLoader] = useState(true); - const [isModal, setIsModal] = useState(false); + const [isModal, setIsModal] = useState({ + generateapi: false, + buyapis: false + }); const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: false }); const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); const [isTour, setIsTour] = useState(false); + const [amount, setAmount] = useState({ + quantity: 1, + priceperapi: 0.15, + totalapis: 0, + price: 0.15 + }); + const [isFormLoader, setIsFormLoader] = useState(false); useEffect(() => { fetchToken(); // eslint-disable-next-line @@ -38,15 +49,16 @@ function GenerateToken() { const subscribe = await checkIsSubscribed(); setIsSubscribe(subscribe); } - const url = parseBaseUrl + "functions/getapitoken"; - const headers = { - "Content-Type": "application/json", - "X-Parse-Application-Id": parseAppId, - sessiontoken: localStorage.getItem("accesstoken") - }; - const res = await axios.post(url, {}, { headers: headers }); + const extUser = + localStorage.getItem("Extand_Class") && + JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + const res = await Parse.Cloud.run("getapitoken"); if (res) { - SetApiToken(res.data?.result?.result); + const allowedapis = await Parse.Cloud.run("allowedapis", { + tenantId: extUser?.TenantId?.objectId + }); + setAmount((obj) => ({ ...obj, totalapis: allowedapis })); + SetApiToken(res?.result); } setIsLoader(false); } catch (err) { @@ -61,7 +73,7 @@ function GenerateToken() { setIsTour(true); } else { setIsLoader(true); - setIsModal(false); + setIsModal((obj) => ({ ...obj, generateapi: false })); try { const url = parseBaseUrl + "functions/generateapitoken"; const headers = { @@ -92,12 +104,51 @@ function GenerateToken() { const copytoclipboard = (text) => { copytoData(text); setIsAlert({ type: "success", msg: t("copied") }); - setTimeout(() => { - setIsAlert({ type: "success", msg: "" }); - }, 1500); // Reset copied state after 1.5 seconds + setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds + }; + const handleModal = () => { + setIsModal((obj) => ({ ...obj, generateapi: !obj.generateapi })); }; - const handleModal = () => setIsModal(!isModal); + const handleBuyAPIsModal = () => { + setIsModal((obj) => ({ ...obj, buyapis: !obj.buyapis })); + }; + + const handlePricePerAPIs = (e) => { + const quantity = e.target?.value; + const price = + quantity > 0 + ? (Math.round(quantity * amount.priceperapi * 100) / 100).toFixed(2) + : 1 * amount.priceperapi; + setAmount((prev) => ({ ...prev, quantity: quantity, price: price })); + }; + const handleAddOnApiSubmit = async (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsFormLoader(true); + const extUser = + localStorage.getItem("Extand_Class") && + JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + try { + const resAddon = await Parse.Cloud.run("buyapis", { + apis: amount.quantity, + tenantId: extUser?.TenantId?.objectId + }); + if (resAddon) { + const _resAddon = JSON.parse(JSON.stringify(resAddon)); + if (_resAddon.status === "success") { + // setAllowedUser(amount.quantity); + setAmount((obj) => ({ ...obj, totalapis: _resAddon.addon })); + } + } + } catch (err) { + console.log("Err in buy addon", err); + setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") }); + } finally { + setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000); + setIsFormLoader(false); + } + }; return ( @@ -112,9 +163,7 @@ function GenerateToken() { <h1 className={"ml-4 mt-3 mb-2 font-semibold"}> OpenSign™ {t("API")}{" "} <Tooltip - url={ - "https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1" - } + url="https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1" isSubscribe={true} /> </h1> @@ -151,8 +200,12 @@ function GenerateToken() { {apiToken ? t("regenerate-token") : t("generate-token")} </button> </li> + <div className="text-xs md:text-[15px] my-4"> + {t("remainingapis")} {amount.totalapis}{" "} + </div> + <hr /> </ul> - <div className="flex items-center justify-center"> + <div className="flex flex-col md:flex-row items-center justify-center gap-0 md:gap-1"> <button type="button" onClick={() => @@ -160,13 +213,20 @@ function GenerateToken() { "https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1" ) } - className="op-btn op-btn-secondary mt-2 mb-3 px-8" + className="op-btn op-btn-secondary mt-2 md:mb-3 px-8" > {t("view-docs")} </button> + <button + type="button" + onClick={() => handleBuyAPIsModal()} + className="op-btn op-btn-secondary mt-2 mb-3 px-8" + > + {t("buyapiaddon")} + </button> </div> <ModalUi - isOpen={isModal} + isOpen={isModal.generateapi} title={apiToken ? t("regenerate-token") : t("generate-token")} handleClose={handleModal} > @@ -191,6 +251,52 @@ function GenerateToken() { </div> </div> </ModalUi> + {/* buy apis */} + <ModalUi + isOpen={isModal.buyapis} + title={"Addon apis"} + handleClose={handleBuyAPIsModal} + > + {isFormLoader && ( + <div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50"> + <Loader /> + </div> + )} + <form onSubmit={handleAddOnApiSubmit} className="p-3"> + <p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium"> + {t("additionalapis")} + </p> + <div className="mb-3 flex justify-between"> + <label + htmlFor="quantity" + className="block text-xs text-gray-700 font-semibold" + > + {t("quantityofapis")} + <span className="text-[red] text-[13px]"> *</span> + </label> + <input + type="number" + name="quantity" + value={amount.quantity} + onChange={(e) => handlePricePerAPIs(e)} + className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs" + required + /> + </div> + <div className="mb-3 flex justify-between"> + <label className="block text-xs text-gray-700 font-semibold"> + {t("Price")} (1 * {amount.priceperapi}) + </label> + <div className="w-1/4 flex justify-center items-center text-sm"> + USD {amount.price} + </div> + </div> + <hr className="text-base-content mb-3" /> + <button className="op-btn op-btn-primary w-full mt-2"> + {t("Proceed")} + </button> + </form> + </ModalUi> </div> {!validplan[isSubscribe.plan] && isEnableSubscription && ( <div data-tut="apisubscribe"> diff --git a/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js b/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js index 513213aa4..e9e167036 100644 --- a/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js +++ b/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js @@ -4,14 +4,9 @@ import { cloudServerUrl, customAPIurl, replaceMailVaribles } from '../../../../U // `sendDoctoWebhook` is used to send res data of document on webhook async function sendDoctoWebhook(doc, WebhookUrl, userId) { if (WebhookUrl) { - const params = { - event: 'created', - ...doc, - }; + const params = { event: 'created', ...doc }; await axios - .post(WebhookUrl, params, { - headers: { 'Content-Type': 'application/json' }, - }) + .post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } }) .then(res => { try { // console.log('res ', res); @@ -62,6 +57,7 @@ export default async function createDocumentWithTemplate(request, response) { if (!reqToken) { return response.status(400).json({ error: 'Please Provide API Token' }); } + const tokenQuery = new Parse.Query('appToken'); tokenQuery.equalTo('token', reqToken); tokenQuery.include('userId'); @@ -74,275 +70,306 @@ export default async function createDocumentWithTemplate(request, response) { className: '_User', objectId: parseUser.userId.objectId, }; - const templateQuery = new Parse.Query('contracts_Template'); - templateQuery.include('ExtUserPtr'); - const templateRes = await templateQuery.get(templateId, { useMasterKey: true }); - if (templateRes) { - const template = JSON.parse(JSON.stringify(templateRes)); - if (template?.Placeholders?.length > 0) { - const emptyplaceholder = template?.Placeholders.filter(x => !x.signerObjId); - const isValid = - signers.length >= emptyplaceholder.length && - signers.length <= template?.Placeholders?.length; - const placeholder = - signers.length > emptyplaceholder.length ? template.Placeholders : emptyplaceholder; - const updateSigners = placeholder.every(y => signers?.some(x => x.role === y.Role)); - // console.log('isValid ', isValid); - if (isValid && updateSigners) { - //Check if every item's placeholders contain at least one placeholder with type 'signature'. - let isSignature = template?.Placeholders?.every(item => - item?.placeHolder.some(x => x?.pos.some(data => data?.type === 'signature')) - ); - if (!isSignature) { - return response - .status(400) - .json({ error: 'Please add at least one signature widget for all signers' }); - } - const folderPtr = { - __type: 'Pointer', - className: 'contracts_Document', - objectId: folderId, - }; - const object = new Parse.Object('contracts_Document'); - object.set('Name', template.Name); - if (template?.Note) { - object.set('Note', template.Note); - } - if (template?.Description) { - object.set('Description', template.Description); - } - object.set('IsSendMail', send_email); - if (sendInOrder) { - object.set('SendinOrder', sendInOrder); - } else if (template?.SendinOrder && template?.SendinOrder) { - object.set('SendinOrder', template?.SendinOrder); - } - let templateSigner = template?.Signers ? template?.Signers : []; - let contact = []; - if (signers && signers.length > 0) { - let parseSigners = [...signers]; - let createContactUrl = protocol + '/v1/createcontact'; + const extUsers = new Parse.Query('contracts_Users'); + extUsers.equalTo('UserId', userPtr); + const extUser = await extUsers.first({ useMasterKey: true }); - for (const obj of parseSigners) { - const body = { - name: obj?.name || '', - email: obj?.email || '', - phone: obj?.phone || '', - }; - try { - const res = await axios.post(createContactUrl, body, { - headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken }, - }); - const contactPtr = { - __type: 'Pointer', - className: 'contracts_Contactbook', - objectId: res.data?.objectId, + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: extUser.get('TenantId').id, + }); + subscription.include('ExtUserPtr'); + subscription.greaterThan('AllowedApis', 0); + const resSub = await subscription.first({ useMasterKey: true }); + + if (resSub) { + const templateQuery = new Parse.Query('contracts_Template'); + templateQuery.include('ExtUserPtr'); + const templateRes = await templateQuery.get(templateId, { useMasterKey: true }); + if (templateRes) { + const template = JSON.parse(JSON.stringify(templateRes)); + if (template?.Placeholders?.length > 0) { + const emptyplaceholder = template?.Placeholders.filter(x => !x.signerObjId); + const isValid = + signers.length >= emptyplaceholder.length && + signers.length <= template?.Placeholders?.length; + const placeholder = + signers.length > emptyplaceholder.length ? template.Placeholders : emptyplaceholder; + const updateSigners = placeholder.every(y => signers?.some(x => x.role === y.Role)); + // console.log('isValid ', isValid); + if (isValid && updateSigners) { + //Check if every item's placeholders contain at least one placeholder with type 'signature'. + let isSignature = template?.Placeholders?.every(item => + item?.placeHolder.some(x => x?.pos.some(data => data?.type === 'signature')) + ); + if (!isSignature) { + return response + .status(400) + .json({ error: 'Please add at least one signature widget for all signers' }); + } + const folderPtr = { + __type: 'Pointer', + className: 'contracts_Document', + objectId: folderId, + }; + const object = new Parse.Object('contracts_Document'); + object.set('Name', template.Name); + if (template?.Note) { + object.set('Note', template.Note); + } + if (template?.Description) { + object.set('Description', template.Description); + } + object.set('IsSendMail', send_email); + if (sendInOrder) { + object.set('SendinOrder', sendInOrder); + } else if (template?.SendinOrder && template?.SendinOrder) { + object.set('SendinOrder', template?.SendinOrder); + } + let templateSigner = template?.Signers ? template?.Signers : []; + let contact = []; + if (signers && signers.length > 0) { + let parseSigners = [...signers]; + let createContactUrl = protocol + '/v1/createcontact'; + + for (const obj of parseSigners) { + const body = { + name: obj?.name || '', + email: obj?.email || '', + phone: obj?.phone || '', }; - const newObj = { ...obj, contactPtr: contactPtr }; - contact.push(newObj); - } catch (err) { - // console.log('err ', err); - if (err?.response?.data?.objectId) { + try { + const res = await axios.post(createContactUrl, body, { + headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken }, + }); const contactPtr = { __type: 'Pointer', className: 'contracts_Contactbook', - objectId: err.response.data?.objectId, + objectId: res.data?.objectId, }; const newObj = { ...obj, contactPtr: contactPtr }; contact.push(newObj); - } else { - console.log('err ', err); + } catch (err) { + // console.log('err ', err); + if (err?.response?.data?.objectId) { + const contactPtr = { + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: err.response.data?.objectId, + }; + const newObj = { ...obj, contactPtr: contactPtr }; + contact.push(newObj); + } else { + console.log('err ', err); + } } } - } - const contactPtrs = contact.map(x => x.contactPtr); - object.set('Signers', [...templateSigner, ...contactPtrs]); + const contactPtrs = contact.map(x => x.contactPtr); + object.set('Signers', [...templateSigner, ...contactPtrs]); - let updatedPlaceholder = template?.Placeholders?.map(x => { - let matchingSigner = contact.find(y => x.Role && x.Role === y.role); + let updatedPlaceholder = template?.Placeholders?.map(x => { + let matchingSigner = contact.find(y => x.Role && x.Role === y.role); - if (matchingSigner) { - return { - ...x, - signerObjId: matchingSigner?.contactPtr?.objectId, - signerPtr: matchingSigner?.contactPtr, - }; - } else { - return { - ...x, - }; - } - }); - object.set('Placeholders', updatedPlaceholder); - } else { - object.set('Signers', templateSigner); - } - object.set('URL', template.URL); - object.set('SignedUrl', template.URL); - object.set('SentToOthers', true); - if (TimeToCompleteDays) { - object.set('TimeToCompleteDays', TimeToCompleteDays); - } - object.set('CreatedBy', template.CreatedBy); - object.set('ExtUserPtr', { - __type: 'Pointer', - className: 'contracts_Users', - objectId: template.ExtUserPtr.objectId, - }); - if (folderId) { - object.set('Folder', folderPtr); - } - const newACL = new Parse.ACL(); - newACL.setPublicReadAccess(false); - newACL.setPublicWriteAccess(false); - newACL.setReadAccess(userPtr.objectId, true); - newACL.setWriteAccess(userPtr.objectId, true); - object.setACL(newACL); - const res = await object.save(null, { useMasterKey: true }); - - const newDate = new Date(); - newDate.setDate(newDate.getDate() + 15); - const localExpireDate = newDate.toLocaleDateString('en-US', { - day: 'numeric', - month: 'long', - year: 'numeric', - }); - let sender = template.ExtUserPtr.Email; - let sendMail; - if (send_email === false) { - console.log("don't send mail"); - } else { - let contactMail = contact; - if (sendInOrder) { - contactMail = contact.slice(); - contactMail.splice(1); - } - for (let i = 0; i < contactMail.length; i++) { - try { - const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; - let url = `${cloudServerUrl}/functions/sendmailv3/`; - const headers = { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': process.env.APP_ID, - 'X-Parse-Master-Key': process.env.MASTER_KEY, - }; - - const objectId = contactMail[i].contactPtr.objectId; - const hostUrl = baseUrl.origin; - //encode this url value `${res.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function - const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`); - let signPdf = `${hostUrl}/login/${encodeBase64}`; - const openSignUrl = 'https://www.opensignlabs.com/contact-us'; - const orgName = template.ExtUserPtr.Company ? template.ExtUserPtr.Company : ''; - const themeBGcolor = '#47a3ad'; - const email_html = - "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src='" + - imgPng + - "' height='50' style='padding:20px; width:170px; height:40px;' /></div><div style='padding: 2px;font-family: system-ui;background-color:" + - themeBGcolor + - ";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " + - template.ExtUserPtr.Name + - ' has requested you to review and sign <strong> ' + - template.Name + - "</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" + - sender + - "</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " + - orgName + - "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + - localExpireDate + - "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + - signPdf + - "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + - sender + - ' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' + - openSignUrl + - ' target=_blank>here</a>.</p> </div></div></body> </html>'; - - let replaceVar; - const variables = { - document_title: template.Name, - sender_name: template.ExtUserPtr.Name, - sender_mail: template.ExtUserPtr.Email, - sender_phone: template.ExtUserPtr?.Phone || '', - receiver_name: contactMail[i].name, - receiver_email: contactMail[i].email, - receiver_phone: contactMail[i]?.phone || '', - expiry_date: localExpireDate, - company_name: orgName, - signing_url: signPdf, - }; - if (email_subject && email_body) { - replaceVar = replaceMailVaribles(email_subject, email_body, variables); - } else if (email_subject) { - replaceVar = replaceMailVaribles(email_subject, '', variables); - replaceVar = { subject: replaceVar.subject, body: email_html }; - } else if (email_body) { - replaceVar = replaceMailVaribles( - `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`, - email_body, - variables - ); - } else { - replaceVar = { - subject: `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`, - body: email_html, + if (matchingSigner) { + return { + ...x, + signerObjId: matchingSigner?.contactPtr?.objectId, + signerPtr: matchingSigner?.contactPtr, }; + } else { + return { ...x }; } + }); + object.set('Placeholders', updatedPlaceholder); + } else { + object.set('Signers', templateSigner); + } + object.set('URL', template.URL); + object.set('SignedUrl', template.URL); + object.set('SentToOthers', true); + if (TimeToCompleteDays) { + object.set('TimeToCompleteDays', TimeToCompleteDays); + } + object.set('CreatedBy', template.CreatedBy); + object.set('ExtUserPtr', { + __type: 'Pointer', + className: 'contracts_Users', + objectId: template.ExtUserPtr.objectId, + }); + if (folderId) { + object.set('Folder', folderPtr); + } + const newACL = new Parse.ACL(); + newACL.setPublicReadAccess(false); + newACL.setPublicWriteAccess(false); + newACL.setReadAccess(userPtr.objectId, true); + newACL.setWriteAccess(userPtr.objectId, true); + object.setACL(newACL); + const res = await object.save(null, { useMasterKey: true }); - const subject = replaceVar.subject; - const html = replaceVar.body; - let params = { - recipient: contactMail[i].email, - subject: subject, - from: sender, - html: html, - extUserId: template.ExtUserPtr.objectId, - }; - sendMail = await axios.post(url, params, { headers: headers }); - } catch (error) { - console.log('error', error); + const newDate = new Date(); + newDate.setDate(newDate.getDate() + 15); + const localExpireDate = newDate.toLocaleDateString('en-US', { + day: 'numeric', + month: 'long', + year: 'numeric', + }); + let sender = template.ExtUserPtr.Email; + let sendMail; + if (send_email === false) { + console.log("don't send mail"); + } else { + let contactMail = contact; + if (sendInOrder) { + contactMail = contact.slice(); + contactMail.splice(1); + } + for (let i = 0; i < contactMail.length; i++) { + try { + const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; + let url = `${cloudServerUrl}/functions/sendmailv3/`; + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': process.env.APP_ID, + 'X-Parse-Master-Key': process.env.MASTER_KEY, + }; + + const objectId = contactMail[i].contactPtr.objectId; + const hostUrl = baseUrl.origin; + //encode this url value `${res.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function + const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`); + let signPdf = `${hostUrl}/login/${encodeBase64}`; + const openSignUrl = 'https://www.opensignlabs.com/contact-us'; + const orgName = template.ExtUserPtr.Company ? template.ExtUserPtr.Company : ''; + const themeBGcolor = '#47a3ad'; + const email_html = + "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src='" + + imgPng + + "' height='50' style='padding:20px; width:170px; height:40px;' /></div><div style='padding: 2px;font-family: system-ui;background-color:" + + themeBGcolor + + ";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " + + template.ExtUserPtr.Name + + ' has requested you to review and sign <strong> ' + + template.Name + + "</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" + + sender + + "</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " + + orgName + + "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + + localExpireDate + + "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + + signPdf + + "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + + sender + + ' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' + + openSignUrl + + ' target=_blank>here</a>.</p> </div></div></body> </html>'; + + let replaceVar; + const variables = { + document_title: template.Name, + sender_name: template.ExtUserPtr.Name, + sender_mail: template.ExtUserPtr.Email, + sender_phone: template.ExtUserPtr?.Phone || '', + receiver_name: contactMail[i].name, + receiver_email: contactMail[i].email, + receiver_phone: contactMail[i]?.phone || '', + expiry_date: localExpireDate, + company_name: orgName, + signing_url: signPdf, + }; + if (email_subject && email_body) { + replaceVar = replaceMailVaribles(email_subject, email_body, variables); + } else if (email_subject) { + replaceVar = replaceMailVaribles(email_subject, '', variables); + replaceVar = { subject: replaceVar.subject, body: email_html }; + } else if (email_body) { + replaceVar = replaceMailVaribles( + `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`, + email_body, + variables + ); + } else { + replaceVar = { + subject: `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`, + body: email_html, + }; + } + + const subject = replaceVar.subject; + const html = replaceVar.body; + let params = { + recipient: contactMail[i].email, + subject: subject, + from: sender, + html: html, + extUserId: template.ExtUserPtr.objectId, + }; + sendMail = await axios.post(url, params, { headers: headers }); + } catch (error) { + console.log('error', error); + } } } - } - // if (sendMail.data.result.status === 'success') { - try { - const doc = { - objectId: res.id, - file: template?.URL, - name: template?.Name, - note: template?.Note || '', - description: template?.Description || '', - signers: contact?.map(x => ({ - name: x.name, - email: x.email, - phone: x?.phone || '', - })), - createdAt: res.createdAt, - }; - if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) { - sendDoctoWebhook(doc, template.ExtUserPtr?.Webhook, userPtr?.objectId); + // if (sendMail.data.result.status === 'success') { + try { + const doc = { + objectId: res.id, + file: template?.URL, + name: template?.Name, + note: template?.Note || '', + description: template?.Description || '', + signers: contact?.map(x => ({ + name: x.name, + email: x.email, + phone: x?.phone || '', + })), + createdAt: res.createdAt, + }; + if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) { + sendDoctoWebhook(doc, template.ExtUserPtr?.Webhook, userPtr?.objectId); + } + } catch (err) { + console.log('Err', err); } - } catch (err) { - console.log('Err', err); - } - if (request.posthog) { - request.posthog?.capture({ - distinctId: parseUser.userId.email, - event: 'api_create_document_with_templateid', - properties: { response_code: 200 }, + if (request.posthog) { + request.posthog?.capture({ + distinctId: parseUser.userId.email, + event: 'api_create_document_with_templateid', + properties: { response_code: 200 }, + }); + } + const updateApiCount = + resSub?.get('AllowedApis') && resSub.get('AllowedApis') > 0 + ? resSub.get('AllowedApis') - 1 + : 0; + const subscriptionCls = new Parse.Object('contracts_Subscriptions'); + subscriptionCls.id = resSub.id; + subscriptionCls.set('AllowedApis', updateApiCount); + const resSubcription = await subscriptionCls.save(null, { useMasterKey: true }); + // console.log("resSubcription ", resSubcription) + return response.json({ + objectId: res.id, + signurl: contact.map(x => ({ + email: x.email, + url: `${baseUrl.origin}/login/${btoa( + `${res.id}/${x.email}/${x.contactPtr.objectId}` + )}`, + })), + message: 'Document sent successfully!', }); + } else { + if (request.posthog) { + request.posthog?.capture({ + distinctId: parseUser.userId.email, + event: 'api_create_document_with_templateid', + properties: { response_code: 400 }, + }); + } + return response.status(400).json({ error: 'Please provide signers properly!' }); } - return response.json({ - objectId: res.id, - signurl: contact.map(x => ({ - email: x.email, - url: `${baseUrl.origin}/login/${btoa( - `${res.id}/${x.email}/${x.contactPtr.objectId}` - )}`, - })), - message: 'Document sent successfully!', - }); - // } } else { if (request.posthog) { request.posthog?.capture({ @@ -351,27 +378,22 @@ export default async function createDocumentWithTemplate(request, response) { properties: { response_code: 400 }, }); } - return response.status(400).json({ error: 'Please provide signers properly!' }); + return response.status(400).json({ error: 'Please setup template properly!' }); } } else { if (request.posthog) { request.posthog?.capture({ distinctId: parseUser.userId.email, event: 'api_create_document_with_templateid', - properties: { response_code: 400 }, + properties: { response_code: 404 }, }); } - return response.status(400).json({ error: 'Please setup template properly!' }); + return response.status(404).json({ error: 'Invalid template id!' }); } } else { - if (request.posthog) { - request.posthog?.capture({ - distinctId: parseUser.userId.email, - event: 'api_create_document_with_templateid', - properties: { response_code: 404 }, - }); - } - return response.status(404).json({ error: 'Invalid template id!' }); + return response + .status(429) + .json({ error: 'Quota reached. Please buy API credits and try again later.' }); } } else { return response.status(405).json({ error: 'Invalid API Token!' }); diff --git a/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js b/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js index 82f792059..0278ab811 100644 --- a/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js +++ b/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js @@ -12,15 +12,10 @@ import { // `sendDoctoWebhook` is used to send res data of document on webhook async function sendDoctoWebhook(doc, WebhookUrl, userId) { if (WebhookUrl) { - const params = { - event: 'created', - ...doc, - }; + const params = { event: 'created', ...doc }; await axios - .post(WebhookUrl, params, { - headers: { 'Content-Type': 'application/json' }, - }) + .post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } }) .then(res => { try { // console.log('res ', res); @@ -89,309 +84,336 @@ export default async function createDocumentwithCoordinate(request, response) { className: '_User', objectId: parseUser.userId.objectId, }; - if (signers && signers.length > 0) { - // Check if at least one signature exists among all items in the signers array - let isSignExist = signers.every(item => - item.widgets.some(data => data?.type === 'signature') - ); - if (!isSignExist) { - return response - .status(400) - .json({ error: 'Please add at least one signature widget for all signers' }); - } - let fileUrl; - if (request.files?.[0]) { - const base64 = fileData?.toString('base64'); - const file = new Parse.File(request.files?.[0]?.originalname, { - base64: base64, - }); - await file.save({ useMasterKey: true }); - fileUrl = file.url(); - const buffer = Buffer.from(base64, 'base64'); - saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); - } else { - const filename = sanitizeFileName(`${name}.pdf`); - const file = new Parse.File(filename, { base64: base64File }, 'application/pdf'); - await file.save({ useMasterKey: true }); - fileUrl = file.url(); - const buffer = Buffer.from(base64File, 'base64'); - saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); - } - const contractsUser = new Parse.Query('contracts_Users'); - contractsUser.equalTo('UserId', userPtr); - const extUser = await contractsUser.first({ useMasterKey: true }); - const parseExtUser = JSON.parse(JSON.stringify(extUser)); + const extUsers = new Parse.Query('contracts_Users'); + extUsers.equalTo('UserId', userPtr); + const extUser = await extUsers.first({ useMasterKey: true }); - const extUserPtr = { - __type: 'Pointer', - className: 'contracts_Users', - objectId: extUser.id, - }; - - const object = new Parse.Object('contracts_Document'); - object.set('Name', name); - - if (note) { - object.set('Note', note); - } - if (description) { - object.set('Description', description); - } - if (sendInOrder) { - object.set('SendinOrder', sendInOrder); - } - object.set('URL', fileUrl); - object.set('SignedUrl', fileUrl); - object.set('SentToOthers', true); - object.set('CreatedBy', userPtr); - object.set('ExtUserPtr', extUserPtr); - if (TimeToCompleteDays) { - object.set('TimeToCompleteDays', TimeToCompleteDays); - } - object.set('IsSendMail', send_email); - let contact = []; + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: extUser.get('TenantId').id, + }); + subscription.include('ExtUserPtr'); + subscription.greaterThan('AllowedApis', 0); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { if (signers && signers.length > 0) { - let parseSigners; - if (base64File) { - parseSigners = signers; - } else { - parseSigners = JSON.parse(signers); + // Check if at least one signature exists among all items in the signers array + let isSignExist = signers.every(item => + item.widgets.some(data => data?.type === 'signature') + ); + if (!isSignExist) { + return response + .status(400) + .json({ error: 'Please add at least one signature widget for all signers' }); } - let createContactUrl = protocol + '/v1/createcontact'; + let fileUrl; + if (request.files?.[0]) { + const base64 = fileData?.toString('base64'); + const file = new Parse.File(request.files?.[0]?.originalname, { + base64: base64, + }); + await file.save({ useMasterKey: true }); + fileUrl = file.url(); + const buffer = Buffer.from(base64, 'base64'); + saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); + } else { + const filename = sanitizeFileName(`${name}.pdf`); + const file = new Parse.File(filename, { base64: base64File }, 'application/pdf'); + await file.save({ useMasterKey: true }); + fileUrl = file.url(); + const buffer = Buffer.from(base64File, 'base64'); + saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); + } + const contractsUser = new Parse.Query('contracts_Users'); + contractsUser.equalTo('UserId', userPtr); + const extUser = await contractsUser.first({ useMasterKey: true }); + const parseExtUser = JSON.parse(JSON.stringify(extUser)); - for (const [index, element] of parseSigners.entries()) { - const body = { - name: element?.name || '', - email: element?.email || '', - phone: element?.phone || '', - }; - try { - const res = await axios.post(createContactUrl, body, { - headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken }, - }); - // console.log('res ', res.data); - const contactPtr = { - __type: 'Pointer', - className: 'contracts_Contactbook', - objectId: res.data?.objectId, + const extUserPtr = { + __type: 'Pointer', + className: 'contracts_Users', + objectId: extUser.id, + }; + + const object = new Parse.Object('contracts_Document'); + object.set('Name', name); + + if (note) { + object.set('Note', note); + } + if (description) { + object.set('Description', description); + } + if (sendInOrder) { + object.set('SendinOrder', sendInOrder); + } + object.set('URL', fileUrl); + object.set('SignedUrl', fileUrl); + object.set('SentToOthers', true); + object.set('CreatedBy', userPtr); + object.set('ExtUserPtr', extUserPtr); + if (TimeToCompleteDays) { + object.set('TimeToCompleteDays', TimeToCompleteDays); + } + object.set('IsSendMail', send_email); + let contact = []; + if (signers && signers.length > 0) { + let parseSigners; + if (base64File) { + parseSigners = signers; + } else { + parseSigners = JSON.parse(signers); + } + let createContactUrl = protocol + '/v1/createcontact'; + + for (const [index, element] of parseSigners.entries()) { + const body = { + name: element?.name || '', + email: element?.email || '', + phone: element?.phone || '', }; - const newObj = { ...element, contactPtr: contactPtr, index: index }; - contact.push(newObj); - } catch (err) { - // console.log('err ', err.response); - if (err?.response?.data?.objectId) { + try { + const res = await axios.post(createContactUrl, body, { + headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken }, + }); + // console.log('res ', res.data); const contactPtr = { __type: 'Pointer', className: 'contracts_Contactbook', - objectId: err.response.data?.objectId, + objectId: res.data?.objectId, }; const newObj = { ...element, contactPtr: contactPtr, index: index }; contact.push(newObj); + } catch (err) { + // console.log('err ', err.response); + if (err?.response?.data?.objectId) { + const contactPtr = { + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: err.response.data?.objectId, + }; + const newObj = { ...element, contactPtr: contactPtr, index: index }; + contact.push(newObj); + } } } - } - object.set( - 'Signers', - contact?.map(x => x.contactPtr) - ); - let updatePlaceholders = contact.map(signer => { - const placeHolder = []; + object.set( + 'Signers', + contact?.map(x => x.contactPtr) + ); + let updatePlaceholders = contact.map(signer => { + const placeHolder = []; - for (const widget of signer.widgets) { - const pageNumber = widget.page; - const options = formatWidgetOptions(widget.type, widget.options); - const page = placeHolder.find(page => page.pageNumber === pageNumber); - const widgetData = { - isStamp: widget.type === 'stamp' || widget.type === 'image', - key: randomId(), - isDrag: false, - scale: 1, - isMobile: false, - zIndex: 1, - type: widget.type === 'textbox' ? 'text input' : widget.type, - options: options, - Width: widget.w, - Height: widget.h, - xPosition: widget.x, - yPosition: widget.y, - }; - - if (page) { - page.pos.push(widgetData); - } else { - placeHolder.push({ - pageNumber, - pos: [widgetData], - }); - } - } - - return { - signerObjId: signer?.contactPtr?.objectId, - signerPtr: signer?.contactPtr, - Role: signer.role, - Id: randomId(), - blockColor: color[signer?.index], - placeHolder, - }; - }); - object.set('Placeholders', updatePlaceholders); - } - if (folderId) { - object.set('Folder', { - __type: 'Pointer', - className: 'contracts_Document', - objectId: folderId, - }); - } - const newACL = new Parse.ACL(); - newACL.setPublicReadAccess(false); - newACL.setPublicWriteAccess(false); - newACL.setReadAccess(userPtr.objectId, true); - newACL.setWriteAccess(userPtr.objectId, true); - object.setACL(newACL); - const res = await object.save(null, { useMasterKey: true }); - const doc = { - objectId: res.id, - file: fileUrl, - name: name, - note: note || '', - description: description || '', - signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })), - createdAt: res.createdAt, - }; - if (parseExtUser && parseExtUser.Webhook) { - sendDoctoWebhook(doc, parseExtUser?.Webhook, userPtr?.objectId); - } - const newDate = new Date(); - newDate.setDate(newDate.getDate() + 15); - const localExpireDate = newDate.toLocaleDateString('en-US', { - day: 'numeric', - month: 'long', - year: 'numeric', - }); - let sender = parseExtUser.Email; - if (send_email === false) { - console.log("don't send mail"); - } else { - let contactMail = contact; - if (sendInOrder) { - contactMail = contact.slice(); - contactMail.splice(1); - } - for (let i = 0; i < contactMail.length; i++) { - try { - const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; - let url = `${cloudServerUrl}/functions/sendmailv3/`; - const headers = { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': process.env.APP_ID, - 'X-Parse-Master-Key': process.env.MASTER_KEY, - }; - - const objectId = contactMail[i].contactPtr.objectId; - const hostUrl = baseUrl.origin; - //encode this url value `${response.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function - const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`); - let signPdf = `${hostUrl}/login/${encodeBase64}`; - const openSignUrl = 'https://www.opensignlabs.com/contact-us'; - const orgName = parseExtUser.Company ? parseExtUser.Company : ''; - const themeBGcolor = '#47a3ad'; - const email_html = - "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" + - imgPng + - " height='50' style='padding:20px; width:170px; height:40px;' /></div> <div style='padding:2px; font-family: system-ui;background-color:" + - themeBGcolor + - ";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " + - parseExtUser.Name + - ' has requested you to review and sign <strong> ' + - name + - "</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" + - sender + - "</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " + - orgName + - "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + - localExpireDate + - "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + - signPdf + - "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + - sender + - ' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' + - openSignUrl + - ' target=_blank>here</a>.</p> </div></div></body> </html>'; - let replaceVar; - const variables = { - document_title: name, - sender_name: parseExtUser.Name, - sender_mail: parseExtUser.Email, - sender_phone: parseExtUser?.Phone || '', - receiver_name: contactMail[i].name, - receiver_email: contactMail[i].email, - receiver_phone: contactMail[i]?.phone || '', - expiry_date: localExpireDate, - company_name: orgName, - signing_url: signPdf, - }; - if (email_subject && email_body) { - replaceVar = replaceMailVaribles(email_subject, email_body, variables); - } else if (email_subject) { - replaceVar = replaceMailVaribles(email_subject, '', variables); - replaceVar = { subject: replaceVar.subject, body: email_html }; - } else if (email_body) { - replaceVar = replaceMailVaribles( - `${parseExtUser.Name} has requested you to sign "${name}"`, - email_body, - variables - ); - } else { - replaceVar = { - subject: `${parseExtUser.Name} has requested you to sign "${name}"`, - body: email_html, + for (const widget of signer.widgets) { + const pageNumber = widget.page; + const options = formatWidgetOptions(widget.type, widget.options); + const page = placeHolder.find(page => page.pageNumber === pageNumber); + const widgetData = { + isStamp: widget.type === 'stamp' || widget.type === 'image', + key: randomId(), + isDrag: false, + scale: 1, + isMobile: false, + zIndex: 1, + type: widget.type === 'textbox' ? 'text input' : widget.type, + options: options, + Width: widget.w, + Height: widget.h, + xPosition: widget.x, + yPosition: widget.y, }; + + if (page) { + page.pos.push(widgetData); + } else { + placeHolder.push({ pageNumber, pos: [widgetData] }); + } } - const subject = replaceVar.subject; - const html = replaceVar.body; - let params = { - recipient: contactMail[i].email, - subject: subject, - from: sender, - html: html, - extUserId: extUser.id, + return { + signerObjId: signer?.contactPtr?.objectId, + signerPtr: signer?.contactPtr, + Role: signer.role, + Id: randomId(), + blockColor: color[signer?.index], + placeHolder, }; + }); + object.set('Placeholders', updatePlaceholders); + } + if (folderId) { + object.set('Folder', { + __type: 'Pointer', + className: 'contracts_Document', + objectId: folderId, + }); + } + const newACL = new Parse.ACL(); + newACL.setPublicReadAccess(false); + newACL.setPublicWriteAccess(false); + newACL.setReadAccess(userPtr.objectId, true); + newACL.setWriteAccess(userPtr.objectId, true); + object.setACL(newACL); + const res = await object.save(null, { useMasterKey: true }); + const doc = { + objectId: res.id, + file: fileUrl, + name: name, + note: note || '', + description: description || '', + signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })), + createdAt: res.createdAt, + }; + if (parseExtUser && parseExtUser.Webhook) { + sendDoctoWebhook(doc, parseExtUser?.Webhook, userPtr?.objectId); + } + const newDate = new Date(); + newDate.setDate(newDate.getDate() + 15); + const localExpireDate = newDate.toLocaleDateString('en-US', { + day: 'numeric', + month: 'long', + year: 'numeric', + }); + let sender = parseExtUser.Email; + if (send_email === false) { + console.log("don't send mail"); + } else { + let contactMail = contact; + if (sendInOrder) { + contactMail = contact.slice(); + contactMail.splice(1); + } + for (let i = 0; i < contactMail.length; i++) { + try { + const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; + let url = `${cloudServerUrl}/functions/sendmailv3/`; + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': process.env.APP_ID, + 'X-Parse-Master-Key': process.env.MASTER_KEY, + }; - await axios.post(url, params, { headers: headers }); - } catch (error) { - console.log('error', error); + const objectId = contactMail[i].contactPtr.objectId; + const hostUrl = baseUrl.origin; + //encode this url value `${response.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function + const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`); + let signPdf = `${hostUrl}/login/${encodeBase64}`; + const openSignUrl = 'https://www.opensignlabs.com/contact-us'; + const orgName = parseExtUser.Company ? parseExtUser.Company : ''; + const themeBGcolor = '#47a3ad'; + const email_html = + "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" + + imgPng + + " height='50' style='padding:20px; width:170px; height:40px;' /></div> <div style='padding:2px; font-family: system-ui;background-color:" + + themeBGcolor + + ";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " + + parseExtUser.Name + + ' has requested you to review and sign <strong> ' + + name + + "</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" + + sender + + "</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " + + orgName + + "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + + localExpireDate + + "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + + signPdf + + "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + + sender + + ' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' + + openSignUrl + + ' target=_blank>here</a>.</p> </div></div></body> </html>'; + let replaceVar; + const variables = { + document_title: name, + sender_name: parseExtUser.Name, + sender_mail: parseExtUser.Email, + sender_phone: parseExtUser?.Phone || '', + receiver_name: contactMail[i].name, + receiver_email: contactMail[i].email, + receiver_phone: contactMail[i]?.phone || '', + expiry_date: localExpireDate, + company_name: orgName, + signing_url: signPdf, + }; + if (email_subject && email_body) { + replaceVar = replaceMailVaribles(email_subject, email_body, variables); + } else if (email_subject) { + replaceVar = replaceMailVaribles(email_subject, '', variables); + replaceVar = { subject: replaceVar.subject, body: email_html }; + } else if (email_body) { + replaceVar = replaceMailVaribles( + `${parseExtUser.Name} has requested you to sign "${name}"`, + email_body, + variables + ); + } else { + replaceVar = { + subject: `${parseExtUser.Name} has requested you to sign "${name}"`, + body: email_html, + }; + } + const subject = replaceVar.subject; + const html = replaceVar.body; + + let params = { + recipient: contactMail[i].email, + subject: subject, + from: sender, + html: html, + extUserId: extUser.id, + }; + + await axios.post(url, params, { headers: headers }); + } catch (error) { + console.log('error', error); + } } } - } - // if (sendMail.data.result.status === 'success') { - if (request.posthog) { - request.posthog?.capture({ - distinctId: parseUser.userId.email, - event: 'api_create_document', - properties: { response_code: 200 }, + // if (sendMail.data.result.status === 'success') { + if (request.posthog) { + request.posthog?.capture({ + distinctId: parseUser.userId.email, + event: 'api_create_document', + properties: { response_code: 200 }, + }); + } + const updateApiCount = + resSub?.get('AllowedApis') && resSub.get('AllowedApis') > 0 + ? resSub.get('AllowedApis') - 1 + : 0; + const subscriptionCls = new Parse.Object('contracts_Subscriptions'); + subscriptionCls.id = resSub.id; + subscriptionCls.set('AllowedApis', updateApiCount); + const resSubcription = await subscriptionCls.save(null, { useMasterKey: true }); + // console.log('resSubcription ', resSubcription); + return response.json({ + objectId: res.id, + signurl: contact.map(x => ({ + email: x.email, + url: `${baseUrl.origin}/login/${btoa( + `${res.id}/${x.email}/${x.contactPtr.objectId}` + )}`, + })), + message: 'Document sent successfully!', }); + // } + } else { + if (request.posthog) { + request.posthog?.capture({ + distinctId: parseUser.userId.email, + event: 'api_create_document', + properties: { response_code: 400 }, + }); + } + return response.status(400).json({ error: 'Please provide signers!' }); } - return response.json({ - objectId: res.id, - signurl: contact.map(x => ({ - email: x.email, - url: `${baseUrl.origin}/login/${btoa(`${res.id}/${x.email}/${x.contactPtr.objectId}`)}`, - })), - message: 'Document sent successfully!', - }); - // } } else { - if (request.posthog) { - request.posthog?.capture({ - distinctId: parseUser.userId.email, - event: 'api_create_document', - properties: { response_code: 400 }, - }); - } - return response.status(400).json({ error: 'Please provide signers!' }); + return response + .status(429) + .json({ error: 'Quota reached. Please buy API credits and try again later.' }); } } else { return response.status(405).json({ error: 'Invalid API Token!' }); diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index 9672e33bd..fc83e124e 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -62,6 +62,8 @@ import getOrgAdmins from './parsefunction/getOrgAdmins.js'; import getAllUserTeamByOrg from './parsefunction/getAllUserTeamByOrg.js'; import AllowedUsers from './parsefunction/AlllowedUsers.js'; import Buyaddon from './parsefunction/BuyAddon.js'; +import AllowedApis from './parsefunction/AllowedApis.js'; +import BuyApis from './parsefunction/BuyApis.js'; // This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic. Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); @@ -133,3 +135,5 @@ Parse.Cloud.define('getorgadmins', getOrgAdmins); Parse.Cloud.define('getalluserteambyorg', getAllUserTeamByOrg); Parse.Cloud.define('allowedusers', AllowedUsers); Parse.Cloud.define('buyaddon', Buyaddon); +Parse.Cloud.define('allowedapis', AllowedApis); +Parse.Cloud.define('buyapis', BuyApis); diff --git a/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js b/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js new file mode 100644 index 000000000..878d1f099 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js @@ -0,0 +1,35 @@ +export default async function AllowedApis(request) { + if (!request?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } + try { + const extUser = new Parse.Query('contracts_Users'); + extUser.equalTo('UserId', { + __type: 'Pointer', + className: '_User', + objectId: request.user.id, + }); + const resExtUser = await extUser.first({ useMasterKey: true }); + if (resExtUser) { + const _resExtUser = JSON.parse(JSON.stringify(resExtUser)); + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: _resExtUser.TenantId.objectId, + }); + subscription.include('ExtUserPtr'); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const _resSub = JSON.parse(JSON.stringify(resSub)); + const allowedapis = _resSub?.AllowedApis || 0; + return allowedapis; + } + } + } catch (err) { + console.log('err in allowedapis', err); + const code = err?.code || 400; + const msg = err?.message || 'Something went wrong.'; + throw new Parse.Error(code, msg); + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/BuyApis.js b/apps/OpenSignServer/cloud/parsefunction/BuyApis.js new file mode 100644 index 000000000..2c0584642 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/BuyApis.js @@ -0,0 +1,87 @@ +import axios from 'axios'; +export default async function BuyApis(request) { + const apis = request.params.apis; + if (!request?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } + if (apis) { + try { + const extUser = new Parse.Query('contracts_Users'); + extUser.equalTo('UserId', { + __type: 'Pointer', + className: '_User', + objectId: request.user.id, + }); + const resExtUser = await extUser.first({ useMasterKey: true }); + if (resExtUser) { + const _resExtUser = JSON.parse(JSON.stringify(resExtUser)); + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: _resExtUser.TenantId.objectId, + }); + subscription.include('ExtUserPtr'); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const _resSub = JSON.parse(JSON.stringify(resSub)); + // Define the URL + const url = 'https://accounts.zoho.in/oauth/v2/token'; + + // Convert the data to x-www-form-urlencoded format + const formData = new URLSearchParams(); + formData.append('refresh_token', process.env.ZOHO_REFRESH_TOKEN); + formData.append('client_id', process.env.ZOHO_CLIENT_ID); + formData.append('client_secret', process.env.ZOHO_CLIENT_SECRET); + formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI); + formData.append('grant_type', 'refresh_token'); + + const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + // Make the POST request using Axios + const res = await axios.post(url, formData, { headers }); + if (res.data.access_token) { + const subscriptionId = _resSub.SubscriptionId; + const plan_code = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.plan_code; + const quantity = parseInt(apis); + const addoncode = 'api-signatures'; + const data = JSON.stringify({ + plan: { plan_code: plan_code }, + addons: [ + { addon_code: addoncode, addon_description: 'API Signatures', quantity: quantity }, + ], + }); + const apiUrl = `https://www.zohoapis.in/billing/v1/subscriptions/${subscriptionId}/buyonetimeaddon`; + const resAPis = await axios.post(apiUrl, data, { + headers: { + Authorization: 'Zoho-oauthtoken ' + res.data.access_token, + 'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID, + }, + }); + // console.log('resAPis ', resAPis.data); + if (resAPis.data) { + const updateSub = new Parse.Object('contracts_Subscriptions'); + updateSub.id = resSub.id; + // updateSub.set('AllowedApis', quantity); + const resupdateSub = await updateSub.save(null, { useMasterKey: true }); + // console.log('resupdateSub ', resupdateSub); + return { status: 'success', addon: quantity }; + } + } else { + throw new Parse.Error('400', 'Invalid access token.'); + } + } + } + } catch (err) { + const code = err?.response?.data?.code || err?.response?.status || err?.code || 400; + const msg = + err?.response?.data?.error || + err?.response?.data || + err?.message || + 'Something went wrong.'; + console.log('err in buyaddon', code, msg); + throw new Parse.Error(code, msg); + } + } else { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide parameters.'); + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/getapitoken.js b/apps/OpenSignServer/cloud/parsefunction/getapitoken.js index ac78e8ab0..b76217f85 100644 --- a/apps/OpenSignServer/cloud/parsefunction/getapitoken.js +++ b/apps/OpenSignServer/cloud/parsefunction/getapitoken.js @@ -1,29 +1,24 @@ -import axios from 'axios'; -import { cloudServerUrl } from '../../Utils.js'; export default async function getapitoken(request) { - const serverUrl = cloudServerUrl; //process.env.SERVER_URL; try { - const userRes = await axios.get(serverUrl + '/users/me', { - headers: { - 'X-Parse-Application-Id': process.env.APP_ID, - 'X-Parse-Session-Token': request.headers['sessiontoken'], - }, - }); - const userId = userRes.data && userRes.data.objectId; - if (userId) { + if (request?.user) { + const userId = request?.user?.id; const tokenQuery = new Parse.Query('appToken'); tokenQuery.equalTo('userId', { __type: 'Pointer', className: '_User', objectId: userId }); const res = await tokenQuery.first({ useMasterKey: true }); if (res) { return { status: 'success', result: res.get('token') }; + } else { + return { error: 'api token found.' }; } + } else { + return { error: 'Invalid session token.' }; } } catch (err) { console.log('Err in getapitoken', err); if (err.code == 209) { - return { error: 'Invalid session token' }; + return { error: 'Invalid session token.' }; } else { - return { error: "You don't have access!" }; + return { error: "You don't have access." }; } } } From 5d89f724f4be24dc887650d409d5b2f4ebb87ff1 Mon Sep 17 00:00:00 2001 From: prafull-opensignlabs <prafull.navkar@nxglabs.com> Date: Tue, 20 Aug 2024 15:56:00 +0530 Subject: [PATCH 2/5] feat: validtion of quicksend --- .../public/locales/en/translation.json | 6 +- .../public/locales/fr/translation.json | 7 +- apps/OpenSign/src/components/AddUser.js | 17 +- apps/OpenSign/src/components/BulkSendUi.js | 330 ++++++++++++------ apps/OpenSign/src/pages/GenerateToken.js | 32 +- .../src/primitives/GetReportDisplay.js | 16 +- .../v1/routes/CreateDocumentWithTemplate.js | 2 +- .../v1/routes/createDocumentwithCoordinate.js | 2 +- apps/OpenSignServer/cloud/main.js | 9 +- .../cloud/parsefunction/AlllowedUsers.js | 59 ++-- .../cloud/parsefunction/AllowedApis.js | 2 + .../cloud/parsefunction/AllowedQuicksend.js | 37 ++ .../cloud/parsefunction/BuyAddon.js | 113 ------ .../cloud/parsefunction/BuyAddonUsers.js | 124 +++++++ .../cloud/parsefunction/BuyApis.js | 4 +- .../cloud/parsefunction/BuyQuicksend.js | 89 +++++ 16 files changed, 561 insertions(+), 288 deletions(-) create mode 100644 apps/OpenSignServer/cloud/parsefunction/AllowedQuicksend.js delete mode 100644 apps/OpenSignServer/cloud/parsefunction/BuyAddon.js create mode 100644 apps/OpenSignServer/cloud/parsefunction/BuyAddonUsers.js create mode 100644 apps/OpenSignServer/cloud/parsefunction/BuyQuicksend.js diff --git a/apps/OpenSign/public/locales/en/translation.json b/apps/OpenSign/public/locales/en/translation.json index a2481a50d..bce494e2c 100644 --- a/apps/OpenSign/public/locales/en/translation.json +++ b/apps/OpenSign/public/locales/en/translation.json @@ -608,5 +608,9 @@ "buyapiaddon":"Buy APIs", "quantityofapis" :"Quantity of APIs", "additionalapis":"Please purchase add-on APIs", - "remainingapis":"Remaining APIs:" + "remainingapis": "Remaining APIs Credits:", + "additional-quicksend": "Please purchase add-on quick send", + "quantityofquicksend": "Quantity of Quick send", + "quotaerrquicksend": "Quota Reached, You don't have enough credits.", + "buycredits": "Buy credits" } \ No newline at end of file diff --git a/apps/OpenSign/public/locales/fr/translation.json b/apps/OpenSign/public/locales/fr/translation.json index 7bcd7e372..ead8ea1b3 100644 --- a/apps/OpenSign/public/locales/fr/translation.json +++ b/apps/OpenSign/public/locales/fr/translation.json @@ -608,5 +608,10 @@ "buyapiaddon":"Acheter des API", "quantityofapis":"Quantité d'API", "additionalapis":"Veuillez acheter des API complémentaires", - "remainingapis":"API restantes :" + "remainingapis":"API restantes Crédits:", + "additional-quicksend":"Veuillez acheter le module complémentaire quick send", + "quantityofquicksend": "Quantité de Quick send", + "quotaerrquicksend": "Quota atteint, vous n'avez pas assez de crédits.", + "buycredits": "Acheter des crédits" + } diff --git a/apps/OpenSign/src/components/AddUser.js b/apps/OpenSign/src/components/AddUser.js index 6a1d21c78..f1c77eb1c 100644 --- a/apps/OpenSign/src/components/AddUser.js +++ b/apps/OpenSign/src/components/AddUser.js @@ -52,9 +52,6 @@ const AddUser = (props) => { const getTeamList = async () => { if (isEnableSubscription) { - const extUser = - localStorage.getItem("Extand_Class") && - JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; try { setIsLoader(true); const resSub = await fetchSubscriptionInfo(); @@ -70,9 +67,7 @@ const AddUser = (props) => { price: resSub.price, totalPrice: resSub.price + resSub.totalPrice })); - const res = await Parse.Cloud.run("allowedusers", { - tenantId: extUser?.TenantId?.objectId - }); + const res = await Parse.Cloud.run("allowedusers"); if (props.setFormHeader) { if (res > 0) { props.setFormHeader(t("add-user")); @@ -304,13 +299,9 @@ const AddUser = (props) => { e.preventDefault(); e.stopPropagation(); setIsFormLoader(true); - const extUser = - localStorage.getItem("Extand_Class") && - JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; try { - const resAddon = await Parse.Cloud.run("buyaddon", { - users: amount.quantity, - tenantId: extUser?.TenantId?.objectId + const resAddon = await Parse.Cloud.run("buyaddonusers", { + users: amount.quantity }); if (resAddon) { const _resAddon = JSON.parse(JSON.stringify(resAddon)); @@ -352,7 +343,7 @@ const AddUser = (props) => { allowedUser < 2 ? "op-text-accent" : "op-text-primary" } font-medium ml-1`} > - {allowedUser} of {planInfo.totalAllowedUser} + {allowedUser} {t("of")} {planInfo.totalAllowedUser} </span> </p> <div className="mb-3"> diff --git a/apps/OpenSign/src/components/BulkSendUi.js b/apps/OpenSign/src/components/BulkSendUi.js index dfeea9921..0ca90d39e 100644 --- a/apps/OpenSign/src/components/BulkSendUi.js +++ b/apps/OpenSign/src/components/BulkSendUi.js @@ -3,7 +3,8 @@ import axios from "axios"; import SuggestionInput from "./shared/fields/SuggestionInput"; import Loader from "../primitives/Loader"; import { useTranslation } from "react-i18next"; - +import Parse from "parse"; +import ModalUi from "../primitives/ModalUi"; const BulkSendUi = (props) => { const { t } = useTranslation(); const [forms, setForms] = useState([]); @@ -13,6 +14,16 @@ const BulkSendUi = (props) => { const [isSubmit, setIsSubmit] = useState(false); const [allowedForm, setAllowedForm] = useState(0); const [isSignatureExist, setIsSignatureExist] = useState(); + const [isBulkAvailable, setIsBulkAvailable] = useState(false); + const quantityList = [500, 1000, 5000, 50000]; + const [amount, setAmount] = useState({ + price: (75.0).toFixed(2), + quantity: 500, + totalPrice: 0, + priceperbulksend: 0.15, + totalQuickSend: 0 + }); + const [isQuotaReached, setIsQuotaReached] = useState(false); const allowedSigners = 50; useEffect(() => { signatureExist(); @@ -20,14 +31,26 @@ const BulkSendUi = (props) => { }, []); //function to check atleast one signature field exist - const signatureExist = () => { - const getPlaceholder = props.item?.Placeholders; - const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) => - placeholderObj?.placeHolder?.some((holder) => - holder?.pos?.some((posItem) => posItem?.type === "signature") - ) - ); - setIsSignatureExist(checkIsSignatureExistt); + const signatureExist = async () => { + setIsSubmit(true); + try { + const allowedquicksend = await Parse.Cloud.run("allowedquicksend"); + if (allowedquicksend > 0) { + setIsBulkAvailable(true); + const getPlaceholder = props.item?.Placeholders; + const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) => + placeholderObj?.placeHolder?.some((holder) => + holder?.pos?.some((posItem) => posItem?.type === "signature") + ) + ); + setIsSignatureExist(checkIsSignatureExistt); + } + setAmount((obj) => ({ ...obj, totalQuickSend: allowedquicksend })); + setIsSubmit(false); + } catch (err) { + setIsSubmit(false); + console.log("Err", err); + } }; useEffect(() => { if (scrollOnNextUpdate && formRef.current) { @@ -76,29 +99,33 @@ const BulkSendUi = (props) => { const handleAddForm = (e) => { e.preventDefault(); // Check if the quick send limit has been reached - if (forms?.length < allowedForm) { - if (props?.Placeholders.length > 0) { - let newForm = []; - props?.Placeholders?.forEach((element) => { - if (!element.signerObjId) { - newForm = [ - ...newForm, - { - fieldId: element.Id, - email: "", - label: element.Role, - signer: {} - } - ]; - } - }); - setForms([...forms, { Id: formId, fields: newForm }]); + if (forms.length <= amount.totalQuickSend) { + if (forms?.length < allowedForm) { + if (props?.Placeholders.length > 0) { + let newForm = []; + props?.Placeholders?.forEach((element) => { + if (!element.signerObjId) { + newForm = [ + ...newForm, + { + fieldId: element.Id, + email: "", + label: element.Role, + signer: {} + } + ]; + } + }); + setForms([...forms, { Id: formId, fields: newForm }]); + } + setFormId(formId + 1); + setScrollOnNextUpdate(true); + } else { + // If the limit has been reached, throw an error with the appropriate message + alert(t("quick-send-alert-4")); } - setFormId(formId + 1); - setScrollOnNextUpdate(true); } else { - // If the limit has been reached, throw an error with the appropriate message - alert(t("quick-send-alert-4")); + setIsQuotaReached(true); } }; @@ -162,13 +189,9 @@ const BulkSendUi = (props) => { : [...existSigner] }); } else { - Documents.push({ - ...props.item, - Placeholders: updatedPlaceholders - }); + Documents.push({ ...props.item, Placeholders: updatedPlaceholders }); } }); - //console.log("Documents ", Documents); await batchQuery(Documents); }; @@ -180,9 +203,7 @@ const BulkSendUi = (props) => { "X-Parse-Application-Id": localStorage.getItem("parseAppId"), sessionToken: localStorage.getItem("accesstoken") }; - const params = { - Documents: JSON.stringify(Documents) - }; + const params = { Documents: JSON.stringify(Documents) }; try { const res = await axios.post(functionsUrl, params, { headers: headers }); // console.log("res ", res); @@ -196,7 +217,48 @@ const BulkSendUi = (props) => { setIsSubmit(false); } }; - + const handleAddOnQuickSubmit = async (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsSubmit(true); + try { + const resAddon = await Parse.Cloud.run("buyquicksend", { + quicksend: amount.quantity + }); + if (resAddon) { + const _resAddon = JSON.parse(JSON.stringify(resAddon)); + if (_resAddon.status === "success") { + setIsBulkAvailable(true); + handleCloseQuotaReached(); + setAmount((obj) => ({ + ...obj, + quantity: 500, + priceperapi: 0.15, + price: (75.0).toFixed(2), + totalapis: _resAddon.addon + })); + } + } + } catch (err) { + console.log("Err in buy addon", err); + alert(t("something-went-wrong-mssg")); + } finally { + setIsSubmit(false); + } + }; + const handlePricePerQuick = async (e) => { + const quantity = e.target?.value; + const price = + quantity > 0 + ? (Math.round(quantity * amount.priceperbulksend * 100) / 100).toFixed( + 2 + ) + : 500 * amount.priceperbulksend; + setAmount((prev) => ({ ...prev, quantity: quantity, price: price })); + }; + const handleCloseQuotaReached = () => { + setIsQuotaReached(false); + }; return ( <> {isSubmit && ( @@ -204,75 +266,141 @@ const BulkSendUi = (props) => { <Loader /> </div> )} - {props.Placeholders?.length > 0 ? ( - isSignatureExist ? ( - <> - {props.Placeholders?.some((x) => !x.signerObjId) ? ( - <form onSubmit={handleSubmit}> - <div className="min-h-max max-h-[250px] overflow-y-auto"> - {forms?.map((form, index) => ( - <div - key={form.Id} - className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative" - > - {form?.fields?.map((field, fieldIndex) => ( - <div className="flex flex-col" key={field.fieldId}> - <label>{field.label}</label> - <SuggestionInput - required - type="email" - value={field.value} - index={fieldIndex} - onChange={(signer) => - handleInputChange(index, signer, fieldIndex) - } - /> - </div> - ))} - {forms?.length > 1 && ( + {isBulkAvailable ? ( + <> + {props.Placeholders?.length > 0 ? ( + isSignatureExist ? ( + <> + {props.Placeholders?.some((x) => !x.signerObjId) ? ( + <div> + <form onSubmit={handleSubmit}> + <div className="min-h-max max-h-[250px] overflow-y-auto"> + {forms?.map((form, index) => ( + <div + key={form.Id} + className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative" + > + {form?.fields?.map((field, fieldIndex) => ( + <div + className="flex flex-col" + key={field.fieldId} + > + <label>{field.label}</label> + <SuggestionInput + required + type="email" + value={field.value} + index={fieldIndex} + onChange={(signer) => + handleInputChange(index, signer, fieldIndex) + } + /> + </div> + ))} + {forms?.length > 1 && ( + <button + onClick={() => handleRemoveForm(index)} + className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]" + > + <i className="fa-light fa-trash"></i> + </button> + )} + <div ref={formRef}></div> + </div> + ))} + </div> + <div className="flex flex-col mx-4 mb-4 gap-3"> <button - onClick={() => handleRemoveForm(index)} - className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]" + onClick={handleAddForm} + className="op-btn op-btn-primary focus:outline-none" > - <i className="fa-light fa-trash"></i> + <i className="fa-light fa-plus"></i>{" "} + <span>{t("add-new")}</span> </button> - )} - <div ref={formRef}></div> - </div> - ))} - </div> - <div className="flex flex-col mx-4 mb-4 gap-3"> - <button - onClick={handleAddForm} - className="op-btn op-btn-primary focus:outline-none" - > - <i className="fa-light fa-plus"></i>{" "} - <span>{t("add-new")}</span> - </button> - <button - type="submit" - className="op-btn op-btn-secondary focus:outline-none" - > - <i className="fa-light fa-paper-plane"></i>{" "} - <span>{t("send")}</span> - </button> - </div> - </form> + <button + type="submit" + className="op-btn op-btn-secondary focus:outline-none" + > + <i className="fa-light fa-paper-plane"></i>{" "} + <span>{t("send")}</span> + </button> + </div> + </form> + <ModalUi + isOpen={isQuotaReached} + handleClose={() => handleCloseQuotaReached()} + > + <div className="p-4 flex justify-center items-center flex-col gap-y-3"> + <p className="text-center text-base-content"> + {t("quotaerrquicksend")} + </p> + <button + onClick={() => setIsBulkAvailable(false)} + className=" op-btn op-btn-primary w-28" + > + {t("buycredits")} + </button> + </div> + </ModalUi> + </div> + ) : ( + <div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center"> + {t("quick-send-alert-1")} + </div> + )} + </> ) : ( <div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center"> - {t("quick-send-alert-1")} + {t("quick-send-alert-2")} </div> - )} - </> - ) : ( - <div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center"> - {t("quick-send-alert-2")} - </div> - ) + ) + ) : ( + <div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center"> + {t("quick-send-alert-3")} + </div> + )} + </> ) : ( - <div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center"> - {t("quick-send-alert-3")} - </div> + <form onSubmit={handleAddOnQuickSubmit} className="p-3"> + <p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium"> + {t("additional-quicksend")} + </p> + <div className="mb-3 flex justify-between"> + <label + htmlFor="quantity" + className="block text-xs text-gray-700 font-semibold" + > + {t("quantityofquicksend")} + <span className="text-[red] text-[13px]">*</span> + </label> + <select + value={amount.quantity} + onChange={(e) => handlePricePerQuick(e)} + name="quantity" + className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs" + required + > + {quantityList.length > 0 && + quantityList.map((x) => ( + <option key={x} value={x}> + {x} + </option> + ))} + </select> + </div> + <div className="mb-3 flex justify-between"> + <label className="block text-xs text-gray-700 font-semibold"> + {t("Price")} (1 * {amount.priceperbulksend}) + </label> + <div className="w-1/4 flex justify-center items-center text-sm"> + USD {amount.price} + </div> + </div> + <hr className="text-base-content mb-3" /> + <button className="op-btn op-btn-primary w-full mt-2"> + {t("Proceed")} + </button> + </form> )} </> ); diff --git a/apps/OpenSign/src/pages/GenerateToken.js b/apps/OpenSign/src/pages/GenerateToken.js index 89e896c0a..0611aa238 100644 --- a/apps/OpenSign/src/pages/GenerateToken.js +++ b/apps/OpenSign/src/pages/GenerateToken.js @@ -27,12 +27,13 @@ function GenerateToken() { const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); const [isTour, setIsTour] = useState(false); const [amount, setAmount] = useState({ - quantity: 1, + quantity: 500, priceperapi: 0.15, totalapis: 0, - price: 0.15 + price: (75.0).toFixed(2) }); const [isFormLoader, setIsFormLoader] = useState(false); + const quantityList = [500, 1000, 5000, 50000]; useEffect(() => { fetchToken(); // eslint-disable-next-line @@ -49,14 +50,9 @@ function GenerateToken() { const subscribe = await checkIsSubscribed(); setIsSubscribe(subscribe); } - const extUser = - localStorage.getItem("Extand_Class") && - JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; const res = await Parse.Cloud.run("getapitoken"); if (res) { - const allowedapis = await Parse.Cloud.run("allowedapis", { - tenantId: extUser?.TenantId?.objectId - }); + const allowedapis = await Parse.Cloud.run("allowedapis"); setAmount((obj) => ({ ...obj, totalapis: allowedapis })); SetApiToken(res?.result); } @@ -119,7 +115,7 @@ function GenerateToken() { const price = quantity > 0 ? (Math.round(quantity * amount.priceperapi * 100) / 100).toFixed(2) - : 1 * amount.priceperapi; + : 500 * amount.priceperapi; setAmount((prev) => ({ ...prev, quantity: quantity, price: price })); }; const handleAddOnApiSubmit = async (e) => { @@ -137,7 +133,7 @@ function GenerateToken() { ...obj, quantity: 1, priceperapi: 0.15, - price: 0.15, + price: (75.0).toFixed(2), totalapis: _resAddon.addon })); } @@ -276,14 +272,20 @@ function GenerateToken() { {t("quantityofapis")} <span className="text-[red] text-[13px]"> *</span> </label> - <input - type="number" - name="quantity" + <select value={amount.quantity} onChange={(e) => handlePricePerAPIs(e)} - className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs" + name="quantity" + className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs" required - /> + > + {quantityList.length > 0 && + quantityList.map((x) => ( + <option key={x} value={x}> + {x} + </option> + ))} + </select> </div> <div className="mb-3 flex justify-between"> <label className="block text-xs text-gray-700 font-semibold"> diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 16564a45f..a445822ca 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -74,15 +74,7 @@ const ReportTable = (props) => { const extClass = Extand_Class && JSON.parse(Extand_Class); const startIndex = (currentPage - 1) * props.docPerPage; const { isMoreDocs, setIsNextRecord } = props; - // For loop is used to calculate page numbers visible below table - // Initialize pageNumbers using useMemo to avoid unnecessary re-creation - // const pageNumbers = useMemo(() => { - // const calculatedPageNumbers = []; - // for (let i = 1; i <= Math.ceil(props.List.length / props.docPerPage); i++) { - // calculatedPageNumbers.push(i); - // } - // return calculatedPageNumbers; - // }, [props.List, props.docPerPage]); + const getPaginationRange = () => { const totalPageNumbers = 7; // Adjust this value to show more/less page numbers const pages = []; @@ -449,7 +441,6 @@ const ReportTable = (props) => { } setReason(""); }; - const handleShare = (item) => { setActLoader({ [item.objectId]: true }); const host = window.location.origin; @@ -839,10 +830,7 @@ const ReportTable = (props) => { console.log("err in fetch template in bulk modal", err); setIsBulkSend({}); setIsAlert(true); - setAlertMsg({ - type: "danger", - message: t("something-went-wrong-mssg") - }); + setAlertMsg({ type: "danger", message: t("something-went-wrong-mssg") }); setTimeout(() => setIsAlert(false), 1500); } }; diff --git a/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js b/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js index e9e167036..7843f7f5f 100644 --- a/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js +++ b/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js @@ -393,7 +393,7 @@ export default async function createDocumentWithTemplate(request, response) { } else { return response .status(429) - .json({ error: 'Quota reached. Please buy API credits and try again later.' }); + .json({ error: 'Quota reached, Please buy API credits and try again later.' }); } } else { return response.status(405).json({ error: 'Invalid API Token!' }); diff --git a/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js b/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js index 0278ab811..ffa310bfd 100644 --- a/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js +++ b/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js @@ -413,7 +413,7 @@ export default async function createDocumentwithCoordinate(request, response) { } else { return response .status(429) - .json({ error: 'Quota reached. Please buy API credits and try again later.' }); + .json({ error: 'Quota reached, Please buy API credits and try again later.' }); } } else { return response.status(405).json({ error: 'Invalid API Token!' }); diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index fc83e124e..490c491d3 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -61,14 +61,15 @@ import updateTeam from './parsefunction/updateTeam.js'; import getOrgAdmins from './parsefunction/getOrgAdmins.js'; import getAllUserTeamByOrg from './parsefunction/getAllUserTeamByOrg.js'; import AllowedUsers from './parsefunction/AlllowedUsers.js'; -import Buyaddon from './parsefunction/BuyAddon.js'; +import BuyAddonUsers from './parsefunction/BuyAddonUsers.js'; import AllowedApis from './parsefunction/AllowedApis.js'; import BuyApis from './parsefunction/BuyApis.js'; +import AllowedQuicksend from './parsefunction/AllowedQuicksend.js'; +import BuyQuickSend from './parsefunction/BuyQuicksend.js'; // This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic. Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave); -// Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave); Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave); Parse.Cloud.afterSave('contracts_Teams', TeamsAftersave); Parse.Cloud.afterSave('contracts_Subscriptions', SubscriptionAftersave); @@ -134,6 +135,8 @@ Parse.Cloud.define('updateteam', updateTeam); Parse.Cloud.define('getorgadmins', getOrgAdmins); Parse.Cloud.define('getalluserteambyorg', getAllUserTeamByOrg); Parse.Cloud.define('allowedusers', AllowedUsers); -Parse.Cloud.define('buyaddon', Buyaddon); +Parse.Cloud.define('buyaddonusers', BuyAddonUsers); Parse.Cloud.define('allowedapis', AllowedApis); +Parse.Cloud.define('allowedquicksend', AllowedQuicksend); Parse.Cloud.define('buyapis', BuyApis); +Parse.Cloud.define('buyquicksend', BuyQuickSend); diff --git a/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js b/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js index 0866119d6..336bccabc 100644 --- a/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js +++ b/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js @@ -1,39 +1,50 @@ export default async function AllowedUsers(request) { - const tenantId = request.params.tenantId; if (!request?.user) { throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); } try { - const subscription = new Parse.Query('contracts_Subscriptions'); - subscription.equalTo('TenantId', { + const extUser = new Parse.Query('contracts_Users'); + extUser.equalTo('UserId', { __type: 'Pointer', - className: 'partners_Tenant', - objectId: tenantId, + className: '_User', + objectId: request.user.id, }); - subscription.include('ExtUserPtr'); - const resSub = await subscription.first({ useMasterKey: true }); - if (resSub) { - const _resSub = JSON.parse(JSON.stringify(resSub)); - const userCls = new Parse.Query('contracts_Users'); - userCls.equalTo('OrganizationId', { + const resExtUser = await extUser.first({ useMasterKey: true }); + if (resExtUser) { + const _resExtUser = JSON.parse(JSON.stringify(resExtUser)); + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { __type: 'Pointer', - className: 'contracts_Organizations', - objectId: _resSub.ExtUserPtr.OrganizationId.objectId, + className: 'partners_Tenant', + objectId: _resExtUser.TenantId.objectId, }); - userCls.notEqualTo('IsDisabled', true); - const count = await userCls.count({ useMasterKey: true }); - if (count > 0) { - const allowedUser = resSub.get('AllowedUsers') || 0; - const remainUsers = allowedUser - count; - if (remainUsers > 0) { - return remainUsers; + subscription.include('ExtUserPtr'); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const _resSub = JSON.parse(JSON.stringify(resSub)); + const userCls = new Parse.Query('contracts_Users'); + userCls.equalTo('OrganizationId', { + __type: 'Pointer', + className: 'contracts_Organizations', + objectId: _resSub.ExtUserPtr.OrganizationId.objectId, + }); + userCls.notEqualTo('IsDisabled', true); + const count = await userCls.count({ useMasterKey: true }); + if (count > 0) { + const allowedUser = resSub.get('AllowedUsers') || 0; + const remainUsers = allowedUser - count; + if (remainUsers > 0) { + return remainUsers; + } else { + return 0; + } } else { - return 0; + const alloweduser = resSub.get('AllowedUsers') || 0; + return alloweduser; } - } else { - const alloweduser = resSub.get('AllowedUsers') || 0; - return alloweduser; } + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); } } catch (err) { console.log('err in allowedusers', err); diff --git a/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js b/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js index 878d1f099..debecc649 100644 --- a/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js +++ b/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js @@ -25,6 +25,8 @@ export default async function AllowedApis(request) { const allowedapis = _resSub?.AllowedApis || 0; return allowedapis; } + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); } } catch (err) { console.log('err in allowedapis', err); diff --git a/apps/OpenSignServer/cloud/parsefunction/AllowedQuicksend.js b/apps/OpenSignServer/cloud/parsefunction/AllowedQuicksend.js new file mode 100644 index 000000000..ab69c55c2 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/AllowedQuicksend.js @@ -0,0 +1,37 @@ +export default async function AllowedQuicksend(request) { + if (!request?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } + try { + const extUser = new Parse.Query('contracts_Users'); + extUser.equalTo('UserId', { + __type: 'Pointer', + className: '_User', + objectId: request.user.id, + }); + const resExtUser = await extUser.first({ useMasterKey: true }); + if (resExtUser) { + const _resExtUser = JSON.parse(JSON.stringify(resExtUser)); + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: _resExtUser.TenantId.objectId, + }); + subscription.include('ExtUserPtr'); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const _resSub = JSON.parse(JSON.stringify(resSub)); + const allowedquicksend = _resSub?.AllowedQuicksend || 0; + return allowedquicksend; + } + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); + } + } catch (err) { + console.log('err in allowedapis', err); + const code = err?.code || 400; + const msg = err?.message || 'Something went wrong.'; + throw new Parse.Error(code, msg); + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/BuyAddon.js b/apps/OpenSignServer/cloud/parsefunction/BuyAddon.js deleted file mode 100644 index a47aed0d4..000000000 --- a/apps/OpenSignServer/cloud/parsefunction/BuyAddon.js +++ /dev/null @@ -1,113 +0,0 @@ -import axios from 'axios'; -export default async function Buyaddon(request) { - const tenantId = request.params.tenantId; - const users = request.params.users; - if (!request?.user) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); - } - if (users && tenantId) { - try { - const subscription = new Parse.Query('contracts_Subscriptions'); - subscription.equalTo('TenantId', { - __type: 'Pointer', - className: 'partners_Tenant', - objectId: tenantId, - }); - subscription.include('ExtUserPtr'); - const resSub = await subscription.first({ useMasterKey: true }); - if (resSub) { - const _resSub = JSON.parse(JSON.stringify(resSub)); - // Define the URL - const url = 'https://accounts.zoho.in/oauth/v2/token'; - - // Convert the data to x-www-form-urlencoded format - const formData = new URLSearchParams(); - formData.append('refresh_token', process.env.ZOHO_REFRESH_TOKEN); - formData.append('client_id', process.env.ZOHO_CLIENT_ID); - formData.append('client_secret', process.env.ZOHO_CLIENT_SECRET); - formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI); - formData.append('grant_type', 'refresh_token'); - - const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; - // Make the POST request using Axios - const res = await axios.post(url, formData, { headers }); - if (res.data.access_token) { - const subscriptionId = _resSub.SubscriptionId; - const price = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.price; - const plan_code = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.plan_code; - const addonsArr = _resSub?.SubscriptionDetails?.data?.subscription?.addons || []; - let addon = 0; - if (addonsArr?.length > 0) { - let allowedUsersMonthly = 0; - let allowedUsersYearly = 0; - addonsArr?.forEach(item => { - if (item.addon_code === 'extra-teams-users-monthly') { - allowedUsersMonthly += item.quantity; - } else if (item.addon_code === 'extra-teams-users-yearly') { - allowedUsersYearly += item.quantity; - } else if (item.addon_code === 'extra-users') { - allowedUsersMonthly += item.quantity; - } - }); - if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) { - addon = allowedUsersMonthly + allowedUsersYearly; - } - } - const quantity = parseInt(users) + parseInt(addon); - const addoncode = plan_code.includes('yearly') - ? 'extra-teams-users-yearly' - : 'extra-teams-users-monthly'; - const data = JSON.stringify({ - plan: { plan_code: plan_code }, - addons: [ - { - addon_code: addoncode, - addon_description: 'Extra users', - price: price, - quantity: quantity, - }, - ], - }); - await axios.put( - 'https://www.zohoapis.in/billing/v1/subscriptions/' + subscriptionId, - data, - { - headers: { - Authorization: 'Zoho-oauthtoken ' + res.data.access_token, - 'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID, - }, - } - ); - const hostedpage_id = _resSub.SubscriptionDetails.hostedpage_id; - const userData = await axios.get( - 'https://www.zohoapis.in/billing/v1/hostedpages/' + hostedpage_id, - { - headers: { - Authorization: 'Zoho-oauthtoken ' + res.data.access_token, - 'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID, - }, - } - ); - const allowedUsers = quantity + 1; - const updateSub = new Parse.Object('contracts_Subscriptions'); - updateSub.id = resSub.id; - updateSub.set('SubscriptionDetails', userData.data); - updateSub.set('AllowedUsers', allowedUsers); - const resupdateSub = await updateSub.save(null, { useMasterKey: true }); - return { status: 'success', addon: allowedUsers }; - } else { - throw new Parse.Error('400', 'Invalid access token.'); - } - } - } catch (err) { - const code = err?.response?.data?.code || err?.response?.status || err?.code || 400; - const msg = - err?.response?.data?.error || - err?.response?.data || - err?.message || - 'Something went wrong.'; - console.log('err in buyaddon', code, msg); - throw new Parse.Error(code, msg); - } - } -} diff --git a/apps/OpenSignServer/cloud/parsefunction/BuyAddonUsers.js b/apps/OpenSignServer/cloud/parsefunction/BuyAddonUsers.js new file mode 100644 index 000000000..d97617f4a --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/BuyAddonUsers.js @@ -0,0 +1,124 @@ +import axios from 'axios'; +export default async function BuyAddonUsers(request) { + const users = request.params.users; + if (!request?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } + if (users) { + try { + const extUser = new Parse.Query('contracts_Users'); + extUser.equalTo('UserId', { + __type: 'Pointer', + className: '_User', + objectId: request.user.id, + }); + const resExtUser = await extUser.first({ useMasterKey: true }); + if (resExtUser) { + const _resExtUser = JSON.parse(JSON.stringify(resExtUser)); + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: _resExtUser.TenantId.objectId, + }); + subscription.include('ExtUserPtr'); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const _resSub = JSON.parse(JSON.stringify(resSub)); + // Define the URL + const url = 'https://accounts.zoho.in/oauth/v2/token'; + + // Convert the data to x-www-form-urlencoded format + const formData = new URLSearchParams(); + formData.append('refresh_token', process.env.ZOHO_REFRESH_TOKEN); + formData.append('client_id', process.env.ZOHO_CLIENT_ID); + formData.append('client_secret', process.env.ZOHO_CLIENT_SECRET); + formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI); + formData.append('grant_type', 'refresh_token'); + + const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + // Make the POST request using Axios + const res = await axios.post(url, formData, { headers }); + if (res.data.access_token) { + const subscriptionId = _resSub.SubscriptionId; + const price = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.price; + const plan_code = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.plan_code; + const addonsArr = _resSub?.SubscriptionDetails?.data?.subscription?.addons || []; + let addon = 0; + if (addonsArr?.length > 0) { + let allowedUsersMonthly = 0; + let allowedUsersYearly = 0; + addonsArr?.forEach(item => { + if (item.addon_code === 'extra-teams-users-monthly') { + allowedUsersMonthly += item.quantity; + } else if (item.addon_code === 'extra-teams-users-yearly') { + allowedUsersYearly += item.quantity; + } else if (item.addon_code === 'extra-users') { + allowedUsersMonthly += item.quantity; + } + }); + if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) { + addon = allowedUsersMonthly + allowedUsersYearly; + } + } + const quantity = parseInt(users) + parseInt(addon); + const addoncode = plan_code.includes('yearly') + ? 'extra-teams-users-yearly' + : 'extra-teams-users-monthly'; + const data = JSON.stringify({ + plan: { plan_code: plan_code }, + addons: [ + { + addon_code: addoncode, + addon_description: 'Extra users', + price: price, + quantity: quantity, + }, + ], + }); + await axios.put( + 'https://www.zohoapis.in/billing/v1/subscriptions/' + subscriptionId, + data, + { + headers: { + Authorization: 'Zoho-oauthtoken ' + res.data.access_token, + 'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID, + }, + } + ); + const hostedpage_id = _resSub.SubscriptionDetails.hostedpage_id; + const userData = await axios.get( + 'https://www.zohoapis.in/billing/v1/hostedpages/' + hostedpage_id, + { + headers: { + Authorization: 'Zoho-oauthtoken ' + res.data.access_token, + 'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID, + }, + } + ); + const allowedUsers = quantity + 1; + const updateSub = new Parse.Object('contracts_Subscriptions'); + updateSub.id = resSub.id; + updateSub.set('SubscriptionDetails', userData.data); + updateSub.set('AllowedUsers', allowedUsers); + const resupdateSub = await updateSub.save(null, { useMasterKey: true }); + return { status: 'success', addon: allowedUsers }; + } else { + throw new Parse.Error('400', 'Invalid access token.'); + } + } + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); + } + } catch (err) { + const code = err?.response?.data?.code || err?.response?.status || err?.code || 400; + const msg = + err?.response?.data?.error || + err?.response?.data || + err?.message || + 'Something went wrong.'; + console.log('err in buyaddon', code, msg); + throw new Parse.Error(code, msg); + } + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/BuyApis.js b/apps/OpenSignServer/cloud/parsefunction/BuyApis.js index 3e31458e5..8cc53d8d2 100644 --- a/apps/OpenSignServer/cloud/parsefunction/BuyApis.js +++ b/apps/OpenSignServer/cloud/parsefunction/BuyApis.js @@ -72,6 +72,8 @@ export default async function BuyApis(request) { throw new Parse.Error('400', 'Invalid access token.'); } } + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); } } catch (err) { const code = err?.response?.data?.code || err?.response?.status || err?.code || 400; @@ -80,7 +82,7 @@ export default async function BuyApis(request) { err?.response?.data || err?.message || 'Something went wrong.'; - console.log('err in buyaddon', code, msg); + console.log('err in Buyaddonusers', code, msg); throw new Parse.Error(code, msg); } } else { diff --git a/apps/OpenSignServer/cloud/parsefunction/BuyQuicksend.js b/apps/OpenSignServer/cloud/parsefunction/BuyQuicksend.js new file mode 100644 index 000000000..7abcc4dde --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/BuyQuicksend.js @@ -0,0 +1,89 @@ +import axios from 'axios'; +export default async function BuyQuickSend(request) { + const apis = request.params.quicksend; + if (!request?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } + if (apis) { + try { + const extUser = new Parse.Query('contracts_Users'); + extUser.equalTo('UserId', { + __type: 'Pointer', + className: '_User', + objectId: request.user.id, + }); + const resExtUser = await extUser.first({ useMasterKey: true }); + if (resExtUser) { + const _resExtUser = JSON.parse(JSON.stringify(resExtUser)); + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: _resExtUser.TenantId.objectId, + }); + subscription.include('ExtUserPtr'); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const _resSub = JSON.parse(JSON.stringify(resSub)); + // Define the URL + const url = 'https://accounts.zoho.in/oauth/v2/token'; + + // Convert the data to x-www-form-urlencoded format + const formData = new URLSearchParams(); + formData.append('refresh_token', process.env.ZOHO_REFRESH_TOKEN); + formData.append('client_id', process.env.ZOHO_CLIENT_ID); + formData.append('client_secret', process.env.ZOHO_CLIENT_SECRET); + formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI); + formData.append('grant_type', 'refresh_token'); + + const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + // Make the POST request using Axios + const res = await axios.post(url, formData, { headers }); + if (res.data.access_token) { + const subscriptionId = _resSub.SubscriptionId; + const plan_code = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.plan_code; + const quantity = parseInt(apis); + const addoncode = 'bulk-signatures'; + const data = JSON.stringify({ + plan: { plan_code: plan_code }, + addons: [ + { addon_code: addoncode, addon_description: 'Bulk Signatures', quantity: quantity }, + ], + }); + const apiUrl = `https://www.zohoapis.in/billing/v1/subscriptions/${subscriptionId}/buyonetimeaddon`; + const resAPis = await axios.post(apiUrl, data, { + headers: { + Authorization: 'Zoho-oauthtoken ' + res.data.access_token, + 'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID, + }, + }); + if (resAPis.data) { + const existQuickSend = _resSub?.AllowedQuicksend ? _resSub.AllowedQuicksend : 0; + const allowedQuicksend = existQuickSend + quantity; + const updateSub = new Parse.Object('contracts_Subscriptions'); + updateSub.id = resSub.id; + updateSub.set('AllowedQuicksend', allowedQuicksend); + const resupdateSub = await updateSub.save(null, { useMasterKey: true }); + return { status: 'success', addon: allowedQuicksend }; + } + } else { + throw new Parse.Error('400', 'Invalid access token.'); + } + } + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); + } + } catch (err) { + const code = err?.response?.data?.code || err?.response?.status || err?.code || 400; + const msg = + err?.response?.data?.error || + err?.response?.data || + err?.message || + 'Something went wrong.'; + console.log('err in buyaddon', code, msg); + throw new Parse.Error(code, msg); + } + } else { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide parameters.'); + } +} From 523d210503d0c36c012582e06b5fb3f63d9e6e58 Mon Sep 17 00:00:00 2001 From: prafull-opensignlabs <prafull.navkar@nxglabs.com> Date: Wed, 21 Aug 2024 12:57:08 +0530 Subject: [PATCH 3/5] fix: quicksend validation --- .../public/locales/en/translation.json | 2 +- .../public/locales/fr/translation.json | 2 +- apps/OpenSign/src/components/BulkSendUi.js | 55 +++-- apps/OpenSign/src/constant/const.js | 14 +- apps/OpenSign/src/pages/GenerateToken.js | 7 +- .../src/primitives/GetReportDisplay.js | 4 +- .../cloud/parsefunction/createBatchDocs.js | 218 +++++++++++------- 7 files changed, 188 insertions(+), 114 deletions(-) diff --git a/apps/OpenSign/public/locales/en/translation.json b/apps/OpenSign/public/locales/en/translation.json index bce494e2c..bb0531210 100644 --- a/apps/OpenSign/public/locales/en/translation.json +++ b/apps/OpenSign/public/locales/en/translation.json @@ -605,7 +605,7 @@ "user-name-exist": "user name already exist", "format": "format", "select-date-format": "Select a date format", - "buyapiaddon":"Buy APIs", + "buyapiaddon":"Buy APIs Credits", "quantityofapis" :"Quantity of APIs", "additionalapis":"Please purchase add-on APIs", "remainingapis": "Remaining APIs Credits:", diff --git a/apps/OpenSign/public/locales/fr/translation.json b/apps/OpenSign/public/locales/fr/translation.json index ead8ea1b3..8cf77b179 100644 --- a/apps/OpenSign/public/locales/fr/translation.json +++ b/apps/OpenSign/public/locales/fr/translation.json @@ -605,7 +605,7 @@ "Add-seats": "Ajouter des sièges", "format": "format", "select-date-format": "Sélectionnez un format de date", - "buyapiaddon":"Acheter des API", + "buyapiaddon":"Acheter des API Crédits", "quantityofapis":"Quantité d'API", "additionalapis":"Veuillez acheter des API complémentaires", "remainingapis":"API restantes Crédits:", diff --git a/apps/OpenSign/src/components/BulkSendUi.js b/apps/OpenSign/src/components/BulkSendUi.js index 0ca90d39e..9e6a4b657 100644 --- a/apps/OpenSign/src/components/BulkSendUi.js +++ b/apps/OpenSign/src/components/BulkSendUi.js @@ -5,6 +5,7 @@ import Loader from "../primitives/Loader"; import { useTranslation } from "react-i18next"; import Parse from "parse"; import ModalUi from "../primitives/ModalUi"; +import { isEnableSubscription } from "../constant/const"; const BulkSendUi = (props) => { const { t } = useTranslation(); const [forms, setForms] = useState([]); @@ -32,24 +33,36 @@ const BulkSendUi = (props) => { //function to check atleast one signature field exist const signatureExist = async () => { - setIsSubmit(true); - try { - const allowedquicksend = await Parse.Cloud.run("allowedquicksend"); - if (allowedquicksend > 0) { - setIsBulkAvailable(true); - const getPlaceholder = props.item?.Placeholders; - const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) => - placeholderObj?.placeHolder?.some((holder) => - holder?.pos?.some((posItem) => posItem?.type === "signature") - ) - ); - setIsSignatureExist(checkIsSignatureExistt); + if (isEnableSubscription) { + setIsSubmit(true); + try { + const allowedquicksend = await Parse.Cloud.run("allowedquicksend"); + if (allowedquicksend > 0) { + setIsBulkAvailable(true); + const getPlaceholder = props.item?.Placeholders; + const checkIsSignatureExistt = getPlaceholder?.every( + (placeholderObj) => + placeholderObj?.placeHolder?.some((holder) => + holder?.pos?.some((posItem) => posItem?.type === "signature") + ) + ); + setIsSignatureExist(checkIsSignatureExistt); + } + setAmount((obj) => ({ ...obj, totalQuickSend: allowedquicksend })); + setIsSubmit(false); + } catch (err) { + setIsSubmit(false); + console.log("Err", err); } - setAmount((obj) => ({ ...obj, totalQuickSend: allowedquicksend })); - setIsSubmit(false); - } catch (err) { - setIsSubmit(false); - console.log("Err", err); + } else { + setIsBulkAvailable(true); + const getPlaceholder = props.item?.Placeholders; + const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) => + placeholderObj?.placeHolder?.some((holder) => + holder?.pos?.some((posItem) => posItem?.type === "signature") + ) + ); + setIsSignatureExist(checkIsSignatureExistt); } }; useEffect(() => { @@ -99,7 +112,9 @@ const BulkSendUi = (props) => { const handleAddForm = (e) => { e.preventDefault(); // Check if the quick send limit has been reached - if (forms.length <= amount.totalQuickSend) { + if (isEnableSubscription && forms.length >= amount.totalQuickSend) { + setIsQuotaReached(true); + } else { if (forms?.length < allowedForm) { if (props?.Placeholders.length > 0) { let newForm = []; @@ -124,8 +139,6 @@ const BulkSendUi = (props) => { // If the limit has been reached, throw an error with the appropriate message alert(t("quick-send-alert-4")); } - } else { - setIsQuotaReached(true); } }; @@ -319,7 +332,7 @@ const BulkSendUi = (props) => { </button> <button type="submit" - className="op-btn op-btn-secondary focus:outline-none" + className="op-btn op-btn-accent focus:outline-none" > <i className="fa-light fa-paper-plane"></i>{" "} <span>{t("send")}</span> diff --git a/apps/OpenSign/src/constant/const.js b/apps/OpenSign/src/constant/const.js index 8b91c2589..3796e3e46 100644 --- a/apps/OpenSign/src/constant/const.js +++ b/apps/OpenSign/src/constant/const.js @@ -4,12 +4,12 @@ export const documentCls = "contracts_Document"; export const themeColor = "#47a3ad"; export const iconColor = "#686968"; export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; -export const isEnableSubscription = - process.env.REACT_APP_ENABLE_SUBSCRIPTION && - process.env.REACT_APP_ENABLE_SUBSCRIPTION?.toLowerCase() === "true" - ? true - : false; -export const isStaging = - window.location.origin === "https://staging-app.opensignlabs.com"; +export const isEnableSubscription = false; +// process.env.REACT_APP_ENABLE_SUBSCRIPTION && +// process.env.REACT_APP_ENABLE_SUBSCRIPTION?.toLowerCase() === "true" +// ? true +// : false; +export const isStaging = "http://localhost:3001"; +// window.location.origin === "https://staging-app.opensignlabs.com"; export const isPublicStaging = window.location.origin === "https://staging.opensign.me"; diff --git a/apps/OpenSign/src/pages/GenerateToken.js b/apps/OpenSign/src/pages/GenerateToken.js index 0611aa238..c63539370 100644 --- a/apps/OpenSign/src/pages/GenerateToken.js +++ b/apps/OpenSign/src/pages/GenerateToken.js @@ -249,10 +249,10 @@ function GenerateToken() { </div> </div> </ModalUi> - {/* buy apis */} + {/* buy APIs */} <ModalUi isOpen={isModal.buyapis} - title={"Addon apis"} + title={"Buy Addon APIs"} handleClose={handleBuyAPIsModal} > {isFormLoader && ( @@ -261,9 +261,6 @@ function GenerateToken() { </div> )} <form onSubmit={handleAddOnApiSubmit} className="p-3"> - <p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium"> - {t("additionalapis")} - </p> <div className="mb-3 flex justify-between"> <label htmlFor="quantity" diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index a445822ca..983d213bd 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -782,13 +782,13 @@ const ReportTable = (props) => { if (count > 1) { setAlertMsg({ type: "success", - message: count + t("document-sent-alert") + message: count + " " + t("document-sent-alert") }); setTimeout(() => setIsAlert(false), 1500); } else { setAlertMsg({ type: "success", - message: count + t("document-sent-alert") + message: count + " " + t("document-sent-alert") }); setTimeout(() => setIsAlert(false), 1500); } diff --git a/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js b/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js index 3cb789e19..a78c434d6 100644 --- a/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js +++ b/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js @@ -3,7 +3,35 @@ import { cloudServerUrl } from '../../Utils.js'; const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const appId = process.env.APP_ID; - +async function deductcount(tenantId, docs) { + try { + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: tenantId, + }); + subscription.include('ExtUserPtr'); + subscription.greaterThan('AllowedQuicksend', 0); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const updateApiCount = + resSub?.get('AllowedQuicksend') && resSub.get('AllowedQuicksend') > 0 + ? resSub.get('AllowedQuicksend') - docs + : 0; + const subscriptionCls = new Parse.Object('contracts_Subscriptions'); + subscriptionCls.id = resSub.id; + if (updateApiCount > 0) { + subscriptionCls.set('AllowedQuicksend', updateApiCount); + } else { + subscriptionCls.set('AllowedQuicksend', 0); + } + const resSubcription = await subscriptionCls.save(null, { useMasterKey: true }); + } + } catch (err) { + console.log('Err in deduct in quick send', err); + } +} async function sendMail(document, sessionToken) { const baseUrl = new URL(process.env.PUBLIC_URL); @@ -90,7 +118,6 @@ export default async function createBatchDocs(request) { const sessionToken = request.headers['sessiontoken']; const Documents = JSON.parse(strDocuments); const Ip = request?.headers?.['x-real-ip'] || ''; - // console.log('Documents ', Documents); const parseConfig = { baseURL: serverUrl, //localStorage.getItem('baseUrl'), headers: { @@ -100,84 +127,121 @@ export default async function createBatchDocs(request) { }, }; try { - const requests = Documents.map(x => { - const Signers = x.Signers; - const allSigner = x?.Placeholders?.map( - item => Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr - ).filter(signer => Object.keys(signer).length > 0); - const date = new Date(); - const isoDate = date.toISOString(); - let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } }; - if (allSigner && allSigner.length > 0) { - allSigner.forEach(x => { - const obj = { [x.CreatedBy.objectId]: { read: true, write: true } }; - Acl = { ...Acl, ...obj }; - }); - } - return { - method: 'POST', - path: '/app/classes/contracts_Document', - body: { - Name: x.Name, - URL: x.URL, - Note: x.Note, - Description: x.Description, - CreatedBy: x.CreatedBy, - SendinOrder: x.SendinOrder || true, - ExtUserPtr: { - __type: 'Pointer', - className: x.ExtUserPtr.className, - objectId: x.ExtUserPtr.objectId, - }, - Placeholders: x.Placeholders.map(y => - y?.signerPtr?.objectId - ? { - ...y, - signerPtr: { - __type: 'Pointer', - className: 'contracts_Contactbook', - objectId: y.signerPtr.objectId, - }, - signerObjId: y.signerObjId, - } - : { ...y, signerPtr: {}, signerObjId: '' } - ), - SignedUrl: x.URL || x.SignedUrl, - SentToOthers: true, - Signers: allSigner?.map(y => ({ - __type: 'Pointer', - className: 'contracts_Contactbook', - objectId: y.objectId, - })), - ACL: Acl, - SentToOthers: true, - RemindOnceInEvery: x.RemindOnceInEvery || 5, - AutomaticReminders: x.AutomaticReminders || false, - TimeToCompleteDays: x.TimeToCompleteDays || 15, - OriginIp: Ip, - DocSentAt: { __type: 'Date', iso: isoDate }, - }, - }; + const user = await axios.get(serverUrl + '/users/me', { + headers: { + 'X-Parse-Application-Id': appId, + 'X-Parse-Session-Token': request.headers['sessiontoken'], + }, }); - // console.log('requests ', requests); + if (user.data) { + const extCls = new Parse.Query('contracts_Users'); + extCls.equalTo('UserId', { + __type: 'Pointer', + className: '_User', + objectId: user.data.objectId, + }); + const resExt = await extCls.first({ useMasterKey: true }); + if (resExt) { + const _resExt = JSON.parse(JSON.stringify(resExt)); + console.log('here'); + try { + const requests = Documents.map(x => { + const Signers = x.Signers; + const allSigner = x?.Placeholders?.map( + item => + Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr + ).filter(signer => Object.keys(signer).length > 0); + const date = new Date(); + const isoDate = date.toISOString(); + let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } }; + if (allSigner && allSigner.length > 0) { + allSigner.forEach(x => { + const obj = { [x.CreatedBy.objectId]: { read: true, write: true } }; + Acl = { ...Acl, ...obj }; + }); + } + return { + method: 'POST', + path: '/app/classes/contracts_Document', + body: { + Name: x.Name, + URL: x.URL, + Note: x.Note, + Description: x.Description, + CreatedBy: x.CreatedBy, + SendinOrder: x.SendinOrder || true, + ExtUserPtr: { + __type: 'Pointer', + className: x.ExtUserPtr.className, + objectId: x.ExtUserPtr.objectId, + }, + Placeholders: x.Placeholders.map(y => + y?.signerPtr?.objectId + ? { + ...y, + signerPtr: { + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: y.signerPtr.objectId, + }, + signerObjId: y.signerObjId, + } + : { ...y, signerPtr: {}, signerObjId: '' } + ), + SignedUrl: x.URL || x.SignedUrl, + SentToOthers: true, + Signers: allSigner?.map(y => ({ + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: y.objectId, + })), + ACL: Acl, + SentToOthers: true, + RemindOnceInEvery: x.RemindOnceInEvery || 5, + AutomaticReminders: x.AutomaticReminders || false, + TimeToCompleteDays: x.TimeToCompleteDays || 15, + OriginIp: Ip, + DocSentAt: { __type: 'Date', iso: isoDate }, + }, + }; + }); + // console.log('requests ', requests); - const response = await axios.post('batch', { requests: requests }, parseConfig); - // // Handle the batch query response - // console.log('Batch query response:', response.data); - if (response.data && response.data.length > 0) { - const updateDocuments = Documents.map((x, i) => ({ - ...x, - objectId: response.data[i]?.success?.objectId, - createdAt: response.data[i]?.success?.createdAt, - })); - for (let i = 0; i < updateDocuments.length; i++) { - sendMail(updateDocuments[i], sessionToken); + const response = await axios.post('batch', { requests: requests }, parseConfig); + // // Handle the batch query response + // console.log('Batch query response:', response.data); + if (response.data && response.data.length > 0) { + const updateDocuments = Documents.map((x, i) => ({ + ...x, + objectId: response.data[i]?.success?.objectId, + createdAt: response.data[i]?.success?.createdAt, + })); + deductcount(_resExt.TenantId.objectId, response.data.length); + for (let i = 0; i < updateDocuments.length; i++) { + sendMail(updateDocuments[i], sessionToken); + } + return 'success'; + } + + // Handle individual responses within response.data.results + } catch (error) { + const code = err?.response?.data?.code || err?.response?.status || err?.code || 400; + const msg = + err?.response?.data?.error || + err?.response?.data || + err?.message || + 'Something went wrong.'; + console.log('Error performing batch query:', code, msg); + throw new Parse.Error(code, msg); + } } - return 'success'; + } else { + new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); } - - // Handle individual responses within response.data.results - } catch (error) { - console.error('Error performing batch query:', error); + } catch (err) { + console.log('err in createbatchdoc', err); + const code = err?.code || 400; + const msg = err?.message || 'Something went wrong.'; + new Parse.Error(code, msg); } } From bf56348c625ddf84dba0ad58864e14b5268e4de5 Mon Sep 17 00:00:00 2001 From: prafull-opensignlabs <prafull.navkar@nxglabs.com> Date: Wed, 21 Aug 2024 14:39:36 +0530 Subject: [PATCH 4/5] fix: add users count --- apps/OpenSign/src/components/AddUser.js | 22 +++++----- apps/OpenSign/src/constant/const.js | 10 ++--- apps/OpenSignServer/cloud/main.js | 7 +++ .../cloud/parsefunction/AlllowedUsers.js | 9 +--- .../cloud/parsefunction/DocumentBeforesave.js | 2 +- .../cloud/parsefunction/ExtUserAfterdelete.js | 31 +++++++++++++ .../cloud/parsefunction/ExtUserAftersave.js | 44 +++++++++++++++++++ .../cloud/parsefunction/SubscribeFree.js | 11 +++++ .../cloud/parsefunction/createBatchDocs.js | 10 +++-- 9 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 apps/OpenSignServer/cloud/parsefunction/ExtUserAfterdelete.js create mode 100644 apps/OpenSignServer/cloud/parsefunction/ExtUserAftersave.js diff --git a/apps/OpenSign/src/components/AddUser.js b/apps/OpenSign/src/components/AddUser.js index f1c77eb1c..b849580a4 100644 --- a/apps/OpenSign/src/components/AddUser.js +++ b/apps/OpenSign/src/components/AddUser.js @@ -336,16 +336,18 @@ const AddUser = (props) => { <div className="w-full mx-auto"> {!isEnableSubscription || allowedUser > 0 ? ( <form onSubmit={handleSubmit}> - <p className="flex justify-center mb-1"> - {t("remaing-users")}{" "} - <span - className={`${ - allowedUser < 2 ? "op-text-accent" : "op-text-primary" - } font-medium ml-1`} - > - {allowedUser} {t("of")} {planInfo.totalAllowedUser} - </span> - </p> + {isEnableSubscription && ( + <p className="flex justify-center mb-1"> + {t("remaing-users")}{" "} + <span + className={`${ + allowedUser < 2 ? "op-text-accent" : "op-text-primary" + } font-medium ml-1`} + > + {allowedUser} {t("of")} {planInfo.totalAllowedUser} + </span> + </p> + )} <div className="mb-3"> <label htmlFor="name" diff --git a/apps/OpenSign/src/constant/const.js b/apps/OpenSign/src/constant/const.js index 3796e3e46..ad5ae7b53 100644 --- a/apps/OpenSign/src/constant/const.js +++ b/apps/OpenSign/src/constant/const.js @@ -4,11 +4,11 @@ export const documentCls = "contracts_Document"; export const themeColor = "#47a3ad"; export const iconColor = "#686968"; export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; -export const isEnableSubscription = false; -// process.env.REACT_APP_ENABLE_SUBSCRIPTION && -// process.env.REACT_APP_ENABLE_SUBSCRIPTION?.toLowerCase() === "true" -// ? true -// : false; +export const isEnableSubscription = + process.env.REACT_APP_ENABLE_SUBSCRIPTION && + process.env.REACT_APP_ENABLE_SUBSCRIPTION?.toLowerCase() === "true" + ? true + : false; export const isStaging = "http://localhost:3001"; // window.location.origin === "https://staging-app.opensignlabs.com"; export const isPublicStaging = diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index 490c491d3..fc4f61817 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -66,6 +66,8 @@ import AllowedApis from './parsefunction/AllowedApis.js'; import BuyApis from './parsefunction/BuyApis.js'; import AllowedQuicksend from './parsefunction/AllowedQuicksend.js'; import BuyQuickSend from './parsefunction/BuyQuicksend.js'; +import ExtUserAftersave from './parsefunction/ExtUserAftersave.js'; +import ExtUserAfterdelete from './parsefunction/ExtUserAfterdelete.js'; // This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic. Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); @@ -73,6 +75,8 @@ Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave); Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave); Parse.Cloud.afterSave('contracts_Teams', TeamsAftersave); Parse.Cloud.afterSave('contracts_Subscriptions', SubscriptionAftersave); +Parse.Cloud.afterSave('contracts_Users', ExtUserAftersave); + // This beforeSave function triggers before an object is added or updated in the specified class, allowing for validation or modification. Parse.Cloud.beforeSave('contracts_Document', DocumentBeforesave); Parse.Cloud.beforeSave('contracts_Template', TemplateBeforeSave); @@ -84,6 +88,9 @@ Parse.Cloud.afterFind('contracts_Template', TemplateAfterFind); Parse.Cloud.afterFind('contracts_Signature', SignatureAfterFind); Parse.Cloud.afterFind('partners_Tenant', TenantAterFind); +// This afterDelete function triggers after an object get deleted. +Parse.Cloud.afterDelete('contracts_Users', ExtUserAfterdelete); + // This define function creates a custom Cloud Function that can be called from the client-side, enabling custom business logic on the server. Parse.Cloud.define('signPdf', PDF); Parse.Cloud.define('sendmailv3', sendmailv3); diff --git a/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js b/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js index 336bccabc..bcca9951b 100644 --- a/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js +++ b/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js @@ -22,14 +22,7 @@ export default async function AllowedUsers(request) { const resSub = await subscription.first({ useMasterKey: true }); if (resSub) { const _resSub = JSON.parse(JSON.stringify(resSub)); - const userCls = new Parse.Query('contracts_Users'); - userCls.equalTo('OrganizationId', { - __type: 'Pointer', - className: 'contracts_Organizations', - objectId: _resSub.ExtUserPtr.OrganizationId.objectId, - }); - userCls.notEqualTo('IsDisabled', true); - const count = await userCls.count({ useMasterKey: true }); + const count = _resSub?.UsersCount || 0; if (count > 0) { const allowedUser = resSub.get('AllowedUsers') || 0; const remainUsers = allowedUser - count; diff --git a/apps/OpenSignServer/cloud/parsefunction/DocumentBeforesave.js b/apps/OpenSignServer/cloud/parsefunction/DocumentBeforesave.js index 3d9f85454..266148613 100644 --- a/apps/OpenSignServer/cloud/parsefunction/DocumentBeforesave.js +++ b/apps/OpenSignServer/cloud/parsefunction/DocumentBeforesave.js @@ -5,7 +5,7 @@ async function DocumentBeforesave(request) { const oldDocument = request.original; // Check if SignedUrl field has been added (transition from undefined to defined) - if (!oldDocument?.get('SignedUrl') && document?.get('SignedUrl')) { + if (oldDocument && !oldDocument?.get('SignedUrl') && document?.get('SignedUrl')) { // Update count in contracts_Users class const query = new Parse.Query('contracts_Users'); query.equalTo('objectId', oldDocument.get('ExtUserPtr').id); diff --git a/apps/OpenSignServer/cloud/parsefunction/ExtUserAfterdelete.js b/apps/OpenSignServer/cloud/parsefunction/ExtUserAfterdelete.js new file mode 100644 index 000000000..3a7fc4bbd --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/ExtUserAfterdelete.js @@ -0,0 +1,31 @@ +async function ExtUserAfterdelete(request) { + try { + const extUser = request.object; + const tenantId = extUser.get('TenantId')?.id; + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: tenantId, + }); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const updateSub = new Parse.Object('contracts_Subscriptions'); + updateSub.id = resSub.id; + if (resSub?.get('UsersCount') > 0) { + updateSub.decrement('UsersCount', 1); + } else { + updateSub.decrement('UsersCount', 0); + } + updateSub.save(null, { useMasterKey: true }); + } else { + const addSub = new Parse.Object('contracts_Subscriptions'); + addSub.set('UsersCount', 0); + addSub.save(null, { useMasterKey: true }); + } + } catch (err) { + console.log('err in extuser afterdelete', err); + } +} + +export default ExtUserAfterdelete; diff --git a/apps/OpenSignServer/cloud/parsefunction/ExtUserAftersave.js b/apps/OpenSignServer/cloud/parsefunction/ExtUserAftersave.js new file mode 100644 index 000000000..59a23b354 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/ExtUserAftersave.js @@ -0,0 +1,44 @@ +async function ExtUserAftersave(request) { + try { + if (!request.original) { + const extUser = request.object; + const tenantId = extUser.get('TenantId')?.id; + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: tenantId, + }); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const updateSub = new Parse.Object('contracts_Subscriptions'); + updateSub.id = resSub.id; + updateSub.increment('UsersCount', 1); + updateSub.save(null, { useMasterKey: true }); + } else { + const addSub = new Parse.Object('contracts_Subscriptions'); + addSub.set('UsersCount', 1); + addSub.set('ExtUserPtr', { + __type: 'Pointer', + className: 'contracts_Users', + objectId: extUser.id, + }); + addSub.set('CreatedBy', { + __type: 'Pointer', + className: '_User', + objectId: extUser.get('UserId').id, + }); + addSub.set('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: extUser.get('TenantId').id, + }); + addSub.save(null, { useMasterKey: true }); + } + } + } catch (err) { + console.log('err in extuser aftersave', err); + } +} + +export default ExtUserAftersave; diff --git a/apps/OpenSignServer/cloud/parsefunction/SubscribeFree.js b/apps/OpenSignServer/cloud/parsefunction/SubscribeFree.js index 4b0e53956..11bab60f9 100644 --- a/apps/OpenSignServer/cloud/parsefunction/SubscribeFree.js +++ b/apps/OpenSignServer/cloud/parsefunction/SubscribeFree.js @@ -29,6 +29,17 @@ export default async function SubscribeFree(request) { } } else if (subcripitions?.get('Next_billing_date') > new Date()) { return { status: 'success', result: 'already subscribed!' }; + } else if (!subcripitions?.get('PlanCode')) { + try { + const updateSubscription = new Parse.Object('contracts_Subscriptions'); + updateSubscription.id = subcripitions.id; + updateSubscription.set('PlanCode', 'freeplan'); + await updateSubscription.save(null, { useMasterKey: true }); + return { status: 'success', result: 'subscribed!' }; + } catch (err) { + console.log('err ', err); + return { status: 'error', result: err.message }; + } } else { try { const createSubscription = new Parse.Object('contracts_Subscriptions'); diff --git a/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js b/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js index a78c434d6..35e602cc3 100644 --- a/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js +++ b/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js @@ -3,7 +3,7 @@ import { cloudServerUrl } from '../../Utils.js'; const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const appId = process.env.APP_ID; -async function deductcount(tenantId, docs) { +async function deductcount(tenantId, docs, extUserId) { try { const subscription = new Parse.Query('contracts_Subscriptions'); subscription.equalTo('TenantId', { @@ -28,6 +28,11 @@ async function deductcount(tenantId, docs) { } const resSubcription = await subscriptionCls.save(null, { useMasterKey: true }); } + + const extCls = new Parse.Object('contracts_Users'); + extCls.id = extUserId; + extCls.increment('DocumentCount', docs); + const resExt = await extCls.save(null, { useMasterKey: true }); } catch (err) { console.log('Err in deduct in quick send', err); } @@ -143,7 +148,6 @@ export default async function createBatchDocs(request) { const resExt = await extCls.first({ useMasterKey: true }); if (resExt) { const _resExt = JSON.parse(JSON.stringify(resExt)); - console.log('here'); try { const requests = Documents.map(x => { const Signers = x.Signers; @@ -216,7 +220,7 @@ export default async function createBatchDocs(request) { objectId: response.data[i]?.success?.objectId, createdAt: response.data[i]?.success?.createdAt, })); - deductcount(_resExt.TenantId.objectId, response.data.length); + deductcount(_resExt.TenantId.objectId, response.data.length, resExt.id); for (let i = 0; i < updateDocuments.length; i++) { sendMail(updateDocuments[i], sessionToken); } From 937b306128265bf35a1b44daf0a0f8dcd2822df2 Mon Sep 17 00:00:00 2001 From: prafull-opensignlabs <prafull.navkar@nxglabs.com> Date: Wed, 21 Aug 2024 18:10:57 +0530 Subject: [PATCH 5/5] fix: remove unncessary query from buyaddonusers --- .../cloud/parsefunction/BuyAddonUsers.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/OpenSignServer/cloud/parsefunction/BuyAddonUsers.js b/apps/OpenSignServer/cloud/parsefunction/BuyAddonUsers.js index d97617f4a..3ca147f4a 100644 --- a/apps/OpenSignServer/cloud/parsefunction/BuyAddonUsers.js +++ b/apps/OpenSignServer/cloud/parsefunction/BuyAddonUsers.js @@ -76,7 +76,7 @@ export default async function BuyAddonUsers(request) { }, ], }); - await axios.put( + const updatedSubscription = await axios.put( 'https://www.zohoapis.in/billing/v1/subscriptions/' + subscriptionId, data, { @@ -86,20 +86,11 @@ export default async function BuyAddonUsers(request) { }, } ); - const hostedpage_id = _resSub.SubscriptionDetails.hostedpage_id; - const userData = await axios.get( - 'https://www.zohoapis.in/billing/v1/hostedpages/' + hostedpage_id, - { - headers: { - Authorization: 'Zoho-oauthtoken ' + res.data.access_token, - 'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID, - }, - } - ); + const subscriptionInfo = { data: updatedSubscription.data }; const allowedUsers = quantity + 1; const updateSub = new Parse.Object('contracts_Subscriptions'); updateSub.id = resSub.id; - updateSub.set('SubscriptionDetails', userData.data); + updateSub.set('SubscriptionDetails', subscriptionInfo); updateSub.set('AllowedUsers', allowedUsers); const resupdateSub = await updateSub.save(null, { useMasterKey: true }); return { status: 'success', addon: allowedUsers };