ldc-gen-game-web/app/Http/Controllers/Fo/ParticipateController.php
noah.lancien 218a3a7891 phpstan
2024-01-31 09:03:18 +00:00

362 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Customers\CreateAccountRequest;
use App\Http\Requests\Fo\ParticipateRequest;
use App\Lib\Discount as LibDiscount;
use App\Mail\Won\PrizeWonAdminMail;
use App\Models\Customer;
use App\Models\InstantWin;
use App\Models\Participation;
use App\Models\Discount;
use App\Models\Settings;
use App\Notifications\Customer\HasWonDiscount;
use App\Notifications\Customer\HasWonInstantWin;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
class ParticipateController extends Controller
{
/**
* ParticipateController
*
* @return void
*/
public function __construct()
{
$this->middleware(function (Request $request, callable $next) {
$this->authorize('fo.gameEnabled');
$this->assertUserHasEnoughInfoToUseMesBr();
return $next($request);
});
}
/**
* Display participation form
*
* @return \Illuminate\Contracts\View\View
*/
public function participate(): \Illuminate\Contracts\View\View
{
if (!Gate::check('participate', Participation::class)) {
return view('theme::pages.participation.participation-exhausted');
}
return view('theme::pages.participation.participate');
}
/**
* Participate Method
*
* @param \App\Http\Requests\Fo\ParticipateRequest $request
* @return \Illuminate\Contracts\View\View
*/
public function participateSubmit(ParticipateRequest $request): \Illuminate\Contracts\View\View
{
return DB::transaction(function () use ($request) {
// ! On verrouille le ou les instants gagnant
/** @var \App\Models\InstantWin|null */
$instantWin = InstantWin::query()->lockForUpdate()
->where('available_at', '<', now())
->whereDoesntHave('participation')
->get()->sortBy('available_at')->first();
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var \App\Models\Customer */
$customer = auth('frontend')->user();
/** @var \App\Models\Code */
$code = $request->post('unique_code');
/** @var \App\Models\Product|null */
$product = $request->post('product');
// * Make new participation
$participation = Participation::makeNew(
app('clientIp'),
$customer,
$code,
$product
);
// * Si l'utilisateur actuel à déjà gagné un gros lot, pas d'instant gagnant.
// ? As-t-on un instant gagnant ?
if (Participation::customerAlreadyHasAnInstantWin($customer) or !$instantWin) {
// * --- DISCOUNT WIN ---
// ? Les BR sont activés ?
if (!$settings->discounts_activated) {
// * On enregistre la participation du joueur.
$participation->save();
// * Pas de coupons disponibles, le joueur à perdu
return view('theme::pages.participation.loose');
}
// * Le joueur à gagné un coupon.
/** @var \App\Lib\Discount Coupon aléatoire en fonction du ratio de chaque coupon */
$discount = Arr::get(
$settings->web_coupons,
$this->getRandomWeightedElementIndex($settings->web_coupons)
);
$mesBrDiscount = $this->setDiscountAsWon($customer, $discount);
// * Le joueur à remporté un coupon
$participation->discount()->associate($mesBrDiscount);
// * On enregistre la participation du joueur.
$participation->save();
$crmCustomer = $this->fetchCrmCustomer($customer);
$customer->notify(new HasWonDiscount(
$discount,
$mesBrDiscount,
$crmCustomer->firstname
));
return view('theme::pages.participation.won', compact('discount', 'mesBrDiscount'));
} //end if
// * --- INSTANT WIN ---
// * On enregistre la participation du joueur.
$participation->save();
// * Le joueur à remporté un instant gagnant
$instantWin->participation()->associate($participation);
$instantWin->save();
$crmCustomer = $this->fetchCrmCustomer($customer);
// * Notify customer for his win
$customer->notify(new HasWonInstantWin(
$instantWin->prize,
$crmCustomer->firstname
));
// * Dispatch notification to mail list
Mail::to($settings->notification_emails)->send(new PrizeWonAdminMail(
$settings->name,
$participation->created_at,
$instantWin->prize->label,
$crmCustomer,
$code->code_unique,
optional($product)->ean_code
));
return view('theme::pages.participation.won', [
'prizeModel' => $instantWin->prize,
'participationModel' => $participation,
'crmCustomer' => $crmCustomer
]);
});
}
// * --- PRIVATE METHODS ---
/**
* Try Call CRM + MesBr to setup discount
*
* @param \App\Models\Customer $customer
* @param LibDiscount $discount
* @return \App\Models\Discount
* @throws \Illuminate\Http\Exceptions\HttpResponseException If Any error occurs.
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
private function setDiscountAsWon(Customer $customer, LibDiscount $discount): \App\Models\Discount
{
// phpcs:enable
// * Phase - 1 Recupération des informations CRM
/** @var \LDC\LDCCRM\Models\Customer*/
$crmUser = $this->fetchCrmCustomer($customer);
// * Phase - 2 Mise à jour du coupon dans MesBr
try {
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var \LDC\MESBR\MesBr */
$mesBr = app('mesBrAPI');
// * MaJ Customer
/** @var integer */
$crm_customer_id = retry(3, function () use ($mesBr, $crmUser) {
/**
* Parce que MesBr n'est pas fiable il faut retenter pour avoir
* l'id client, des fois cet id sera dans hashcode...
*
* Id est necessaire pour update panier..
*/
$result = $mesBr->updateCustomer($crmUser->hashcode, $crmUser->email, $crmUser->country);
if (optional($result)->status !== "OK") {
throw new \RuntimeException(
\sprintf(
'MESBR : Une erreur est survenue. Code : %s Message : %s',
$result->code ?? 'Undefined',
$result->message ?? 'Undefined'
)
);
}
/** @var object|null */
$crm_customer = optional($result)->customer;
if (empty(optional($crm_customer)->id)) {
throw new \RuntimeException(
'MESBR : Une erreur est survenue. Update Customer n\'as pas retourné le customer id'
);
}
return intval($crm_customer->id);
}, 100);
// * Maj Panier
$result = $mesBr->updatePanier(
$crmUser,
$crm_customer_id,
$settings->discounts_site,
strval($discount->web_coupon_identifiant)
);
if (Arr::get($result, 'status') === "OK") {
// * La réponse est conforme ?
$needed_fields = ['link', 'verif', 'basketId'];
if (!Arr::has($result, $needed_fields)) {
throw new \RuntimeException(sprintf(
'MESBR : Il manques des champs dans la réponse, attendu => %s, donné => %s',
json_encode($needed_fields),
json_encode($result),
));
}
$discountModel = new Discount();
$discountModel->link = Arr::get($result, 'link');
$discountModel->verification = Arr::get($result, 'verif');
$discountModel->basket_id = Arr::get($result, 'basketId');
$discountModel->save();
// * Tout est OK
return $discountModel;
}
throw new \RuntimeException(
\sprintf(
'MESBR : Une erreur est survenue. Code : %s Message : %s',
$result["code"] ?? 'Undefined',
$result["message"] ?? 'Undefined'
)
);
} catch (\RuntimeException $e) {
report($e);
// * Retenter ?
back()->with('error', trans('Une erreur est survenue, veuillez réssayer.'))->throwResponse();
} //end try
throw new \Error('Should not reach this point');
}
/**
* Fetch Crm Customer
*
* @param \App\Models\Customer $customer
* @return \LDC\LDCCRM\Models\Customer|\Illuminate\Http\RedirectResponse
* @throws \Illuminate\Http\Exceptions\HttpResponseException In case of failure or customer not activated.
*/
private function fetchCrmCustomer(Customer $customer): \LDC\LDCCRM\Models\Customer|\Illuminate\Http\RedirectResponse
{
try {
$crmUser = $customer->api_data;
// ? Le compte du joueur est vérifié ?
if (!$crmUser->status) {
// * Le joueur doit avoir un compte vérifié pour participer.
response()->redirectToRoute('fo.verification.notice')
->with('error', trans('Pour participer votre compte doit être activé'))
->throwResponse();
}
return $crmUser;
} catch (ApiErrorException $e) {
// * Customer is not found.
if ($e->getApiError()->equals(ApiError::modelNotFound())) {
// * Déconnexion immédiate et retour maison.
auth('frontend')->logout();
response()->redirectToRoute('fo.home')
->with('error', trans('Une erreur est survenue, compte introuvable.'))
->throwResponse();
}
report($e);
Log::error('Unhandled response ' . $e->getApiError()->label);
// * Retenter ?
back()->with('error', trans('Une erreur est survenue, veuillez réssayer.'))->throwResponse();
} catch (HttpResponseException $e) {
// * continue this
throw $e;
} catch (CrmException | \Exception $e) {
report($e);
// * Retenter ?
back()->with('error', trans('Une erreur est survenue, veuillez réssayer.'))->throwResponse();
} //end try
return back()->withErrors(trans('Une erreur est survenue, merci de réessayer ultérieurement.'));
}
/**
* Assert that user has enough info filled on his profile to win a Discount
* Or redirect him to the fill form
* ! Also validates its birth date (because beeing an adult is required to participate)
*
* @return void
* @throws \Illuminate\Http\Exceptions\HttpResponseException If customer data doest not validate
* or and error occurs.
*/
private function assertUserHasEnoughInfoToUseMesBr(): void
{
// * Valider que le client dispose des donnés requises pour MesBr
try {
/** @var \App\Models\Customer */
$authUser = auth('frontend')->user();
$crmCustomer = $this->fetchCrmCustomer($authUser);
// * On valide les données selon les critères du site
$rules = collect((new CreateAccountRequest())->rules());
Validator::make(
[
'birth_date' => $crmCustomer->birth_date,
'address' => $crmCustomer->address,
'postal_code' => $crmCustomer->postcode,
'country' => $crmCustomer->country->value,
'city' => $crmCustomer->city
],
[
'birth_date' => $rules->get('birth_date'),
'address' => $rules->get('address'),
'postal_code' => $rules->get('postal_code'),
'country' => $rules->get('country'),
'city' => $rules->get('city')
]
)->validate();
Session::remove('needCrmInfoUpdate');
} catch (ValidationException) {
response()->redirectToRoute('fo.profil.verify')
->with('warning', trans(
'Vous devez compléter des informations sur votre profil pour pouvoir participer.'
))->throwResponse();
} catch (\Exception $e) {
report($e);
// * Retenter ?
response()->redirectToRoute('fo.homepage')
->with('error', trans('Une erreur est survenue, veuillez réssayer.'))
->throwResponse();
} //end try
}
// * --- END PRIVATE METHODS ---
}