391 lines
15 KiB
PHP
391 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->civility = ContactFormCivility::m; // FIXME: Remove this field
|
|
$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"));
|
|
}
|
|
}
|
|
}
|