427 lines
16 KiB
PHP
427 lines
16 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\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::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)
|
|
));
|
|
}
|
|
} 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 view('front.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) {
|
|
// phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
|
|
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');
|
|
}
|
|
case PaymentResponse::canceled():
|
|
$order->status = OrderStatus::failed();
|
|
$order->saveOrFail();
|
|
return view('front.club.boutique.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)
|
|
));
|
|
}
|
|
} 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', 'returnDatas');
|
|
}
|
|
}
|