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->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") ); } } }