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->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->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) { // FIXME: uncomment when mercanet cb is ready. // 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'))]); } 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->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 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->getKey()), 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->getKey(), $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->getKey(), $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->getKey(), $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->getKey(), $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 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'); } }