*/ 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)); }//end try /** @var \App\Models\Customers */ $customer = auth('customer')->user(); $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); }//end switch } /** * 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) )); }//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('front.club.boutique.error'); }//end try } /** * * 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) )); }//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 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) )); }//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('front.club.boutique.error'); }//end try } /** * 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'); } }