diff --git a/apps/OpenSign/public/locales/en/translation.json b/apps/OpenSign/public/locales/en/translation.json index 80dfbaed3..6077d59dd 100644 --- a/apps/OpenSign/public/locales/en/translation.json +++ b/apps/OpenSign/public/locales/en/translation.json @@ -606,6 +606,14 @@ "user-name-exist": "user name already exist", "format": "format", "select-date-format": "Select a date format", + "buyapiaddon":"Buy APIs Credits", + "quantityofapis" :"Quantity of APIs", + "additionalapis":"Please purchase add-on 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", "rotate-right":"Rotate right", "rotate-left":"Rotate left", "rotate-alert-mssg":"All widgets on this page will be lost. Are you sure you want to proceed?" diff --git a/apps/OpenSign/public/locales/fr/translation.json b/apps/OpenSign/public/locales/fr/translation.json index bffc22749..53b329712 100644 --- a/apps/OpenSign/public/locales/fr/translation.json +++ b/apps/OpenSign/public/locales/fr/translation.json @@ -1,3 +1,4 @@ + { "create-account": "Créer un compte", "login": "Se Connecter", @@ -198,7 +199,7 @@ "parent-folder": "Dossier Parent", "create": "Créer", "signers": "Signataires", - "signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.", + "signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.", "add-signer": "Ajouter un signataire", "contact-not-found": "Contact introuvable", "add-yourself": "Ajoutez-vous", @@ -253,11 +254,11 @@ "view": "voir", "make-template-public": "Rendre le modèle public", "make-template-private": "Rendre le modèle privé", - "make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?", + "make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?", "make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.", "public-role": "Rôle public", "public-url": "URL publique", - "public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.", + "public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.", "add-public-url-alert": "Veuillez ajouter votre URL publique et vous pourra rendre public modèle.", "share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.", "share-with": "Partager avec", @@ -275,14 +276,14 @@ "copy-link": "Copier le lien", "copy": "Copier", "revoke-document": "Révoquer le document", - "revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?", + "revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?", "resend-mail": "Renvoyer le courrier", - "resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.", + "resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.", "subject": "Sujet", "body": "Corps", "add-contact": "Ajouter le contact", "add-signer-alert": "Le contact existe déjà !", - "record-delete-alert": "Enregistrement supprimé avec succès !", + "record-delete-alert": "Enregistrement supprimé avec succès !", "record-revoke-alert": " Enregistrement révoqué avec succès ! ", "mail-sent-alert": "Courriel envoyé avec succès.", "document-sent-alert": "Document envoyé avec succès !", @@ -338,7 +339,7 @@ "finish": "terminé", "mail": "Mail", "sign-now": "Signez maintenant", - "successfully-signed": "Signé avec succès !", + "successfully-signed": "Signé avec succès !", "email-mssg": "Les destinataires ajoutés ici recevront une copie du document signé.", "email-error-1": "veuillez fournir une adresse e-mail correcte", "email-error-2": "Vous ne pouvez envoyer qu'à dix destinataires à la fois.", @@ -359,7 +360,7 @@ "signature-tab": "Signature", "your-signature": "Votre signature", "copy-to-all-pages": "Copier sur toutes les pages", - "contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?", + "contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?", "reset-password": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie", "reset-password-alert-2": "Veuillez configurer l'adaptateur de messagerie", "reset-password-alert-3": "Réinitialisez votre mot de passe", @@ -395,7 +396,7 @@ "create-document": "Créer un document", "template-created-alert": "Voulez-vous créer un document en utilisant le modèle que vous vient de créer ?", "signers-alert": "alerte des signataires", - "template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !", + "template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !", "ok": "D'accord", "radio-group": "Groupe de radio", "dropdown-options": "Options déroulantes", @@ -411,9 +412,9 @@ "hint": "Indice", "color": "couleur", "role-help": { - "p1": "Que sont les rôles modèles ?", + "p1": "Que sont les rôles modèles ?", "p2": "Commencez par spécifier chaque rôle nécessaire à la finalisation du document. Pensez aux parties impliquées dans le processus de signature et à leurs responsabilités. Les rôles courants incluent les RH pour les documents internes, le Client pour les accords ou le Fournisseur pour les accords commerciaux.", - "p3": "Pourquoi pré-attacher les utilisateurs à certains rôles ?", + "p3": "Pourquoi pré-attacher les utilisateurs à certains rôles ?", "p4": "Pour les rôles qui impliquent systématiquement la même personne (par exemple, la signature du PDG sur les lettres d'offre des employés), vous pouvez pré-attacher un utilisateur à un rôle dans le modèle. Cette étape est facultative mais recommandée pour des raisons d'efficacité et de cohérence entre les documents.", "p5": "Quand dois-je spécifier l'utilisateur attaché à chaque rôle ?", "p6": "Lorsque vous créez un document à partir de votre modèle, vous serez invité à attacher des utilisateurs à chaque rôle défini. Si un rôle est déjà associé à un utilisateur, celui-ci sera pré-rempli, mais vous pourrez le modifier si nécessaire avant d'envoyer le document." @@ -426,8 +427,8 @@ "placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ", "placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les", "placeholder-alert-5": "Voulez-vous signer des documents maintenant ?", - "placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !", - "placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !", + "placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !", + "placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !", "email-subject": "Sujet du courriel", "email-body": "corps de l'e-mail", "email-placeholder": "Ajouter le corps de l'e-mail", @@ -449,11 +450,11 @@ "get-otp-alert": "vous recevrez un mot de passe à usage unique par e-mail", "user-already-exist": "L'utilisateur existe déjà.", "server-error": "Erreur interne du serveur !", - "fill-required-details!": "Veuillez remplir les informations requises !", + "fill-required-details!": "Veuillez remplir les informations requises !", "sign-up-form": "Formulaire d'inscription", "sign-up": "S'inscrire", "folder-already-exist!": "le dossier existe déjà !", - "please-select-position!": "Veuillez sélectionner un poste !", + "please-select-position!": "Veuillez sélectionner un poste !", "default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les emplacements demandés ? ", "Your-Signature": "Votre signature", "auto-sign-all": "Tout signer automatiquement", @@ -461,10 +462,10 @@ "pdf-download": "Téléchargement PDF", "generate-certificate-alert": " Votre certificat d'achèvement est en cours de génération. Veuillez patienter quelques instants. Si le téléchargement ne démarre pas sous peu, cliquez à nouveau sur le bouton. ", "folder-already-exist": "Le dossier existe déjà !", - "folder-created-successfully": "Dossier créé avec succès !", + "folder-created-successfully": "Dossier créé avec succès !", "fill-folder-name": "Veuillez indiquer le nom du dossier", - "already-exists-this-username": "Un utilisateur existe déjà avec ce nom d'utilisateur !", - "verification-code-sent": "Un e-mail de vérification a été envoyé à votre adresse e-mail !", + "already-exists-this-username": "Un utilisateur existe déjà avec ce nom d'utilisateur !", + "verification-code-sent": "Un e-mail de vérification a été envoyé à votre adresse e-mail !", "registered-user-successfully": "Utilisateur enregistré avec succès.", "role-not-found": "Rôle introuvable.", "do-not-access": "Vous n'y avez pas accès, veuillez contacter l'administrateur.", @@ -513,14 +514,14 @@ "choose-password": "Choisissez un mot de passe", "enter-password": "Entrer le mot de passe", "sure": "bien sûr", - "subscribed-alert": "Vous êtes déjà abonné au forfait !", + "subscribed-alert": "Vous êtes déjà abonné au forfait !", "monthly": "Mensuel", - "yearly-upto": "Annuel (jusqu'à 66 % de réduction)", + "yearly-upto": "Annuel (jusqu'à 66 % de réduction)", "billed-yearly": "Facturé annuellement", "billed-monthly": "Facturé mensuellement", "plansubscription-1": "Hébergez-le vous-même gratuitement", "visit-github": "Visitez Github", - "user-already-exist-name": "Un utilisateur existe déjà avec ce nom d'utilisateur !", + "user-already-exist-name": "Un utilisateur existe déjà avec ce nom d'utilisateur !", "do-not-access-contact-admin": "Vous n'y avez pas accès, veuillez contacter l'administrateur.", "filed-required-correctly": "Veuillez remplir correctement les informations requises.", "admin-created": "Administrateur créé", @@ -548,9 +549,9 @@ "discord": "Discorde" }, "tour-mssg": { - "home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.", + "home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.", "home-layout-2": "Pour télécharger des documents à auto-signature ou pour demander la signature d'autres personnes, sélectionnez simplement les boutons correspondants.", - "home-layout-3": "Vous êtes prêt à commencer à utiliser OpenSign ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.", + "home-layout-3": "Vous êtes prêt à commencer à utiliser OpenSign ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.", "generate-token": "Mettez à niveau maintenant pour générer un jeton API", "opensign-drive-1": "Cliquez sur les liens du fil d'Ariane pour naviguer facilement dans la hiérarchie des dossiers et afficher les documents dans chaque dossier.", "opensign-drive-2": "Cliquez sur le bouton Ajouter pour créer un nouveau dossier ou document.", @@ -605,6 +606,14 @@ "Add-seats": "Ajouter des sièges", "format": "format", "select-date-format": "Sélectionnez un format de date", + "buyapiaddon":"Acheter des API Crédits", + "quantityofapis":"Quantité d'API", + "additionalapis":"Veuillez acheter des API complémentaires", + "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", "rotate-right" :"Faire pivoter à droite", "rotate-left" :"Faire pivoter à gauche", "rotate-alert-mssg" :"Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?" diff --git a/apps/OpenSign/src/components/AddUser.js b/apps/OpenSign/src/components/AddUser.js index 6a1d21c78..b849580a4 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)); @@ -345,16 +336,18 @@ const AddUser = (props) => {
{!isEnableSubscription || allowedUser > 0 ? (
-

- {t("remaing-users")}{" "} - - {allowedUser} of {planInfo.totalAllowedUser} - -

+ {isEnableSubscription && ( +

+ {t("remaing-users")}{" "} + + {allowedUser} {t("of")} {planInfo.totalAllowedUser} + +

+ )}
)} - {props.Placeholders?.length > 0 ? ( - isSignatureExist ? ( - <> - {props.Placeholders?.some((x) => !x.signerObjId) ? ( - -
- {forms?.map((form, index) => ( -
- {form?.fields?.map((field, fieldIndex) => ( -
- - - handleInputChange(index, signer, fieldIndex) - } - /> -
- ))} - {forms?.length > 1 && ( + {isBulkAvailable ? ( + <> + {props.Placeholders?.length > 0 ? ( + isSignatureExist ? ( + <> + {props.Placeholders?.some((x) => !x.signerObjId) ? ( +
+ +
+ {forms?.map((form, index) => ( +
+ {form?.fields?.map((field, fieldIndex) => ( +
+ + + handleInputChange(index, signer, fieldIndex) + } + /> +
+ ))} + {forms?.length > 1 && ( + + )} +
+
+ ))} +
+
- )} -
-
- ))} -
-
- - -
- + +
+ + handleCloseQuotaReached()} + > +
+

+ {t("quotaerrquicksend")} +

+ +
+
+
+ ) : ( +
+ {t("quick-send-alert-1")} +
+ )} + ) : (
- {t("quick-send-alert-1")} + {t("quick-send-alert-2")}
- )} - - ) : ( -
- {t("quick-send-alert-2")} -
- ) + ) + ) : ( +
+ {t("quick-send-alert-3")} +
+ )} + ) : ( -
- {t("quick-send-alert-3")} -
+
+

+ {t("additional-quicksend")} +

+
+ + +
+
+ +
+ USD {amount.price} +
+
+
+ +
)} ); diff --git a/apps/OpenSign/src/constant/const.js b/apps/OpenSign/src/constant/const.js index 8b91c2589..ad5ae7b53 100644 --- a/apps/OpenSign/src/constant/const.js +++ b/apps/OpenSign/src/constant/const.js @@ -9,7 +9,7 @@ export const isEnableSubscription = process.env.REACT_APP_ENABLE_SUBSCRIPTION?.toLowerCase() === "true" ? true : false; -export const isStaging = - window.location.origin === "https://staging-app.opensignlabs.com"; +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 1d2b73312..c63539370 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,21 @@ 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: 500, + priceperapi: 0.15, + totalapis: 0, + price: (75.0).toFixed(2) + }); + const [isFormLoader, setIsFormLoader] = useState(false); + const quantityList = [500, 1000, 5000, 50000]; useEffect(() => { fetchToken(); // eslint-disable-next-line @@ -38,15 +50,11 @@ 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 res = await Parse.Cloud.run("getapitoken"); if (res) { - SetApiToken(res.data?.result?.result); + const allowedapis = await Parse.Cloud.run("allowedapis"); + setAmount((obj) => ({ ...obj, totalapis: allowedapis })); + SetApiToken(res?.result); } setIsLoader(false); } catch (err) { @@ -61,7 +69,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 +100,53 @@ 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) + : 500 * amount.priceperapi; + setAmount((prev) => ({ ...prev, quantity: quantity, price: price })); + }; + const handleAddOnApiSubmit = async (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsFormLoader(true); + try { + const resAddon = await Parse.Cloud.run("buyapis", { + apis: amount.quantity + }); + if (resAddon) { + const _resAddon = JSON.parse(JSON.stringify(resAddon)); + if (_resAddon.status === "success") { + setAmount((obj) => ({ + ...obj, + quantity: 1, + priceperapi: 0.15, + price: (75.0).toFixed(2), + totalapis: _resAddon.addon + })); + } + } + setIsModal((obj) => ({ ...obj, buyapis: false })); + } 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 +161,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 +198,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 +211,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 +249,55 @@ function GenerateToken() { </div> </div> </ModalUi> + {/* buy APIs */} + <ModalUi + isOpen={isModal.buyapis} + title={"Buy 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"> + <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> + <select + value={amount.quantity} + onChange={(e) => handlePricePerAPIs(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.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/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index af057522f..169fe93d0 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; @@ -791,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); } @@ -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 513213aa4..7843f7f5f 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..ffa310bfd 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..fc4f61817 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -61,15 +61,22 @@ 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'; +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); 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); +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); @@ -81,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); @@ -132,4 +142,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..bcca9951b 100644 --- a/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js +++ b/apps/OpenSignServer/cloud/parsefunction/AlllowedUsers.js @@ -1,39 +1,43 @@ 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 count = _resSub?.UsersCount || 0; + 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 new file mode 100644 index 000000000..debecc649 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js @@ -0,0 +1,37 @@ +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; + } + } 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/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..3ca147f4a --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/BuyAddonUsers.js @@ -0,0 +1,115 @@ +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, + }, + ], + }); + const updatedSubscription = 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 subscriptionInfo = { data: updatedSubscription.data }; + const allowedUsers = quantity + 1; + const updateSub = new Parse.Object('contracts_Subscriptions'); + updateSub.id = resSub.id; + updateSub.set('SubscriptionDetails', subscriptionInfo); + 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 new file mode 100644 index 000000000..8cc53d8d2 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/BuyApis.js @@ -0,0 +1,91 @@ +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 existAllowedApis = _resSub?.AllowedApis ? _resSub.AllowedApis : 0; + const allowedapis = existAllowedApis + quantity; + const updateSub = new Parse.Object('contracts_Subscriptions'); + updateSub.id = resSub.id; + updateSub.set('AllowedApis', allowedapis); + const resupdateSub = await updateSub.save(null, { useMasterKey: true }); + // console.log('resupdateSub ', resupdateSub); + return { status: 'success', addon: allowedapis }; + } + } 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 Buyaddonusers', 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/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.'); + } +} 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 3cb789e19..35e602cc3 100644 --- a/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js +++ b/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js @@ -3,7 +3,40 @@ import { cloudServerUrl } from '../../Utils.js'; const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const appId = process.env.APP_ID; +async function deductcount(tenantId, docs, extUserId) { + 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 }); + } + 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); + } +} async function sendMail(document, sessionToken) { const baseUrl = new URL(process.env.PUBLIC_URL); @@ -90,7 +123,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 +132,120 @@ 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)); + 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, resExt.id); + 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); } } 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." }; } } }