ldc-legaulois/app/Http/Controllers/Club/ShopController.php
2026-02-25 15:32:35 +01:00

397 lines
15 KiB
PHP

<?php
namespace App\Http\Controllers\Club;
use App\Enums\Shop\ContactFormCivility;
use App\Enums\Shop\OrderStatus;
use App\Enums\Shop\OrderType;
use App\Exceptions\CBPayementError;
use App\Http\Controllers\Controller;
use App\Http\Requests\Club\Shop\ShopBuyRequest;
use App\Jobs\Arcadia\SendOrderToArcadia;
use App\Jobs\Shop\SendShopMail;
use App\Lib\Axepta\AxeptaPayment;
use App\Lib\Axepta\Enums\PaymentResponse;
use App\Lib\Axepta\Objects\AxeptaOrder;
use App\Models\DeliveryAddress;
use App\Models\Notification;
use App\Models\Order;
use App\Models\SellItem;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ShopController extends Controller
{
/**
* Shop index.
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): \Illuminate\Contracts\View\View
{
$sellItemModels = SellItem::all();
return view('club.pages.shop.index', compact('sellItemModels'));
}
/**
* Ajax service to validate transaction details.
*
* @param \App\Http\Requests\Club\Shop\ShopBuyRequest $request
* @return \Illuminate\Http\JsonResponse
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public function validateBuy(ShopBuyRequest $request): \Illuminate\Http\JsonResponse
{
// phpcs:enable
/** @var \App\Models\Customer */
$customerModel = auth('club')->user();
$items = [];
$orderTotalPrice = 0;
foreach ($request->cart as $item) {
$sellItem = SellItem::query()->where('id', $item['item']['id'])->firstOrFail();
$items[] = [
'id' => $sellItem->getKey(),
'name' => $sellItem->name,
'quantity' => $item['count'],
'unitPrice' => $sellItem->price,
'totalPrice' => $item['count'] * $sellItem->price,
'subject' => $sellItem->arcadia_id
];
$orderTotalPrice += $item['count'] * $sellItem->price;
}
// * Pseudo mutex let database fail (unique ref) to check if order has already been processed
try {
$order = new Order();
$order->ref = str_replace('-', '', $request->ref);
$order->payementType = OrderType::make(intval($request->payementMethod));
$order->status = OrderStatus::waiting;
$order->customer_id = $customerModel->getKey();
$order->fill($request->coords);
$order->saveOrFail();
} catch (QueryException $e) {
$order = Order::query()->where('ref', str_replace('-', '', $request->ref))->firstOrFail();
if ($order->payementType->equals(OrderType::check)) {
return response()->json(['redirect' => route(
'club.shop.validated',
compact('order')
)]);
}
if ($order->payementType->equals(OrderType::cb)) {
return response()->json(['redirect' => route(
'club.shop.payementCB',
compact('order')
)]);
}
abort(500, sprintf('Order already exists but type is unkown %d', $order->getKey()));
} //end try
$order->customer()->associate($customerModel);
foreach ($items as $item) {
$order->sellItems()->withTimestamps()->attach($item['id'], ['quantity' => $item['quantity']]);
}
$order->saveOrFail();
if ($request->shipping) {
$shipping = new DeliveryAddress();
$shipping->fill($request->shipping);
$shipping->order()->associate($order);
$shipping->saveOrFail();
}
switch ($request->payementMethod) {
case OrderType::cb->value():
\session()->put(sprintf('shop.order.%d.time', $order->getKey()), \time());
\session()->save();
return response()->json(['redirect' => route('club.shop.payementCB', compact('order'))]);
case OrderType::check->value():
try {
\session()->put(sprintf('shop.order.%d.time', $order->getKey()), \time());
\session()->save();
SendOrderToArcadia::dispatch($order);
SendShopMail::dispatch($order);
$order->status = OrderStatus::waitingCheck;
$order->saveOrFail();
} catch (Exception $e) {
report($e);
Log::critical(sprintf(
'Could not end customer transaction (save and send mail) in shop for order %d, %s',
$order->getKey(),
$e->getMessage()
));
} finally {
return response()->json(['redirect' => route('club.shop.validated', compact('order'))]);
}
default:
abort(422);
} //end switch
}
/**
* Redirection After Ajax validate.
*
* @param \App\Models\Order $order
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View
*/
public function validatedBuy(Order $order): \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View
{
Notification::getNotifications();
$time = session()->get(sprintf('shop.order.%d.time', $order->getKey()), 0);
if (!($order->status->equals(OrderStatus::payed) || $order->status->equals(OrderStatus::waitingCheck))) {
return response()->redirectToRoute('club.shop.index');
}
// 4 minutes
if (($time + (60 * 4)) < \time()) {
return response()->redirectToRoute('club.shop.index');
}
return view('club.pages.shop.validated');
}
/**
* Pay with CB.
*
* @param \App\Models\Order $order
* @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
* @throws \App\Exceptions\CBPayementError Error response from Axepta Lib.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public function payWithCb(Order $order): \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
{
// phpcs:enable
Notification::getNotifications();
try {
$time = session()->get(sprintf('shop.order.%d.time', $order->getKey()), 0);
if ($order->status->equals(OrderStatus::failed)) {
return view('club.pages.shop.info')
->with('title', trans('Commande annulée'))
->with('message', trans("Le paiement n'a pu aboutir. Veuillez réessayer")
);
}
// 4 minutes
if (($time + (60 * 4)) < \time()) {
return view('club.pages.shop.info')
->with('title', trans('Erreur'))
->with('message', trans('Délai d\'attente expiré, veuillez réessayer'));
}
$totalToPay = $order->sellItems->map(function ($sellItem) {
/**
* Defined in Order model
*
* @phpstan-ignore property.notFound
*/
return $sellItem->pivot->quantity * $sellItem->price;
})->sum();
// * Convert total in cents
$totalToPay = (int) (\number_format((float) $totalToPay, 2)) * 100;
$aPayment = new AxeptaPayment(
config('axepta.endpoint'),
config('axepta.merchantId'),
config('axepta.apiKey'),
config('axepta.hmac')
);
$aOrder = new AxeptaOrder(
$order->ref,
$totalToPay,
$order->customer->email
);
try {
$payment = $aPayment->getCheckoutUrl(
$aOrder,
route('club.shop.reglement.payementConfirmCB', $order),
route('club.shop.reglement.payementConfirmCB', ['order' => $order, 'cancel' => true]),
route('club.shop.reglement.payementConfirmCBServ', $order)
);
return response()->redirectTo($payment);
} catch (\RuntimeException $e) {
$order->status = OrderStatus::failed;
$order->save();
throw new CBPayementError(sprintf(
"Error response from Axepta Lib : %s",
$e->getMessage()
));
}
} catch (ModelNotFoundException | CBPayementError $e) {
report($e);
Log::critical(sprintf(
'Could not end customer transaction in boutique for order %d, %s',
$order->getKey(),
$e->getMessage()
));
return view('club.pages.shop.info')
->with('title', trans('Commande annulée'))
->with('message', trans("Le paiement n'a pu aboutir. Veuillez réessayer")
);
} //end try
}
/**
* Pas de validate ou custom request sur la réponse de Axepta,
* C'est la Lib Axepta qui fait.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Order $order
* @return \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory
* @throws \App\Exceptions\CBPayementError Data were altered from response.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public function confirmCbFromAxepta(
Request $request,
Order $order
): \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory {
// phpcs:enable
try {
$aPayment = new AxeptaPayment(
config('axepta.endpoint'),
config('axepta.merchantId'),
config('axepta.apiKey'),
config('axepta.hmac')
);
if (!$aPayment->isValidWebhookAccess($request)) {
$order->status = OrderStatus::failed;
$order->save();
// @phpstan-ignore finally.exitPoint
throw new CBPayementError('Data were altered from response');
}
$response = $aPayment->getPaymentStatusById($request->payId);
if (is_null($response)) {
if (
!$order->confirmMailSent and
!$order->status->equals(OrderStatus::payed)
) {
$order->status = OrderStatus::failed;
$order->save();
}
return response('', 200);
}
switch (intval($response[0])) {
case PaymentResponse::ok->value:
if (
!$order->confirmMailSent and
!$order->status->equals(OrderStatus::payed)
) {
$order->status = OrderStatus::payed;
$order->save();
SendOrderToArcadia::dispatch($order);
SendShopMail::dispatch($order);
}
break;
case PaymentResponse::transient->value:
case PaymentResponse::emvinfo->value:
break;
case PaymentResponse::error->value:
case PaymentResponse::fatalerror->value:
if (
!$order->confirmMailSent and
!$order->status->equals(OrderStatus::payed)
) {
$order->status = OrderStatus::failed;
$order->save();
}
break;
default:
// * ERROR
if (
!$order->confirmMailSent and
!$order->status->equals(OrderStatus::payed)
) {
$order->status = OrderStatus::failed;
$order->save();
}
// @phpstan-ignore finally.exitPoint
throw new CBPayementError(sprintf(
'Unhandled response from Axepta Server %s on ref %s',
$response,
$order->ref
));
} //end switch
} catch (ModelNotFoundException | CBPayementError | \Exception $e) {
report($e);
Log::critical(sprintf(
'Could not end customer transaction in boutique for order %d, %s',
$order->getKey(),
$e->getMessage()
));
} finally {
// @phpstan-ignore finally.exitPoint
return response('', 200);
} //end try
}
/**
* Confirm CB method
*
* @param Request $request
* @param Order $order
* @return \Illuminate\Contracts\View\View
* @throws \App\Exceptions\CBPayementError Data were altered from response.
*/
public function confirmCb(Request $request, Order $order): \Illuminate\Contracts\View\View
{
Notification::getNotifications();
if (
!$request->has('PayID') or
$request->has('cancel') or
$order->status->equals(OrderStatus::failed)
) {
return view('club.pages.shop.info')
->with('title', trans('Commande annulée'))
->with('message', trans("Le paiement n'a pu aboutir. Veuillez réessayer")
);
}
if ($order->status->equals(OrderStatus::payed)) {
return view('club.pages.shop.validated');
}
$aPayment = new AxeptaPayment(
config('axepta.endpoint'),
config('axepta.merchantId'),
config('axepta.apiKey'),
config('axepta.hmac')
);
$response = $aPayment->getPaymentStatusById($request->PayID);
if (is_null($response)) {
return view('club.pages.shop.info')
->with('title', trans('Commande annulée'))
->with('message', trans("Le paiement n'a pu aboutir. Veuillez réessayer")
);
}
switch (intval($response[0])) {
case PaymentResponse::ok->value:
return view('club.pages.shop.validated');
case PaymentResponse::transient->value:
case PaymentResponse::emvinfo->value:
return view('club.pages.shop.info')
->with('title', 'Paiement en cours')
->with('message', 'Paiement en attente de traitement par la banque');
case PaymentResponse::error->value:
case PaymentResponse::fatalerror->value:
default:
return view('club.pages.shop.info')
->with('title', trans('Commande annulée'))
->with('message', trans("Le paiement n'a pu aboutir. Veuillez réessayer")
);
}
}
}