Implement iLink World checkout page to handle Yoomee provider

This commit is contained in:
Djery-Tom 2022-12-05 20:27:42 +01:00
parent 97de7fefc8
commit 54feda58ee
36 changed files with 12903 additions and 2276 deletions

View File

@ -17,6 +17,8 @@ DB_PASSWORD=vps@2017GA
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
ACCEPTED_KEYS=U14YhuyFhweMeYpIYj8Ft2jm4cVgbMzD
@ -34,3 +36,5 @@ CINETPAY_API_KEY=176445314662bebd39b1b6f8.42908045
CINETPAY_SECRET_KEY=140983310662e79736e8ae45.38146680
CINETPAY_SITE_ID=862736
CINETPAY_API_URL=https://api-checkout.cinetpay.com/v2/
RECEIVER_NAME="Commune X"

View File

@ -6,6 +6,7 @@ namespace App\Enums;
abstract class PaymentTransactionState
{
const INITIATED = 'INITIATED';
const ACCEPTED = 'ACCEPTED';
const PENDING = 'PENDING';
const PENDING_OTP = 'PENDING_OTP';

View File

@ -2,11 +2,14 @@
namespace App\Http\Controllers;
use App\Enums\PaymentTransactionState;
use App\Models\PaymentAggregator;
use App\Models\PaymentTransaction;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Log;
use function Symfony\Component\Translation\t;
class PaymentController extends Controller
{
@ -41,7 +44,7 @@ class PaymentController extends Controller
case 'yoomee':
return app(YoomeeController::class)->pay($request);
case 'yoomeev2':
return app(YoomeeV2Controller::class)->pay($request);
return app(YoomeeV2Controller::class)->initPay($request);
case 'cinetpay':
return app(CinetpayController::class)->pay($request);
default:
@ -57,4 +60,29 @@ class PaymentController extends Controller
return $this->errorResponse($request->all());
}
}
public function checkout(Request $request, $payment_token)
{
$transaction = PaymentTransaction::where('payment_token',$payment_token)->firstOrFail();
$transaction_id = $transaction->transaction_id;
$method = $transaction->payment_method;
$amount = money($transaction->amount, $transaction->currency)->format(app()->getLocale());
$receiver = config('variables.receiver_name');
$receiver_logo = asset('assets/images/logo.jpeg');
if($transaction->state == PaymentTransactionState::INITIATED){
return view('checkout',compact('payment_token','method','amount', 'receiver','receiver_logo'));
}
if($transaction->state == PaymentTransactionState::PENDING){
return view('verify-payment',compact('transaction_id','method','amount', 'receiver','receiver_logo'));
}
$status = $transaction->state == PaymentTransactionState::ACCEPTED;
return view('payment-status',compact('transaction_id','method','amount', 'receiver','receiver_logo','status'));
}
}

View File

