refactor(GBOService, MainModel): clean up code and enhance model structure for better clarity and maintainability
All checks were successful
GBCI / build (push) Successful in 1m30s
All checks were successful
GBCI / build (push) Successful in 1m30s
This commit is contained in:
parent
9e08cb5e64
commit
e3ac4f58b3
7 changed files with 248 additions and 538 deletions
|
@ -459,6 +459,7 @@ export class GBMinService {
|
|||
|
||||
this.createCheckHealthAddress(GBServer.globals.server, min, min.instance);
|
||||
|
||||
|
||||
// Setups official handler for WhatsApp.
|
||||
|
||||
GBServer.globals.server
|
||||
|
|
219
packages/saas.gbapp/dialog/NewUserDialog.ts
Executable file → Normal file
219
packages/saas.gbapp/dialog/NewUserDialog.ts
Executable file → Normal file
|
@ -1,36 +1,4 @@
|
|||
// BotServer/packages/saas.gbapp/dialog/NewUserDialog.ts
|
||||
/*****************************************************************************\
|
||||
| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® |
|
||||
| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
||||
| ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ |
|
||||
| ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
||||
| █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ |
|
||||
| |
|
||||
| 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 { IGBDialog, GBMinInstance } from 'botlib';
|
||||
import { Messages } from '../strings.js';
|
||||
import { MainService } from '../service/MainService.js';
|
||||
|
@ -44,22 +12,22 @@ export class NewUserDialog extends IGBDialog {
|
|||
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:');
|
||||
await step.context.sendActivity('1. Free - $0/month (basic features)');
|
||||
await step.context.sendActivity('2. Personal - $50/month (more features)');
|
||||
await step.context.sendActivity('3. Professional - $150/month (advanced features)');
|
||||
return await step.prompt('textPrompt', 'Enter 1, 2 or 3 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;
|
||||
step.activeDialog.state.options.planId = 'free';
|
||||
} else if (planChoice === '2') {
|
||||
step.activeDialog.state.options.planId = 'personal';
|
||||
} else if (planChoice === '3') {
|
||||
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.');
|
||||
await step.context.sendActivity('Invalid choice. Please select 1, 2 or 3.');
|
||||
return await step.replaceDialog('/welcome_saas_plan');
|
||||
}
|
||||
return await step.replaceDialog('/welcome_saas_botname', step.activeDialog.state.options);
|
||||
|
@ -102,50 +70,16 @@ 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',
|
||||
waterfall: [
|
||||
async step => {
|
||||
const locale = 'en-US';
|
||||
await step.context.sendActivity('Aqui estão alguns modelos para você escolher:');
|
||||
await step.context.sendActivity('Here are some templates to choose from:');
|
||||
let gboService = new GBOService();
|
||||
const list = await gboService.listTemplates(min);
|
||||
|
||||
let templateMessage = undefined;
|
||||
|
||||
await CollectionUtil.asyncForEach(list, async item => {
|
||||
if (item.name !== 'Shared.gbai') {
|
||||
templateMessage = templateMessage ? `${templateMessage}\n- ${item.name}` : `- ${item.name}`;
|
||||
|
@ -154,108 +88,63 @@ export class NewUserDialog extends IGBDialog {
|
|||
await step.context.sendActivity(templateMessage);
|
||||
|
||||
step.activeDialog.state.options.templateList = list;
|
||||
return await step.prompt('textPrompt', `Qual modelo de bot você gostaria de usar?`);
|
||||
return await step.prompt('textPrompt', `Which bot template would you like to use?`);
|
||||
},
|
||||
async step => {
|
||||
const list = step.activeDialog.state.options.templateList;
|
||||
let template = null;
|
||||
let gboService = new GBOService();
|
||||
await CollectionUtil.asyncForEach(list, async item => {
|
||||
|
||||
if (gboService.kmpSearch(step.context.activity.originalText, item.name) != -1) {
|
||||
template = item.name;
|
||||
}
|
||||
});
|
||||
|
||||
if (template === null) {
|
||||
await step.context.sendActivity(`Escolha, por favor, um destes modelos listados.`);
|
||||
|
||||
await step.context.sendActivity(`Please choose one of the listed templates.`);
|
||||
return await step.replaceDialog('/welcome_saas_bottemplate', step.activeDialog.state.options);
|
||||
} else {
|
||||
step.activeDialog.state.options.templateName = template;
|
||||
|
||||
await NewUserDialog.createBot(step, min, true);
|
||||
const service = new MainService();
|
||||
const result: any = await service.startSubscriptionProcess(
|
||||
min,
|
||||
step.activeDialog.state.options.name,
|
||||
step.activeDialog.state.options.email,
|
||||
step.activeDialog.state.options.mobile,
|
||||
step.activeDialog.state.options.botName,
|
||||
template,
|
||||
step.activeDialog.state.options.planId
|
||||
);
|
||||
|
||||
return await step.replaceDialog('/ask', { isReturning: true });
|
||||
if (step.activeDialog.state.options.planId === 'free') {
|
||||
await step.context.sendActivity(`Your free bot has been created! Access it here: ${result.botUrl}`);
|
||||
return await step.replaceDialog('/ask', { isReturning: true });
|
||||
} else {
|
||||
await step.context.sendActivity(`Please complete your payment here: ${result.paymentUrl}`);
|
||||
await step.context.sendActivity('I will check for payment completion every few seconds...');
|
||||
|
||||
try {
|
||||
const finalResult = await service.waitForPaymentCompletion(
|
||||
min,
|
||||
result.subscriptionId,
|
||||
template
|
||||
);
|
||||
|
||||
await step.context.sendActivity(`Payment verified and bot created successfully!`);
|
||||
await step.context.sendActivity(`Access your bot here: ${finalResult.botUrl}`);
|
||||
return await step.replaceDialog('/ask', { isReturning: true });
|
||||
} catch (error) {
|
||||
await step.context.sendActivity(`Error: ${error.message}`);
|
||||
return await step.replaceDialog('/welcome_saas_plan');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
static getReturnFromCC(min: GBMinInstance) {
|
||||
return {
|
||||
id: '/welcome_saas_return_cc',
|
||||
waterfall: [
|
||||
async step => {
|
||||
const locale = 'en-US';
|
||||
await step.context.sendActivity(Messages[locale].thanks_payment);
|
||||
await NewUserDialog.createBot(step, min, false);
|
||||
|
||||
return await step.replaceDialog('/ask', { isReturning: true });
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
static getReturnFromDocument(min: GBMinInstance) {
|
||||
return {
|
||||
id: '/welcome_saas_return_document',
|
||||
waterfall: [
|
||||
async step => {
|
||||
step.activeDialog.state.options.nextDialog = 'welcome_saas_return_payment';
|
||||
|
||||
return await step.replaceDialog('/bank_payment_type', step.activeDialog.state.options);
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
static getReturnFromPayment(min: GBMinInstance) {
|
||||
return {
|
||||
id: '/welcome_saas_return_payment',
|
||||
waterfall: [
|
||||
async step => {
|
||||
if (step.activeDialog.state.options.paymentType === 'cc') {
|
||||
step.activeDialog.state.options.nextDialog = 'welcome_saas_return_cc';
|
||||
await step.replaceDialog(`/bank_ccnumber`, step.activeDialog.state.options);
|
||||
} else {
|
||||
const locale = 'en-US';
|
||||
await step.context.sendActivity(Messages[locale].boleto_mail);
|
||||
|
||||
await step.context.sendActivity('textPrompt', Messages[locale].thanks_payment);
|
||||
await NewUserDialog.createBot(step, min, false);
|
||||
|
||||
return await step.replaceDialog('/ask', { isReturning: true });
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
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 = `${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(
|
||||
min,
|
||||
step.activeDialog.state.options.name,
|
||||
step.activeDialog.state.options.document,
|
||||
step.activeDialog.state.options.email,
|
||||
step.activeDialog.state.options.mobile,
|
||||
step.activeDialog.state.options.botName,
|
||||
step.activeDialog.state.options.ccNumber,
|
||||
step.activeDialog.state.options.ccExpiresOnMonth,
|
||||
step.activeDialog.state.options.ccExpiresOnYear,
|
||||
step.activeDialog.state.options.ccSecuritycode,
|
||||
step.activeDialog.state.options.templateName,
|
||||
free, step.activeDialog.state.options.planId,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
static getDialog(min: GBMinInstance) {
|
||||
return {
|
||||
id: '/welcome_saas',
|
||||
|
@ -263,23 +152,21 @@ export class NewUserDialog extends IGBDialog {
|
|||
async step => {
|
||||
const locale = 'en-US';
|
||||
|
||||
step.activeDialog.state.options.document = null;
|
||||
step.activeDialog.state.options.email = null;
|
||||
step.activeDialog.state.options.botName = null;
|
||||
step.activeDialog.state.options.ccNumber = null;
|
||||
step.activeDialog.state.options.ccExpiresOnMonth = null;
|
||||
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;
|
||||
step.activeDialog.state.options = {
|
||||
document: null,
|
||||
email: null,
|
||||
botName: null,
|
||||
templateName: null,
|
||||
planId: null,
|
||||
name: null,
|
||||
mobile: null,
|
||||
nextDialog: 'welcome_saas_plan'
|
||||
};
|
||||
|
||||
await step.context.sendActivity(Messages[locale].welcome);
|
||||
|
||||
const mobile = step.context.activity.from.id;
|
||||
|
||||
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);
|
||||
|
|
|
@ -48,9 +48,9 @@ export class SaaSPackage implements IGBPackage {
|
|||
return [NewUserDialog.getDialog(min),
|
||||
NewUserDialog.getBotNameDialog(min),
|
||||
NewUserDialog.getBotTemplateDialog(min),
|
||||
NewUserDialog.getReturnFromPayment(min),
|
||||
NewUserDialog.getReturnFromCC(min),
|
||||
NewUserDialog.getReturnFromDocument(min),
|
||||
|
||||
NewUserDialog.getPlanSelectionDialog(min),
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
|
131
packages/saas.gbapp/model/MainModel.ts
Executable file → Normal file
131
packages/saas.gbapp/model/MainModel.ts
Executable file → Normal file
|
@ -1,88 +1,59 @@
|
|||
// BotServer/packages/saas.gbapp/model/MainModel.ts
|
||||
/*****************************************************************************\
|
||||
| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® |
|
||||
| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
||||
| ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ |
|
||||
| ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
||||
| █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ |
|
||||
| |
|
||||
| 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 {
|
||||
Table,
|
||||
Column,
|
||||
Model,
|
||||
HasMany,
|
||||
BelongsTo,
|
||||
BelongsToMany,
|
||||
Length,
|
||||
ForeignKey,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DataType,
|
||||
IsUUID,
|
||||
PrimaryKey,
|
||||
AutoIncrement
|
||||
} from "sequelize-typescript"
|
||||
import { Table, Column, Model, DataType } from 'sequelize-typescript';
|
||||
|
||||
@Table({ tableName: 'GBOnlineSubscription' })
|
||||
export class GBOnlineSubscription extends Model<GBOnlineSubscription> {
|
||||
|
||||
@PrimaryKey
|
||||
@AutoIncrement
|
||||
@Column
|
||||
Id: number
|
||||
|
||||
@Column
|
||||
instanceId: number;
|
||||
|
||||
@Column
|
||||
externalSubscriptionId: string;
|
||||
|
||||
@Column
|
||||
saasSubscriptionStatus: string;
|
||||
|
||||
@Column
|
||||
isFreeTrial: boolean;
|
||||
|
||||
@Column
|
||||
planId: string;
|
||||
|
||||
@Column
|
||||
quantity: number;
|
||||
|
||||
@Column
|
||||
lastCCFourDigits: string;
|
||||
|
||||
@Column
|
||||
status: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.DECIMAL(10, 2)
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
amount: number;
|
||||
declare subscriptionId: number;
|
||||
|
||||
}
|
||||
@Column(DataType.INTEGER)
|
||||
declare instanceId: number;
|
||||
|
||||
@Column(DataType.STRING(100))
|
||||
declare customerName: string;
|
||||
|
||||
@Column(DataType.STRING(100))
|
||||
declare customerEmail: string;
|
||||
|
||||
@Column(DataType.STRING(100))
|
||||
declare stripeSessionId: string;
|
||||
|
||||
@Column(DataType.STRING(100))
|
||||
declare stripePaymentIntentId: string;
|
||||
|
||||
|
||||
@Column(DataType.STRING(20))
|
||||
declare customerMobile: string;
|
||||
|
||||
@Column(DataType.STRING(50))
|
||||
declare botName: string;
|
||||
|
||||
@Column(DataType.STRING(20))
|
||||
declare planId: string;
|
||||
|
||||
@Column(DataType.STRING(20))
|
||||
declare status: string; // 'pending_payment', 'active', 'cancelled'
|
||||
|
||||
@Column(DataType.FLOAT)
|
||||
declare paymentAmount: number;
|
||||
|
||||
@Column(DataType.STRING(500))
|
||||
declare paymentUrl: string;
|
||||
|
||||
@Column(DataType.STRING(100))
|
||||
declare paymentToken: string;
|
||||
|
||||
@Column(DataType.STRING(4))
|
||||
declare lastCCFourDigits: string;
|
||||
|
||||
@Column(DataType.DATE)
|
||||
declare createdAt: Date;
|
||||
|
||||
@Column(DataType.DATE)
|
||||
declare activatedAt: Date;
|
||||
}
|
||||
|
|
|
@ -1,32 +1,4 @@
|
|||
/*****************************************************************************\
|
||||
| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® |
|
||||
| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
||||
| ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ |
|
||||
| ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
||||
| █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ |
|
||||
| |
|
||||
| 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. |
|
||||
| |
|
||||
\*****************************************************************************/
|
||||
// General Bots Copyright (c) pragmatismo.com.br. All rights reserved. Licensed under the AGPL-3.0.
|
||||
|
||||
"use strict"
|
||||
|
||||
|
@ -44,17 +16,12 @@ import path from "path";
|
|||
import { Client } from "minio";
|
||||
|
||||
|
||||
|
||||
export class GBOService {
|
||||
|
||||
public isValidCardNumber(ccNumber) {
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public isValidSecurityCode(ccNumber, cvcNumber) {
|
||||
}
|
||||
|
||||
public isValidExpireDate(month, year) {
|
||||
}
|
||||
|
||||
public async sendEmail(token: string, to: string, from: string,
|
||||
subject: string, text: string, html: string) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
|
|
349
packages/saas.gbapp/service/MainService.ts
Executable file → Normal file
349
packages/saas.gbapp/service/MainService.ts
Executable file → Normal file
|
@ -1,35 +1,4 @@
|
|||
/*****************************************************************************\
|
||||
| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® |
|
||||
| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
||||
| ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ |
|
||||
| ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
||||
| █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ |
|
||||
| |
|
||||
| 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';
|
||||
|
||||
// BotServer/packages/saas.gbapp/service/MainService.ts
|
||||
import { GBOnlineSubscription } from '../model/MainModel.js';
|
||||
import { GBMinInstance, GBLog } from 'botlib';
|
||||
import { CollectionUtil } from 'pragmatismo-io-framework';
|
||||
|
@ -37,124 +6,142 @@ import urlJoin from 'url-join';
|
|||
import { GBOService } from './GBOService.js';
|
||||
import { GBConfigService } from '../../core.gbapp/services/GBConfigService.js';
|
||||
import Stripe from 'stripe';
|
||||
import { GBUtil } from '../../../src/util.js';
|
||||
|
||||
export class MainService {
|
||||
private gboService: GBOService;
|
||||
private stripe: Stripe;
|
||||
private readonly PAYMENT_CHECK_INTERVAL = 5000; // 5 seconds
|
||||
private readonly PAYMENT_CHECK_TIMEOUT = 300000; // 5 minutes timeout
|
||||
|
||||
constructor() {
|
||||
this.gboService = new GBOService();
|
||||
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(
|
||||
public async startSubscriptionProcess(
|
||||
min: GBMinInstance,
|
||||
name: string,
|
||||
document: string,
|
||||
email: string,
|
||||
mobile: string,
|
||||
botName: string,
|
||||
ccNumber: string,
|
||||
ccExpiresOnMonth: number,
|
||||
ccExpiresOnYear: number,
|
||||
ccCode: string,
|
||||
templateName: string,
|
||||
free: boolean, planId: string,
|
||||
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 quantity = 1;
|
||||
const amount = 1;
|
||||
|
||||
// Create initial subscription record
|
||||
const subscription = await GBOnlineSubscription.create({
|
||||
instanceId: min.instance.instanceId,
|
||||
isFreeTrial: free,
|
||||
customerName: name,
|
||||
customerEmail: email,
|
||||
customerMobile: mobile,
|
||||
botName: botName,
|
||||
planId: planId,
|
||||
quantity: quantity,
|
||||
status: status,
|
||||
amount: amount,
|
||||
lastCCFourDigits: ccNumber ? ccNumber.slice(-4) : null
|
||||
status: planId === 'free' ? 'active' : 'pending_payment',
|
||||
createdAt: new Date(),
|
||||
activatedAt: planId === 'free' ? new Date() : null
|
||||
});
|
||||
|
||||
// Creates a bot
|
||||
GBLog.info('Deploying a blank bot to storage...');
|
||||
const instance = await min.deployService.deployBlankBot(botName, mobile, email);
|
||||
if (planId === 'free') {
|
||||
return await this.createBotResources(min, subscription, templateName);
|
||||
} else {
|
||||
const priceId = this.getPriceIdForPlan(planId);
|
||||
const session = await this.stripe.checkout.sessions.create({
|
||||
payment_method_types: ['card'],
|
||||
line_items: [{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
}],
|
||||
|
||||
mode: 'subscription',
|
||||
metadata: {
|
||||
subscriptionId: subscription.subscriptionId.toString(),
|
||||
botName: botName
|
||||
}
|
||||
});
|
||||
|
||||
GBLog.info('Creating subscription...');
|
||||
subscription.instanceId = instance.instanceId;
|
||||
subscription.externalSubscriptionId = externalSubscriptionId;
|
||||
await subscription.save();
|
||||
await subscription.update({
|
||||
stripeSessionId: session.id
|
||||
});
|
||||
|
||||
return {
|
||||
paymentUrl: session.url,
|
||||
subscriptionId: subscription.subscriptionId,
|
||||
nextStep: 'Please complete the payment in the new window. I will check for completion automatically.'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async waitForPaymentCompletion(
|
||||
min: GBMinInstance,
|
||||
subscriptionId: number,
|
||||
templateName: string
|
||||
): Promise<any> {
|
||||
const startTime = Date.now();
|
||||
|
||||
while ((Date.now() - startTime) < this.PAYMENT_CHECK_TIMEOUT) {
|
||||
const subscription = await GBOnlineSubscription.findOne({
|
||||
where: { subscriptionId }
|
||||
});
|
||||
|
||||
if (!subscription) {
|
||||
throw new Error('Subscription not found');
|
||||
}
|
||||
|
||||
if (subscription.stripeSessionId) {
|
||||
const session = await this.stripe.checkout.sessions.retrieve(
|
||||
subscription.stripeSessionId,
|
||||
{ expand: ['payment_intent'] }
|
||||
);
|
||||
|
||||
if (session.payment_status === 'paid') {
|
||||
await subscription.update({
|
||||
status: 'active',
|
||||
activatedAt: new Date(),
|
||||
stripePaymentIntentId: (session.payment_intent as any)?.id
|
||||
});
|
||||
|
||||
return await this.createBotResources(min, subscription, templateName);
|
||||
}
|
||||
|
||||
if (session.payment_status === 'unpaid' || session.status === 'expired') {
|
||||
throw new Error('Payment failed or session expired. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
await GBUtil.sleep(this.PAYMENT_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
throw new Error('Payment processing timed out. Please check your payment and try again.');
|
||||
}
|
||||
|
||||
private getPriceIdForPlan(planId: string): string {
|
||||
const priceIds = {
|
||||
personal: process.env.STRIPE_PERSONAL_PLAN_PRICE_ID,
|
||||
professional: process.env.STRIPE_PROFESSIONAL_PLAN_PRICE_ID
|
||||
};
|
||||
|
||||
if (!priceIds[planId]) {
|
||||
throw new Error(`No price ID configured for plan: ${planId}`);
|
||||
}
|
||||
|
||||
return priceIds[planId];
|
||||
}
|
||||
|
||||
private async createBotResources(
|
||||
min: GBMinInstance,
|
||||
subscription: GBOnlineSubscription,
|
||||
templateName: string
|
||||
) {
|
||||
GBLog.info('Deploying a blank bot to storage...');
|
||||
const instance = await min.deployService.deployBlankBot(
|
||||
subscription.botName,
|
||||
subscription.customerMobile,
|
||||
subscription.customerEmail
|
||||
);
|
||||
|
||||
await subscription.update({
|
||||
instanceId: instance.instanceId
|
||||
});
|
||||
|
||||
let token =
|
||||
GBConfigService.get('GB_MODE') === 'legacy' ?
|
||||
|
@ -163,98 +150,32 @@ export class MainService {
|
|||
|
||||
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 ...');
|
||||
let item = await gboService.createRootFolder(token, `${botName}.gbai`, siteId, libraryId);
|
||||
let item = await this.gboService.createRootFolder(
|
||||
token,
|
||||
`${subscription.botName}.gbai`,
|
||||
siteId,
|
||||
libraryId
|
||||
);
|
||||
|
||||
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);
|
||||
await gboService.copyTemplates(min, item, templateName, 'gbdata', botName);
|
||||
await gboService.copyTemplates(min, item, templateName, 'gbdialog', botName);
|
||||
await gboService.copyTemplates(min, item, templateName, 'gbdrive', botName);
|
||||
await this.gboService.copyTemplates(min, item, templateName, 'gbkb', subscription.botName);
|
||||
await this.gboService.copyTemplates(min, item, templateName, 'gbot', subscription.botName);
|
||||
await this.gboService.copyTemplates(min, item, templateName, 'gbtheme', subscription.botName);
|
||||
await this.gboService.copyTemplates(min, item, templateName, 'gbdata', subscription.botName);
|
||||
await this.gboService.copyTemplates(min, item, templateName, 'gbdialog', subscription.botName);
|
||||
await this.gboService.copyTemplates(min, item, templateName, 'gbdrive', subscription.botName);
|
||||
|
||||
await sleep(10000);
|
||||
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, "Can Publish", subscription.customerMobile + ";");
|
||||
await min.core['setConfig'](min, instance.botId, "Admin Notify E-mail", subscription.customerEmail);
|
||||
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.');
|
||||
return {
|
||||
success: true,
|
||||
botUrl: urlJoin(process.env.BOT_URL, subscription.botName)
|
||||
};
|
||||
}
|
||||
|
||||
public async otherTasks(min, botName, webUrl, instance, language) {
|
||||
let message = `Seu bot ${botName} está disponível no endereço:
|
||||
<br/><a href="${urlJoin(process.env.BOT_URL, botName)}">${urlJoin(process.env.BOT_URL, botName)}</a>.
|
||||
<br/>
|
||||
<br/>Os pacotes do General Bots (ex: .gbkb, .gbtheme) para seu Bot devem ser editados no repositório de pacotes:
|
||||
<br/>
|
||||
<br/><a href="${webUrl}">${webUrl}</a>.
|
||||
<br/>
|
||||
<br/> Digite /publish do seu WhatsApp para publicar os pacotes. Seu número está autorizado na pasta ${botName}.gbot/Config.xlsx
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>O arquivo .zip em anexo pode ser importado no Teams conforme instruções em:
|
||||
<br/><a href="https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload">https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload</a>.
|
||||
<br/>
|
||||
<br/>Log in to the Teams client with your Microsoft 365 account.
|
||||
<br/>Select Apps and choose Upload a custom app.
|
||||
<br/>Select this .zip file attached to this e-mail. An install dialog displays.
|
||||
<br/>Add your Bot to Teams.
|
||||
<br/>
|
||||
<br/>Atenciosamente,
|
||||
<br/>General Bots Online.
|
||||
<br/><a href=""></a>
|
||||
<br/>
|
||||
<br/>E-mail remetido por Pragmatismo.
|
||||
<br/>`;
|
||||
|
||||
message = await min.conversationalService.translate(
|
||||
min,
|
||||
message,
|
||||
language
|
||||
);
|
||||
|
||||
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,
|
||||
// `${botName}`,
|
||||
// message,
|
||||
// message,
|
||||
// {
|
||||
// content: appManifest,
|
||||
// filename: `${min.instance.botId}-Teams.zip`,
|
||||
// type: `application/zip`,
|
||||
// disposition: "attachment"
|
||||
// }
|
||||
// );
|
||||
|
||||
const contacts = process.env.SECURITY_LIST.split(';');
|
||||
|
||||
// TODO: await CollectionUtil.asyncForEach(contacts, async item => {
|
||||
// await (min.whatsAppDirectLine as any)['sendToDevice'](
|
||||
// item,
|
||||
// `Novo bot criado agora: http://gb.pragmatismo.com.br/${botName} para *${name}* (${email}, ${mobile}). Por favor, entre em contato para que mais um bot seja configurado adequadamente. `
|
||||
// );
|
||||
// });
|
||||
|
||||
// GBLog.info( 'Sharing .gbai folder...');
|
||||
// await gboService.shareFolder(token, item.parentReference.driveId, item.id, email);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
37
web.config
37
web.config
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<handlers>
|
||||
<add name="iisnode" path="boot.js" verb="*" modules="iisnode"/>
|
||||
</handlers>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<!-- Do not interfere with requests for node-inspector debugging -->
|
||||
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
|
||||
<match url="^server.js\/debug[\/]?" />
|
||||
</rule>
|
||||
<!-- First we consider whether the incoming URL matches a physical file in the /public folder. -->
|
||||
<rule name="StaticContent">
|
||||
<action type="Rewrite" url="public{REQUEST_URI}"/>
|
||||
</rule>
|
||||
<!-- All other URLs are mapped to the node.js site entry point -->
|
||||
<rule name="DynamicContent">
|
||||
<conditions>
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
|
||||
</conditions>
|
||||
<action type="Rewrite" url="boot.js"/>
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
<!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->
|
||||
<security>
|
||||
<requestFiltering>
|
||||
<hiddenSegments>
|
||||
<remove segment="bin"/>
|
||||
</hiddenSegments>
|
||||
</requestFiltering>
|
||||
</security>
|
||||
<!-- Make sure error responses are left untouched -->
|
||||
<httpErrors existingResponse="PassThrough" />
|
||||
</system.webServer>
|
||||
</configuration>
|
Loading…
Add table
Reference in a new issue