ldc-legaulois/app/Http/Controllers/BoutiqueController.php
2021-05-03 10:24:30 +02:00

411 lines
15 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Enums\Mercanet\PaymentResponse;
use App\Enums\Mercanet\RedirectionStatusCode;
use App\Enums\Mercanet\TransactionResponse;
use App\Enums\OrderStatus;
use App\Enums\OrderType;
use App\Exceptions\CBPayementError;
use App\Http\Requests\BoutiqueBuyRequest;
use App\Http\Resources\CustomerCrmResource;
use App\Jobs\SendBoutiqueMails;
use App\Jobs\SendOrderToArchadia;
use App\Lib\Mercanet\Mercanet;
use App\Models\Customers;
use App\Models\DeliveryAddress;
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\Auth;
use Illuminate\Support\Facades\Log;
/**
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
*/
class BoutiqueController extends Controller
{
public function index()
{
ClubController::getNotifications();
$sellItems = SellItem::all();
return view('front.club.boutique.boutique', compact('sellItems'));
}
/**
* Ajax service to validate transaction details
*
* @param BoutiqueBuyRequest $request
* @return void
*/
public function validateBuy(BoutiqueBuyRequest $request)
{
$items = [];
$orderTotalPrice = 0;
foreach ($request->cart as $item) {
$sellItem = SellItem::where('id', $item['item']['id'])->firstOrFail();
$items[] = [
'id' => $sellItem->id,
'name' => $sellItem->name,
'quantity' => $item['count'],
'unitPrice' => $sellItem->price,
'totalPrice' => $item['count'] * $sellItem->price,
'subject' => $sellItem->archadia_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((int)$request->payementMethod);
$order->status = OrderStatus::waiting();
$order->fill($request->coords);
$order->saveOrFail();
} catch (QueryException $e) {
$order = Order::where('ref', str_replace('-', '', $request->ref))->firstOrFail();
if ($order->payementType->equals(OrderType::check())) {
return response()->json(['redirect' => route(
'club.boutique.validated',
compact('order')
)]);
}
if ($order->payementType->equals(OrderType::cb())) {
return response()->json(['redirect' => route(
'club.boutique.payementCB',
compact('order')
)]);
}
abort(500, sprintf('Order already exists but type is unkown %d', $order->id));
}
/** @var \App\Models\Customers */
$customer = Auth::guard('customer')->user();
if ($customer) {
$customer->updateApiData(new CustomerCrmResource($customer));
$order->customer()->associate($customer);
}
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():
\session()->put(sprintf('boutique.order.%d.time', $order->id), \time());
\session()->save();
return response()->json(['redirect' => route('club.boutique.payementCB', compact('order'))]);
case OrderType::check():
try {
\session()->put(sprintf('boutique.order.%d.time', $order->id), \time());
\session()->save();
SendOrderToArchadia::dispatch($order);
SendBoutiqueMails::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 boutique for order %d, %s',
$order->id,
$e->getMessage()
));
} finally {
return response()->json(['redirect' => route('club.boutique.validated', compact('order'))]);
}
break; // not used
default:
abort(422);
}
}
/**
* Redirection After Ajax validate
*
* @param Order $order
* @return void
*/
public function validatedBuy(Order $order)
{
ClubController::getNotifications();
$time = session()->get(sprintf('boutique.order.%d.time', $order->id), 0);
// phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
if (
!($order->status->equals(OrderStatus::payed()) ||
$order->status->equals(OrderStatus::waitingCheck()))
) {
return response()->redirectToRoute('club.boutique.index');
}
// 4 minutes
if (($time + (60 * 4)) < \time()) {
return response()->redirectToRoute('club.boutique.index');
}
return view('front.club.boutique.validated');
}
public function payWithCb(Order $order)
{
ClubController::getNotifications();
try {
$time = session()->get(sprintf('boutique.order.%d.time', $order->id), 0);
if ($order->status->equals(OrderStatus::failed())) {
return response()->redirectToRoute('club.boutique.index');
}
// 4 minutes
if (($time + (60 * 4)) < \time()) {
return response()->redirectToRoute('club.boutique.index');
}
$totalToPay = $order->sellItems->map(function ($sellItem) {
return $sellItem->pivot->quantity * $sellItem->price;
})->sum();
// Convert total in cents
$totalToPay = (int)(\number_format((float)$totalToPay, 2) * 100);
$mercanet = $this->getMercanet($order);
$payment = $mercanet->QuickPayment($totalToPay, $order->ref);
switch ($payment->code) {
case RedirectionStatusCode::redirect():
return view('front.club.boutique.redirectMercanet', [
'url' => $payment->url,
'data' => $payment->data,
'version' => $payment->version
]);
case RedirectionStatusCode::referenceAlreadyUsed():
return response()->redirectToRoute('club.boutique.index');
case RedirectionStatusCode::requestError03():
case RedirectionStatusCode::requestError12():
case RedirectionStatusCode::requestError30():
case RedirectionStatusCode::requestError34():
case RedirectionStatusCode::serviceDisponibiltyIssue():
$order->status = OrderStatus::failed();
$order->save();
throw new CBPayementError(sprintf(
"Error response from Mercanet Lib : %s\n %s",
RedirectionStatusCode::make(intval($payment->code))->label,
json_encode($payment)
));
default:
$order->status = OrderStatus::failed();
$order->save();
throw new CBPayementError(sprintf(
'Un handled response from Mercanet Lib %s',
json_encode($payment)
));
}
} catch (ModelNotFoundException | CBPayementError $e) {
report($e);
Log::critical(sprintf(
'Could not end customer transaction in boutique for order %d, %s',
$order->id,
$e->getMessage()
));
return view('front.club.boutique.error');
}
}
/**
** Pas de validate ou custom request sur la réponse de mercanet,
** c'est la Lib mercanet qui fait
*/
public function confirmCbFromMercanet(Request $request, Order $order)
{
try {
\extract($this->getMercanetData($request));
$order = Order::where('ref', $ref)
->firstOrFail();
$mercanet = $this->getMercanet($order);
$mercanet->setResponse($request->post());
if (!$mercanet->isValid()) {
$order->status = OrderStatus::failed();
$order->save();
throw new CBPayementError('Data were altered from response');
}
switch ($responseCode) {
case TransactionResponse::accepted():
$order->status = OrderStatus::payed();
$order->save();
SendOrderToArchadia::dispatch($order);
SendBoutiqueMails::dispatch($order);
break;
case TransactionResponse::refused():
case TransactionResponse::fraud():
case TransactionResponse::maxReached():
$order->status = OrderStatus::failed();
$order->save();
break;
case TransactionResponse::technicalIssue():
throw new CBPayementError(sprintf(
'Error from Mercanet Server %s on ref %s, %s',
$responseCode,
$ref,
$formDatas
));
default: // ERROR
$order->status = OrderStatus::failed();
$order->save();
throw new CBPayementError(sprintf(
'Un handled response from Mercanet Server %s on ref %s',
$responseCode,
$ref,
$formDatas
));
}
} catch (ModelNotFoundException | CBPayementError | \Exception $e) {
report($e);
Log::critical(sprintf(
'Could not end customer transaction in boutique for order %d, %s',
$order->id,
$e->getMessage()
));
} finally {
return response('', 200);
}
}
/**
*
** Pas de validate ou custom request sur la réponse de mercanet,
** c'est la Lib mercanet qui fait
*
* @param Request $request
* @param Order $order
* @return void
*/
public function confirmCb(Request $request, Order $order)
{
ClubController::getNotifications();
try {
if ($order->status->equals(OrderStatus::failed())) {
return response()->redirectToRoute('club.boutique.error');
}
if ($order->status->equals(OrderStatus::payed())) {
return view('front.club.boutique.validated');
}
$mercanet = $this->getMercanet($order);
$mercanet->setResponse($request->post());
if (!$mercanet->isValid()) {
try {
$order->status = OrderStatus::failed();
$order->saveOrFail();
} finally {
throw new CBPayementError('Data were altered from response');
}
}
\extract($this->getMercanetData($request));
switch ($responseCode) {
case PaymentResponse::ok():
try {
$status = $order->status;
$order->status = OrderStatus::payed();
$order->saveOrFail();
if ($status->equals(OrderStatus::waiting())) {
SendOrderToArchadia::dispatch($order);
SendBoutiqueMails::dispatch($order);
}
} catch (Exception $e) {
report($e);
Log::critical(sprintf(
'Could not end customer transaction (save and send mail) in boutique for order %d, %s',
$order->id,
$e->getMessage()
));
} finally {
return view('front.club.boutique.validated');
}
break; // not used
case PaymentResponse::invalidIds():
case PaymentResponse::invalidRequestFormat():
case PaymentResponse::invalidParameters():
case PaymentResponse::unreachableService():
case PaymentResponse::securityIssue():
case PaymentResponse::transactionAlreadyExists():
case PaymentResponse::internalIssue():
$order->status = OrderStatus::failed();
$order->save();
throw new CBPayementError(sprintf(
"Error response from Mercanet Lib : %s\n %s",
PaymentResponse::make($responseCode)->label,
json_encode($returnDatas)
));
default:
$order->status = OrderStatus::failed();
$order->save();
throw new CBPayementError(sprintf(
'Un handled response from Mercanet Lib %s',
json_encode($returnDatas)
));
}
} catch (ModelNotFoundException | CBPayementError $e) {
report($e);
Log::critical(sprintf(
'Could not end customer transaction in boutique for order %d, %s',
$order->id,
$e->getMessage()
));
return view('front.club.boutique.error');
}
}
/**
* Get Initialized Mercanet object
*
* @return Mercanet
*/
private function getMercanet(Order $order): Mercanet
{
return new Mercanet(
config('mercanet.secret'),
config('mercanet.merchantId'),
config('mercanet.env'),
route('club.boutique.reglement.payementConfirmCB', compact('order')),
route('club.boutique.reglement.payementConfirmCBServ', compact('order'))
);
}
/**
* Get Mercanet ref and responseCode for request
* @return array['ref' => 'value', 'responseCode' => 'value']
*/
private function getMercanetData(Request $request): array
{
$formDatas = [];
$returnDatas = explode("|", $request->post('Data'));
foreach ($returnDatas as $item) {
$tmp = explode("=", $item);
$formDatas[$tmp[0]] = $tmp[1];
}
$ref = $formDatas['transactionReference'];
$responseCode = $formDatas['responseCode'];
return compact('ref', 'responseCode');
}
}