fix conflicts
11
.env.example
|
|
@ -75,11 +75,6 @@ GOOGLE_RECAPTCHA_PUBLIC=
|
|||
GOOGLE_RECAPTCHA_PRIVATE=
|
||||
GOOGLE_RECAPTCHA_VERSION=
|
||||
|
||||
## MERCANET
|
||||
MERCANET_SECRET=
|
||||
MERCANET_MERCHAND_ID=
|
||||
MERCANET_ENV=
|
||||
|
||||
## TELESCOPE
|
||||
TELESCOPE_ENABLED=
|
||||
|
||||
|
|
@ -101,3 +96,9 @@ REDIS_USERNAME=
|
|||
REDIS_PASSWORD=
|
||||
REDIS_PREFIX=
|
||||
REDIS_SCHEME=
|
||||
|
||||
## AXEPTA
|
||||
AXEPTA_ENDPOINT=
|
||||
AXEPTA_MERCHANT_ID=
|
||||
AXEPTA_APIKEY=
|
||||
AXEPTA_HMAC=
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Mercanet;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum PaymentResponse: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case ok = 00;
|
||||
case invalidIds = 03;
|
||||
case invalidParameters = 12;
|
||||
case canceled = 17;
|
||||
case invalidRequestFormat = 30;
|
||||
case securityIssue = 34;
|
||||
case transactionAlreadyExists = 94;
|
||||
case expired = 97;
|
||||
case unreachableService = 99;
|
||||
case internalIssue = 500;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
*
|
||||
* @phpcs:disable Generic.Files.LineLength.TooLong
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::ok->name => 'Situation normale suivie du processus normal
|
||||
d\'affichage des pages de paiement.',
|
||||
self::invalidIds->name => 'L’identifiant commerçant ou le contrat acquéreur sont invalides.',
|
||||
self::invalidParameters->name => 'Les paramètres de la transaction sont invalides. Vérifiez les
|
||||
paramètres de la requête.',
|
||||
self::canceled->name => 'Transaction annulée',
|
||||
self::invalidRequestFormat->name => 'Le format de la requête est invalide.',
|
||||
self::securityIssue->name => 'Problème de sécurité, par ex. le sceau calculé est incorrect.',
|
||||
self::transactionAlreadyExists->name => 'La transaction existe déjà.',
|
||||
self::expired->name => 'Délais expiré',
|
||||
self::unreachableService->name => 'Service temporairement indisponible',
|
||||
self::internalIssue->name => 'Erreur interne, eg: probleme appel CURL'
|
||||
];
|
||||
//phpcs:enable
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Mercanet;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum RedirectionStatusCode: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case redirect = 0;
|
||||
case requestError03 = 3;
|
||||
case requestError12 = 12;
|
||||
case requestError30 = 30;
|
||||
case requestError34 = 34;
|
||||
case referenceAlreadyUsed = 94;
|
||||
case serviceDisponibiltyIssue = 99;
|
||||
case internalIssue = 500;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::redirect->name => 'Redirection Acceptée',
|
||||
self::requestError03->name => 'Problème de request code 03',
|
||||
self::requestError12->name => 'Problème de request code 12',
|
||||
self::requestError30->name => 'Problème de request code 30',
|
||||
self::requestError34->name => 'Problème de request code 34',
|
||||
self::referenceAlreadyUsed->name => 'La référence de transaction à déja été utilisée',
|
||||
self::serviceDisponibiltyIssue->name => 'Problème de disponibilité du service de paiement',
|
||||
self::internalIssue->name => 'Erreur interne, eg: probleme appel CURL'
|
||||
];
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Mercanet;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum TransactionResponse: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case accepted = 00;
|
||||
case refused = 05;
|
||||
case canceled = 17;
|
||||
case fraud = 34;
|
||||
case maxReached = 75;
|
||||
case technicalIssue = 90;
|
||||
case duplicate = 94;
|
||||
case expired = 97;
|
||||
case internalIssue = 500;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::accepted->name => 'Paiement accepté',
|
||||
self::refused->name => 'Paiement refusé',
|
||||
self::canceled->name => 'Paiement annulé',
|
||||
self::fraud->name => 'Paiement refusé pour fraude',
|
||||
self::maxReached->name => 'Paiement refusé pour nombre max essais atteint',
|
||||
self::technicalIssue->name => 'Paiement refusé pour problème technique',
|
||||
self::duplicate->name => 'Transaction dupliquée',
|
||||
self::expired->name => 'Paiement expiré',
|
||||
self::internalIssue->name => 'Erreur interne, eg: probleme appel CURL'
|
||||
];
|
||||
}
|
||||
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
namespace App\Http\Controllers\Club;
|
||||
|
||||
use App\Enums\Mercanet\PaymentResponse;
|
||||
use App\Enums\Mercanet\RedirectionStatusCode;
|
||||
use App\Enums\Mercanet\TransactionResponse;
|
||||
use App\Enums\Shop\ContactFormCivility;
|
||||
use App\Enums\Shop\OrderStatus;
|
||||
use App\Enums\Shop\OrderType;
|
||||
|
|
@ -13,7 +10,9 @@ 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\Mercanet\Mercanet;
|
||||
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;
|
||||
|
|
@ -68,7 +67,6 @@ class ShopController extends Controller
|
|||
try {
|
||||
$order = new Order();
|
||||
$order->ref = str_replace('-', '', $request->ref);
|
||||
$order->civility = ContactFormCivility::make(intval($request->coords['civility']));
|
||||
$order->payementType = OrderType::make(intval($request->payementMethod));
|
||||
$order->status = OrderStatus::waiting;
|
||||
$order->customer_id = $customerModel->getKey();
|
||||
|
|
@ -82,13 +80,12 @@ class ShopController extends Controller
|
|||
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')
|
||||
// )]);
|
||||
// }
|
||||
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
|
||||
|
||||
|
|
@ -107,11 +104,10 @@ class ShopController extends Controller
|
|||
}
|
||||
|
||||
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::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());
|
||||
|
|
@ -161,7 +157,7 @@ class ShopController extends Controller
|
|||
*
|
||||
* @param \App\Models\Order $order
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
|
||||
* @throws \App\Exceptions\CBPayementError Error response from Mercanet Lib.
|
||||
* @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
|
||||
|
|
@ -171,14 +167,17 @@ class ShopController extends Controller
|
|||
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'));
|
||||
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.error')
|
||||
->with('error', trans('Délai d\'attente expiré, veuillez réessayer'));
|
||||
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) {
|
||||
|
|
@ -193,39 +192,36 @@ class ShopController extends Controller
|
|||
// * Convert total in cents
|
||||
$totalToPay = (int) (\number_format((float) $totalToPay, 2)) * 100;
|
||||
|
||||
$mercanet = $this->getMercanet($order);
|
||||
$aPayment = new AxeptaPayment(
|
||||
config('axepta.endpoint'),
|
||||
config('axepta.merchantId'),
|
||||
config('axepta.apiKey'),
|
||||
config('axepta.hmac')
|
||||
);
|
||||
|
||||
$aOrder = new AxeptaOrder(
|
||||
$order->ref,
|
||||
$totalToPay,
|
||||
$order->customer->email
|
||||
);
|
||||
|
||||
$payment = $mercanet->QuickPayment($totalToPay, $order->ref);
|
||||
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()
|
||||
));
|
||||
}
|
||||
|
||||
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(
|
||||
|
|
@ -233,14 +229,16 @@ class ShopController extends Controller
|
|||
$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'));
|
||||
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 mercanet,
|
||||
* c'est la Lib mercanet qui fait.
|
||||
* 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
|
||||
|
|
@ -248,40 +246,56 @@ class ShopController extends Controller
|
|||
* @throws \App\Exceptions\CBPayementError Data were altered from response.
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
*/
|
||||
public function confirmCbFromMercanet(
|
||||
public function confirmCbFromAxepta(
|
||||
Request $request,
|
||||
Order $order
|
||||
): \Illuminate\Http\Response|\Illuminate\Contracts\Routing\ResponseFactory {
|
||||
// phpcs:enable
|
||||
try {
|
||||
$data = $this->getMercanetData($request);
|
||||
$aPayment = new AxeptaPayment(
|
||||
config('axepta.endpoint'),
|
||||
config('axepta.merchantId'),
|
||||
config('axepta.apiKey'),
|
||||
config('axepta.hmac')
|
||||
);
|
||||
|
||||
/** @var \App\Models\Order */
|
||||
$order = Order::query()->where('ref', $data['ref'])->firstOrFail();
|
||||
|
||||
$mercanet = $this->getMercanet($order);
|
||||
$mercanet->setResponse($request->post());
|
||||
|
||||
if (!$mercanet->isValid()) {
|
||||
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);
|
||||
|
||||
switch ($data['responseCode']) {
|
||||
case TransactionResponse::accepted:
|
||||
$order->status = OrderStatus::payed;
|
||||
if (is_null($response)) {
|
||||
if (
|
||||
!$order->confirmMailSent and
|
||||
!$order->status->equals(OrderStatus::payed)
|
||||
) {
|
||||
$order->status = OrderStatus::failed;
|
||||
$order->save();
|
||||
SendOrderToArcadia::dispatch($order);
|
||||
SendShopMail::dispatch($order);
|
||||
}
|
||||
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 TransactionResponse::canceled:
|
||||
case TransactionResponse::expired:
|
||||
case TransactionResponse::refused:
|
||||
case TransactionResponse::fraud:
|
||||
case TransactionResponse::duplicate:
|
||||
case TransactionResponse::maxReached:
|
||||
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)
|
||||
|
|
@ -290,17 +304,9 @@ class ShopController extends Controller
|
|||
$order->save();
|
||||
}
|
||||
break;
|
||||
case TransactionResponse::technicalIssue:
|
||||
// @phpstan-ignore finally.exitPoint
|
||||
throw new CBPayementError(sprintf(
|
||||
'Error from Mercanet Server %s on ref %s, %s',
|
||||
$data['responseCode'],
|
||||
$data['ref'],
|
||||
json_encode($data['returnDatas']),
|
||||
));
|
||||
default:
|
||||
// * ERROR
|
||||
if (
|
||||
// * ERROR
|
||||
if (
|
||||
!$order->confirmMailSent and
|
||||
!$order->status->equals(OrderStatus::payed)
|
||||
) {
|
||||
|
|
@ -309,12 +315,12 @@ class ShopController extends Controller
|
|||
}
|
||||
// @phpstan-ignore finally.exitPoint
|
||||
throw new CBPayementError(sprintf(
|
||||
'Un handled response from Mercanet Server %s on ref %s, %s',
|
||||
$data['responseCode'],
|
||||
$data['ref'],
|
||||
json_encode($data['returnDatas']),
|
||||
'Unhandled response from Axepta Server %s on ref %s',
|
||||
$response,
|
||||
$order->ref
|
||||
));
|
||||
} //end switch
|
||||
|
||||
} //end switch
|
||||
} catch (ModelNotFoundException | CBPayementError | \Exception $e) {
|
||||
report($e);
|
||||
Log::critical(sprintf(
|
||||
|
|
@ -329,135 +335,63 @@ class ShopController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Pas de validate ou custom request sur la réponse de mercanet,
|
||||
* c'est la Lib mercanet qui fait.
|
||||
* Confirm CB method
|
||||
*
|
||||
* @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");
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->getMercanetData($request);
|
||||
|
||||
switch ($data['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($data['responseCode'])->label(),
|
||||
json_encode($data['returnDatas'])
|
||||
));
|
||||
default:
|
||||
$order->status = OrderStatus::failed;
|
||||
$order->save();
|
||||
throw new CBPayementError(sprintf(
|
||||
'Un handled response from Mercanet Lib %s',
|
||||
json_encode($data['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<string,string> array['ref' => 'value', 'responseCode' => 'value', 'returnDatas' => '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];
|
||||
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")
|
||||
);
|
||||
}
|
||||
|
||||
$ref = $formDatas['transactionReference'];
|
||||
$responseCode = $formDatas['responseCode'];
|
||||
return compact('ref', 'responseCode', 'returnDatas');
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class SecureHeaders
|
|||
'https://use.fontawesome.com',
|
||||
'https://fonts.googleapis.com',
|
||||
// * VITE DEV
|
||||
config('app.debug') ? "https://{$host}:4443" : '',
|
||||
config('app.debug') ? "https://{$host}:4443" : ''
|
||||
]) . ';',
|
||||
// ! If you dont use ckeditor use this
|
||||
// "style-src 'self' 'nonce-$nonce';",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ class ShopBuyRequest extends FormRequest
|
|||
return [
|
||||
'ref' => 'required|string|uuid|unique:orders,ref',
|
||||
'coords' => 'required|array',
|
||||
'coords.civility' => ['required', new Enum(ContactFormCivility::class)],
|
||||
'coords.firstname' => 'required|string|between:3,255',
|
||||
'coords.lastname' => 'required|string|between:3,255',
|
||||
'coords.address' => 'required|string|between:3,255',
|
||||
|
|
@ -38,7 +37,6 @@ class ShopBuyRequest extends FormRequest
|
|||
'coords.phone' => 'nullable|phone:FR|max:255',
|
||||
'coords.email' => 'required|email:rfc,strict,dns,spoof,filter',
|
||||
'shipping' => 'sometimes|array',
|
||||
'shipping.civility' => ['required_with:shipping', new Enum(ContactFormCivility::class)],
|
||||
'shipping.firstname' => 'required_with:shipping|string|between:3,255',
|
||||
'shipping.lastname' => 'required_with:shipping|string|between:3,255',
|
||||
'shipping.address' => 'required_with:shipping|string|between:3,255',
|
||||
|
|
@ -62,7 +60,6 @@ class ShopBuyRequest extends FormRequest
|
|||
{
|
||||
return [
|
||||
'coords' => trans('vos coordonnées'),
|
||||
'coords.civility' => trans('civilité dans vos coordonnées'),
|
||||
'coords.firstname' => trans('prénom dans vos coordonnées'),
|
||||
'coords.lastname' => trans('nom dans vos coordonnées'),
|
||||
'coords.address' => trans('adresse dans vos coordonnées'),
|
||||
|
|
@ -71,7 +68,6 @@ class ShopBuyRequest extends FormRequest
|
|||
'coords.city' => trans('ville dans vos coordonnées'),
|
||||
'coords.email' => trans('e-mail dans vos coordonnées'),
|
||||
'shipping' => trans('votre adresse de livraison'),
|
||||
'shipping.civility' => trans('civilité dans votre adresse de livraison'),
|
||||
'shipping.firstname' => trans('prénom dans votre adresse de livraison'),
|
||||
'shipping.lastname' => trans('nom dans votre adresse de livraison'),
|
||||
'shipping.address' => trans('adresse dans votre adresse de livraison'),
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class DeleteOldMessages implements ShouldQueue
|
|||
{
|
||||
Log::debug(sprintf(
|
||||
'Deleted %d old messages that are more than 30 days',
|
||||
Message::query()->getMessagesOlderThan(30)->delete()
|
||||
Message::getMessagesOlderThan(30)->delete()
|
||||
));
|
||||
|
||||
Log::debug(sprintf(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Lib\Arcadia;
|
||||
|
||||
use App\Enums\Arcadia\ArcadiaOrderType;
|
||||
use App\Enums\Shop\OrderType;
|
||||
use App\Models\Order;
|
||||
use App\Models\SellItem;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
|
|
@ -103,10 +104,7 @@ class HasOrderedAnItemData implements Jsonable, Arrayable
|
|||
'label_type_support' => $sellItem->name,
|
||||
'order_date' => $order->created_at,
|
||||
'order_num' => $order->getKey(),
|
||||
'state_order' => ArcadiaOrderType::check->value(),
|
||||
// FIXME: uncomment when mercanet cb is ready.
|
||||
// 'state_order' => $order->payementType->equals(OrderType::cb) ?
|
||||
// ArcadiaOrderType::cb->value() : ArcadiaOrderType::check->value(),
|
||||
'state_order' => $order->payementType->equals(OrderType::cb) ? ArcadiaOrderType::cb->value() : ArcadiaOrderType::check->value(),
|
||||
'package_unit_weight' => $sellItem->arcadia_weight,
|
||||
'email' => $order->email,
|
||||
'firstname' => $firstname,
|
||||
|
|
|
|||
301
app/Lib/Axepta/AxeptaPayment.php
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Axepta;
|
||||
|
||||
use App\Lib\Axepta\Objects\AxeptaOrder;
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AxeptaPayment
|
||||
{
|
||||
/**
|
||||
* Guzzle Client instance
|
||||
* @var GuzzleHttp\Client
|
||||
*/
|
||||
private \GuzzleHttp\Client $client;
|
||||
|
||||
/**
|
||||
* Additionnal or Update Axepta configuration
|
||||
* @var array
|
||||
*/
|
||||
private array $axeptaCheckoutConfig;
|
||||
|
||||
/**
|
||||
* AxeptaBearerToken cache key
|
||||
* @var string
|
||||
*/
|
||||
private string $cacheKey = "AxeptaBearerToken";
|
||||
|
||||
/**
|
||||
* Construct method
|
||||
*
|
||||
* @param string $endpoint Axepta Endpoint
|
||||
* @param string $merchantId Axepta Merchant ID
|
||||
* @param string $apiKey Axepta Secret API Key
|
||||
* @param string $hmac Axepta HMAC
|
||||
* @param array $axeptaConfig Additionnal or Update Axepta configuration
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(private string $endpoint, private string $merchantId, private string $apiKey, private string $hmac, private array $axeptaConfig = [])
|
||||
{
|
||||
$this->endpoint = $endpoint;
|
||||
$this->merchantId = $merchantId;
|
||||
$this->apiKey = $apiKey;
|
||||
$this->hmac = $hmac;
|
||||
$this->client = new Client([
|
||||
'base_uri' => $this->endpoint,
|
||||
'timeout' => 5.0,
|
||||
]);
|
||||
$this->axeptaCheckoutConfig = [
|
||||
...[
|
||||
"language" => "fr",
|
||||
"amount" => [
|
||||
"currency" => "EUR",
|
||||
],
|
||||
"captureMethod" => [
|
||||
"type" => "AUTOMATIC"
|
||||
],
|
||||
"allowedPaymentMethods" => [
|
||||
"CARD"
|
||||
],
|
||||
"paymentMethods" => [
|
||||
"card" => [
|
||||
"subType" => ["VISA", "MASTERCARD"],
|
||||
],
|
||||
]
|
||||
],
|
||||
...$axeptaConfig
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* getAccessToken method
|
||||
* https://axeptabnpparibas-docs.redocly.app/authentication/authorizationservice
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function getAccessToken(): string
|
||||
{
|
||||
if (Cache::has($this->cacheKey)) {
|
||||
return Cache::get($this->cacheKey);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->client->post('/authorization/oauth/token', [
|
||||
'form_params' => [
|
||||
'grant_type' => 'client_credentials',
|
||||
'client_id' => $this->merchantId,
|
||||
'client_secret' => $this->apiKey
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() !== 200) {
|
||||
throw new \RuntimeException('Token request failed');
|
||||
}
|
||||
|
||||
$responseBody = json_decode($response->getBody()->getContents());
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE || !isset($responseBody->access_token) || !isset($responseBody->expires_in)) {
|
||||
throw new \RuntimeException('Invalid token response');
|
||||
}
|
||||
|
||||
Cache::put($this->cacheKey, $responseBody->access_token, $responseBody->expires_in);
|
||||
|
||||
return $responseBody->access_token;
|
||||
} catch (RequestException $e) {
|
||||
$errorBody = $e->hasResponse() ? $e->getResponse()->getBody()->getContents() : '{}';
|
||||
$errorData = json_decode($errorBody, true);
|
||||
$message = $errorData['error'] ?? $errorData['responseDescription'] ?? $e->getMessage();
|
||||
throw new \RuntimeException("Axepta token error: {$message}", 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getCheckoutUrl method
|
||||
*
|
||||
* @param \App\Lib\Axepta\Objects\AxeptaOrder $order
|
||||
* @param string $returnUrl
|
||||
* @param string $cancelUrl
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function getCheckoutUrl(AxeptaOrder $order, string $returnUrl, string $cancelUrl, string $webhookUrl): string
|
||||
{
|
||||
$payload = [
|
||||
"transId" => $this->generateTransId(),
|
||||
"urls" => [
|
||||
"return" => $returnUrl,
|
||||
"cancel" => $cancelUrl,
|
||||
"webhook" => $webhookUrl
|
||||
],
|
||||
"amount" => [
|
||||
"value" => $order->amount
|
||||
],
|
||||
"order" => [
|
||||
"merchantReference" => $order->reference,
|
||||
"creationDate" => gmdate('c'), // ISO 8601 now
|
||||
],
|
||||
"customerInfo" => [
|
||||
"email" => $order->customerEmail,
|
||||
]
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $this->client->post('/api/v2/payments/sessions', [
|
||||
'headers' => [
|
||||
'Authorization' => sprintf(
|
||||
"Bearer %s",
|
||||
$this->getAccessToken()
|
||||
),
|
||||
'Content-Type' => 'application/json',
|
||||
'Idempotency-Key' => (string) Str::uuid()
|
||||
],
|
||||
'json' => array_merge_recursive($payload, $this->axeptaCheckoutConfig) // Automatically JSON encodes and sets Content-Type
|
||||
]);
|
||||
|
||||
if ($response->getStatusCode() !== 202) {
|
||||
throw new \RuntimeException('Session creation failed');
|
||||
}
|
||||
|
||||
$responseBody = json_decode($response->getBody()->getContents());
|
||||
|
||||
if (!isset($responseBody->_links->redirect->href)) {
|
||||
throw new \RuntimeException('Missing redirect URL in response');
|
||||
}
|
||||
|
||||
return $responseBody->_links->redirect->href;
|
||||
} catch (RequestException $e) {
|
||||
throw new \RuntimeException("Payment session error: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generateTransId method
|
||||
*
|
||||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
private function generateTransId(int $length = 64): string
|
||||
{
|
||||
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?';
|
||||
$bytes = random_bytes($length);
|
||||
$result = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$result .= $chars[ord($bytes[$i]) % strlen($chars)];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getPaymentStatusById(string $paymentId): string|null
|
||||
{
|
||||
try {
|
||||
$response = $this->client->get(
|
||||
sprintf(
|
||||
'/api/v2/payments/getByPayId/%s',
|
||||
$paymentId
|
||||
),
|
||||
[
|
||||
'headers' => [
|
||||
'Authorization' => sprintf(
|
||||
"Bearer %s",
|
||||
$this->getAccessToken()
|
||||
),
|
||||
'Content-Type' => 'application/json',
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
$responseBody = json_decode($response->getBody()->getContents());
|
||||
return $responseBody->responseCode;
|
||||
} catch (RequestException $e) {
|
||||
if ($e->hasResponse()) {
|
||||
$response = $e->getResponse();
|
||||
$responseBody = json_decode($response->getBody()->getContents());
|
||||
return $responseBody->responseCode;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check signature version
|
||||
*
|
||||
* https://axepta.computop.com/display/DOCBNP/Gestion+des+callbacks+et+notification+de+paiement
|
||||
*
|
||||
* @param string|null $version
|
||||
* @return bool
|
||||
*/
|
||||
private function isValidSignatureVersion(?string $version): bool
|
||||
{
|
||||
return $version === 'v1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check signature
|
||||
*
|
||||
* https://axepta.computop.com/display/DOCBNP/Gestion+des+callbacks+et+notification+de+paiement
|
||||
*
|
||||
* @param string $signature
|
||||
* @param string $timestamp
|
||||
* @param string $rawPayload
|
||||
* @return bool
|
||||
*/
|
||||
private function isValidSignature(string $signature, string $timestamp, string $rawPayload): bool
|
||||
{
|
||||
$providedSignature = $this->extractSignature($signature);
|
||||
if (!$providedSignature) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$signedPayload = "{$timestamp}.{$rawPayload}";
|
||||
$expectedSignature = hash_hmac('sha256', $signedPayload, $this->hmac);
|
||||
|
||||
return hash_equals($expectedSignature, $providedSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the hex value after "v1=" from the signature header.
|
||||
*
|
||||
* https://axepta.computop.com/display/DOCBNP/Gestion+des+callbacks+et+notification+de+paiement
|
||||
*
|
||||
* @param string $signature
|
||||
* @return string|null
|
||||
*/
|
||||
private function extractSignature(string $signature): ?string
|
||||
{
|
||||
if (!str_starts_with($signature, 'v1=')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$hex = substr($signature, 3);
|
||||
return strlen($hex) === 64 ? $hex : null; // SHA256 hex = 64 chars
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is valid webhook access
|
||||
*
|
||||
* From Axepta 2.0 Doc
|
||||
* https://axepta.computop.com/display/DOCBNP/Gestion+des+callbacks+et+notification+de+paiement
|
||||
*
|
||||
* X-Paygate-Signature-Version Signature format version (currently v1)
|
||||
* X-Paygate-Timestamp Unix epoch timestamp (seconds since 1970-01-01T00:00:00Z, UTC), the timestamp is within ±5 minutes of your local clock.
|
||||
* X-Paygate-Signature v1=<hex-hmac>
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidWebhookAccess(\Illuminate\Http\Request $request): bool
|
||||
{
|
||||
$signatureVersion = $request->header('x-paygate-signature-version');
|
||||
$timestamp = $request->header('x-paygate-timestamp', '0');
|
||||
$signature = $request->header('x-paygate-signature');
|
||||
|
||||
return $this->isValidSignatureVersion($signatureVersion) and
|
||||
Carbon::createFromTimestampUTC($timestamp)->diffInMinutes(Carbon::now('UTC')) <= 5 and
|
||||
$this->isValidSignature($signature, $timestamp, $request->getContent());
|
||||
}
|
||||
}
|
||||
32
app/Lib/Axepta/Enums/PaymentResponse.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Axepta\Enums;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum PaymentResponse: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case ok = 0;
|
||||
case error = 2;
|
||||
case fatalerror = 4;
|
||||
case transient = 6;
|
||||
case emvinfo = 7;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
*
|
||||
* @phpcs:disable Generic.Files.LineLength.TooLong
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::ok->name => 'Transaction successful, other digits may indicate further information',
|
||||
self::error->name => 'Transaction failed, other digits may indicate further information',
|
||||
self::fatalerror->name => 'Transaction failed, other digits may indicate further information, e.g. mandatory parameter is missing',
|
||||
self::transient->name => 'Transaction pending. Final status will be sent asynchronously when available',
|
||||
self::emvinfo->name => 'Intermediate states in the EMV 3-D Secure sequence'
|
||||
];
|
||||
//phpcs:enable
|
||||
}
|
||||
20
app/Lib/Axepta/Objects/AxeptaOrder.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Axepta\Objects;
|
||||
|
||||
class AxeptaOrder
|
||||
{
|
||||
/**
|
||||
* Construct method
|
||||
*
|
||||
* @param string $reference Internal system reference order
|
||||
* @param int $amount Order amount ex: 10000 = 100$
|
||||
* @param string $customerEmail Customer email
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
public string $reference,
|
||||
public int $amount,
|
||||
public string $customerEmail
|
||||
) {}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ use App\Models\Notification;
|
|||
use App\Interfaces\Notifiable as INotifiable;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class Message extends Model implements INotifiable
|
||||
{
|
||||
|
|
@ -79,9 +80,9 @@ class Message extends Model implements INotifiable
|
|||
* Get older messages.
|
||||
*
|
||||
* @param integer $days
|
||||
* @return \Illuminate\Contracts\Database\Eloquent\Builder
|
||||
* @return Builder
|
||||
*/
|
||||
public static function getMessagesOlderThan(int $days): \Illuminate\Contracts\Database\Eloquent\Builder
|
||||
public static function getMessagesOlderThan(int $days): Builder
|
||||
{
|
||||
return self::query()->where('updated_at', '<', now()->subDays($days));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ class Order extends Model
|
|||
{
|
||||
return [
|
||||
'ref' => $this->ref,
|
||||
'civility' => $this->civility->value(),
|
||||
'lastname' => $this->lastname,
|
||||
'firstname' => $this->firstname,
|
||||
'address' => $this->address,
|
||||
|
|
|
|||
8
config/axepta.php
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'endpoint' => env('AXEPTA_ENDPOINT'),
|
||||
'merchantId' => env('AXEPTA_MERCHANT_ID'),
|
||||
'apiKey' => env('AXEPTA_APIKEY'),
|
||||
'hmac' => env('AXEPTA_HMAC')
|
||||
];
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'secret' => env('MERCANET_SECRET'),
|
||||
'merchantId' => env('MERCANET_MERCHAND_ID'),
|
||||
// Ex: DEVELOPMENT, PRODUCTION.
|
||||
'env' => env('MERCANET_ENV', 'DEVELOPMENT'),
|
||||
];
|
||||
|
|
@ -58,6 +58,10 @@ data:
|
|||
REDIS_PREFIX: "${APP_REDIS_PREFIX}"
|
||||
REDIS_SCHEME: "${APP_REDIS_SCHEME}"
|
||||
UWIL_ADMIN_SECURE_CIDR_LIST: "${APP_GLOBAL_UWIL_ADMIN_SECURE_CIDR_LIST}"
|
||||
AXEPTA_ENDPOINT: "${APP_AXEPTA_ENDPOINT}"
|
||||
AXEPTA_MERCHANT_ID: "${APP_AXEPTA_MERCHANT_ID}"
|
||||
AXEPTA_APIKEY: "${APP_AXEPTA_APIKEY}"
|
||||
AXEPTA_HMAC: "${APP_AXEPTA_HMAC}"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
|
|
|
|||
|
|
@ -144,7 +144,9 @@
|
|||
<tr>
|
||||
<td style="width:550px" class="mj-full-width-mobile"> <a
|
||||
href="https://www.legaulois.fr/?mtm_campaign=le_gaulois_mailing_consentement_newsletter_nov2025_club_des_gaulois&mtm_source=mailing_consentement_nl_le_gaulois&mtm_medium=mailing_consentement_le_gaulois"
|
||||
target="_blank"> <img alt="Le Club Le Gaulois"
|
||||
target="_blank"> <img
|
||||
alt="Bienvenue sur le club des Gaulois"
|
||||
title="Bienvenue sur le club des Gaulois"
|
||||
src="https://www.legaulois.fr/newsletters/251201/assets/header.png"
|
||||
style="border:0;display:block;font-size:13px;height:auto;outline:none;text-decoration:none;width:100%"
|
||||
width="550" height="auto"> </a> </td>
|
||||
|
|
@ -337,7 +339,8 @@
|
|||
<tr>
|
||||
<td style="width:180px" class="mj-full-width-mobile"> <a
|
||||
href="https://www.legaulois.fr/?mtm_campaign=le_gaulois_mailing_consentement_newsletter_nov2025_club_des_gaulois&mtm_source=mailing_consentement_nl_le_gaulois&mtm_medium=mailing_consentement_le_gaulois"
|
||||
target="_blank"> <img alt="Logo Le Gaulois"
|
||||
target="_blank"> <img alt="Logo marque Le Gaulois"
|
||||
title="Logo marque Le Gaulois"
|
||||
src="https://www.legaulois.fr/newsletters/251201/assets/logo.png"
|
||||
style="border:0;display:block;font-size:13px;height:auto;outline:none;text-decoration:none;width:100%"
|
||||
width="180" height="auto"> </a> </td>
|
||||
|
|
@ -391,6 +394,7 @@
|
|||
href="https://www.legaulois.fr/club?mtm_campaign=le_gaulois_mailing_consentement_newsletter_oct2025_club_des_gaulois&mtm_source=mailing_consentement_nl_le_gaulois&mtm_medium=mailing_consentement_le_Gaulois"
|
||||
target="_blank"> <img
|
||||
alt="Page internet Club des Gaulois"
|
||||
title="Page internet Club des Gaulois"
|
||||
src="https://www.legaulois.fr/newsletters/ref/picto_club_lgl.png"
|
||||
style="border:0;display:block;font-size:13px;height:auto;outline:none;text-decoration:none;width:100%"
|
||||
width="60" height="auto"> </a> </td>
|
||||
|
|
@ -420,6 +424,7 @@
|
|||
href="https://www.linkedin.com/company/legaulois"
|
||||
target="_blank"> <img
|
||||
alt="Logo Linkedin Le Gaulois"
|
||||
title="Logo Linkedin Le Gaulois"
|
||||
src="https://www.legaulois.fr/newsletters/ref/picto_in.png"
|
||||
style="border:0;display:block;font-size:13px;height:auto;outline:none;text-decoration:none;width:100%"
|
||||
width="60" height="auto"> </a> </td>
|
||||
|
|
@ -449,6 +454,7 @@
|
|||
href="https://www.instagram.com/legaulois_officiel/"
|
||||
target="_blank"> <img
|
||||
alt="Logo Instagram Le Gaulois"
|
||||
title="Logo Instagram Le Gaulois"
|
||||
src="https://www.legaulois.fr/newsletters/ref/picto_insta.png"
|
||||
style="border:0;display:block;font-size:13px;height:auto;outline:none;text-decoration:none;width:100%"
|
||||
width="60" height="auto"> </a> </td>
|
||||
|
|
@ -478,7 +484,8 @@
|
|||
href="https://www.tiktok.com/@legaulois_off"
|
||||
target="_blank"> <img
|
||||
alt="Logo Tiktok Le Gaulois"
|
||||
src="https://www.legaulois.fr/newsletters/ref/tiktok.png"
|
||||
title="Logo Tiktok Le Gaulois"
|
||||
src="https://www.legaulois.fr/newsletters/ref/picto_tiktok.png"
|
||||
style="border:0;display:block;font-size:13px;height:auto;outline:none;text-decoration:none;width:100%"
|
||||
width="60" height="auto"> </a> </td>
|
||||
</tr>
|
||||
|
|
@ -507,6 +514,7 @@
|
|||
href="https://www.youtube.com/channel/UCiTpBkZrwH9_xUpTv1UTuLA"
|
||||
target="_blank">
|
||||
<img alt="Logo Youtube Le Gaulois"
|
||||
title="Logo Youtube Le Gaulois"
|
||||
src="https://www.legaulois.fr/newsletters/ref/picto_yt.png"
|
||||
style="border:0;display:block;font-size:13px;height:auto;outline:none;text-decoration:none;width:100%"
|
||||
width="60" height="auto"> </a> </td>
|
||||
|
|
@ -536,6 +544,7 @@
|
|||
href="https://www.facebook.com/LeGauloisFrance/"
|
||||
target="_blank"> <img
|
||||
alt="Logo Facebook Le Gaulois"
|
||||
title="Logo Facebook Le Gaulois"
|
||||
src="https://www.legaulois.fr/newsletters/ref/picto_fb.png"
|
||||
style="border:0;display:block;font-size:13px;height:auto;outline:none;text-decoration:none;width:100%"
|
||||
width="60" height="auto"> </a> </td>
|
||||
|
|
|
|||
BIN
public/newsletters/260317/assets/bloc_2_1.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/newsletters/260317/assets/bloc_2_2.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/newsletters/260317/assets/bloc_2_3.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
public/newsletters/260317/assets/bloc_2_4.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/newsletters/260317/assets/border_1.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
public/newsletters/260317/assets/border_2.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
public/newsletters/260317/assets/border_3.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
public/newsletters/260317/assets/border_4.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/newsletters/260317/assets/border_5.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/newsletters/260317/assets/chaussettes.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
public/newsletters/260317/assets/cuisses_de_canard.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
public/newsletters/260317/assets/filets_de_poulet.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/newsletters/260317/assets/footer.png
Normal file
|
After Width: | Height: | Size: 584 KiB |
BIN
public/newsletters/260317/assets/header.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
public/newsletters/260317/assets/magnets.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/newsletters/260317/assets/panier.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
public/newsletters/260317/assets/parasols.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
1509
public/newsletters/260317/index.html
Normal file
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
BIN
resources/assets/images/footer/socials/tiktok.png
Normal file
|
After Width: | Height: | Size: 5 KiB |
|
|
@ -12,63 +12,35 @@
|
|||
</h3>
|
||||
<!-- CRM ADDRESS -->
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<fieldset class="row py-2">
|
||||
<div class="col-12 col-sm-3 d-flex justify-content-start align-items-center">
|
||||
<label
|
||||
for="userCivility"
|
||||
class="pb-2"
|
||||
>{{ trans.methods.__("Civilité") }} *</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-9">
|
||||
<select
|
||||
id="userCivility"
|
||||
class="form-select"
|
||||
:aria-label="trans.methods.__('Choisir la civilité dans la liste déroulante')"
|
||||
@change="setCivility($event)"
|
||||
>
|
||||
<option
|
||||
:value="null"
|
||||
disabled
|
||||
selected
|
||||
>
|
||||
{{ trans.methods.__("Choisissez une civilité parmi la liste déroulante") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="civility in currentShop.civility"
|
||||
:key="civility.value"
|
||||
:value="civility.value"
|
||||
>
|
||||
{{ civility.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div
|
||||
<template
|
||||
v-for="(input, inputIndex) in formInputs"
|
||||
:key="inputIndex"
|
||||
class="col-12 col-md-6"
|
||||
>
|
||||
<fieldset class="row py-2">
|
||||
<div class="col-12 col-sm-3 d-flex justify-content-start align-items-center">
|
||||
<label
|
||||
:for="`user${input.name}`"
|
||||
class="pb-2"
|
||||
>{{ trans.methods.__(input.label) }} *</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-9">
|
||||
<input
|
||||
:id="`user${input.name}`"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:placeholder="trans.methods.__(input.label) + '*'"
|
||||
v-model="input.coords"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<fieldset class="row py-2">
|
||||
<div class="col-12 col-sm-3 d-flex align-items-center">
|
||||
<label
|
||||
:for="`user${input.name}`"
|
||||
class="pb-2"
|
||||
>{{ trans.methods.__(input.label) }} *</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-9">
|
||||
<input
|
||||
:id="`user${input.name}`"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:placeholder="trans.methods.__(input.label) + '*'"
|
||||
v-model="input.coords"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div
|
||||
v-if="input.name === 'address'"
|
||||
class="d-none d-md-block col-md-6"
|
||||
></div>
|
||||
</template>
|
||||
<div class="col-12">
|
||||
<div class="form-check my-4">
|
||||
<input
|
||||
|
|
@ -96,39 +68,6 @@
|
|||
{{ trans.methods.__('Adresse de livraison') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<fieldset class="row py-2">
|
||||
<div class="col-12 col-sm-3 d-flex justify-content-start align-items-center">
|
||||
<label
|
||||
for="userCivility"
|
||||
class="pb-2"
|
||||
>{{ trans.methods.__("Civilité") }} *</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-9">
|
||||
<select
|
||||
id="userCivility"
|
||||
class="form-select"
|
||||
:aria-label="trans.methods.__('Choisir la civilité dans la liste déroulante')"
|
||||
@change="setShippingCivility($event)"
|
||||
>
|
||||
<option
|
||||
:value="null"
|
||||
disabled
|
||||
selected
|
||||
>
|
||||
{{ trans.methods.__("Choisissez une civilité parmi la liste déroulante") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="civility in currentShop.civility"
|
||||
:key="civility.value"
|
||||
:value="civility.value"
|
||||
>
|
||||
{{ civility.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<template
|
||||
v-for="(input, inputIndex) in formInputs"
|
||||
:key="inputIndex"
|
||||
|
|
@ -276,6 +215,7 @@
|
|||
>{{ payementMethod.label }}</label>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- A supprimer à la mise en place de la CB -->
|
||||
<p class="fst-italic m-0">
|
||||
{{ trans.methods.__('(CB prochainement)') }}
|
||||
</p>
|
||||
|
|
@ -407,7 +347,6 @@ const form = ref<{
|
|||
total: number,
|
||||
payementMethod: number|null,
|
||||
coords: {
|
||||
civility: number|null,
|
||||
firstname: string,
|
||||
lastname: string,
|
||||
address: string,
|
||||
|
|
@ -417,7 +356,6 @@ const form = ref<{
|
|||
email: string,
|
||||
},
|
||||
shipping?: {
|
||||
civility: number|null,
|
||||
firstname: string,
|
||||
lastname: string,
|
||||
address: string,
|
||||
|
|
@ -431,7 +369,6 @@ const form = ref<{
|
|||
total: 0,
|
||||
payementMethod: null,
|
||||
coords: {
|
||||
civility: null,
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
address: "",
|
||||
|
|
@ -441,7 +378,6 @@ const form = ref<{
|
|||
email: ""
|
||||
},
|
||||
shipping: {
|
||||
civility: null,
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
address: "",
|
||||
|
|
@ -466,10 +402,10 @@ onMounted(() => {
|
|||
form.value.coords.phone = currentShop.value.user.phone ? Number(currentShop.value.user.phone) : null;
|
||||
form.value.coords.email = String(currentShop.value.user.email);
|
||||
formInputs.value = [
|
||||
{"name": "firstname", "label": "Prénom", "coords": form.value.coords.firstname, "shipping": form.value.shipping!.firstname},
|
||||
{"name": "lastname", "label": "Nom", "coords": form.value.coords.lastname, "shipping": form.value.shipping!.lastname},
|
||||
{"name": "email", "label": "E-mail", "coords": form.value.coords.email, "shipping": null},
|
||||
{"name": "firstname", "label": "Prénom", "coords": form.value.coords.firstname, "shipping": form.value.shipping!.firstname},
|
||||
{"name": "phone", "label": "Téléphone", "coords": form.value.coords.phone, "shipping": form.value.shipping!.phone},
|
||||
{"name": "email", "label": "E-mail", "coords": form.value.coords.email, "shipping": null},
|
||||
{"name": "address", "label": "Adresse", "coords": form.value.coords.address, "shipping": form.value.shipping!.address},
|
||||
{"name": "postal_code", "label": "Code postal", "coords": form.value.coords.postcode, "shipping": form.value.shipping!.postcode},
|
||||
{"name": "city", "label": "Ville", "coords": form.value.coords.city, "shipping": form.value.shipping!.city},
|
||||
|
|
@ -492,29 +428,6 @@ function selectPayement(payment: number): void {
|
|||
form.value.payementMethod = payment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add specific item to the customer cart.
|
||||
* e.target.value = "{label: string, value: number}"
|
||||
*
|
||||
* @param e Event
|
||||
* @return void
|
||||
*/
|
||||
function setCivility(e: Event): void {
|
||||
const input = e.target as HTMLOptionElement;
|
||||
form.value.coords.civility = parseInt(input.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add specific item to the customer cart.
|
||||
* e.target.value = "{label: string, value: number}"
|
||||
*
|
||||
* @param e Event
|
||||
* @return void
|
||||
*/
|
||||
function setShippingCivility(e: Event): void {
|
||||
const input = e.target as HTMLOptionElement;
|
||||
form.value.shipping!.civility = parseInt(input.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return destroy route for a game.
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@
|
|||
<span class="header-icon position-relative mb-3 mb-sm-0 me-sm-3">
|
||||
@include('club.icons.bell')
|
||||
</span>
|
||||
<h1 class="text-uppercase m-0">{{ __('Commande annulée') }}</h1>
|
||||
|
||||
<h1 class="text-uppercase m-0">{{ $title }}</h1>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<p class="text-center">
|
||||
{{ __('La commande que vous venez de passer n\'a pu aboutir !') }}
|
||||
{{ $message }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
@extends('club.layout')
|
||||
|
||||
@section('title', __('Redirection vers Mercanet'))
|
||||
@section('description', __('Page de redirection vers le service de BNP Paribas nommé Mercanet.'))
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('container-fluid', true)
|
||||
@section('no-bottom-space', true)
|
||||
@section('page-class', 'shop')
|
||||
@section('metaIndex', 'noindex,nofollow')
|
||||
|
||||
@section('content')
|
||||
<section class="container mb-5 pb-5">
|
||||
<form name="redirectForm" method="POST" action="{{ $url }}">
|
||||
<input type="hidden" name="redirectionVersion" value="{{ $version }}">
|
||||
<input type="hidden" name="redirectionData" value="{{ $data }}">
|
||||
<h2 class="text-center">{{ __('Redirection en cours') }}</h2>
|
||||
<noscript>
|
||||
<input type="submit" name="Go" value="{{ __('Cliquez ici pour accéder à la page de paiement') }}" />
|
||||
</noscript>
|
||||
</form>
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script nonce="{{ $nonce }}">
|
||||
document.redirectForm.submit();
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -7,9 +7,9 @@
|
|||
@if ($orderType->equals(\App\Enums\Arcadia\ArcadiaOrderType::check))
|
||||
<Etat_Commande_CB>Null</Etat_Commande_CB>
|
||||
<Etat_Commande_Cheque>1</Etat_Commande_Cheque>
|
||||
{{-- @elseif($orderType->equals(\App\Enums\Arcadia\ArcadiaOrderType::cb))
|
||||
@elseif($orderType->equals(\App\Enums\Arcadia\ArcadiaOrderType::cb))
|
||||
<Etat_Commande_CB>1</Etat_Commande_CB>
|
||||
<Etat_Commande_Cheque>Null</Etat_Commande_Cheque> --}}
|
||||
<Etat_Commande_Cheque>Null</Etat_Commande_Cheque>
|
||||
@endif
|
||||
<Email_conso>
|
||||
<![CDATA[{{ $email }}]]>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@
|
|||
title="{{ __('Go to page :page :appname', ['page' => 'Instagram', 'appname' => config('app.name')]) }}" target="_blank">
|
||||
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/footer/socials/instagram.png') }}" alt="Instagram" role="img">
|
||||
</a>
|
||||
<a class="mx-sm-1 btn btn-link btn-social text-decoration-none mx-2" href="https://www.tiktok.com/@legaulois_off"
|
||||
title="{{ __('Go to page :page :appname', ['page' => 'Tiktok', 'appname' => config('app.name')]) }}" target="_blank">
|
||||
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/footer/socials/tiktok.png') }}" alt="Tiktok" role="img">
|
||||
</a>
|
||||
<a class="mx-sm-1 btn btn-link btn-social text-decoration-none mx-2" href="https://www.linkedin.com/company/legaulois"
|
||||
title="{{ __('Go to page :page :appname', ['page' => 'LinkedIn', 'appname' => config('app.name')]) }}" target="_blank">
|
||||
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/footer/socials/linkedin.png') }}" alt="LinkedIn" role="img">
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ Route::name('club.')->prefix('club')->group(function () {
|
|||
Route::get('{order}/reglement', [ShopController::class, 'payWithCb'])->name('payementCB');
|
||||
});
|
||||
Route::prefix('/reglement')->name('reglement.')->group(function () {
|
||||
Route::post('/{order}/confirmServ', [ShopController::class, 'confirmCbFromMercanet'])
|
||||
Route::post('/{order}/confirmServ', [ShopController::class, 'confirmCbFromAxepta'])
|
||||
->name('payementConfirmCBServ')
|
||||
->withoutMiddleware([
|
||||
'auth:club',
|
||||
|
|
@ -115,19 +115,8 @@ Route::name('club.')->prefix('club')->group(function () {
|
|||
XssSanitizer::class,
|
||||
EnsureEmailIsVerified::class,
|
||||
]);
|
||||
Route::post('/{order}/confirm', [ShopController::class, 'confirmCb'])
|
||||
->name('payementConfirmCB')
|
||||
->withoutMiddleware([
|
||||
'auth:club',
|
||||
Authenticate::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
EncryptCookies::class,
|
||||
VerifyCsrfToken::class,
|
||||
StartSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
XssSanitizer::class,
|
||||
EnsureEmailIsVerified::class,
|
||||
]);
|
||||
Route::get('/{order}/confirm', [ShopController::class, 'confirmCb'])
|
||||
->name('payementConfirmCB');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,5 @@ spec:
|
|||
services:
|
||||
- name: ${CI_PROJECT_PATH_SLUG}-service
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: admin-ldc-whitelist
|
||||
namespace: common-services
|
||||
tls:
|
||||
secretName: ${CRT_TLS_NAME}
|
||||
|
|
|
|||