@ -6,6 +6,7 @@ use App\Enums\PaymentTransactionState;
use App\Models\PaymentTransaction;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Log;
@ -66,13 +67,139 @@ class YoomeeV2Controller extends Controller
$methods[] = $method;
}
return $this->successResponse([
'hasWebview' => false,
'hasWebview' => true,
'methods' => $methods
]
);
}
/*
* Init payment and provide iLink World checkout page to handle payment
*/
public function initPay(Request $request)
{
$this->validate($request, [
'aggregator_id' => 'required|integer|exists:payment_aggregators,id',
'amount' => 'required|numeric|min:5',
'currency' => 'required|string|size:3',
'payment_method' => 'required|string',
'customer_id' => 'required|integer',
'customer_email' => 'required|email',
'customer_name' => 'required|string',
'customer_surname' => 'required|string',
'customer_phone_number' => 'required|string',
'customer_address' => 'required|string',
'customer_country' => 'required|string|size:2',
'reason' => 'required|string',
]);
$transaction_id = $this->getTransactionID();
$payment_method = $request->input('payment_method');
$customer_phone_number = $request->input('customer_phone_number');
if(str_contains($customer_phone_number,'+237')){
$customer_phone_number = substr($customer_phone_number,4);
}
do{
$payment_token = Str::random(64);
}while(PaymentTransaction::where('payment_token', $payment_token)->exists());
$payment_url = route('checkout',['payment_token' => $payment_token]);
PaymentTransaction::create([
'aggregator_id' => $request->input('aggregator_id'),
"currency" => $request->input('currency'),
"transaction_id" => $transaction_id,
"amount" => $request->input('amount'),
"payment_method" => $payment_method,
"payment_url" => $payment_url,
'payment_token' => $payment_token,
'state' => PaymentTransactionState::INITIATED,
"reason" => $request->input('reason'),
"customer_id" => $request->input('customer_id'),
"customer_name" => $request->input('customer_name'),
"customer_surname" => $request->input('customer_surname'),
"customer_email" => $request->input('customer_email'),
"customer_phone_number" => $customer_phone_number,
"customer_address" => $request->input('customer_address'),
"customer_city" => $request->input('customer_city'),
"customer_country" => $request->input('customer_country'),
"customer_state" => $request->input('customer_state'),
"customer_zip_code" => $request->input('customer_zip_code'),
]);
return $this->successResponse([
'message' => 'Payment initiated',
'payment_url' => $payment_url
]);
}
public function checkoutPay(Request $request)
{
$this->validate($request, [
'payment_token' => 'required|string|exists:payment_transactions,payment_token',
'phone_number' => 'required|string',
]);
$token = $request->input('payment_token');
$transaction = PaymentTransaction::where('payment_token', $token)
->where('state', PaymentTransactionState::INITIATED)->firstOrFail();
$customer_phone_number = $request->input('phone_number');
if(str_contains($customer_phone_number,'+237')){
$customer_phone_number = substr($customer_phone_number,4);
}
// Create passport payment
$createResponse = $this->client->post('start/v1', [
'json' => [
'order_merchant' => config('variables.yoomee_username'),
'order_merchant_password' => config('variables.yoomee_password'),
'order_type' => 'MemberAccount.merchantPaymentWithoutFees',
'order_payer' => $customer_phone_number,
'order_method' => $transaction->payment_method,
'order_app_id' => config('variables.yoomee_app_id'),
'order_ext_id' => $transaction->transaction_id,
"order_base_amount" => $transaction->amount,
"order_currency" => $transaction->currency,
"order_description" => $transaction->reason,
"order_wait_final_status" => $this->isSyncRequest
]
]);
if ($createResponse->getStatusCode() == 400) {
$createResponse = json_decode($createResponse->getBody()->getContents());
if(!empty($createResponse->error)){
//If error regenerate transaction id to avoid 'Numero de commande deja utilisé'
$transaction->update([
'transaction_id' => $this->getTransactionID()
]);
//Convert into single line
session()->flash('error',str_replace(array("\n", "\r"), '',$createResponse->error_description));
return redirect()->route('checkout',['payment_token' => $token]);
}
$transaction->update([
'aggregator_payment_ref' => $createResponse->transaction_number,
'payment_date' => $createResponse->transaction_date,
'state' => strtoupper($createResponse->transaction_status),
"customer_phone_number" => $customer_phone_number,
]);
redirect()->route('checkout',['payment_token' => $token]);
}
session()->flash('error',__('errors.unexpected_error'));
return redirect()->route('checkout',['payment_token' => $token]);
}
public function pay(Request $request)
{
$this->validate($request, [
@ -129,7 +256,7 @@ class YoomeeV2Controller extends Controller
"amount" => $createResponse->transaction_amount,
'payment_date' => $createResponse->transaction_date,
"payment_method" => $payment_method,
'payment_token' => Str::random(32),
'payment_token' => Str::random(64),
'state' => strtoupper($createResponse->transaction_status),
"reason" => $request->input('reason'),
"customer_id" => $request->input('customer_id'),
@ -170,9 +297,24 @@ class YoomeeV2Controller extends Controller
return $this->getPaymentStatus($transaction);
}
private function getPaymentStatus(PaymentTransaction $transaction)
public function getPaymentStatus(Request $request)
{
$this->validate($request, [
'transaction_id' => 'required|string|exists:payment_transactions,transaction_id',
'verify_btn' => 'nullable|boolean'
]);
$transaction = PaymentTransaction::where('transaction_id',$request->input('transaction_id'))->first();
$verify_btn = $request->input('verify_btn');
try {
// Si le paiement fait plus de 5 min on l'annule
if($transaction->state == PaymentTransactionState::PENDING && $transaction->created_at->diffInMinutes(Carbon::now()) > 5){
$transaction->update([
'state' => PaymentTransactionState::CANCELLED
]);
}
$response = $this->client->post('status/v1', [
'json' => [
'order_merchant' => config('variables.yoomee_username'),
@ -214,18 +356,46 @@ class YoomeeV2Controller extends Controller
}
if ($transaction->state == PaymentTransactionState::ACCEPTED) {
return redirect()->route('paymentResult',[
return [
'message' => "Payment accepted",
'status' => 1,
'refresh' => 1,
];
} else {
if($verify_btn){
return redirect()->route('checkout',['payment_token' => $transaction->payment_token]);
}else{
return [
'message' => "Payment failed",
'status' => 0
];
}
}
}
public function merchantRedirect(Request $request)
{
$this->validate($request, [
'transaction_id' => 'required|string|exists:payment_transactions,transaction_id'
]);
$transaction = PaymentTransaction::where('transaction_id',$request->input('transaction_id'))->first();
if ($transaction->state == PaymentTransactionState::ACCEPTED) {
return redirect()->route('paymentResult', [
'transaction_id' => $transaction->transaction_id,
'token' => $transaction->payment_token,
'status' => 1
]);
} else {
return redirect()->route('paymentResult',[
return redirect()->route('paymentResult', [
'message' => "Payment failed",
'status' => 0
]);
}
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Symfony\Component\HttpFoundation\Cookie;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Session\TokenMismatchException;
class VerifyCsrfToken {
/**
* The encrypter implementation.
*
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
protected $encrypter;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @return void
*/
public function __construct(Encrypter $encrypter) {
$this->encrypter = $encrypter;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Illuminate\Session\TokenMismatchException
*/
public function handle($request, Closure $next) {
if ($this->isReading($request) || $this->tokensMatch($request)) {
$request->session()->regenerateToken();
return $this->addCookieToResponse($request, $next($request));
}
throw new TokenMismatchException;
}
/**
* Determine if the session and input CSRF tokens match.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function tokensMatch($request) {
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
if (!$token && $header = $request->header('X-XSRF-TOKEN')) {
$token = $this->encrypter->decrypt($header);
}
return $request->session()->token() == $token;
}
/**
* Add the CSRF token to the response cookies.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return \Illuminate\Http\Response
*/
protected function addCookieToResponse($request, $response) {
$response->headers->setCookie(
new Cookie('XSRF-TOKEN', $request->session()->token(), time() + 60 * 120, '/', null, false, false)
);
return $response;
}
/**
* Determine if the HTTP request uses a read verb.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function isReading($request) {
return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}
}

View File

@ -60,6 +60,7 @@ $app->singleton(
*/
$app->configure('app');
$app->configure('session');
$app->configure('swagger-lume');
$app->configure('variables');
/*
@ -80,7 +81,9 @@ $app->middleware([
$app->routeMiddleware([
// 'auth' => App\Http\Middleware\Authenticate::class,
'docs' => App\Http\Middleware\SecureApiDocs::class,
'auth' => App\Http\Middleware\AuthenticateAccess::class
'auth' => App\Http\Middleware\AuthenticateAccess::class,
'session' => \Illuminate\Session\Middleware\StartSession::class,
'csrf' => \App\Http\Middleware\VerifyCsrfToken::class
]);
/*
@ -94,11 +97,19 @@ $app->routeMiddleware([
|
*/
// $app->register(App\Providers\AppServiceProvider::class);
$app->register(App\Providers\AppServiceProvider::class);
// $app->register(App\Providers\AuthServiceProvider::class);
// $app->register(App\Providers\EventServiceProvider::class);
$app->register(App\Providers\EventServiceProvider::class);
$app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class);
$app->register(\SwaggerLume\ServiceProvider::class);
$app->singleton(Illuminate\Session\SessionManager::class, function () use ($app) {
return $app->loadComponent('session', Illuminate\Session\SessionServiceProvider::class, 'session');
});
$app->singleton('session.store', function () use ($app) {
return $app->loadComponent('session', Illuminate\Session\SessionServiceProvider::class, 'session.store');
});
/*
|--------------------------------------------------------------------------
| Load The Application Routes

View File

@ -5,11 +5,14 @@
"license": "MIT",
"type": "project",
"require": {
"php": "^8.0",
"php": "^7.3|^8.0",
"ext-json": "*",
"cknow/laravel-money": "^7.0",
"darkaonline/swagger-lume": "^9.0",
"flipbox/lumen-generator": "^9.1",
"guzzlehttp/guzzle": "^7.4",
"laravel/lumen-framework": "^9.0"
"illuminate/session": "^8.83",
"laravel/lumen-framework": "^8.0"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",

4079
composer.lock generated

File diff suppressed because it is too large Load Diff

201
config/session.php Normal file
View File

@ -0,0 +1,201 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option controls the default session "driver" that will be used on
| requests. By default, we will use the lightweight native driver but
| you may specify any of the other wonderful drivers provided here.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to immediately expire on the browser closing, set that option.
|
*/
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it is stored. All encryption will be run
| automatically by Laravel and you can use the Session like normal.
|
*/
'encrypt' => false,
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When using the native session driver, we need a location where session
| files may be stored. A default has been set for you but a different
| location may be specified. This is only needed for file sessions.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table we
| should use to manage the sessions. Of course, a sensible default is
| provided for you; however, you are free to change this as needed.
|
*/
'table' => 'sessions',
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| While using one of the framework's cache driven session backends you may
| list a cache store that should be used for these sessions. This value
| must match with one of the application's configured cache "stores".
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the cookie used to identify a session
| instance by ID. The name specified here will get used every time a
| new session cookie is created by the framework for every driver.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary.
|
*/
'path' => '/',
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. You are free to modify this option if needed.
|
*/
'http_only' => true,
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" since this is a secure default value.
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => 'lax',
];

View File

@ -4,6 +4,7 @@ return [
'app_env' => env('APP_ENV', 'local'),
'app_debug' => env('APP_DEBUG', true),
'swagger_docs_token' => env('SWAGGER_DOCS_TOKEN', true),
'receiver_name' => env('RECEIVER_NAME', 'iLink World'),
'yoomee_merchant_code' => env('YOOMEE_MERCHANT_CODE', ''),
'yoomee_api_url' => env('YOOMEE_API_URL', ''),
'yoomee_api_v2_url' => env('YOOMEE_API_V2_URL', ''),

1
public/assets/app.css Normal file

File diff suppressed because one or more lines are too long

252
public/assets/app.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/assets/images/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
public/assets/images/logo.jpeg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

14
public/assets/utils.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,616 @@
<!DOCTYPE html>
<html lang="fr" data-kantu="1">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Effectuer un paiement - {{__('providers.'.$method)}}</title>
<meta name="theme-color" content="#000000">
<meta name="csrf-token" content="">
<link rel="icon" href="{{asset('assets/images/favicon.ico')}}">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="{{asset('assets/app.css')}}">
<style>
.option-content1::after {
left: 42%;
}
</style>
<style type="text/css">
#page {
display: none;
}
#loading {
display: block;
position: absolute;
z-index: 100;
background-position: center;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
}
@keyframes ldio-5owbnf6l9j7 {
0% {
transform: translate(12px, 80px) scale(0);
}
25% {
transform: translate(12px, 80px) scale(0);
}
50% {
transform: translate(12px, 80px) scale(1);
}
75% {
transform: translate(80px, 80px) scale(1);
}
100% {
transform: translate(148px, 80px) scale(1);
}
}
@keyframes ldio-5owbnf6l9j7-r {
0% {
transform: translate(148px, 80px) scale(1):
}
100% {
transform: translate(148px, 80px) scale(0);
}
}
@keyframes ldio-5owbnf6l9j7-c {
0% {
background: var(--loader-color1)
}
25% {
background: var(--loader-color2)
}
50% {
background: var(--loader-color1)
}
75% {
background: var(--loader-color2)
}
100% {
background: var(--loader-color1)
}
}
.ldio-5owbnf6l9j7 div {
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
transform: translate(80px, 80px) scale(1);
background: var(--loader-color1);
animation: ldio-5owbnf6l9j7 1.2048192771084336s infinite cubic-bezier(0, 0.5, 0.5, 1);
}
.ldio-5owbnf6l9j7 div:nth-child(1) {
background: var(--loader-color2);
transform: translate(148px, 80px) scale(1);
animation: ldio-5owbnf6l9j7-r 0.3012048192771084s infinite cubic-bezier(0, 0.5, 0.5, 1), ldio-5owbnf6l9j7-c 1.2048192771084336s infinite step-start;
}
.ldio-5owbnf6l9j7 div:nth-child(2) {
animation-delay: -0.3012048192771084s;
background: var(--loader-color1);
}
.ldio-5owbnf6l9j7 div:nth-child(3) {
animation-delay: -0.6024096385542168s;
background: var(--loader-color2);
}
.ldio-5owbnf6l9j7 div:nth-child(4) {
animation-delay: -0.9036144578313252s;
background: var(--loader-color1);
}
.ldio-5owbnf6l9j7 div:nth-child(5) {
animation-delay: -1.2048192771084336s;
background: var(--loader-color2);
}
.loadingio-spinner-ellipsis-99po1h19hjs {
width: 200px;
height: 200px;
display: inline-block;
overflow: hidden;
background: none;
}
.ldio-5owbnf6l9j7 {
width: 100%;
height: 100%;
position: relative;
transform: translateZ(0) scale(1);
backface-visibility: hidden;
transform-origin: 0 0; /* see note above */
}
.ldio-5owbnf6l9j7 div {
box-sizing: content-box;
}
.toggle-content {
display: none;
height: 0;
opacity: 0;
overflow: hidden;
transition: height 350ms ease-in-out, opacity 750ms ease-in-out;
}
.toggle-content.is-visible {
display: block;
height: auto;
opacity: 1;
}
</style>
</head>
<body id="body">
<div id="loading">
<div class="loadingio-spinner-ellipsis-99po1h19hjs">
<div class="ldio-5owbnf6l9j7">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
<div id="page" class="container">
<div class="row h-100 justify-content-center">
<div class="col-md-8 col-lg-6 my-auto">
<div class="desk bg-white shadow-sm">
<div class="desk-head">
<div class="row">
<div class="col-12">
<div class="">
<div class="media align-items-center">
<div class="media-head">
<img src="{{$receiver_logo}}" class="rounded media-body mr-2 marchand-logo" alt="{{$receiver}}">
</div>
<div class="media-body">
<p class="marchand-name p-0 m-0">{{$receiver}}</p>
<h4 class="due-amount p-0 m-0">{{$amount}}</h4>
</div>
</div>
</div>
</div>
</div>
</div>
<hr class="ml-2 mr-2">
<div class="desk-body">
<form id="payment_form" name="begin_form" method="POST" action="{{route('yoomee.v2.checkoutPay')}}">
<input type="hidden" name="_token" value="{{ app('request')->session()->get('_token') }}">
<input type="hidden" name="payment_token" value="{{ $payment_token }}">
<div class="desk-content">
<p class="text-center">Payer avec</p>
<div class="choose-payment-type">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<svg xmlns="http://www.w3.org/2000/svg" width="60px" height="60px" viewBox="0 0 100 100">
<g transform="translate(-2978 2535)">
<rect width="100" height="100" transform="translate(2978 -2535)" fill="var()" opacity="0.003"></rect>
<g transform="translate(2403.753 -3004.222)">
<path d="M65.889,1.928H12.373A5.964,5.964,0,0,0,6.427,7.874V91.122a5.964,5.964,0,0,0,5.946,5.946H65.889a5.964,5.964,0,0,0,5.946-5.946V7.874A5.964,5.964,0,0,0,65.889,1.928ZM39.131,92.774a4.625,4.625,0,1,1,4.625-4.625A4.625,4.625,0,0,1,39.131,92.774ZM65.889,79.23H12.373V13.821H65.889Z" transform="translate(584.82 469.294)" fill="var(--cinetpay1)"></path>
<path d="M21.585,0V3.6H0V7.2H21.585v3.6l7.2-5.4ZM7.2,14.39,0,19.787l7.2,5.4v-3.6H28.78v-3.6H7.2Z" transform="translate(609.43 500.731)" fill=" var(--cinetpay1)"></path>
</g>
</g>
</svg>
<p class="descrip" style="font-size:medium; margin-top: 5px">{{__('providers.'.$method)}}</p>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade active show" id="mobile-money" role="tabpanel" aria-labelledby="mobile-money-tab">
<div class="option-content1">
<div class="p-1">
<div class="desk-form">
<div class="phone-input form-group">
<label class="input-title input-phone" for="phone_number">
Numéro de téléphone
</label>
<div class="iti iti--allow-dropdown iti--separate-dial-code">
<div class="iti__flag-container">
<div class="iti__selected-flag" role="combobox"
aria-controls="iti-0__country-listbox"
aria-owns="iti-0__country-listbox"
aria-expanded="false" tabindex="0"
title="Cameroon (Cameroun): +237"
aria-activedescendant="iti-0__item-cm">
<div class="iti__flag iti__cm"></div>
<div class="iti__selected-dial-code">+237</div>
<div class="iti__arrow"></div>
</div>
<ul class="iti__country-list iti__hide"
id="iti-0__country-listbox" role="listbox"
aria-label="List of countries">
<li class="iti__country iti__standard iti__active"
tabindex="-1" id="iti-0__item-cm" role="option"
data-dial-code="237" data-country-code="cm"
aria-selected="true">
<div class="iti__flag-box">
<div class="iti__flag iti__cm"></div>
</div>
<span class="iti__country-name">Cameroon (Cameroun)</span><span
class="iti__dial-code">+237</span></li>
</ul>
</div>
<input type="tel" id="phone_number" name="phone_number"
class="form-control input-tel" value="" autofocus
required autocomplete="off"
data-intl-tel-input-id="0"
style="padding-left: 94px;" minlength="9" maxlength="9"
placeholder="6 71 23 45 67">
</div>
<img id="payment-method-icon" width="2rem"
alt="payment method logo"
src="{{url('assets/images/default.png')}}">
<div>
</div>
</div>
<div>
<div class="col-12 px-0">
<div>
<p id="methodscm" class="payment-method-box operator-box">
@if(str_contains(strtolower($method),'orange'))
<img
src="{{url('assets/images/orange.png')}}"
class="rounded mx-1 payment-method-logo"
alt="ORANGE">
@elseif(str_contains(strtolower($method),'mtn'))
<img
src="{{url('assets/images/mtn.png')}}"
class="rounded mx-1 payment-method-logo"
alt="MTN">
@elseif(str_contains(strtolower($method),'yoomee'))
<img
src="{{url('assets/images/yoomee.png')}}"
class="rounded mx-1 payment-method-logo"
alt="yoomee">
@elseif(str_contains(strtolower($method),'express'))
<img
src="{{url('assets/images/express-union.png')}}"
class="rounded mx-1 payment-method-logo"
alt="express">
@else
<img
src="{{url('assets/images/default.png')}}"
class="rounded mx-1 payment-method-logo"
alt="default">
@endif
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="desk-action p-3" id="desk-action">
<button type="submit" class="btn btn-next btn-block" id="del">
Payer {{$amount}}
</button>
</div>
</form>
</div>
</div>
@include('footer')
</div>
</div>
</div>
<script src="{{asset('assets/sweetalert2/sweetalert2.all.min.js')}}"></script>
<script src="{{asset('assets/app.js')}}"></script>
<script>
document.addEventListener('readystatechange', function (event) {
if (event.target.readyState === 'complete') {
ready();
}
});
function ready() {
setTimeout(function () {
fadeIn(document.getElementById('page'))
}, 217)
fadeOut(document.getElementById('loading'));
}
// ** FADE OUT FUNCTION **
function fadeOut(el) {
el.style.opacity = 1;
(function fade() {
if ((el.style.opacity -= .1) < 0) {
el.style.display = "none";
} else {
requestAnimationFrame(fade);
}
})();
}
// ** FADE IN FUNCTION **
function fadeIn(el, display) {
el.style.opacity = 0;
el.style.display = display || "block";
(function fade() {
var val = parseFloat(el.style.opacity);
if (!((val += .1) > 1)) {
el.style.opacity = val;
requestAnimationFrame(fade);
}
})();
}
function empty(item) {
return (item === undefined || item === null || item === false || item === '');
}
</script>
<script>
document.addEventListener('readystatechange', function (event) {
if (event.target.readyState === 'complete') {
disableButtonOnSubmit();
}
});
function disableButtonOnSubmit() {
var form = document.getElementById('payment_form');
var submitButton = document.getElementById('del');
form.addEventListener('submit', function () {
submitButton.disabled = true;
setTimeout(function () {
submitButton.disabled = false;
}, 15000)
})
}
</script>
<script>
@if(session('error'))
Swal.fire({
title: 'Error!',
text: "{{session('error')}}",
icon: 'error',
confirmButtonText: 'OK'
})
@endif
function initializeIntelInput() {
try {
if (empty(window.intlTelInput) && intlTelInput) {
window.intlTelInput = intlTelInput;
}
var phoneNumberInput = document.querySelector("#phone_number");
var iti = window.intlTelInput(phoneNumberInput, {
hiddenInput: "phone_number",
onlyCountries: ["cm"],
placeholderNumberType: "MOBILE",
initialCountry: 'cm',
localizedCountries: {"cd": "Congo RDC"},
separateDialCode: true,
utilsScript: "/assets/utils.js",
customPlaceholder: function (selectedCountryPlaceholder, selectedCountryData) {
if (selectedCountryData.iso2 === 'ci') {
return '01 23 45 67 89';
} else {
return selectedCountryPlaceholder;
}
},
});
var countryJsData = iti.getSelectedCountryData();
var countryJs = 'cm';
if (!empty(countryJsData) && !empty(countryJsData.iso2)) {
countryJs = countryJsData.iso2;
getMobileMoneyPaymentMethodLogo(iti);
}
var methodCountry = document.getElementById("methods" + countryJs);
showCurrentLogos(methodCountry);
phoneNumberInput.addEventListener('countrychange', function (e) {
const methodCountry = document.getElementById("methods" + iti.getSelectedCountryData().iso2)
showCurrentLogos(methodCountry);
getMobileMoneyPaymentMethodLogo(iti);
});
phoneNumberInput.addEventListener('paste', function (e) {
getMobileMoneyPaymentMethodLogo(iti);
});
phoneNumberInput.addEventListener('keyup', function (e) {
getMobileMoneyPaymentMethodLogo(iti);
});
if (!empty(phoneNumberInput)) {
setInputFilter(phoneNumberInput, function (value) {
return /^\d*\.?\d*$/.test(value.replace(/\s/g, ''));
});
}
var countryData = iti.getSelectedCountryData()
var countryIso = countryData.iso2;
var phoneNumber = phoneNumberInput.value.replace("+" + iti.dialCode, "").trim();
displayPaymentMethodLogo(countryIso, phoneNumber);
return true;
} catch (e) {
}
}
function getMobileMoneyPaymentMethodLogo(itiEvent) {
var countryData = itiEvent.getSelectedCountryData()
var countryIso = countryData.iso2;
var phoneNumber = itiEvent.getNumber().replace("+" + countryData.dialCode, "").trim();
return displayPaymentMethodLogo(countryIso, phoneNumber);
}
if (document.readyState !== 'loading') {
initializeIntelInput();
} else {
document.addEventListener("DOMContentLoaded", initializeIntelInput);
}
</script>
<script>
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
value: function (search, rawPos) {
var pos = rawPos > 0 ? rawPos | 0 : 0;
return this.substring(pos, pos + search.length) === search;
}
});
}
var paymentMethods = {
"bf": {
"regex": [],
"prefixes": [{
"values": ["55", "64", "65", "66", "67", "74", "75", "76", "77"],
"name": "orange"
}, {"values": ["51", "53", "60", "61", "62", "63", "70", "71", "72", "73"], "name": "moov"}],
"max_digits": 8,
"currencies": ["XOF"]
},
"bj": {
"regex": [],
"prefixes": [{
"values": ["51", "52", "53", "54", "61", "62", "66", "67", "69", "90", "91", "96", "97"],
"name": "mtn"
}, {"values": ["60", "63", "64", "65", "94", "95", "98", "99"], "name": "moov"}],
"max_digits": 8,
"currencies": ["XOF"]
},
"cd": {
"regex": [],
"prefixes": [{
"values": ["80", "84", "85", "89"],
"currencies": ["CDF", "USD"],
"name": "orange"
}, {
"currencies": ["CDF", "USD"],
"values": ["97", "99"],
"name": "airtel"
}, {
"values": ["81", "82", "83", "081", "082", "080", "083"],
"currencies": ["CDF", "USD"],
"name": "mpesa"
}],
"max_digits": 10,
"currencies": ["CDF", "USD"]
},
"ci": {
"regex": [{
"values": "^[0,4,5,6,7,8,9]{1}[7,8,9]\\d{0,8}",
"name": "orange"
}, {
"values": "^[0,4,5,6,7,8,9]{1}[4,5,6]\\d{0,8}",
"name": "mtn"
}, {"values": "^[0,4,5,6,7,8,9]{1}[0,1,2,3]{1}\\d{0,8}", "name": "moov"}],
"prefixes": [],
"max_digits": 10,
"currencies": ["XOF"]
},
"cm": {
"regex": [],
"prefixes": [
{
"values": ["690", "691", "692", "693", "694", "695", "696", "697", "698", "699", "655", "656", "657", "658", "659"],
"name": "orange"
},
{
"values": ["680", "681", "682", "683", "684", "685", "686", "687", "688", "689", "650", "651", "652", "653", "654", "678"],
"name": "mtn"
},
{
"values": ["230", "231", "232" , "233" , "240","241","242"],
"name": "yoomee"
}
],
"max_digits": 10,
"currencies": ["XAF"]
},
"ml": {
"regex": [],
"prefixes": [{"values": ["65", "66", "67", "68", "69", "95", "96", "97", "98", "99"], "name": "moov"}],
"max_digits": 10,
"currencies": ["XOF"]
},
"ne": {
"regex": [],
"prefixes": [{"values": ["86", "87", "88", "89", "96", "97", "98", "99"], "name": "airtel"}],
"max_digits": 8,
"currencies": ["XOF"]
},
"sn": {
"regex": [],
"prefixes": [{"values": ["77", "78"], "name": "orange"}, {
"values": ["65", "66", "76"],
"name": "freemoney"
}, {"values": ["70"], "name": "expresso"}],
"max_digits": 10,
"currencies": ["XOF"]
},
"tg": {
"regex": [],
"prefixes": [{
"values": ["79", "96", "97", "98", "99"],
"name": "moov"
}, {"values": ["70", "90", "91", "92", "93"], "name": "tmoney"}],
"max_digits": 10,
"currencies": ["XOF"]
},
"gn": {
"regex": [],
"prefixes": [{"values": ["610", "611", "62"], "name": "orange"}, {"values": ["66"], "name": "mtn"}],
"max_digits": 8,
"currencies": ["GNF"]
}
};
function displayPaymentMethodLogo(country, phoneNumber) {
var countriesPaymentMethods = paymentMethods[country.toLowerCase()];
var logosPath = '/assets/images/';
var logo = 'default';
if (countriesPaymentMethods !== undefined) {
var countriesPaymentMethodsRegexEntries = countriesPaymentMethods['regex'];
var countriesPaymentMethodsValuesEntries = countriesPaymentMethods['prefixes'];
if (typeof countriesPaymentMethodsRegexEntries !== undefined && countriesPaymentMethodsRegexEntries.length > 0) {
for (var regexItem of countriesPaymentMethodsRegexEntries) {
if (new RegExp(regexItem.values).test(phoneNumber) && phoneNumber.length <= countriesPaymentMethods['max_digits']) {
logo = regexItem.name;
document.getElementById("payment-method-icon").src = logosPath + logo + '.png';
return;
}
}
}
if (typeof countriesPaymentMethodsValuesEntries !== undefined && countriesPaymentMethodsValuesEntries.length > 0) {
for (var prefixItem of countriesPaymentMethodsValuesEntries) {
var prefixes = prefixItem['values'];
for (var prefix of prefixes) {
if (phoneNumber.startsWith(prefix) && phoneNumber.length <= countriesPaymentMethods['max_digits']) {
logo = prefixItem['name'];
document.getElementById("payment-method-icon").src = logosPath + logo + '.png';
return;
}
}
}
}
}
document.getElementById("payment-method-icon").src = logosPath + 'default.png';
}
</script>
</body>
</html>

View File

@ -0,0 +1,10 @@
<p class="text-center pt-2">
<svg viewBox="0 0 24 24" width="15" height="15" stroke="var(--lockStroke)" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
<span style="color: var(--secured-by-text)">Sécurisé par </span>
<strong class="text-cinetpay">
<span style="color: #6787B1">iLink World</span>
</strong>
</p>

View File

@ -0,0 +1,280 @@
<!DOCTYPE html>
<html lang="fr" data-kantu="1">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paiement {{$status ? 'réussi' : 'échoué'}} - {{__('providers.'.$method)}}</title>
<meta name="theme-color" content="#000000">
<meta name="csrf-token" content="">
<link rel="icon" href="{{asset('assets/images/favicon.ico')}}">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="{{asset('assets/app.css')}}">
<style>
.option-content1::after {
left: 42%;
}
</style>
<style type="text/css">
#page {
display: none;
}
#loading {
display: block;
position: absolute;
z-index: 100;
background-position: center;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
}
@keyframes ldio-5owbnf6l9j7 {
0% {
transform: translate(12px, 80px) scale(0);
}
25% {
transform: translate(12px, 80px) scale(0);
}
50% {
transform: translate(12px, 80px) scale(1);
}
75% {
transform: translate(80px, 80px) scale(1);
}
100% {
transform: translate(148px, 80px) scale(1);
}
}
@keyframes ldio-5owbnf6l9j7-r {
0% {
transform: translate(148px, 80px) scale(1):
}
100% {
transform: translate(148px, 80px) scale(0);
}
}
@keyframes ldio-5owbnf6l9j7-c {
0% {
background: var(--loader-color1)
}
25% {
background: var(--loader-color2)
}
50% {
background: var(--loader-color1)
}
75% {
background: var(--loader-color2)
}
100% {
background: var(--loader-color1)
}
}
.ldio-5owbnf6l9j7 div {
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
transform: translate(80px, 80px) scale(1);
background: var(--loader-color1);
animation: ldio-5owbnf6l9j7 1.2048192771084336s infinite cubic-bezier(0, 0.5, 0.5, 1);
}
.ldio-5owbnf6l9j7 div:nth-child(1) {
background: var(--loader-color2);
transform: translate(148px, 80px) scale(1);
animation: ldio-5owbnf6l9j7-r 0.3012048192771084s infinite cubic-bezier(0, 0.5, 0.5, 1), ldio-5owbnf6l9j7-c 1.2048192771084336s infinite step-start;
}
.ldio-5owbnf6l9j7 div:nth-child(2) {
animation-delay: -0.3012048192771084s;
background: var(--loader-color1);
}
.ldio-5owbnf6l9j7 div:nth-child(3) {
animation-delay: -0.6024096385542168s;
background: var(--loader-color2);
}
.ldio-5owbnf6l9j7 div:nth-child(4) {
animation-delay: -0.9036144578313252s;
background: var(--loader-color1);
}
.ldio-5owbnf6l9j7 div:nth-child(5) {
animation-delay: -1.2048192771084336s;
background: var(--loader-color2);
}
.loadingio-spinner-ellipsis-99po1h19hjs {
width: 200px;
height: 200px;
display: inline-block;
overflow: hidden;
background: none;
}
.ldio-5owbnf6l9j7 {
width: 100%;
height: 100%;
position: relative;
transform: translateZ(0) scale(1);
backface-visibility: hidden;
transform-origin: 0 0; /* see note above */
}
.ldio-5owbnf6l9j7 div {
box-sizing: content-box;
}
.toggle-content {
display: none;
height: 0;
opacity: 0;
overflow: hidden;
transition: height 350ms ease-in-out, opacity 750ms ease-in-out;
}
.toggle-content.is-visible {
display: block;
height: auto;
opacity: 1;
}
</style>
</head>
<body id="body">
<div id="loading">
<div class="loadingio-spinner-ellipsis-99po1h19hjs">
<div class="ldio-5owbnf6l9j7">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
<div id="page" class="container">
<div class="row h-100 justify-content-center">
<div class="col-md-8 col-lg-6 my-auto">
<div class="desk bg-white shadow-sm">
<div class="desk-head">
<div class="row">
<div class="col-12">
<div class="">
<div class="media align-items-center">
<div class="media-head">
<img src="{{$receiver_logo}}" class="rounded media-body mr-2 marchand-logo" alt="{{$receiver}}">
</div>
<div class="media-body">
<p class="marchand-name p-0 m-0">{{$receiver}}</p>
<h4 class="due-amount p-0 m-0">{{$amount}}</h4>
</div>
</div>
</div>
</div>
</div>
</div>
<hr class="ml-2 mr-2">
<div class="desk-body">
<div class="desk-content">
@if($status)
<div class="desk-animation">
<img alt="failed_icon" height="85" width="85"
src="{{asset('assets/images/success.png')}}">
</div>
<p class="text-center my-4">Votre paiement a réussi</p>
@else
<div class="desk-animation">
<img alt="failed_icon" height="85" width="85"
src="{{asset('assets/images/failed.png')}}">
</div>
<p class="text-center my-4">Votre paiement a échoué</p>
@endif
<div class="">
{{-- <p class="p-2 text-center">--}}
{{-- Contacter <a href="https://checkout.cinetpay.com/payment/reclamation">--}}
{{-- le support--}}
{{-- </a>--}}
{{-- </p>--}}
</div>
</div>
<div class="desk-action p-3">
<div class="row">
<div class="col-md-12 pb-2 text-center">
<a href="{{route('yoomee.v2.merchantRedirect',['transaction_id' => $transaction_id])}}"
class="btn btn-clear btn-block" id="closeCinetPaySeamlessModal">
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor"
stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"
class="css-i6dzq1">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
Retourner sur le site
</a>
</div>
</div>
</div>
</div>
</div>
@include('footer')
</div>
</div>
</div>
<script>
document.addEventListener('readystatechange', function (event) {
if (event.target.readyState === 'complete') {
ready();
}
});
function ready() {
setTimeout(function () {
fadeIn(document.getElementById('page'))
}, 217)
fadeOut(document.getElementById('loading'));
}
// ** FADE OUT FUNCTION **
function fadeOut(el) {
el.style.opacity = 1;
(function fade() {
if ((el.style.opacity -= .1) < 0) {
el.style.display = "none";
} else {
requestAnimationFrame(fade);
}
})();
}
// ** FADE IN FUNCTION **
function fadeIn(el, display) {
el.style.opacity = 0;
el.style.display = display || "block";
(function fade() {
var val = parseFloat(el.style.opacity);
if (!((val += .1) > 1)) {
el.style.opacity = val;
requestAnimationFrame(fade);
}
})();
}
function empty(item) {
return (item === undefined || item === null || item === false || item === '');
}
</script>
<script src="{{asset('assets/app.js')}}"></script>
</body>
</html>

View File

@ -0,0 +1,306 @@
<!DOCTYPE html>
<html lang="fr" data-kantu="1">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vérification du paiement - {{__('providers.'.$method)}}</title>
<meta name="theme-color" content="#000000">
<meta name="csrf-token" content="">
<link rel="icon" href="{{asset('assets/images/favicon.ico')}}">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="{{asset('assets/app.css')}}">
<style>
.option-content1::after {
left: 42%;
}
</style>
<style type="text/css">
#page {
display: none;
}
#loading {
display: block;
position: absolute;
z-index: 100;
background-position: center;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
}
@keyframes ldio-5owbnf6l9j7 {
0% {
transform: translate(12px, 80px) scale(0);
}
25% {
transform: translate(12px, 80px) scale(0);
}
50% {
transform: translate(12px, 80px) scale(1);
}
75% {
transform: translate(80px, 80px) scale(1);
}
100% {
transform: translate(148px, 80px) scale(1);
}
}
@keyframes ldio-5owbnf6l9j7-r {
0% {
transform: translate(148px, 80px) scale(1):
}
100% {
transform: translate(148px, 80px) scale(0);
}
}
@keyframes ldio-5owbnf6l9j7-c {
0% {
background: var(--loader-color1)
}
25% {
background: var(--loader-color2)
}
50% {
background: var(--loader-color1)
}
75% {
background: var(--loader-color2)
}
100% {
background: var(--loader-color1)
}
}
.ldio-5owbnf6l9j7 div {
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
transform: translate(80px, 80px) scale(1);
background: var(--loader-color1);
animation: ldio-5owbnf6l9j7 1.2048192771084336s infinite cubic-bezier(0, 0.5, 0.5, 1);
}
.ldio-5owbnf6l9j7 div:nth-child(1) {
background: var(--loader-color2);
transform: translate(148px, 80px) scale(1);
animation: ldio-5owbnf6l9j7-r 0.3012048192771084s infinite cubic-bezier(0, 0.5, 0.5, 1), ldio-5owbnf6l9j7-c 1.2048192771084336s infinite step-start;
}
.ldio-5owbnf6l9j7 div:nth-child(2) {
animation-delay: -0.3012048192771084s;
background: var(--loader-color1);
}
.ldio-5owbnf6l9j7 div:nth-child(3) {
animation-delay: -0.6024096385542168s;
background: var(--loader-color2);
}
.ldio-5owbnf6l9j7 div:nth-child(4) {
animation-delay: -0.9036144578313252s;
background: var(--loader-color1);
}
.ldio-5owbnf6l9j7 div:nth-child(5) {
animation-delay: -1.2048192771084336s;
background: var(--loader-color2);
}
.loadingio-spinner-ellipsis-99po1h19hjs {
width: 200px;
height: 200px;
display: inline-block;
overflow: hidden;
background: none;
}
.ldio-5owbnf6l9j7 {
width: 100%;
height: 100%;
position: relative;
transform: translateZ(0) scale(1);
backface-visibility: hidden;
transform-origin: 0 0; /* see note above */
}
.ldio-5owbnf6l9j7 div {
box-sizing: content-box;
}
.toggle-content {
display: none;
height: 0;
opacity: 0;
overflow: hidden;
transition: height 350ms ease-in-out, opacity 750ms ease-in-out;
}
.toggle-content.is-visible {
display: block;
height: auto;
opacity: 1;
}
</style>
</head>
<body id="body">
<div id="loading">
<div class="loadingio-spinner-ellipsis-99po1h19hjs">
<div class="ldio-5owbnf6l9j7">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
<div id="page" class="container">
<div class="row h-100 justify-content-center">
<div class="col-md-8 col-lg-6 my-auto">
<div class="desk bg-white shadow-sm">
<div class="desk-head">
<div class="row">
<div class="col-12">
<div class="">
<div class="media align-items-center">
<div class="media-head">
<img src="{{$receiver_logo}}" class="rounded media-body mr-2 marchand-logo" alt="{{$receiver}}">
</div>
<div class="media-body">
<p class="marchand-name p-0 m-0">{{$receiver}}</p>
<h4 class="due-amount p-0 m-0">{{$amount}}</h4>
</div>
</div>
</div>
</div>
</div>
</div>
<hr class="ml-2 mr-2">
<form method="POST" action="{{route('yoomee.v2.verify')}}">
<input type="hidden" name="_token" value="{{ app('request')->session()->get('_token') }}">
<input type="hidden" name="transaction_id" value="{{ $transaction_id }}">
<input type="hidden" name="verify_btn" value="1">
<div class="desk-body">
<div class="desk-content">
<p class="text-center">Vérification du paiement</p>
<div class="desk-info">
<p class="p-2 text-center">
Veuillez confirmer le paiement en saisissant votre code PIN sur votre telephone.
</p>
</div>
<p id="time">02:01</p>
</div>
<div class="desk-action p-3 requestNewCode">
<button type="submit"
class="btn btn-next btn-block">
Vérifier votre paiement <span id="timer"></span>
</button>
</div>
</div>
</form>
</div>
@include('footer')
</div>
</div>
</div>
<script src="{{asset('assets/sweetalert2/sweetalert2.all.min.js')}}"></script>
<script src="{{asset('assets/app.js')}}"></script>
<script>
document.addEventListener('readystatechange', function (event) {
if (event.target.readyState === 'complete') {
ready();
}
});
function ready() {
setTimeout(function () {
fadeIn(document.getElementById('page'))
}, 217)
fadeOut(document.getElementById('loading'));
}
// ** FADE OUT FUNCTION **
function fadeOut(el) {
el.style.opacity = 1;
(function fade() {
if ((el.style.opacity -= .1) < 0) {
el.style.display = "none";
} else {
requestAnimationFrame(fade);
}
})();
}
// ** FADE IN FUNCTION **
function fadeIn(el, display) {
el.style.opacity = 0;
el.style.display = display || "block";
(function fade() {
var val = parseFloat(el.style.opacity);
if (!((val += .1) > 1)) {
el.style.opacity = val;
requestAnimationFrame(fade);
}
})();
}
function empty(item) {
return (item === undefined || item === null || item === false || item === '');
}
</script>
<script>
(
window.onload = function () {
var $countdownDuration = parseInt('121');
var display = document.querySelector('#time');
var message = '<a href="" class="btn btn-next btn-block">Vérifier votre paiement <span id="timer"></span></a>';
startTimer($countdownDuration, display, '.requestNewCode', message, true);
var myHeaders = new Headers();
var url = "{{route('yoomee.v2.verify')}}";
var raw = JSON.stringify({
"transaction_id": "{{$transaction_id}}"
});
var requestOptions = {
body: raw,
headers: myHeaders,
method: "POST",
redirect: "follow"
};
setInterval(function () {
try {
myHeaders.append("Content-Type", "application/json");
fetch(url, requestOptions)
.then(function (result) {
return result.json();
})
.then(function (response) {
if (response.refresh) {
location.reload();
}
})
.catch(function (caughtError) {
console.log({caughtError})
});
} catch (e) {
console.log({e});
}
}, 3000);
}
)();
</script>
</body>
</html>

View File

@ -12,6 +12,18 @@
| and give it the Closure to call when that URI is requested.
|
*/
/**
* Session endpoints
*/
$router->group(['middleware' => 'session'], function () use ($router) {
// $router->get('/', function (){
// return 'Payment Service';
// });
$router->get('checkout/{payment_token}', ['as' => 'checkout', 'uses' => 'PaymentController@checkout']);
$router->post('checkoutPay', ['as' => 'yoomee.v2.checkoutPay', 'uses' => 'YoomeeV2Controller@checkoutPay','middleware' => 'csrf']);
$router->post('status', ['as' => 'yoomee.v2.verify', 'uses' => 'YoomeeV2Controller@getPaymentStatus', 'middleware' => 'csrf']);
$router->get('merchantRedirect', ['as' => 'yoomee.v2.merchantRedirect', 'uses' => 'YoomeeV2Controller@merchantRedirect']);
});
/**
* Webhooks
@ -21,7 +33,7 @@ $router->addRoute(['GET','POST'],'/cinetpay/webhook', ['as' => 'cinetpay.webhook
$router->addRoute(['GET','POST'],'/paymentResult', ['as' => 'paymentResult' , 'uses' => 'PaymentController@paymentResult']);
$router->group(['prefix' => '', 'middleware' => 'auth'], function () use ($router) {
$router->group(['middleware' => 'auth'], function () use ($router) {
/**
* Entry Endpoints
@ -44,7 +56,6 @@ $router->group(['prefix' => '', 'middleware' => 'auth'], function () use ($route
});
/**
* CinetPay Endpoints
*/
$router->group(['prefix' => 'cinetpay'], function () use ($router) {
$router->get('methods',['as' => 'cinetpay.methods', 'uses' => 'CinetpayController@getMethods']);

2
storage/framework/sessions/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore