Changeset View
Changeset View
Standalone View
Standalone View
src/app/Http/Controllers/API/SignupController.php
<?php | <?php | ||||
namespace App\Http\Controllers\API; | namespace App\Http\Controllers\API; | ||||
use App\Http\Controllers\Controller; | use App\Http\Controllers\Controller; | ||||
use App\Jobs\SignupVerificationEmail; | use App\Jobs\SignupVerificationEmail; | ||||
use App\Discount; | use App\Discount; | ||||
use App\Domain; | use App\Domain; | ||||
use App\Plan; | use App\Plan; | ||||
use App\Providers\PaymentProvider; | |||||
use App\Rules\SignupExternalEmail; | use App\Rules\SignupExternalEmail; | ||||
use App\Rules\SignupToken; | use App\Rules\SignupToken; | ||||
use App\Rules\Password; | use App\Rules\Password; | ||||
use App\Rules\UserEmailDomain; | use App\Rules\UserEmailDomain; | ||||
use App\Rules\UserEmailLocal; | use App\Rules\UserEmailLocal; | ||||
use App\SignupCode; | use App\SignupCode; | ||||
use App\SignupInvitation; | use App\SignupInvitation; | ||||
use App\User; | use App\User; | ||||
Show All 11 Lines | class SignupController extends Controller | ||||
* Returns plans definitions for signup. | * Returns plans definitions for signup. | ||||
* | * | ||||
* @param \Illuminate\Http\Request $request HTTP request | * @param \Illuminate\Http\Request $request HTTP request | ||||
* | * | ||||
* @return \Illuminate\Http\JsonResponse JSON response | * @return \Illuminate\Http\JsonResponse JSON response | ||||
*/ | */ | ||||
public function plans(Request $request) | public function plans(Request $request) | ||||
{ | { | ||||
$plans = []; | |||||
// Use reverse order just to have individual on left, group on right ;) | // Use reverse order just to have individual on left, group on right ;) | ||||
// But prefer monthly on left, yearly on right | // But prefer monthly on left, yearly on right | ||||
Plan::withEnvTenantContext()->orderBy('months')->orderByDesc('title')->get() | $plans = Plan::withEnvTenantContext()->orderBy('months')->orderByDesc('title')->get() | ||||
->map(function ($plan) use (&$plans) { | ->map(function ($plan) { | ||||
// Allow themes to set custom button label | $button = self::trans("app.planbutton-{$plan->title}"); | ||||
$button = \trans('theme::app.planbutton-' . $plan->title); | if (strpos($button, 'app.planbutton') !== false) { | ||||
if ($button == 'theme::app.planbutton-' . $plan->title) { | $button = self::trans('app.planbutton', ['plan' => $plan->name]); | ||||
$button = \trans('app.planbutton', ['plan' => $plan->name]); | |||||
} | } | ||||
$plans[] = [ | return [ | ||||
'title' => $plan->title, | 'title' => $plan->title, | ||||
'name' => $plan->name, | 'name' => $plan->name, | ||||
'button' => $button, | 'button' => $button, | ||||
'description' => $plan->description, | 'description' => $plan->description, | ||||
'mode' => $plan->mode ?: 'email', | 'mode' => $plan->mode ?: Plan::MODE_EMAIL, | ||||
'isDomain' => $plan->hasDomain(), | 'isDomain' => $plan->hasDomain(), | ||||
]; | ]; | ||||
}); | }) | ||||
->all(); | |||||
return response()->json(['status' => 'success', 'plans' => $plans]); | return response()->json(['status' => 'success', 'plans' => $plans]); | ||||
} | } | ||||
/** | /** | ||||
* Returns list of public domains for signup. | * Returns list of public domains for signup. | ||||
* | * | ||||
* @param \Illuminate\Http\Request $request HTTP request | * @param \Illuminate\Http\Request $request HTTP request | ||||
Show All 20 Lines | public function init(Request $request) | ||||
$rules = [ | $rules = [ | ||||
'first_name' => 'max:128', | 'first_name' => 'max:128', | ||||
'last_name' => 'max:128', | 'last_name' => 'max:128', | ||||
'voucher' => 'max:32', | 'voucher' => 'max:32', | ||||
]; | ]; | ||||
$plan = $this->getPlan(); | $plan = $this->getPlan(); | ||||
if ($plan->mode == 'token') { | if ($plan->mode == Plan::MODE_TOKEN) { | ||||
$rules['token'] = ['required', 'string', new SignupToken()]; | $rules['token'] = ['required', 'string', new SignupToken()]; | ||||
} else { | } else { | ||||
$rules['email'] = ['required', 'string', new SignupExternalEmail()]; | $rules['email'] = ['required', 'string', new SignupExternalEmail()]; | ||||
} | } | ||||
// Check required fields, validate input | // Check required fields, validate input | ||||
$v = Validator::make($request->all(), $rules); | $v = Validator::make($request->all(), $rules); | ||||
if ($v->fails()) { | if ($v->fails()) { | ||||
return response()->json(['status' => 'error', 'errors' => $v->errors()->toArray()], 422); | return response()->json(['status' => 'error', 'errors' => $v->errors()->toArray()], 422); | ||||
} | } | ||||
// Generate the verification code | // Generate the verification code | ||||
$code = SignupCode::create([ | $code = SignupCode::create([ | ||||
'email' => $plan->mode == 'token' ? $request->token : $request->email, | 'email' => $plan->mode == Plan::MODE_TOKEN ? $request->token : $request->email, | ||||
'first_name' => $request->first_name, | 'first_name' => $request->first_name, | ||||
'last_name' => $request->last_name, | 'last_name' => $request->last_name, | ||||
'plan' => $plan->title, | 'plan' => $plan->title, | ||||
'voucher' => $request->voucher, | 'voucher' => $request->voucher, | ||||
]); | ]); | ||||
$response = [ | $response = [ | ||||
'status' => 'success', | 'status' => 'success', | ||||
'code' => $code->code, | 'code' => $code->code, | ||||
'mode' => $plan->mode ?: 'email', | 'mode' => $plan->mode ?: 'email', | ||||
]; | ]; | ||||
if ($plan->mode == 'token') { | if ($plan->mode == Plan::MODE_TOKEN) { | ||||
// Token verification, jump to the last step | // Token verification, jump to the last step | ||||
$has_domain = $plan->hasDomain(); | $has_domain = $plan->hasDomain(); | ||||
$response['short_code'] = $code->short_code; | $response['short_code'] = $code->short_code; | ||||
$response['is_domain'] = $has_domain; | $response['is_domain'] = $has_domain; | ||||
$response['domains'] = $has_domain ? [] : Domain::getPublicDomains(); | $response['domains'] = $has_domain ? [] : Domain::getPublicDomains(); | ||||
} else { | } else { | ||||
// External email verification, send an email message | // External email verification, send an email message | ||||
▲ Show 20 Lines • Show All 85 Lines • ▼ Show 20 Lines | public function verify(Request $request, $update = true) | ||||
'last_name' => $code->last_name, | 'last_name' => $code->last_name, | ||||
'voucher' => $code->voucher, | 'voucher' => $code->voucher, | ||||
'is_domain' => $has_domain, | 'is_domain' => $has_domain, | ||||
'domains' => $has_domain ? [] : Domain::getPublicDomains(), | 'domains' => $has_domain ? [] : Domain::getPublicDomains(), | ||||
]); | ]); | ||||
} | } | ||||
/** | /** | ||||
* Finishes the signup process by creating the user account. | * Validates the input to the final signup request. | ||||
* | * | ||||
* @param \Illuminate\Http\Request $request HTTP request | * @param \Illuminate\Http\Request $request HTTP request | ||||
* | * | ||||
* @return \Illuminate\Http\JsonResponse JSON response | * @return \Illuminate\Http\JsonResponse JSON response | ||||
*/ | */ | ||||
public function signup(Request $request) | public function signupValidate(Request $request) | ||||
{ | { | ||||
// Validate input | // Validate input | ||||
$v = Validator::make( | $v = Validator::make( | ||||
$request->all(), | $request->all(), | ||||
[ | [ | ||||
'login' => 'required|min:2', | 'login' => 'required|min:2', | ||||
'password' => ['required', 'confirmed', new Password()], | 'password' => ['required', 'confirmed', new Password()], | ||||
'domain' => 'required', | 'domain' => 'required', | ||||
'voucher' => 'max:32', | 'voucher' => 'max:32', | ||||
] | ] | ||||
); | ); | ||||
if ($v->fails()) { | if ($v->fails()) { | ||||
return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); | return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); | ||||
} | } | ||||
$settings = []; | $settings = []; | ||||
// Plan parameter is required/allowed in mandate mode | // Plan parameter is required/allowed in mandate mode | ||||
if (!empty($request->plan) && empty($request->code) && empty($request->invitation)) { | if (!empty($request->plan) && empty($request->code) && empty($request->invitation)) { | ||||
$plan = Plan::withEnvTenantContext()->where('title', $request->plan)->first(); | $plan = Plan::withEnvTenantContext()->where('title', $request->plan)->first(); | ||||
if (!$plan || $plan->mode != 'mandate') { | if (!$plan || $plan->mode != Plan::MODE_MANDATE) { | ||||
$msg = \trans('validation.exists', ['attribute' => 'plan']); | $msg = \trans('validation.exists', ['attribute' => 'plan']); | ||||
return response()->json(['status' => 'error', 'errors' => ['plan' => $msg]], 422); | return response()->json(['status' => 'error', 'errors' => ['plan' => $msg]], 422); | ||||
} | } | ||||
} elseif ($request->invitation) { | } elseif ($request->invitation) { | ||||
// Signup via invitation | // Signup via invitation | ||||
$invitation = SignupInvitation::withEnvTenantContext()->find($request->invitation); | $invitation = SignupInvitation::withEnvTenantContext()->find($request->invitation); | ||||
if (empty($invitation) || $invitation->isCompleted()) { | if (empty($invitation) || $invitation->isCompleted()) { | ||||
Show All 32 Lines | public function signupValidate(Request $request) | ||||
// Get user name/email from the verification code database | // Get user name/email from the verification code database | ||||
$code_data = $v->getData(); | $code_data = $v->getData(); | ||||
$settings = [ | $settings = [ | ||||
'first_name' => $code_data->first_name, | 'first_name' => $code_data->first_name, | ||||
'last_name' => $code_data->last_name, | 'last_name' => $code_data->last_name, | ||||
]; | ]; | ||||
if ($plan->mode == 'token') { | if ($plan->mode == Plan::MODE_TOKEN) { | ||||
$settings['signup_token'] = $code_data->email; | $settings['signup_token'] = $code_data->email; | ||||
} else { | } else { | ||||
$settings['external_email'] = $code_data->email; | $settings['external_email'] = $code_data->email; | ||||
} | } | ||||
} | } | ||||
// Find the voucher discount | // Find the voucher discount | ||||
if ($request->voucher) { | if ($request->voucher) { | ||||
$discount = Discount::where('code', \strtoupper($request->voucher)) | $discount = Discount::where('code', \strtoupper($request->voucher)) | ||||
->where('active', true)->first(); | ->where('active', true)->first(); | ||||
if (!$discount) { | if (!$discount) { | ||||
$errors = ['voucher' => \trans('validation.voucherinvalid')]; | $errors = ['voucher' => \trans('validation.voucherinvalid')]; | ||||
return response()->json(['status' => 'error', 'errors' => $errors], 422); | return response()->json(['status' => 'error', 'errors' => $errors], 422); | ||||
} | } | ||||
} | } | ||||
if (empty($plan)) { | if (empty($plan)) { | ||||
$plan = $this->getPlan(); | $plan = $this->getPlan(); | ||||
} | } | ||||
$is_domain = $plan->hasDomain(); | $is_domain = $plan->hasDomain(); | ||||
$login = $request->login; | |||||
$domain_name = $request->domain; | |||||
// Validate login | // Validate login | ||||
if ($errors = self::validateLogin($login, $domain_name, $is_domain)) { | if ($errors = self::validateLogin($request->login, $request->domain, $is_domain)) { | ||||
return response()->json(['status' => 'error', 'errors' => $errors], 422); | return response()->json(['status' => 'error', 'errors' => $errors], 422); | ||||
} | } | ||||
// Set some properties for signup() method | |||||
$request->settings = $settings; | |||||
$request->plan = $plan; | |||||
$request->discount = $discount ?? null; | |||||
$request->invitation = $invitation ?? null; | |||||
$result = []; | |||||
if ($plan->mode == Plan::MODE_MANDATE) { | |||||
$result = $this->mandateForPlan($plan, $request->discount); | |||||
} | |||||
return response()->json($result); | |||||
} | |||||
/** | |||||
* Finishes the signup process by creating the user account. | |||||
* | |||||
* @param \Illuminate\Http\Request $request HTTP request | |||||
* | |||||
* @return \Illuminate\Http\JsonResponse JSON response | |||||
*/ | |||||
public function signup(Request $request) | |||||
{ | |||||
$v = $this->signupValidate($request); | |||||
if ($v->status() !== 200) { | |||||
return $v; | |||||
} | |||||
$is_domain = $request->plan->hasDomain(); | |||||
// We allow only ASCII, so we can safely lower-case the email address | // We allow only ASCII, so we can safely lower-case the email address | ||||
$login = Str::lower($login); | $login = Str::lower($request->login); | ||||
$domain_name = Str::lower($domain_name); | $domain_name = Str::lower($request->domain); | ||||
$domain = null; | $domain = null; | ||||
DB::beginTransaction(); | DB::beginTransaction(); | ||||
// Create domain record | // Create domain record | ||||
if ($is_domain) { | if ($is_domain) { | ||||
$domain = Domain::create([ | $domain = Domain::create([ | ||||
'namespace' => $domain_name, | 'namespace' => $domain_name, | ||||
'type' => Domain::TYPE_EXTERNAL, | 'type' => Domain::TYPE_EXTERNAL, | ||||
]); | ]); | ||||
} | } | ||||
// Create user record | // Create user record | ||||
$user = User::create([ | $user = User::create([ | ||||
'email' => $login . '@' . $domain_name, | 'email' => $login . '@' . $domain_name, | ||||
'password' => $request->password, | 'password' => $request->password, | ||||
'status' => User::STATUS_RESTRICTED, | 'status' => User::STATUS_RESTRICTED, | ||||
]); | ]); | ||||
if (!empty($discount)) { | if ($request->discount) { | ||||
$wallet = $user->wallets()->first(); | $wallet = $user->wallets()->first(); | ||||
$wallet->discount()->associate($discount); | $wallet->discount()->associate($request->discount); | ||||
$wallet->save(); | $wallet->save(); | ||||
} | } | ||||
$user->assignPlan($plan, $domain); | $user->assignPlan($request->plan, $domain); | ||||
// Save the external email and plan in user settings | // Save the external email and plan in user settings | ||||
$user->setSettings($settings); | $user->setSettings($request->settings); | ||||
// Update the invitation | // Update the invitation | ||||
if (!empty($invitation)) { | if ($request->invitation) { | ||||
$invitation->status = SignupInvitation::STATUS_COMPLETED; | $request->invitation->status = SignupInvitation::STATUS_COMPLETED; | ||||
$invitation->user_id = $user->id; | $request->invitation->user_id = $user->id; | ||||
$invitation->save(); | $request->invitation->save(); | ||||
} | } | ||||
// Soft-delete the verification code, and store some more info with it | // Soft-delete the verification code, and store some more info with it | ||||
if ($request->code) { | if ($request->code) { | ||||
$request->code->user_id = $user->id; | $request->code->user_id = $user->id; | ||||
$request->code->submit_ip_address = $request->ip(); | $request->code->submit_ip_address = $request->ip(); | ||||
$request->code->deleted_at = \now(); | $request->code->deleted_at = \now(); | ||||
$request->code->timestamps = false; | $request->code->timestamps = false; | ||||
$request->code->save(); | $request->code->save(); | ||||
} | } | ||||
DB::commit(); | DB::commit(); | ||||
$response = AuthController::logonResponse($user, $request->password); | $response = AuthController::logonResponse($user, $request->password); | ||||
// Redirect the user to the specified page | if ($request->plan->mode == Plan::MODE_MANDATE) { | ||||
// $data = $response->getData(true); | $data = $response->getData(true); | ||||
// $data['redirect'] = 'wallet'; | $data['checkout'] = $this->mandateForPlan($request->plan, $request->discount, $user); | ||||
// $response->setData($data); | $response->setData($data); | ||||
} | |||||
return $response; | return $response; | ||||
} | } | ||||
/** | /** | ||||
* Collects some content to display to the user before redirect to a checkout page. | |||||
* Optionally creates a recurrent payment mandate for specified user/plan. | |||||
*/ | |||||
protected function mandateForPlan(Plan $plan, Discount $discount = null, User $user = null): array | |||||
{ | |||||
$result = []; | |||||
$min = \App\Payment::MIN_AMOUNT; | |||||
$planCost = $plan->cost() * $plan->months; | |||||
if ($discount) { | |||||
$planCost -= ceil($planCost * (100 - $discount->discount) / 100); | |||||
} | |||||
if ($planCost > $min) { | |||||
$min = $planCost; | |||||
} | |||||
if ($user) { | |||||
$wallet = $user->wallets()->first(); | |||||
$wallet->setSettings([ | |||||
'mandate_amount' => sprintf('%.2f', round($min / 100, 2)), | |||||
'mandate_balance' => 0, | |||||
]); | |||||
$mandate = [ | |||||
'currency' => $wallet->currency, | |||||
'description' => \App\Tenant::getConfig($user->tenant_id, 'app.name') . ' Auto-Payment Setup', | |||||
'methodId' => PaymentProvider::METHOD_CREDITCARD, | |||||
'redirectUrl' => \App\Utils::serviceUrl('/payment/status', $user->tenant_id), | |||||
]; | |||||
$provider = PaymentProvider::factory($wallet); | |||||
$result = $provider->createMandate($wallet, $mandate); | |||||
} | |||||
$params = [ | |||||
'cost' => \App\Utils::money($planCost, \config('app.currency')), | |||||
'period' => \trans($plan->months == 12 ? 'app.period-year' : 'app.period-month'), | |||||
]; | |||||
$content = '<b>' . self::trans('app.signup-account-tobecreated') . '</b><br><br>' | |||||
. self::trans('app.signup-account-summary', $params) . '<br><br>' | |||||
. self::trans('app.signup-account-mandate', $params); | |||||
$result['content'] = $content; | |||||
return $result; | |||||
} | |||||
/** | |||||
* Returns plan for the signup process | * Returns plan for the signup process | ||||
* | * | ||||
* @returns \App\Plan Plan object selected for current signup process | * @returns \App\Plan Plan object selected for current signup process | ||||
*/ | */ | ||||
protected function getPlan() | protected function getPlan() | ||||
{ | { | ||||
$request = request(); | $request = request(); | ||||
▲ Show 20 Lines • Show All 71 Lines • Show Last 20 Lines |