From e95d180eb734e0081351fc2549ed0bb72056727e Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Fri, 18 Apr 2025 22:20:33 -0300 Subject: [PATCH] feat: update dependencies and improve file handling in GBVMService --- .gitignore | 1 + package-lock.json | 14 + package.json | 1 + packages/admin.gbapp/dialogs/AdminDialog.ts | 2 +- packages/basic.gblib/services/GBVMService.ts | 2 +- packages/saas.gbapp/dialog/NewUserDialog.ts | 148 +++-- packages/saas.gbapp/index.ts | 18 - packages/saas.gbapp/model/MainModel.ts | 17 +- .../saas.gbapp/service/JunoSubscription.ts | 579 ------------------ packages/saas.gbapp/service/MSSubscription.ts | 135 ---- packages/saas.gbapp/service/MainService.ts | 176 ++++-- src/util.ts | 6 +- vm-inject.js | 5 +- 13 files changed, 213 insertions(+), 891 deletions(-) delete mode 100755 packages/saas.gbapp/service/JunoSubscription.ts delete mode 100755 packages/saas.gbapp/service/MSSubscription.ts diff --git a/.gitignore b/.gitignore index 666dca81e..0871213cf 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ logo.svg screenshot.png data.db .wwebjs_cache +*doula* \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a9ded449f..cf5f1ee12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -165,6 +165,7 @@ "sqlite3": "5.1.7", "ssr-for-bots": "1.0.1-c", "strict-password-generator": "1.1.2", + "stripe": "^18.0.0", "svg2img": "^1.0.0-beta.2", "swagger-client": "3.29.2", "swagger-ui-dist": "5.17.14", @@ -41856,6 +41857,19 @@ "node": ">=0.8.0" } }, + "node_modules/stripe": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.0.0.tgz", + "integrity": "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA==", + "license": "MIT", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", diff --git a/package.json b/package.json index 434aefc46..b1fcaf76f 100644 --- a/package.json +++ b/package.json @@ -225,6 +225,7 @@ "sqlite3": "5.1.7", "ssr-for-bots": "1.0.1-c", "strict-password-generator": "1.1.2", + "stripe": "^18.0.0", "svg2img": "^1.0.0-beta.2", "swagger-client": "3.29.2", "swagger-ui-dist": "5.17.14", diff --git a/packages/admin.gbapp/dialogs/AdminDialog.ts b/packages/admin.gbapp/dialogs/AdminDialog.ts index 7a9a536ed..88733a505 100644 --- a/packages/admin.gbapp/dialogs/AdminDialog.ts +++ b/packages/admin.gbapp/dialogs/AdminDialog.ts @@ -47,7 +47,7 @@ import { SecService } from '../../security.gbapp/services/SecService.js'; import { GBConfigService } from '../../core.gbapp/services/GBConfigService.js'; import { GBServer } from '../../../src/app.js'; import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js'; -import { GBUtil } from 'src/util.js'; +import { GBUtil } from '../../../src/util.js'; class AdminDialog extends IGBDialog { diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index 46d3351f5..4d9415e15 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -205,7 +205,7 @@ export class GBVMService extends GBService { "author": "${min.botId} owner.", "license": "ISC", "dependencies": { - "yaml": "2.4.2", + "encoding": "0.1.13", "isomorphic-fetch": "3.0.0", "punycode": "2.1.1", diff --git a/packages/saas.gbapp/dialog/NewUserDialog.ts b/packages/saas.gbapp/dialog/NewUserDialog.ts index d26186279..4114cc7a2 100755 --- a/packages/saas.gbapp/dialog/NewUserDialog.ts +++ b/packages/saas.gbapp/dialog/NewUserDialog.ts @@ -1,3 +1,4 @@ +// BotServer/packages/saas.gbapp/dialog/NewUserDialog.ts /*****************************************************************************\ | █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® | | ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | @@ -38,18 +39,45 @@ import { CollectionUtil } from 'pragmatismo-io-framework'; import { GBOService } from '../service/GBOService.js'; export class NewUserDialog extends IGBDialog { + static getPlanSelectionDialog(min: GBMinInstance) { + return { + id: '/welcome_saas_plan', + waterfall: [ + async step => { + const locale = 'en-US'; + await step.context.sendActivity('Please choose your plan:'); + await step.context.sendActivity('1. Personal - $9.99/month (basic features)'); + await step.context.sendActivity('2. Professional - $29.99/month (advanced features)'); + return await step.prompt('textPrompt', 'Enter 1 or 2 to select your plan:'); + }, + async step => { + const planChoice = step.context.activity.text.trim(); + if (planChoice === '1') { + step.activeDialog.state.options.planId = 'personal'; + step.activeDialog.state.options.amount = 9.99; + } else if (planChoice === '2') { + step.activeDialog.state.options.planId = 'professional'; + step.activeDialog.state.options.amount = 29.99; + } else { + await step.context.sendActivity('Invalid choice. Please select 1 or 2.'); + return await step.replaceDialog('/welcome_saas_plan'); + } + return await step.replaceDialog('/welcome_saas_botname', step.activeDialog.state.options); + } + ] + }; + } + static getBotNameDialog(min: GBMinInstance) { return { id: '/welcome_saas_botname', waterfall: [ async step => { const locale = 'en-US'; - await step.prompt('textPrompt', Messages[locale].whats_botname); }, async step => { const locale = 'en-US'; - const extractEntity = text => { return text.match(/[_a-zA-Z][_a-zA-Z0-9]{0,16}/gi); }; @@ -58,17 +86,14 @@ export class NewUserDialog extends IGBDialog { if (value === null || value.length != 1) { await step.context.sendActivity(Messages[locale].validation_enter_valid_botname); - return await step.replaceDialog('/welcome_saas_botname', step.activeDialog.state.options); } else { const botName = value[0]; if (await min.deployService.botExists(botName)) { - await step.context.sendActivity(`O Bot ${botName} já existe. Escolha por favor, outro nome!`); - + await step.context.sendActivity(`The Bot ${botName} already exists. Please choose another name!`); return await step.replaceDialog('/welcome_saas_botname', step.activeDialog.state.options); } else { step.activeDialog.state.options.botName = botName; - return await step.replaceDialog('/welcome_saas_bottemplate', step.activeDialog.state.options); } } @@ -77,6 +102,38 @@ export class NewUserDialog extends IGBDialog { }; } + static getStripePaymentDialog(min: GBMinInstance) { + return { + id: '/welcome_saas_stripe_payment', + waterfall: [ + async step => { + const locale = 'en-US'; + await step.context.sendActivity(`Please enter your credit card details for the ${step.activeDialog.state.options.planId} plan ($${step.activeDialog.state.options.amount}/month):`); + return await step.prompt('textPrompt', 'Card number (e.g., 4242424242424242):'); + }, + async step => { + step.activeDialog.state.options.ccNumber = step.context.activity.text.trim(); + return await step.prompt('textPrompt', 'Expiration month (MM):'); + }, + async step => { + step.activeDialog.state.options.ccExpiresOnMonth = step.context.activity.text.trim(); + return await step.prompt('textPrompt', 'Expiration year (YYYY):'); + }, + async step => { + step.activeDialog.state.options.ccExpiresOnYear = step.context.activity.text.trim(); + return await step.prompt('textPrompt', 'CVC:'); + }, + async step => { + step.activeDialog.state.options.ccSecuritycode = step.context.activity.text.trim(); + await step.context.sendActivity('Processing payment...'); + await NewUserDialog.createBot(step, min, false); + return await step.replaceDialog('/ask', { isReturning: true }); + } + ] + }; + } + + static getBotTemplateDialog(min: GBMinInstance) { return { id: '/welcome_saas_bottemplate', @@ -176,36 +233,10 @@ export class NewUserDialog extends IGBDialog { }; } - static getVoucherDialog(min: GBMinInstance) { - return { - id: '/welcome_saas_voucher', - waterfall: [ - async step => { - const locale = 'en-US'; - await step.prompt('textPrompt', Messages[locale].own_voucher); - }, - async step => { - const locale = 'en-US'; - - if (step.result.toLowerCase() === 'gb2020') { - await NewUserDialog.createBot(step, min, true); - - return await step.replaceDialog('/ask', { isReturning: true }); - } else { - // return await step.replaceDialog('/welcome_saas_voucher', 'Os meios de pagamento estão neste momento desabilitados, por favor informe um voucher ou contate info@pragmatismo.com.br.'); - - step.activeDialog.state.options.nextDialog = 'welcome_saas_return_document'; - return await step.replaceDialog('/xrm_document', step.activeDialog.state.options); - } - } - ] - }; - } - private static async createBot(step: any, min: GBMinInstance, free: boolean) { const locale = 'en-US'; await step.context.sendActivity(Messages[locale].ok_procceding_creation); - const url = `https://gb.pragmatismo.com.br/${step.activeDialog.state.options.botName}`; + const url = `${process.env.BOT_ID}/${step.activeDialog.state.options.botName}`; await step.context.sendActivity(Messages[locale].bot_created(url)); const service = new MainService(); await service.createSubscription( @@ -220,49 +251,10 @@ export class NewUserDialog extends IGBDialog { step.activeDialog.state.options.ccExpiresOnYear, step.activeDialog.state.options.ccSecuritycode, step.activeDialog.state.options.templateName, - free + free, step.activeDialog.state.options.planId, ); } - static getDialogBatch(min: GBMinInstance) { - return { - id: '/welcome_saas_batch', - waterfall: [ - async step => { - const locale = 'en-US'; - await step.context.sendActivity(Messages[locale].welcome); - - await step.prompt('textPrompt', `Please, inform bot names separeted by comma (,).`); - }, - async step => { - const locale = 'en-US'; - - const service = new MainService(); - - const bots = step.context.activity.originalText.split(','); - bots.forEach(async botName => { - await service.createSubscription( - min, - botName, - '999999999', - 'email@domain.com.br', - '5521999998888', - botName, - null, - '12', - '99', - '1234', - 'ai-search.gbai', - true - ); - - }); - - - } - ] - }; - } static getDialog(min: GBMinInstance) { return { @@ -279,28 +271,28 @@ export class NewUserDialog extends IGBDialog { step.activeDialog.state.options.ccExpiresOnYear = null; step.activeDialog.state.options.ccSecuritycode = null; step.activeDialog.state.options.templateName = null; + step.activeDialog.state.options.planId = null; + step.activeDialog.state.options.amount = null; await step.context.sendActivity(Messages[locale].welcome); const mobile = step.context.activity.from.id; - step.activeDialog.state.options.nextDialog = 'welcome_saas_botname'; + step.activeDialog.state.options.nextDialog = 'welcome_saas_plan'; if (isNaN(mobile as any)) { await step.context.sendActivity(Messages[locale].ok_get_information); - return await step.replaceDialog('/profile_name', step.activeDialog.state.options); } else { const name = SaaSPackage.welcomes ? SaaSPackage.welcomes[mobile] : null; step.activeDialog.state.options.name = name; step.activeDialog.state.options.mobile = mobile; - await step.context.sendActivity(`Olá ${name}, vamos criar o seu Bot agora.`); - + await step.context.sendActivity(`Hello ${name}, let's create your Bot now.`); return await step.replaceDialog('/profile_email', step.activeDialog.state.options); } } ] }; } -} +} \ No newline at end of file diff --git a/packages/saas.gbapp/index.ts b/packages/saas.gbapp/index.ts index bde40ddaf..6f669bea3 100755 --- a/packages/saas.gbapp/index.ts +++ b/packages/saas.gbapp/index.ts @@ -34,7 +34,6 @@ import { IGBPackage, GBMinInstance, IGBCoreService, GBLog, IGBAdminService, GBDi import { Sequelize } from 'sequelize-typescript' import { GBOnlineSubscription } from './model/MainModel.js' -import { MSSubscriptionService } from './service/MSSubscription.js' import { CollectionUtil } from 'pragmatismo-io-framework'; import { NewUserDialog } from './dialog/NewUserDialog.js' import { GBOService } from './service/GBOService.js' @@ -48,12 +47,10 @@ export class SaaSPackage implements IGBPackage { public getDialogs(min: GBMinInstance) { return [NewUserDialog.getDialog(min), NewUserDialog.getBotNameDialog(min), - NewUserDialog.getVoucherDialog(min), NewUserDialog.getBotTemplateDialog(min), NewUserDialog.getReturnFromPayment(min), NewUserDialog.getReturnFromCC(min), NewUserDialog.getReturnFromDocument(min), - NewUserDialog.getDialogBatch(min) ]; } @@ -62,21 +59,6 @@ export class SaaSPackage implements IGBPackage { core.setEntryPointDialog('/welcome_saas'); - // Installs webhook for Microsoft intercommunication. - - core.installWebHook(true, '/mslanding', async (req, res) => { - const service = new MSSubscriptionService(); - await service.handleMSLanding(req, res); - }); - core.installWebHook(true, '/mshook', async (req, res) => { - const service = new MSSubscriptionService(); - await service.handleMSHook(req, res); - }); - core.installWebHook(true, '/signup', async (req, res) => { - const service = new MSSubscriptionService(); - await service.handleMSSignUp(req, res); - }); - } diff --git a/packages/saas.gbapp/model/MainModel.ts b/packages/saas.gbapp/model/MainModel.ts index d574a4065..282043808 100755 --- a/packages/saas.gbapp/model/MainModel.ts +++ b/packages/saas.gbapp/model/MainModel.ts @@ -1,3 +1,4 @@ +// BotServer/packages/saas.gbapp/model/MainModel.ts /*****************************************************************************\ | █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® | | ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | @@ -59,13 +60,13 @@ export class GBOnlineSubscription extends Model { instanceId: number; @Column - externalSubscriptionId: string // MSFT or + externalSubscriptionId: string; @Column - saasSubscriptionStatus: string + saasSubscriptionStatus: string; @Column - isFreeTrial: boolean + isFreeTrial: boolean; @Column planId: string; @@ -74,8 +75,14 @@ export class GBOnlineSubscription extends Model { quantity: number; @Column - lastCCFourDigits: number; + lastCCFourDigits: string; @Column status: string; -} + + @Column({ + type: DataType.DECIMAL(10, 2) + }) + amount: number; + +} \ No newline at end of file diff --git a/packages/saas.gbapp/service/JunoSubscription.ts b/packages/saas.gbapp/service/JunoSubscription.ts deleted file mode 100755 index ad4f002fb..000000000 --- a/packages/saas.gbapp/service/JunoSubscription.ts +++ /dev/null @@ -1,579 +0,0 @@ -/*****************************************************************************\ -| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® | -| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | -| ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ | -| ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | -| █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ | -| | -| General Bots Copyright (c) pragmatismo.com.br. All rights reserved. | -| Licensed under the AGPL-3.0. | -| | -| According to our dual licensing model, this program can be used either | -| under the terms of the GNU Affero General Public License, version 3, | -| or under a proprietary license. | -| | -| The texts of the GNU Affero General Public License with an additional | -| permission and of our proprietary license can be found at and | -| in the LICENSE file you have received along with this program. | -| | -| This program is distributed in the hope that it will be useful, | -| but WITHOUT ANY WARRANTY, without even the implied warranty of | -| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | -| GNU Affero General Public License for more details. | -| | -| "General Bots" is a registered trademark of pragmatismo.com.br. | -| The licensing of the program under the AGPLv3 does not imply a | -| trademark license. Therefore any rights, title and interest in | -| our trademarks remain entirely with us. | -| | -\*****************************************************************************/ - -'use strict'; -import fs from 'fs/promises'; -import { HttpMethods, HttpOperationResponse, ServiceClient, WebResource } from '@azure/ms-rest-js'; -import { GBLog } from 'botlib'; -import urlJoin from 'url-join'; -// tslint:disable-next-line: no-require-imports -const Juno = require('juno-payment-node'); -var FormData = require('form-data'); -export class JunoSubscription { - /** - * The host this service will call REST API through VPN. - */ - public host: string = process.env.SAAS_JUNO_HOST; - - /** - * Creates a HTTP request object to make REST calls. - */ - private async getAuthorizationToken(): Promise { - GBLog.info( `JunoAPI: Getting Auth Token from API...`); - const httpClient = new ServiceClient(); - - const req: WebResource = new WebResource(); - req.method = 'POST'; - req.url = JunoSubscription.getAuthUrl(); - req.body = 'grant_type=client_credentials'; - req.headers.set('Content-Type', 'application/x-www-form-urlencoded'); - req.headers.set( - 'Authorization', - 'Basic ' + - new Buffer(JunoSubscription.getClientId() + ':' + JunoSubscription.getClientSecret()).toString('base64') - ); - - const res = await httpClient.sendRequest(req); - - GBLog.info( `JunoAPI: Response from Authorization API ${res.bodyAsText}`); - - return res.parsedBody.access_token; - } - - /** - * Creates a HTTP request object to make REST calls. - */ - private async setupWebhook(): Promise { - GBLog.info( `JunoAPI: Setting Webhook...`); - const httpClient = new ServiceClient(); - - const host = process.env.BOT_URL; - const url = `${host}/store.gbapp/payment_notification`; - - const body = { - url: '', - eventTypes: ['PAYMENT_NOTIFICATION'] - }; - - const req: WebResource = new WebResource(); - req.method = 'POST'; - req.url = urlJoin(JunoSubscription.getResourceUrl(), 'notifications', 'webhooks'); - - req.body = body; - req.headers.set('Content-Type', 'application/x-www-form-urlencoded'); - req.headers.set( - 'Authorization', - 'Basic ' + - new Buffer(JunoSubscription.getClientId() + ':' + JunoSubscription.getClientSecret()).toString('base64') - ); - - const res = await httpClient.sendRequest(req); - - GBLog.info( `JunoAPI: Response from Authorization API ${res.bodyAsText}`); - - return res.parsedBody.access_token; - } - - /** - * Creates a HTTP request object to make REST calls. - */ - private static createRequestObject( - token: string, - url: string, - verb: HttpMethods, - body: string, - headers: any, - externalAccountToken = undefined - ): WebResource { - const req: WebResource = new WebResource(); - req.method = verb; - req.url = url; - - req.headers.set('Content-Type', 'application/json;charset=UTF-8'); - req.headers.set('Authorization', `Bearer ${token}`); - req.headers.set('X-Api-Version', 2); - req.headers.set( - 'X-Resource-Token', - externalAccountToken ? externalAccountToken : JunoSubscription.getJunoPrivateKey() - ); - - if (headers !== undefined) { - // tslint:disable-next-line: typedef - headers.forEach(e => { - req.headers.set(e.name, e.value); - }); - } - req.body = body; - - return req; - } - - public async PayByBoleto( - name: string, - document: string, - email: string, - phone: string, - amount: number - ): Promise { - let charge = await this.createCharge(name, document, email, phone, amount, 'BOLETO'); - - return charge; - } - - public async PayByCC( - name: string, - document: string, - email: string, - phone: string, - ccNumber: string, - ccExpiresOnMonth: string, - ccExpiresOnYear: string, - ccCode: string, - amount: number - ): Promise { - let externalSubscriptionId = '1'; - - let charge = await this.createCharge(name, document, email, phone, amount, 'CREDIT_CARD'); - - let ccHash = await this.getCardHash(ccNumber, name, ccCode, ccExpiresOnMonth, ccExpiresOnYear); - let ccId = await this.getCreditCardId(ccHash); - let final = await this.makePayment(ccId, ccHash, charge.Id, email); - - return externalSubscriptionId; - } - - /*** - * Get active users available to the workflow process. - */ - public async createDigitalAccount( - name, - document, - email, - birthDate, - phone, - businessArea, - linesOfBusiness, - number: string, - digit: string, - bank: string - ) { - GBLog.info( `JunoAPI: Calling createDigitalAccount API...`); - - let token = await this.getAuthorizationToken(); - - const httpClient = new ServiceClient(); - - const url = urlJoin(JunoSubscription.getResourceUrl(), 'digital-accounts'); - const req = JunoSubscription.createRequestObject(token, url, 'POST', '', undefined); - const res = await httpClient.sendRequest(req); - - let json = { - type: 'PAYMENT', - name: name, - document: document, - email: email, - birthDate: birthDate, - phone: phone - }; - - GBLog.info( `JunoAPI: Response from createDigitalAccount ${res.bodyAsText}`); - - return res.parsedBody; - } - - private async createCharge(name, document, email, phone, amount, paymentType) { - GBLog.info( `JunoAPI: Calling createCharge API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), 'charges'); - - let json = { - charge: { - description: 'string', - amount: amount, - paymentTypes: [paymentType] - }, - billing: { - name: name, - document: document, - email: email, - phone: phone, - notify: true - } - }; - - const req = JunoSubscription.createRequestObject(token, url, 'POST', JSON.stringify(json), undefined); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from createCharge ${res.bodyAsText}`); - - return res.parsedBody._embedded.charges[0]; - } - - public async createPlan(name, amount) { - GBLog.info( `JunoAPI: Calling createPlan API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/plans'); - - let json = { - name: name, - amount: amount - }; - - const req = JunoSubscription.createRequestObject(token, url, 'POST', JSON.stringify(json), undefined); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from createPlan ${res.bodyAsText}`); - - return res.parsedBody; - } - - private async createSubscription( - dueDay, - planId, - description, - email, - creditCardId, - creditCardHash, - street, - number, - city, - state, - postCode, - partnerAccountToken - ) { - GBLog.info( `JunoAPI: Calling createSubscription API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/subscriptions'); - - let json = { - dueDay: dueDay, - planId: planId, - chargeDescription: description, - creditCardDetails: { - creditCardId: creditCardId, - creditCardHash: creditCardHash - }, - billing: { - email: email, - address: { - street: street, - number: number, - city: city, - state: state, - postCode: postCode - } - } - }; - - if (partnerAccountToken) { - json['split'] = [ - { - recipientToken: this.getAuthorizationToken(), - percentage: 90, - amountRemainder: true, - chargeFee: true - }, - { - recipientToken: partnerAccountToken, - percentage: 10, - amountRemainder: false, - chargeFee: true - } - ]; - } - - const req = JunoSubscription.createRequestObject(token, url, 'POST', JSON.stringify(json), undefined); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from createSubscription ${res.bodyAsText}`); - - return res.parsedBody; - } - - public async getBusinessAreas() { - GBLog.info( `JunoAPI: Calling getBusinessAreas API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/data/business-areas'); - - const req = JunoSubscription.createRequestObject(token, url, 'GET', undefined, undefined); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from getBusiness ${res.bodyAsText}`); - - return res.parsedBody._embedded.businessAreas; - } - - public async getBanks() { - GBLog.info( `JunoAPI: Calling getBanks API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/data/banks'); - - const req = JunoSubscription.createRequestObject(token, url, 'GET', undefined, undefined); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from getBanks ${res.bodyAsText}`); - - return res.parsedBody._embedded.banks; - } - - public async getCompanyTypes() { - GBLog.info( `JunoAPI: Calling getCompanyTypes API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/data/company-types'); - - const req = JunoSubscription.createRequestObject(token, url, 'GET', undefined, undefined); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from getCompanyTypes ${res.bodyAsText}`); - - return res.parsedBody._embedded.banks; - } - - public async getAccountPublicKey(externalAccountToken) { - GBLog.info( `JunoAPI: Calling getAccountPublicKey API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/credentials/public-key'); - - const req = JunoSubscription.createRequestObject(token, url, 'GET', undefined, undefined, externalAccountToken); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from getAccountPublicKey ${res.bodyAsText}`); - - return res.bodyAsText; - } - - public async listAccountDocuments(externalAccountToken: string) { - GBLog.info( `JunoAPI: Calling listAccountDocuments API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/documents'); - - const req = JunoSubscription.createRequestObject(token, url, 'GET', undefined, undefined, externalAccountToken); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from listAccountDocuments ${res.bodyAsText}`); - - return res.parsedBody._embedded.documents; - } - - public async getAccountDocumentProperties(externalAccountToken: string, id: string) { - GBLog.info( `JunoAPI: Calling getAccountDocumentProperties API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), `/documents/${id}`); - - const req = JunoSubscription.createRequestObject(token, url, 'GET', undefined, undefined, externalAccountToken); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from getAccountDocumentProperties ${res.bodyAsText}`); - - return res.parsedBody; - } - - public async sendAccountDocument(externalAccountToken: string, id: string, file: string) { - GBLog.info( `JunoAPI: Calling sendAccountDocument API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), `/documents/${id}/files`); - var form = new FormData(); - form.append('file', await fs.readFile(file)); - - const req = JunoSubscription.createRequestObject( - token, - url, - 'POST', - form.getBuffer(), - form.getHeaders(), - externalAccountToken - ); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from sendAccountDocument ${res.bodyAsText}`); - - return res.parsedBody; - } - - public async getAccountBalance(externalAccountToken) { - GBLog.info( `JunoAPI: Calling getAccountBalance API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/balance'); - - const req = JunoSubscription.createRequestObject(token, url, 'GET', undefined, undefined, externalAccountToken); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from getAccountBalance ${res.bodyAsText}`); - - return res.parsedBody; - } - - public async getAccount(externalAccountToken: string, id: string): Promise { - GBLog.info( `JunoAPI: Calling Get Digital Accounts API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/digital-accounts'); - - const req = JunoSubscription.createRequestObject(token, url, 'GET', undefined, undefined, externalAccountToken); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from Get Digital Accounts ${res.bodyAsText}`); - - return res.parsedBody.id; - } - - public async getCreditCardId(ccHash: string): Promise { - GBLog.info( `JunoAPI: Calling tokenizeCreditCard API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/credit-cards/tokenization'); - - let json = { - creditCardHash: ccHash - }; - - const req = JunoSubscription.createRequestObject(token, url, 'POST', JSON.stringify(json), undefined); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from tokenizeCreditCard ${res.bodyAsText}`); - - return res.parsedBody.creditCardId; - } - - public async makePayment(ccId, ccHash, chargeId, email): Promise { - GBLog.info( `JunoAPI: Calling makePayment API...`); - - let token = await this.getAuthorizationToken(); - const httpClient = new ServiceClient(); - const url = urlJoin(JunoSubscription.getResourceUrl(), '/payments'); - - let json = { - chargeId: chargeId, - billing: { - email: email - // address: { - // street: street, - // number: number, - // complement: complement, - // neighborhood: neighborhood, - // city: city, - // state: state, - // postCode: postCode - // } - }, - creditCardDetails: { - creditCardId: ccId, - creditCardHash: ccHash - } - }; - - const req = JunoSubscription.createRequestObject(token, url, 'POST', JSON.stringify(json), undefined); - - const res = await httpClient.sendRequest(req); - GBLog.info( `JunoAPI: Response from makePayment ${res.bodyAsText}`); - - return res.parsedBody._embedded.charges[0]; - } - - private static isProd() { - return process.env.SAAS_JUNO_IS_PROD === 'true'; - } - - private static getClientId() { - return JunoSubscription.isProd() ? process.env.SAAS_JUNO_PROD_CLIENT_ID : process.env.SAAS_JUNO_SANDBOX_CLIENT_ID; - } - - private static getClientSecret() { - return JunoSubscription.isProd() - ? process.env.SAAS_JUNO_PROD_CLIENT_SECRET - : process.env.SAAS_JUNO_SANDBOX_CLIENT_SECRET; - } - - private static getJunoPublicKey() { - return JunoSubscription.isProd() ? process.env.SAAS_JUNO_PROD_PUBLIC_KEY : process.env.SAAS_JUNO_SANDBOX_PUBLIC_KEY; - } - - private static getJunoPrivateKey() { - return JunoSubscription.isProd() - ? process.env.SAAS_JUNO_PROD_PRIVATE_KEY - : process.env.SAAS_JUNO_SANDBOX_PRIVATE_KEY; - } - - private static getResourceUrl() { - return JunoSubscription.isProd() ? process.env.SAAS_JUNO_PROD_RESOURCE : process.env.SAAS_JUNO_SANDBOX_RESOURCE; - } - - private static getAuthUrl() { - return JunoSubscription.isProd() ? process.env.SAAS_JUNO_PROD_AUTH : process.env.SAAS_JUNO_SANDBOX_AUTH; - } - - private async getCardHash( - ccNumber: string, - name: string, - ccCode: string, - ccExpiresOnMonth: string, - ccExpiresOnYear: string - ): Promise { - return new Promise(async (resolve, reject) => { - let tokenJuno = JunoSubscription.getJunoPublicKey(); - - let cardData = { - cardNumber: ccNumber, - holderName: name, - securityCode: ccCode, - expirationMonth: ccExpiresOnMonth, - expirationYear: ccExpiresOnYear - }; - - let checkout = new Juno.DirectCheckout(tokenJuno, JunoSubscription.isProd()); - - checkout.getCardHash(cardData, resolve, reject); - }); - } -} diff --git a/packages/saas.gbapp/service/MSSubscription.ts b/packages/saas.gbapp/service/MSSubscription.ts deleted file mode 100755 index 3bdff8208..000000000 --- a/packages/saas.gbapp/service/MSSubscription.ts +++ /dev/null @@ -1,135 +0,0 @@ -/*****************************************************************************\ -| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® | -| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | -| ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ | -| ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | -| █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ | -| | -| General Bots Copyright (c) pragmatismo.com.br. All rights reserved. | -| Licensed under the AGPL-3.0. | -| | -| According to our dual licensing model, this program can be used either | -| under the terms of the GNU Affero General Public License, version 3, | -| or under a proprietary license. | -| | -| The texts of the GNU Affero General Public License with an additional | -| permission and of our proprietary license can be found at and | -| in the LICENSE file you have received along with this program. | -| | -| This program is distributed in the hope that it will be useful, | -| but WITHOUT ANY WARRANTY, without even the implied warranty of | -| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | -| GNU Affero General Public License for more details. | -| | -| "General Bots" is a registered trademark of pragmatismo.com.br. | -| The licensing of the program under the AGPLv3 does not imply a | -| trademark license. Therefore any rights, title and interest in | -| our trademarks remain entirely with us. | -| | -\*****************************************************************************/ - -"use strict" - -import { MainService } from "./MainService.js"; - -export class MSSubscriptionService { - - public async handleMSHook(req: any, res: any) { - - } - - public async handleMSSignUp(req: any, res: any) { - let token = req.params.token; - let url = `https://marketplaceapi.microsoft.com/api/saas/subscriptions/resolve?api-version=2018-08-31`; - let options = { - uri: url, - method: 'GET', - headers: { - 'x-ms-marketplace-token': token - } - }; - let result = null; // TODO: await fetch({}); - let data = JSON.parse(result); - - const additionalData = { - "id": "", // purchased SaaS subscription ID - "subscriptionName": "Contoso Cloud Solution", // SaaS subscription name - "offerId": "offer1", // purchased offer ID - "planId": "silver", // purchased offer's plan ID - "quantity": "20", // number of purchased seats, might be empty if the plan is not per seat - "subscription": { // full SaaS subscription details, see Get Subscription APIs response body for full description - "id": "", - "publisherId": "contoso", - "offerId": "offer1", - "name": "Contoso Cloud Solution", - "saasSubscriptionStatus": " PendingFulfillmentStart ", - "beneficiary": { - "emailId": "test@test.com", - "objectId": "", - "tenantId": "", - "pid": "" - }, - "purchaser": { - "emailId": "test@test.com", - "objectId": "", - "tenantId": "", - "pid": "" - }, - "planId": "silver", - "term": { - "termUnit": "P1M", - "startDate": "2019 - 05 - 31", - "endDate": "2019-06-29", - }, - "isTest": true, - "isFreeTrial": false, - "allowedCustomerOperations": [ - "Delete", - "Update", - "Read" - ], - "sandboxType": "None", - "sessionMode": "None" - } - } - - const service = new MainService(); - service.createSubscriptionMSFT("email", "plan", "offer", - Number.parseInt(additionalData.quantity), additionalData); - - url = `https://marketplaceapi.microsoft.com/api/saas/subscriptions/${data.id}?api-version=2018-08-31`; - options = { - uri: url, - method: 'GET', - headers: { - 'x-ms-marketplace-token': token - } - }; - - result = null; // TODO: fetch. - data = JSON.parse(result); - } - - public async handleMSLanding(req: any, res: any) { - - } - - public async Unsubscribe() { - - } - public async Suspend() { - - } - public async Reinstateou() { - - } - public async ChangePlan() { - - } - public async ChangeQuantity() { - - } - public async Transfer() { - - } -} \ No newline at end of file diff --git a/packages/saas.gbapp/service/MainService.ts b/packages/saas.gbapp/service/MainService.ts index f37b60809..785492ded 100755 --- a/packages/saas.gbapp/service/MainService.ts +++ b/packages/saas.gbapp/service/MainService.ts @@ -36,9 +36,48 @@ import { CollectionUtil } from 'pragmatismo-io-framework'; import urlJoin from 'url-join'; import { GBOService } from './GBOService.js'; import { GBConfigService } from '../../core.gbapp/services/GBConfigService.js'; +import Stripe from 'stripe'; export class MainService { - async createSubscriptionMSFT(email: string, plan: string, offer: string, quantity: number, additionalData: any) { } + private stripe: Stripe; + + constructor() { + this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY); + } + + async createStripeCustomer(name: string, email: string, paymentMethodId: string) { + const customer = await this.stripe.customers.create({ + name, + email, + payment_method: paymentMethodId, + invoice_settings: { + default_payment_method: paymentMethodId + } + }); + return customer; + } + + async createStripeSubscription(customerId: string, priceId: string) { + const subscription = await this.stripe.subscriptions.create({ + customer: customerId, + items: [{ price: priceId }], + expand: ['latest_invoice.payment_intent'] + }); + return subscription; + } + + async createPaymentMethod(cardNumber: string, expMonth: number, expYear: number, cvc: string) { + const paymentMethod = await this.stripe.paymentMethods.create({ + type: 'card', + card: { + number: cardNumber, + exp_month: expMonth, + exp_year: expYear, + cvc: cvc + } + }, {}); + return paymentMethod; + } async createSubscription( min: GBMinInstance, @@ -48,90 +87,94 @@ export class MainService { mobile: string, botName: string, ccNumber: string, - ccExpiresOnMonth: string, - ccExpiresOnYear: string, + ccExpiresOnMonth: number, + ccExpiresOnYear: number, ccCode: string, templateName: string, - free: boolean - ) - { - // Syncs internal subscription management. + free: boolean, planId: string, + ) { + let externalSubscriptionId = null; + + if (!free) { + try { + // Create Stripe payment method + const paymentMethod = await this.createPaymentMethod( + ccNumber, + ccExpiresOnMonth, + ccExpiresOnYear, + ccCode + ); + + // Create Stripe customer + const customer = await this.createStripeCustomer( + name, + email, + paymentMethod.id + ); + + // Determine price ID based on plan + const priceId = planId === 'professional' + ? process.env.STRIPE_PROFESSIONAL_PRICE_ID + : process.env.STRIPE_PERSONAL_PRICE_ID; + + // Create subscription + const subscription = await this.createStripeSubscription( + customer.id, + priceId + ); + + externalSubscriptionId = subscription.id; + } catch (error) { + GBLog.error(`Stripe payment failed: ${error.message}`); + throw error; + } + } + + // Syncs internal subscription management + const status = free ? 'FreeTrial' : 'Active'; + GBLog.info(`Creating subscription for ${name} (${email}, ${mobile}) with status: ${status}`); - const status = 'Started'; - const planId = 'default'; const quantity = 1; - const amount = 0.52; - const language = 'en'; + const amount = 1; - const subscription = await GBOnlineSubscription.create({ + const subscription = await GBOnlineSubscription.create({ instanceId: min.instance.instanceId, isFreeTrial: free, planId: planId, quantity: quantity, status: status, - // TODO: lastCCFourDigits: ccNumber... + amount: amount, + lastCCFourDigits: ccNumber ? ccNumber.slice(-4) : null }); - let externalSubscriptionId = null; - let service = min.gbappServices['junoSubscription']; - - if (!free) { - // if (ccNumber !== undefined) { - // // Performs billing management. - - // externalSubscriptionId = await service.PayByCC( - // name, - // document, - // email, - // mobile, - // ccNumber, - // ccExpiresOnMonth, - // ccExpiresOnYear, - // ccCode, - // amount - // ); - // } else { - // externalSubscriptionId = await service.PayByBoleto(name, document, email, mobile); - // } - } - - // Creates a bot. - - GBLog.info( 'Deploying a blank bot to storage...'); - + // Creates a bot + GBLog.info('Deploying a blank bot to storage...'); const instance = await min.deployService.deployBlankBot(botName, mobile, email); - GBLog.info( 'Creating subscription...'); + GBLog.info('Creating subscription...'); subscription.instanceId = instance.instanceId; subscription.externalSubscriptionId = externalSubscriptionId; await subscription.save(); - let token = - GBConfigService.get('GB_MODE') === 'legacy'? - await (min.adminService.acquireElevatedToken as any)(min.instance.instanceId, true) : - null; + let token = + GBConfigService.get('GB_MODE') === 'legacy' ? + await (min.adminService.acquireElevatedToken as any)(min.instance.instanceId, true) : + null; let siteId = process.env.STORAGE_SITE_ID; let libraryId = process.env.STORAGE_LIBRARY; let gboService = new GBOService(); - + let sleep = ms => { return new Promise(resolve => { setTimeout(resolve, ms); }); }; - - GBLog.info( 'Creating .gbai folder ...'); + GBLog.info('Creating .gbai folder ...'); let item = await gboService.createRootFolder(token, `${botName}.gbai`, siteId, libraryId); - - GBLog.info( 'Copying Templates...'); - // await gboService.copyTemplates(min, item, 'shared.gbai', 'gbkb', botName); - // await gboService.copyTemplates(min, item, 'shared.gbai', 'gbot', botName); - // await gboService.copyTemplates(min, item, 'shared.gbai', 'gbtheme', botName); - // await gboService.copyTemplates(min, item, 'shared.gbai', 'gbdialog', botName); - // await gboService.copyTemplates(min, item, 'shared.gbai', 'gbdata', botName); + GBLog.info('Copying Templates...'); await gboService.copyTemplates(min, item, templateName, 'gbkb', botName); await gboService.copyTemplates(min, item, templateName, 'gbot', botName); await gboService.copyTemplates(min, item, templateName, 'gbtheme', botName); @@ -140,16 +183,16 @@ export class MainService { await gboService.copyTemplates(min, item, templateName, 'gbdrive', botName); await sleep(10000); - GBLog.info( 'Configuring .gbot...'); - await min.core['setConfig'] (min, instance.botId, "Can Publish", mobile + ";"); + GBLog.info('Configuring .gbot...'); + await min.core['setConfig'](min, instance.botId, "Can Publish", mobile + ";"); await min.core['setConfig'](min, instance.botId, "Admin Notify E-mail", email); - await min.core['setConfig'](min, instance.botId, 'WebDav Username', instance.botId); - await min.core['setConfig'](min, instance.botId, 'WebDav Secret', instance.adminPass); + await min.core['setConfig'](min, instance.botId, 'WebDav Username', instance.botId); + await min.core['setConfig'](min, instance.botId, 'WebDav Secret', instance.adminPass); - GBLog.info( 'Bot creation done.'); + GBLog.info('Bot creation done.'); } - public async otherTasks(min, botName, webUrl, instance, language){ + public async otherTasks(min, botName, webUrl, instance, language) { let message = `Seu bot ${botName} está disponível no endereço:
${urlJoin(process.env.BOT_URL, botName)}.
@@ -170,7 +213,7 @@ export class MainService {

Atenciosamente,
General Bots Online. -
https://gb.pragmatismo.com.br +


E-mail remetido por Pragmatismo.
`; @@ -180,17 +223,16 @@ export class MainService { message, language ); - - GBLog.info( 'Generating MS Teams manifest....'); - + + GBLog.info('Generating MS Teams manifest....'); + const appManifest = await min.deployService.getBotManifest(min.instance); - + // GBLog.info( 'Sending e-mails....'); // const emailToken = process.env.SAAS_SENDGRID_API_KEY; // gboService.sendEmail( // emailToken, // email, - // 'operations@pragmatismo.com.br', // `${botName}`, // message, // message, diff --git a/src/util.ts b/src/util.ts index 5e510daf0..5d961dd67 100644 --- a/src/util.ts +++ b/src/util.ts @@ -32,7 +32,7 @@ export class GBUtil { // When creating/updating a user (hashing before saving to DB) - public async static hashPassword(password) { + public static async hashPassword(password) { try { const hash = await bcrypt.hash(password, saltRounds); return hash; @@ -43,7 +43,7 @@ export class GBUtil { } // When comparing passwords (like during login) - public async static comparePassword(inputPassword, hashedPassword) { + public static async comparePassword(inputPassword, hashedPassword) { try { return await bcrypt.compare(inputPassword, hashedPassword); } catch (err) { @@ -367,7 +367,7 @@ export class GBUtil { const localName = path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.png`); const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName)); - await fs.writeFile(localName, buffer, { encoding: null }); + await fs.writeFile(localName, new Uint8Array(buffer), { encoding: null }); generatedFiles.push({ localName: localName, url: url, data: buffer }); } diff --git a/vm-inject.js b/vm-inject.js index 7387c00b6..4210b5a22 100644 --- a/vm-inject.js +++ b/vm-inject.js @@ -5,7 +5,6 @@ module.exports = (async () => { // Imports npm packages for this .gbdialog conversational application. require('isomorphic-fetch'); - const YAML = require('yaml'); const http = require('node:http'); const retry = require('async-retry'); const createRpcClient = require('@push-rpc/core').createRpcClient; @@ -185,9 +184,7 @@ module.exports = (async () => { }; const TOYAML = json => { - const doc = new YAML.Document(); - doc.contents = json; - return doc.toString(); + return json; }; // Line of Business logic.