/** * Copyright (c) devAG (www.devag.ca) - All Rights Reserved * Unauthorized copying of this file, via any medium is strictly prohibited * Proprietary and confidential * * @package HUB * @subpackage Controller * @license https://www.devag.ca/license.txt Modified BSD License */ namespace app\ctrl; use app\Exceptions\UserError; use app\Exceptions\UserValidationError; use app\lib\App; use app\lib\Date; use app\lib\Format; use app\lib\Localization; use app\lib\Nuvei; use app\lib\Storage; use app\lib\Validation; use app\lib\Voicemeup; /** * Gestion des inscriptions de nouveaux clients. * * @package HUB * @subpackage Controller * @author Alexandre Gagné * @copyright 2022 devAG (www.devag.ca) * @license https://www.devag.ca/license.txt Modified BSD License */ class Signup extends \app\lib\Controller { protected $pageid = 'signup'; protected $public_controller = true; public function __construct() { $lang = $_COOKIE['signup_language'] ?? null; if ($lang != null) { Localization::set($lang); } parent::__construct(); } protected function en() { return $this->index('en'); } protected function fr() { return $this->index('fr'); } protected function index($lang = false) { $langLocale = Localization::locale($lang); if ($langLocale === null) { $langLocale = Localization::user(); } if ($this->security->user->id != null) { throw new UserError(i('Vous êtes présentement authentifié à Tchat N Sign, veuillez vous déconnecter pour accéder au formulaire d\'inscription.')); } App::setcookie('signup_language', $langLocale, 0); Localization::set($langLocale); $cie_img = Storage::get($this->security->company->getDirectory() . 'logo.png', true); if ($cie_img === false) { $cie_img = file_get_contents(FULLPATH . 'public/static/img/' . Localization::get(true) . '/logo_email.png'); } $this->tpl->addData([ 'url' => $this->url, 'ctrlURL' => $this->dourl, 'img_base64' => base64_encode($cie_img) ]); return [ 'status' => 'success', 'content' => $this->tpl->render('page::index') ]; } protected function doCheckInfos() { Localization::set($this->request()->cookie('signup_language', Localization::get())); $errors = []; $name = trim($this->request()->post('Signup_name')); $email = trim($this->request()->post('Signup_email')); $company = trim($this->request()->post('Signup_company')); if ($name == '') { $errors['Signup_name'] = i('Obligatoire'); } if ($email == '') { $errors['Signup_email'] = i('Obligatoire'); } elseif (Validation::email($email) === false) { $errors['Signup_email'] = i('Invalide'); } else { $User = new \app\db\Core\User(); $stm_user = $User->fromEmail($email); while ($User->fetch($stm_user) !== false) { if ($User->type != 'USER') { $errors['Signup_email'] = i('Un compte ne peut pas être créé avec ce courriel'); break; } } $User->close($stm_user); } if ($company == '') { $errors['Signup_company'] = i('Obligatoire'); } else { $Company = new \app\db\Core\Company(); $Company->name = $company; if ($Company->exists('name') === true) { $errors['Signup_company'] = i('Déjà utilisé'); } else { $Company->generateApiUsername(); $Company->fromUsername($Company->api_username); if ($Company->id != null) { $errors['Signup_company'] = i('Déjà utilisé'); } else { $Company->name = $company; $Company->generateApiUsername(); $name = mb_strtolower($company); if ($Company->api_username == null || strlen($name) <= 2 || $name == 'devag' || $name == 'chat \'n sign' || $name == 'tchat et signe' || $name == 'tchat & signe' || $name == 'mica') { $errors['Signup_company'] = i('Invalide'); } } } } if (empty($errors) === false) { throw new UserValidationError($errors); } return [ 'status' => 'success' ]; } protected function doCheckAddress() { Localization::set($this->request()->cookie('signup_language', Localization::get())); $errors = []; $billing_address1 = trim($this->request()->post('Signup_billing_address1')); $billing_city = trim($this->request()->post('Signup_billing_city')); $billing_province = trim($this->request()->post('Signup_billing_province')); $billing_postalcode = preg_replace('/[^0-9A-Z]/', '', strtoupper($this->request()->post('Signup_billing_postalcode'))); $billing_phone = preg_replace('/\D/', '', $this->request()->post('Signup_billing_phone')); if ($billing_address1 == null) { $errors['Signup_billing_address1'] = i('Obligatoire'); } if ($billing_city == null) { $errors['Signup_billing_city'] = i('Obligatoire'); } if ($billing_province == null) { $errors['Signup_billing_province'] = i('Obligatoire'); } if ($billing_postalcode == null) { $errors['Signup_billing_postalcode'] = i('Obligatoire'); } elseif (preg_match('/^[A-Z]\d[A-Z]\d[A-Z]\d$/', $billing_postalcode) !== 1) { $errors['Signup_billing_postalcode'] = i('Invalide'); } if ($billing_phone == null) { $errors['__display_Signup_billing_phone'] = i('Obligatoire'); } elseif (is_numeric($billing_phone) === false || strlen($billing_phone) != 10) { $errors['__display_Signup_billing_phone'] = i('Invalide'); } if (empty($errors) === false) { throw new UserValidationError($errors); } return [ 'status' => 'success' ]; } protected function doCheckNumber() { Localization::set($this->request()->cookie('signup_language', Localization::get())); $errors = []; $phone_number = $this->request()->post('Signup_phone_number'); if ($phone_number == '') { $errors['Signup_phone_number'] = i('Obligatoire'); } elseif (preg_match('/^(\d{1,4})?[-.\s]?\(?\d{3}?\)?[-.\s]?\d{3}[-.\s]?\d{4}[-.\s]?(\d{0,4})$/', $phone_number) !== 1) { $errors['Signup_phone_number'] = i('Invalide'); } if (empty($errors) === false) { throw new UserValidationError($errors); } return [ 'status' => 'success' ]; } protected function doCheckPromoCode($mandatory = false) { Localization::set($this->request()->cookie('signup_language', Localization::get())); $promo_code = $this->request()->post('Signup_promo_code', null); $errors = []; if ($promo_code == null && $mandatory === false) { // No code sent return [ 'status' => 'success', 'data' => false ]; } $PromoCode = (new \app\db\Core\PromoCode())->fromValidCode($promo_code); if ($PromoCode->id == null) { $errors['Signup_promo_code'] = i('Code promo non valide'); } if (empty($errors) === false) { throw new UserValidationError($errors); } $data = (object)[ 'id' => $PromoCode->id, 'name' => $PromoCode->name(), 'code' => $promo_code, 'free_expires' => null, 'discount_expires' => null ]; // Si un % de rabais est applicable avec une date limit if ($PromoCode->percentage_number_month != null) { $data->discount_expires = (new Date('now + ' . $PromoCode->percentage_number_month . ' months'))->getLongDate(); } // Si une période gratuite if ($PromoCode->number_free_month != null) { $data->free_expires = (new Date('now + ' . $PromoCode->number_free_month . ' months'))->getLongDate(); } // Code valid, return success and code data return [ 'status' => 'success', 'msg' => i('Code promo appliqué avec succès'), 'data' => $data ]; } protected function doCheckCreditCard() { Localization::set($this->request()->cookie('signup_language', Localization::get())); $remote_ip = $_SERVER['REMOTE_ADDR'] ?? null; file_put_contents(__DIR__.'/../../log/suspecious-debug.log', date('c')."\n".$remote_ip."\n\n\n\n", FILE_APPEND); return ['status' => 'error', 'msg' => 'Declined']; $errors = []; $number = preg_replace('/\D/', '', $this->request()->post('Signup_creditcard_number')); $name = trim($this->request()->post('Signup_creditcard_name')); $cvv = trim($this->request()->post('Signup_creditcard_cvv')); $expiration = preg_replace('/\D/', '', $this->request()->post('Signup_creditcard_expiration')); if ($this->request()->post('Signup_finance_cc') != 'YES' && $number == null) { return [ 'status' => 'success', 'data' => 'no_cc' ]; } if ($name == null) { $errors['Signup_creditcard_name'] = i('Obligatoire'); } if ($cvv == null) { $errors['Signup_creditcard_cvv'] = i('Obligatoire'); } elseif (is_numeric($cvv) === false) { $errors['Signup_creditcard_cvv'] = i('Invalide'); } if ($expiration == null) { $errors['Signup_creditcard_expiration'] = i('Obligatoire'); } elseif (is_numeric($expiration) === false || strlen($expiration) != 4) { $errors['Signup_creditcard_expiration'] = i('Invalide'); } else { $month = (int) preg_replace('/^0/', '', substr($expiration, 0, 2)); $year = substr($expiration, -2); $_POST['Signup_creditcard_expiration'] = (string) $month . $year; if ($month < 1 || $month > 12) { $errors['Signup_creditcard_expiration'] = i('Invalide'); } elseif ($year < date('y') || sprintf('%02d%02d', $year, $month) < date('ym')) { $errors['Signup_creditcard_expiration'] = i('Expiré'); } } if ($number == null) { $errors['Signup_creditcard_number'] = i('Obligatoire'); } elseif (Validation::creditCard($number) === false) { $errors['Signup_creditcard_number'] = i('Invalide'); } if (empty($errors) === false) { throw new UserValidationError($errors); } try { $response = Nuvei::verification($name, $number, $expiration, $cvv); } catch (\Exception $e) { throw new UserError(i('Refusé') . ': ' . $e->getMessage()); } if ($response == null) { return [ 'status' => 'success', 'data' => 'has_cc' ]; } throw new UserError(i('Refusé') . ': ' . $response); } public function doCalculateFees() { $Billing = new \app\db\Billing\Billing(); $Billing->term = $this->request()->post('term'); if ($Billing->inEnumList('term') === false) { throw new UserError(i('Terme de facturation invalide')); } $post_modules = $this->request()->post('modules', []); if (empty($post_modules) === true || is_array($post_modules) === false) { throw new UserError(i('Sélection de module invalide, ne peut pas valider les frais mensuels')); } $Billing->month = date('Y-m'); $Billing->setPromoCode($this->request()->post('promo')); $Module = new \app\db\Core\Module(); $modules = array_keys($Module->enumList('module')); $modules_label = $Module->modules(); $billing_modules = []; $selected_modules = []; $detailled_selected_modules = []; foreach ($modules as $module) { $billing_modules[strtolower($module)] = null; } $yearly_fees = 0; $monthly_fees = 0; $valid_levels = $Module->levels(); foreach ($post_modules as $module => $level) { if ($level === '' || $level === null) { unset($post_modules[$module]); continue; } $Module->fromID($module); if ($Module->getID() == null) { throw new UserError(i('Sélection de module invalide, ne peut pas valider les frais mensuels')); } if (in_array($level, $valid_levels) === false) { throw new UserError(i('Niveau invalide sélectionné pour le module "%s", ne peut pas valider les frais mensuels', $Module->format($Module->nameAttribute()))); } $yearly_fees += $Module->{$level}->yearly->price; $monthly_fees += $Module->{$level}->monthly->price; $billing_modules[strtolower($module)] = str_replace('level', '', $level); if ($Billing->term == 'MONTHLY') { $module_price = Format::number($Module->{$level}->monthly->price, 2) . '$'; } elseif ($Billing->term == 'YEARLY') { $module_price = Format::number($Module->{$level}->yearly->price, 2) . '$'; } $module_info = i($module) . ': ' . $Module->label($level) . ' (' . $module_price . ')'; $selected_modules[] = $module_info; $detailled_selected_modules[] = [ 'id' => $module, 'label' => $modules_label[$module] ?? '', 'level' => $Module->label($level), 'price' => $module_price ]; } $Billing->modules_fees = ($Billing->term == 'YEARLY') ? $yearly_fees : $monthly_fees; $Billing ->setModules($billing_modules) ->applyPromoDiscount() ->setSubtotalTaxesTotal(); $data = [ 'term' => ($Billing->term == 'YEARLY') ? i('annuellement') : i('mensuellement'), 'termid' => $Billing->term, 'modules' => $selected_modules, 'detailled_selected_modules' => $detailled_selected_modules, 'raw_modules' => $post_modules, 'tax1' => $Billing->tax1, 'tax1_formatted' => $Billing->format('tax1'), 'tax2' => $Billing->tax2, 'tax2_formatted' => $Billing->format('tax2'), 'first_month_fees' => $Billing->total, 'first_month_fees_formatted' => $Billing->format('total'), 'discount' => $Billing->promo_discount, 'discount_formatted' => $Billing->format('promo_discount') ]; // Si des mois gratuit, récupère le montant après la période gratuite (pour garder le % de rabais si applicable) if ($Billing->promo_free_month_until->isNull() === false) { // Lendemain de la fin de la promo "mois gratuit(s)" $timestamp_after_free_months_promo = $Billing->promo_free_month_until->getTimeStamp() + 86400; $total_during_promo = $Billing->total; $Billing = new \app\db\Billing\Billing(); $Billing->setPromoCode($this->request()->post('promo')); $Billing->term = $this->request()->post('term'); $Billing->modules_fees = ($Billing->term == 'YEARLY') ? $yearly_fees : $monthly_fees; $Billing->billing_date->set($timestamp_after_free_months_promo); $month = clone $Billing->billing_date; $month->apply('-1 month'); // Car on facture l'utilisation du mois précédent $Billing->month = $month->get('Y-m'); $Billing ->setModules($billing_modules) ->applyPromoDiscount() ->setSubtotalTaxesTotal(); $data['fees'] = $Billing->total; $data['fees_formatted'] = $Billing->format('total'); // Pour une facturation annuelle, le rabais de promo n'est pas calculé pour les mois gratuits // comme la facturation est simplement repoussée après la période d'essai gratuite. // Donc pour donner une valeur qui fait du sens, on prend le total, divisé par le nombre de mois // de la promo et on l'assigne if ($Billing->term == 'YEARLY') { $days_until_promo_end = $Billing->promo_free_month_until->getDiffDays(date('Y-m-d')); $Billing->promo_discount = round($Billing->total / 365 * $days_until_promo_end, 2); $data['discount'] = $Billing->promo_discount; $data['discount_formatted'] = $Billing->format('promo_discount'); } else { // Pour une facturation mensuelle, le rabais de promo n'inclus pas les taxes, ajuste pour inclure les taxes $Billing->promo_discount = $Billing->total - $total_during_promo; $data['discount'] = $Billing->promo_discount; $data['discount_formatted'] = $Billing->format('promo_discount'); } } else { $data['fees'] = $data['first_month_fees']; $data['fees_formatted'] = $data['first_month_fees_formatted']; } $Billing = new \app\db\Billing\Billing(); $Billing->term = $this->request()->post('term'); $Billing->modules_fees = ($Billing->term == 'YEARLY') ? $yearly_fees : $monthly_fees; $Billing->month = date('Y-m'); $Billing ->setModules($billing_modules) ->setSubtotalTaxesTotal(); $data['regular_fees'] = $Billing->total; $data['regular_fees_formatted'] = $Billing->format('total'); $data['upsale_yearly_savings'] = round((($monthly_fees * 12) - $yearly_fees) / 12, 2); $data['upsale_yearly_savings_formatted'] = Format::number($data['upsale_yearly_savings'], 2); $Module->fromID('BASE'); $expected_users = (int)$this->request()->post('users_expected'); $base = $Module->{'level' . $billing_modules['base']}->{strtolower($Billing->term)}; if ($base->user_included < $expected_users) { $Billing->extras = ($expected_users - $base->user_included) * $base->user_extra; $Billing->setSubtotalTaxesTotal(); $data['expected_with_users'] = $Billing->total; $data['expected_with_users_formatted'] = $Billing->format('total'); } return [ 'status' => 'success', 'data' => $data ]; } private function getStepResponse($step): array { try { $response = $this->{$step}(); } catch (UserValidationError $e) { $response = [ 'status' => 'error', 'validation' => $e->getFields() ]; } return $response; } public function doSignup() { Localization::set($this->request()->cookie('signup_language', Localization::get())); $promo_code_id = null; $validation = []; $response = $this->getStepResponse('doCheckInfos'); if ($response['status'] != 'success') { $validation = $response['validation']; } $response = $this->getStepResponse('doCheckNumber'); if ($response['status'] != 'success') { $validation = array_merge($validation, $response['validation']); } $response = $this->getStepResponse('doCheckCreditCard'); if ($response['status'] != 'success') { $validation = array_merge($validation, $response['validation']); if (isset($response['msg'])) { $validation['Signup_creditcard_number'] = $response['msg']; } } elseif (($response['data'] ?? '') == 'has_cc') { $_POST['Signup_finance_cc'] = 'YES'; // Si elle n'était pas obligatoire, mais qu'elle a été fournie, on veut la sauvegarder à l'inscription } $response = $this->getStepResponse('doCheckPromoCode'); if ($response['status'] != 'success') { $validation = array_merge($validation, $response['validation']); } else { $promo_code_id = $response['data'] !== false ? $response['data']->id : null; } $fees_response = $this->getStepResponse('doCalculateFees'); $_POST['modules'] = $fees_response['data']['raw_modules']; // Remplace par la sélection de modules "nettoyée" if (empty($validation) === false) { throw new UserValidationError($validation); } $Pending = new \app\db\Core\CompanyPending(); $Pending->payload = $_POST; $Pending->language = Localization::get(); $Pending->insert(); if ($promo_code_id) { $promo_code = new \app\db\Core\PromoCode($promo_code_id); $promo_code->addUse(); } App::task('company_creation', $Pending->id); return [ 'status' => 'success', 'msg' => i('Votre compte sera créé sous peu. Un courriel vous sera envoyé lorsque celui-ci sera activé. Ceci peut prendre quelques minutes. Si vous ne recevez pas de courriel dans les prochaines 24-heures, contactez-nous.'), 'data' => [ 'term' => $fees_response['data']['termid'], 'modules' => $fees_response['data']['raw_modules'], 'first_month_fees' => $fees_response['data']['first_month_fees'], 'fees' => $fees_response['data']['fees'] ] ]; } public function getNumberSuggestions() { Localization::set($this->request()->cookie('signup_language', Localization::get())); $npa = $this->request()->post('Signup_phone_number_npa'); $nxx = $this->request()->post('Signup_phone_number_nxx'); if (is_numeric($npa) === false || strlen($npa) != 3) { $errors['Signup_phone_number_npa'] = i('Code régional invalide'); } if ($nxx == null) { $nxx = null; } elseif (is_numeric($nxx) === false || strlen($nxx) != 3) { $errors['Signup_phone_number_nxx'] = i('Code local invalide'); } if (empty($errors) === false) { throw new UserValidationError($errors); } try { $dids = []; $vmu_dids = Voicemeup::getDidsInInventory(null, false, $npa, $nxx); foreach ($vmu_dids as $did) { $dids[] = [ 'did' => $did->phone_number, 'number' => Format::phone($did->phone_number) ]; } } catch (UserError $e) { $errors['Signup_phone_number_npa'] = $e->getMessage(); if ($nxx != null) { $errors['Signup_phone_number_nxx'] = ''; } throw new UserValidationError($errors); } return [ 'status' => 'success', 'data' => $dids ]; } }

Non trouvé