456 lines
18 KiB
PHP
456 lines
18 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Club;
|
|
|
|
use App\Enums\Customers\Civility;
|
|
use App\Enums\Mercanet\PaymentResponse;
|
|
use App\Enums\Mercanet\RedirectionStatusCode;
|
|
use App\Enums\Mercanet\TransactionResponse;
|
|
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\Mercanet\Mercanet;
|
|
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->id,
|
|
'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->civility = Civility::make(intval($request->civility));
|
|
$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')
|
|
)]);
|
|
}
|
|
// FIXME: uncomment when mercanet cb is ready.
|
|
// 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->id));
|
|
} //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) {
|
|
// FIXME: uncomment when mercanet cb is ready.
|
|
// case OrderType::cb->value():
|
|
// \session()->put(sprintf('shop.order.%d.time', $order->id), \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->id), \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->id,
|
|
$e->getMessage()
|
|
));
|
|
} finally {
|
|
return response()->json(['redirect' => route('club.shop.validated', compact('order'))]);
|
|
}
|
|
break;
|
|
// * not used
|
|
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->id), 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 Mercanet 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->id), 0);
|
|
if ($order->status->equals(OrderStatus::failed)) {
|
|
return view('club.pages.shop.error')
|
|
->with('error', trans('Commande annulée, veuillez réessayer'));
|
|
}
|
|
|
|
// 4 minutes
|
|
if (($time + (60 * 4)) < \time()) {
|
|
return view('club.pages.shop.error')
|
|
->with('error', trans('Délai d\'attente expiré, veuillez réessayer'));
|
|
}
|
|
|
|
$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->value():
|
|
return view('club.pages.shop.redirectMercanet', [
|
|
'url' => $payment->url,
|
|
'data' => $payment->data,
|
|
'version' => $payment->version
|
|
]);
|
|
case RedirectionStatusCode::referenceAlreadyUsed:
|
|
return response()->redirectToRoute('club.shop.index');
|
|
case RedirectionStatusCode::requestError03->value():
|
|
case RedirectionStatusCode::requestError12->value():
|
|
case RedirectionStatusCode::requestError30->value():
|
|
case RedirectionStatusCode::requestError34->value():
|
|
case RedirectionStatusCode::serviceDisponibiltyIssue->value():
|
|
$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)
|
|
));
|
|
} //end switch
|
|
} 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('club.pages.shop.error')
|
|
->with('error', trans('La commande que vous venez de passer n\'a pu aboutir, veuillez réessayer'));
|
|
} //end try
|
|
}
|
|
|
|
/**
|
|
* Pas de validate ou custom request sur la réponse de mercanet,
|
|
* c'est la Lib mercanet 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 confirmCbFromMercanet(
|
|
Request $request,
|
|
Order $order
|
|
): \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory {
|
|
// phpcs:enable
|
|
try {
|
|
\extract($this->getMercanetData($request));
|
|
|
|
/** @var \App\Models\Order */
|
|
$order = Order::query()->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();
|
|
SendOrderToArcadia::dispatch($order);
|
|
SendShopMail::dispatch($order);
|
|
break;
|
|
case TransactionResponse::canceled:
|
|
case TransactionResponse::expired:
|
|
case TransactionResponse::refused:
|
|
case TransactionResponse::fraud:
|
|
case TransactionResponse::duplicate:
|
|
case TransactionResponse::maxReached:
|
|
if (
|
|
!$order->confirmMailSent and
|
|
!$order->status->equals(OrderStatus::payed)
|
|
) {
|
|
$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,
|
|
json_encode($returnDatas)
|
|
));
|
|
default:
|
|
// * ERROR
|
|
if (
|
|
!$order->confirmMailSent and
|
|
!$order->status->equals(OrderStatus::payed)
|
|
) {
|
|
$order->status = OrderStatus::failed;
|
|
$order->save();
|
|
}
|
|
throw new CBPayementError(sprintf(
|
|
'Un handled response from Mercanet Server %s on ref %s',
|
|
$responseCode,
|
|
$ref,
|
|
json_encode($returnDatas)
|
|
));
|
|
} //end switch
|
|
} 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);
|
|
} //end try
|
|
}
|
|
|
|
/**
|
|
*
|
|
* 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 \Illuminate\Contracts\View\View
|
|
* @throws \App\Exceptions\CBPayementError Data were altered from response.
|
|
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
|
*/
|
|
public function confirmCb(Request $request, Order $order): \Illuminate\Contracts\View\View
|
|
{
|
|
// phpcs:enable
|
|
Notification::getNotifications();
|
|
try {
|
|
if ($order->status->equals(OrderStatus::failed)) {
|
|
return view('club.pages.shop.error');
|
|
}
|
|
if ($order->status->equals(OrderStatus::payed)) {
|
|
return view('club.pages.shop.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)) {
|
|
SendOrderToArcadia::dispatch($order);
|
|
SendShopMail::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('club.pages.shop.validated');
|
|
}
|
|
case PaymentResponse::canceled:
|
|
$order->status = OrderStatus::failed;
|
|
$order->saveOrFail();
|
|
return view('club.pages.shop.error');
|
|
case PaymentResponse::invalidIds:
|
|
case PaymentResponse::invalidRequestFormat:
|
|
case PaymentResponse::invalidParameters:
|
|
case PaymentResponse::unreachableService:
|
|
case PaymentResponse::securityIssue:
|
|
case PaymentResponse::transactionAlreadyExists:
|
|
case PaymentResponse::expired:
|
|
case PaymentResponse::internalIssue:
|
|
$order->status = OrderStatus::failed;
|
|
$order->saveOrFail();
|
|
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)
|
|
));
|
|
} //end switch
|
|
} 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('club.pages.shop.error');
|
|
} //end try
|
|
}
|
|
|
|
/**
|
|
* Get initialized Mercanet object.
|
|
*
|
|
* @param \App\Models\Order $order
|
|
* @return \App\Lib\Mercanet\Mercanet
|
|
*/
|
|
private function getMercanet(Order $order): \App\Lib\Mercanet\Mercanet
|
|
{
|
|
return new Mercanet(
|
|
config('mercanet.secret'),
|
|
config('mercanet.merchantId'),
|
|
config('mercanet.env'),
|
|
route('club.shop.reglement.payementConfirmCB', $order),
|
|
route('club.shop.reglement.payementConfirmCBServ', $order)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get Mercanet ref and responseCode for request.
|
|
*
|
|
* @param \Illuminate\Http\Request $request
|
|
* @return array<string,string> 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', 'returnDatas');
|
|
}
|
|
}
|