Init commit

This commit is contained in:
Djery-Tom 2022-08-11 10:46:51 +01:00
commit 93890a13b8
65 changed files with 10626 additions and 0 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2

32
.env.example Normal file
View File

@ -0,0 +1,32 @@
APP_NAME=PaymentService
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8087
APP_TIMEZONE=UTC
LOG_CHANNEL=daily
LOG_SLACK_WEBHOOK_URL=
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=iLink_preprod
DB_USERNAME=root
DB_PASSWORD=vps@2017GA
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
ACCEPTED_KEYS=U14YhuyFhweMeYpIYj8Ft2jm4cVgbMzD
SWAGGER_GENERATE_ALWAYS=TRUE
SWAGGER_DOCS_TOKEN=kTKyDQJhYZLkm7wVZx2pJ90CuYpHcndx
YOOMEE_MERCHANT_CODE=47ttazydghzd/bfzdv85z4dzhutFRYRyzhdgtrehdnfglltmfjkk85421@er
YOOMEE_API_URL=https://quality-env.yoomeemoney.cm/yoomee_ext_paymentv3/
CINETPAY_API_KEY=176445314662bebd39b1b6f8.42908045
CINETPAY_SECRET_KEY=140983310662e79736e8ae45.38146680
CINETPAY_SITE_ID=862736
CINETPAY_API_URL=https://api-checkout.cinetpay.com/v2/

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/vendor
/.idea
Homestead.json
Homestead.yaml
.env
.phpunit.result.cache
/storage/api-docs
/public/swagger-ui-assets

6
.styleci.yml Normal file
View File

@ -0,0 +1,6 @@
php:
preset: laravel
disabled:
- unused_use
js: true
css: true

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# Lumen PHP Framework
[![Build Status](https://travis-ci.org/laravel/lumen-framework.svg)](https://travis-ci.org/laravel/lumen-framework)
[![Total Downloads](https://img.shields.io/packagist/dt/laravel/lumen-framework)](https://packagist.org/packages/laravel/lumen-framework)
[![Latest Stable Version](https://img.shields.io/packagist/v/laravel/lumen-framework)](https://packagist.org/packages/laravel/lumen-framework)
[![License](https://img.shields.io/packagist/l/laravel/lumen)](https://packagist.org/packages/laravel/lumen-framework)
Laravel Lumen is a stunningly fast PHP micro-framework for building web applications with expressive, elegant syntax. We believe development must be an enjoyable, creative experience to be truly fulfilling. Lumen attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as routing, database abstraction, queueing, and caching.
## Official Documentation
Documentation for the framework can be found on the [Lumen website](https://lumen.laravel.com/docs).
## Contributing
Thank you for considering contributing to Lumen! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Security Vulnerabilities
If you discover a security vulnerability within Lumen, please send an e-mail to Taylor Otwell at taylor@laravel.com. All security vulnerabilities will be promptly addressed.
## License
The Lumen framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

29
app/Console/Kernel.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Laravel\Lumen\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
//
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Enums;
abstract class PaymentTransactionState
{
const ACCEPTED = 'ACCEPTED';
const PENDING = 'PENDING';
const PENDING_OTP = 'PENDING_OTP';
const REFUSED = 'REFUSED';
}

10
app/Events/Event.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace App\Events;
use Illuminate\Queue\SerializesModels;
abstract class Event
{
use SerializesModels;
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Events;
class ExampleEvent extends Event
{
/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}
}

134
app/Exceptions/Handler.php Normal file
View File

@ -0,0 +1,134 @@
<?php
namespace App\Exceptions;
use App\Traits\ApiResponser;
use ErrorException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
class Handler extends ExceptionHandler
{
use ApiResponser;
/**
* A list of the exception types that should not be reported.
*
* @var array
*/
protected $dontReport = [
AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Throwable $exception
* @return void
*
* @throws \Exception
*/
public function report(Throwable $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Throwable $exception
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*
* @throws \Throwable
*/
public function render($request, Throwable $exception)
{
// return parent::render($request, $exception);
if ($exception instanceof HttpException) {
$code = $exception->getStatusCode();
$message = Response::$statusTexts[$code];
return $this->errorResponse($message, $code);
}
if ($exception instanceof ModelNotFoundException) {
$model = strtolower(class_basename($exception->getModel()));
return $this->errorResponse(trans('errors.model_not_found', ['model' => $model]),
Response::HTTP_NOT_FOUND);
}
if ($exception instanceof AuthorizationException) {
return $this->errorResponse($exception->getMessage(), Response::HTTP_UNAUTHORIZED);
}
if ($exception instanceof ValidationException) {
$errors = $exception->validator->errors()->getMessages();
$message = '';
foreach ($errors as $val) {
foreach ($val as $validation) {
$message .= $validation . "\n";
}
$message .= "\n";
}
return $this->errorResponse($message, Response::HTTP_UNPROCESSABLE_ENTITY);
}
if ($exception instanceof AuthenticationException) {
return $this->errorResponse($exception->getMessage(), Response::HTTP_UNAUTHORIZED);
}
if ($exception instanceof QueryException) {
return $this->errorResponse($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
if ($exception instanceof ServerException) {
return $this->errorResponse($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
if ($exception instanceof ErrorException) {
return $this->errorResponse($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
if ($exception instanceof ClientException) {
$message = $exception->getResponse()->getBody()->getContents();
$code = $exception->getCode();
Log::error(json_encode($message));
return $this->errorResponse($message, $code);
}
if($exception instanceof ConnectException){
return $this->errorResponse(trans('errors.timeout'), Response::HTTP_REQUEST_TIMEOUT);
}
// if ($exception instanceof AppException) {
// return $this->errorResponse($exception->getMessage(), $exception->getCode());
// }
if (!config('variables.app_debug')) {
return parent::render($request, $exception);
}
return $this->errorResponse(trans('errors.unexpected_error'),
Response::HTTP_INTERNAL_SERVER_ERROR);
}
}

14
app/Helpers/utils.php Normal file
View File

@ -0,0 +1,14 @@
<?php
if(!function_exists('randomString')){
function randomString($length = 12)
{
$characters = '23456789ABCDEFGHJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
}

View File

@ -0,0 +1,204 @@
<?php
namespace App\Http\Controllers;
use App\Enums\PaymentTransactionState;
use App\Models\PaymentTransaction;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Throwable;
class CinetpayController extends Controller
{
private $client;
private $timeout = 60; //In seconds
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
// Create a client with a base URI
$this->client = new Client([
'base_uri' => config('variables.cinetpay_api_url')
]);
}
//
public function pay(Request $request)
{
$this->validate($request, [
'aggregator_id' => 'required|integer',
'amount' => 'required|numeric|min:5',
'currency' => 'required|string|size:3',
'payment_method' => 'nullable|string|in:ALL,MOBILE_MONEY,CREDIT_CARD,WALLET',
'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_city' => 'required_if:payment_method,CREDIT_CARD|string',
'customer_country' => 'required|string|size:2',
'customer_state' => 'required_if:payment_method,CREDIT_CARD|string|size:2', //Etat du pays dans lequel se trouve le client. Cette valeur est obligatoire si le client se trouve au États Unis dAmérique (US) ou au Canada (CA)
'customer_zip_code' => 'required_if:payment_method,CREDIT_CARD|string|size:5',
'reason' => 'required|string'
]);
$transaction_id = $this->getTransactionID();
$payment_method = $request->input('payment_method','ALL');
$amount = $request->input('amount');
$currency = $request->input('currency');
if ($currency != 'USD') {
// Convertir en multiple de 5
$amount = $this->roundUpToAny($amount);
}
// Init payment
$createResponse = $this->client->post('payment', [
'json' => [
"api_key" => config('variables.cinetpay_api_key'),
"site_id" => config('variables.cinetpay_site_id'),
"transaction_id" => $transaction_id,
"amount" => $amount,
"currency" => $request->input('currency'),
"description" => $request->input('reason'),
"notify_url" => route('cinetpay.webhook'),
"return_url" => route('cinetpay.webhook'),
"channels" => $payment_method,
'lang' => app()->getLocale(),
"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" => $request->input('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'),
],
'timeout' => $this->timeout
]);
$responseData = json_decode($createResponse->getBody()->getContents());
$responseCode = $createResponse->getStatusCode();
if ($responseCode == 200) {
PaymentTransaction::create([
'aggregator_id' => $request->input('aggregator_id'),
"currency" => $request->input('currency'),
"transaction_id" => $transaction_id,
"amount" => $amount,
"payment_method" => $payment_method,
"payment_token" => $responseData->data->payment_token,
"payment_url" => $responseData->data->payment_url,
'state' => PaymentTransactionState::PENDING,
"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" => $request->input('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' => $responseData->message,
'payment_url' => $responseData->data->payment_url
]);
}else{
return $this->errorResponse($responseData->error ?? trans('errors.unexpected_error'),$responseCode);
}
}
public function capturePaymentResult(Request $request)
{
$this->validate($request, [
'cpm_site_id' => 'nullable|string',
'cpm_trans_id' => 'nullable|string|exists:payment_transactions,transaction_id'
]);
Log::info(json_encode($request->all()));
if($request->has('cpm_trans_id')) {
$data = $request->input('cpm_site_id') . $request->input('cpm_trans_id') . $request->input('cpm_trans_date') . $request->input('cpm_amount') . $request->input('cpm_currency') .
$request->input('signature') . $request->input('payment_method') . $request->input('cel_phone_num') . $request->input('cpm_phone_prefixe') .
$request->input('cpm_language') . $request->input('cpm_version') . $request->input('cpm_payment_config') . $request->input('cpm_page_action') . $request->input('cpm_custom') . $request->input('cpm_designation') . $request->input('cpm_error_message');
$generated_token = hash_hmac('SHA256', $data, config('variables.cinetpay_secret_key'));
$received_token = $request->header('x-token');
if(hash_equals($received_token, $generated_token)) {
$transaction = PaymentTransaction::where('transaction_id',$request->input('cpm_trans_id'))->firstOrFail();
return $this->getPaymentStatus($transaction);
}else{
return $this->errorResponse("Invalid token");
}
}else if($request->has('transaction_id')){
$transaction = PaymentTransaction::where('transaction_id',$request->input('transaction_id'))->firstOrFail();
return $this->getPaymentStatus($transaction);
}else{
return response("OK");
}
}
private function getPaymentStatus(PaymentTransaction $transaction)
{
try {
$response = $this->client->post('payment/check', [
'json' => [
"apikey" => config('variables.cinetpay_api_key'),
"site_id" => config('variables.cinetpay_site_id'),
'transaction_id' => $transaction->transaction_id,
]
]);
$responseData = json_decode($response->getBody()->getContents());
$responseCode = $response->getStatusCode();
if ($responseCode == 200) {
$transaction->update([
'state' => $responseData->data->status,
'payment_method_exact' => $responseData->data->payment_method ?? null,
'aggregator_payment_ref' => $responseData->data->operator_id ?? null,
'payment_date' => $responseData->data->payment_date ?? null,
]);
}
} catch (Throwable $e) {
Log::info("Get Payment Status Error");
Log::info($e->getMessage());
$transaction->update([
'state' => PaymentTransactionState::REFUSED
]);
}
if($transaction->state == PaymentTransactionState::ACCEPTED){
return $this->successResponse([
'transaction_id' => $transaction->transaction_id,
'token' => $transaction->payment_token
]);
}else{
return $this->errorResponse("Payment failed");
}
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers;
use App\Traits\ApiResponser;
use App\Traits\Helper;
use Laravel\Lumen\Routing\Controller as BaseController;
class Controller extends BaseController
{
/**
* @OA\Info(
* title="Payment Service Interne API",
* version="1.0.0",
* @OA\Contact(
* email="administrateur@ilink-app.com",
* url = "https://ilink-app.com/",
* name="Developer Team"
* )
* )
*
*/
use ApiResponser , Helper;
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers;
use App\Models\PaymentAggregator;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
public function pay(Request $request)
{
$this->validate($request, [
'aggregator_id' => 'required|integer|exists:payment_aggregators,id',
]);
$aggregator = PaymentAggregator::findOrFail($request->input('aggregator_id'));
switch(strtolower($aggregator->name)){
case 'yoomee':
return redirect()->route('yoomee.pay', $request->all());
case 'cinetpay':
return redirect()->route('cinetpay.pay', $request->all());
default:
return $this->errorResponse(__('errors.unexpected_error'));
}
}
}

View File

@ -0,0 +1,179 @@
<?php
namespace App\Http\Controllers;
use App\Enums\PaymentTransactionState;
use App\Models\PaymentTransaction;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class YoomeeController extends Controller
{
private $client;
private $timeout = 60; //In seconds
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
// Create a client with a base URI
$this->client = new Client([
'base_uri' => config('variables.yoomee_api_url')
]);
}
/**
* @OA\Get(
* path="/yoomee/operators",
* summary="Afficher la liste des operateurs de Yoomee",
* tags={"Yoomee"},
* security={{"api_key":{}}},
* @OA\Response(
* response=200,
* description="OK",
* @OA\JsonContent(
* ref="#/components/schemas/ApiResponse",
* example = {
* "status" : 200,
* "response" : {{"Yoomee","MTN","Orange","EU"}},
* "error":null
* }
* )
* )
* )
*/
public function getOperators()
{
$response = $this->client->get('operators');
return $this->successResponse(json_decode($response->getBody()->getContents()));
}
public function pay(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',
'otp' => 'nullable|string'
]);
$transaction_id = $this->getTransactionID();
$payment_method = $request->input('payment_method');
$otp = $request->input('otp');
// Create passport payment
$createResponse = $this->client->post('passport',[
'json' => [
"currency" => $request->input('currency'),
"customerEmail" => $request->input('customer_email'),
"customerName" => $request->input('customer_name'),
"customerPhone" => $request->input('customer_phone_number'),
"merchantCode" => config('variables.yoomee_merchant_code'),
"orderNumber" => $transaction_id,
"transactionAmount" => $request->input('amount')
],
'timeout'=> $this->timeout
]);
if($createResponse->getStatusCode() == 201){
$createResponse = json_decode($createResponse->getBody()->getContents());
$transaction = PaymentTransaction::create([
'aggregator_id' => $request->input('aggregator_id'),
"currency" => $request->input('currency'),
"transaction_id" => $transaction_id,
"amount" => $createResponse->transactionAmount,
"payment_method" => $payment_method,
'state' => $createResponse->paymentStatus,
"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" => $request->input('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'),
]);
// Pay passport payment
$paymentData = [
"customerName" => $createResponse->customerName,
"customerPhone" => $createResponse->customerPhone,
"merchantCode" => config('variables.yoomee_merchant_code'),
"paymentMethod" => $payment_method
];
if(!empty($otp)){
$paymentData['secretOTP'] = $otp;
}
$payResponse = $this->client->put("passport/pay/$transaction_id",[
'json' => $paymentData,
'timeout'=> $this->timeout
]);
$responseData = json_decode($payResponse->getBody()->getContents());
$responseCode = $payResponse->getStatusCode();
if($responseCode == 202){
$transaction->update([
'state' => PaymentTransactionState::ACCEPTED,
'aggregator_payment_ref' => $this->getTransactionRef($responseData->message),
]);
return $this->successResponse([
'transaction_id' => $transaction_id,
'token' => $transaction->payment_token
]);
}else{
return $this->errorResponse($responseData,$responseCode);
}
}
return $this->errorResponse(__('errors.unexpected_error'));
}
private function getTransactionRef($message){
$needle = 'REF: ';
return substr($message,strlen($needle) + strpos($message,$needle));
}
public function capturePaymentResult(Request $request)
{
$this->validate($request, [
'action' => 'required|in:return,cancel',
'cpm_trans_id' => 'required|integer|exists:payment_transactions,transaction_id'
]);
$action = $request->input('action');
$transaction = PaymentTransaction::find($request->input('PaymentTransaction_id'));
if (!isset($transaction)) {
return redirect()->to(route('PaymentTransactions.create'))->with('error', __('messages.not_found_payment'));
}
if ($action == 'cancel') {
$transaction->update([
'state' => PaymentTransactionState::CANCELLED
]);
return redirect()->to(route('PaymentTransactions.create'))->with('success', __('messages.cancelled_payment'));
} else {
return $this->getPaymentStatus($transaction, route('PaymentTransactions.create'));
}
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate
{
/**
* The authentication guard factory instance.
*
* @var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
* @return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->guard($guard)->guest()) {
return response('Unauthorized.', 401);
}
return $next($request);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
class AuthenticateAccess
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$validKeys = explode(',' ,config('variables.accepted_keys'));
if(in_array($request->header('Authorization') , $validKeys)){
return $next($request);
}
abort(Response::HTTP_UNAUTHORIZED);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Closure;
class ExampleMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class Localization
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$enLangs=["en","en-US","en_US","ca","in","gb","GB","us","en-029","en-AU","en-BZ","en-CA","en-GB","en-IE","en-IN","en-JM","en-MY","en-NZ","en-PH","en-SG","en-TT","en-US","en-ZA","en-ZW","au","bz","ie","in","jm","my","nz","ph","sg","tt","za"];
// Check header request and determine localizaton
if ($request->hasHeader('X-localization')){
$local = $request->header('X-localization');
$pos=strpos($local,"-");
if($pos!=false){
$local=strtolower(explode("-",$local)[0]);
}
$local= in_array($local, $enLangs) ? 'en' : 'fr';
}else{
$local ='fr';
}
// set laravel localization
app()->setLocale($local);
// continue request
return $next($request);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Middleware;
use Closure;
class SecureApiDocs
{
public function handle($request, Closure $next)
{
// for local, dont add any authentication
if (config('variables.app_env') === 'local') {
return $next($request);
}
$token = $request->get('token');
if (!$token) {
// try to load the token from referer
$query = array();
parse_str(
parse_url($request->header('referer'), PHP_URL_QUERY),
$query
);
if (isset($query['token'])) {
$token = $query['token'];
}
}
// we will match it against the `SWAGGER_DOCS_TOKEN` environment variable
if ($token === config('variables.swagger_docs_token')) {
return $next($request);
} else {
abort(403);
}
}
}

26
app/Jobs/ExampleJob.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace App\Jobs;
class ExampleJob extends Job
{
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
//
}
}

24
app/Jobs/Job.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
abstract class Job implements ShouldQueue
{
/*
|--------------------------------------------------------------------------
| Queueable Jobs
|--------------------------------------------------------------------------
|
| This job base class provides a central location to place any logic that
| is shared across all of your jobs. The trait included with the class
| provides access to the "queueOn" and "delay" queue helper methods.
|
*/
use InteractsWithQueue, Queueable, SerializesModels;
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Listeners;
use App\Events\ExampleEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class ExampleListener
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param \App\Events\ExampleEvent $event
* @return void
*/
public function handle(ExampleEvent $event)
{
//
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PaymentAggregator extends Model
{
protected $table = 'payment_aggregators';
protected $guarded = ['id'];
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PaymentTransaction extends Model
{
protected $table = 'payment_transactions';
protected $guarded = ['id'];
}

33
app/Models/User.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Lumen\Auth\Authorizable;
class User extends Model implements AuthenticatableContract, AuthorizableContract
{
use Authenticatable, Authorizable, HasFactory;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'name', 'email',
];
/**
* The attributes excluded from the model's JSON form.
*
* @var string[]
*/
protected $hidden = [
'password',
];
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Boot the authentication services for the application.
*
* @return void
*/
public function boot()
{
// Here you may define how you wish users to be authenticated for your Lumen
// application. The callback which receives the incoming request instance
// should return either a User instance or null. You're free to obtain
// the User instance via an API token or any other method necessary.
$this->app['auth']->viaRequest('api', function ($request) {
if ($request->input('api_token')) {
return User::where('api_token', $request->input('api_token'))->first();
}
});
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Providers;
use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
\App\Events\ExampleEvent::class => [
\App\Listeners\ExampleListener::class,
],
];
/**
* Determine if events and listeners should be automatically discovered.
*
* @return bool
*/
public function shouldDiscoverEvents()
{
return false;
}
}

52
app/Traits/ApiResponser.php Executable file
View File

@ -0,0 +1,52 @@
<?php
namespace App\Traits;
use Illuminate\Http\Response;
// Api Response schema
/**
* @OA\Schema(
* schema="ApiResponse",
* title="API Response",
* description="Format d'un message de reponse",
* @OA\Property(
* property="status", description="Code de la requete",
* @OA\Schema(type="number", example=200)
* ),
* @OA\Property(
* property="response", description="Resultat de la requete si pas d'erreur",
* @OA\Schema(type="object", example="{name : 'Djery'}")
* ),
* @OA\Property(
* property="error", description="Message d'erreur si erreur",
* @OA\Schema(type="string", example="There is an error")
* )
* )
*/
trait ApiResponser
{
public function successResponse($data, $code = Response::HTTP_OK)
{
return response($this->formatResponse($code, $data, null), $code)->header('Content-Type', 'application/json');
}
public function errorResponse($message, $code = Response::HTTP_BAD_REQUEST)
{
if ($code == 0) {
$code = Response::HTTP_BAD_REQUEST;
}
return response()->json($this->formatResponse($code, null, $message), $code);
}
// public function errorMessage($message , $code)
// {
// return response($message ,$code)->header('Content-Type', 'application/json');
// }
private function formatResponse(int $status, $response = null, $error = null)
{
return ['status' => $status, 'response' => $response, 'error' => $error];
}
}

25
app/Traits/Helper.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace App\Traits;
use Illuminate\Support\Facades\DB;
trait Helper
{
public function getTransactionID()
{
do {
$code = randomString(8);
$result = collect(DB::select('SELECT * FROM payment_transactions WHERE transaction_id = :code', ['code' => $code]));
$codeCorrect = sizeof($result) < 0;
} while ($codeCorrect);
return $code;
}
// Convertir vers le multiple de 5 le plus proche
function roundUpToAny($n,$x=5) {
return (ceil($n)%$x === 0) ? ceil($n) : round(($n+$x/2)/$x)*$x;
}
}

35
artisan Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env php
<?php
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| First we need to get an application instance. This creates an instance
| of the application / container and bootstraps the application so it
| is ready to receive HTTP / Console requests from the environment.
|
*/
$app = require __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(
'Illuminate\Contracts\Console\Kernel'
);
exit($kernel->handle(new ArgvInput, new ConsoleOutput));

119
bootstrap/app.php Normal file
View File

@ -0,0 +1,119 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
(new Laravel\Lumen\Bootstrap\LoadEnvironmentVariables(
dirname(__DIR__)
))->bootstrap();
date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| Here we will load the environment and create the application instance
| that serves as the central piece of this framework. We'll use this
| application as an "IoC" container and router for this framework.
|
*/
$app = new Laravel\Lumen\Application(
dirname(__DIR__)
);
$app->withFacades();
$app->withEloquent();
/*
|--------------------------------------------------------------------------
| Register Container Bindings
|--------------------------------------------------------------------------
|
| Now we will register a few bindings in the service container. We will
| register the exception handler and the console kernel. You may add
| your own bindings here if you like or you can make another file.
|
*/
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
/*
|--------------------------------------------------------------------------
| Register Config Files
|--------------------------------------------------------------------------
|
| Now we will register the "app" configuration file. If the file exists in
| your configuration directory it will be loaded; otherwise, we'll load
| the default version. You may register other files below as needed.
|
*/
$app->configure('app');
$app->configure('swagger-lume');
$app->configure('variables');
/*
|--------------------------------------------------------------------------
| Register Middleware
|--------------------------------------------------------------------------
|
| Next, we will register the middleware with the application. These can
| be global middleware that run before and after each request into a
| route or middleware that'll be assigned to some specific routes.
|
*/
$app->middleware([
App\Http\Middleware\Localization::class,
]);
$app->routeMiddleware([
// 'auth' => App\Http\Middleware\Authenticate::class,
'docs' => App\Http\Middleware\SecureApiDocs::class,
'auth' => App\Http\Middleware\AuthenticateAccess::class
]);
/*
|--------------------------------------------------------------------------
| Register Service Providers
|--------------------------------------------------------------------------
|
| Here we will register all of the application's service providers which
| are used to bind services into the container. Serce providers are
| totally optional, so you are not required to uncomment this line.
|
*/
// $app->register(App\Providers\AppServiceProvider::class);
// $app->register(App\Providers\AuthServiceProvider::class);
// $app->register(App\Providers\EventServiceProvider::class);
$app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class);
$app->register(\SwaggerLume\ServiceProvider::class);
/*
|--------------------------------------------------------------------------
| Load The Application Routes
|--------------------------------------------------------------------------
|
| Next we will include the routes file so that they can all be added to
| the application. This will provide all of the URLs the application
| can respond to, as well as the controllers that may handle them.
|
*/
$app->router->group([
'namespace' => 'App\Http\Controllers',
], function ($router) {
require __DIR__.'/../routes/web.php';
});
return $app;

49
composer.json Normal file
View File

@ -0,0 +1,49 @@
{
"name": "laravel/lumen",
"description": "The Laravel Lumen Framework.",
"keywords": ["framework", "laravel", "lumen"],
"license": "MIT",
"type": "project",
"require": {
"php": "^8.0",
"darkaonline/swagger-lume": "^9.0",
"flipbox/lumen-generator": "^9.1",
"guzzlehttp/guzzle": "^7.4",
"laravel/lumen-framework": "^9.0"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
"mockery/mockery": "^1.4.4",
"phpunit/phpunit": "^9.5.10"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
},
"files": [
"app/Helpers/utils.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-install-cmd": [
"cp -a vendor/swagger-api/swagger-ui/dist public/swagger-ui-assets"
]
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}

8278
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

210
config/swagger-lume.php Normal file
View File

@ -0,0 +1,210 @@
<?php
return [
'api' => [
/*
|--------------------------------------------------------------------------
| Edit to set the api's title
|--------------------------------------------------------------------------
*/
'title' => 'iLink APP API',
],
'routes' => [
/*
|--------------------------------------------------------------------------
| Route for accessing api documentation interface
|--------------------------------------------------------------------------
*/
'api' => '/api/documentation',
/*
|--------------------------------------------------------------------------
| Route for accessing parsed swagger annotations.
|--------------------------------------------------------------------------
*/
'docs' => '/docs',
/*
|--------------------------------------------------------------------------
| Route for Oauth2 authentication callback.
|--------------------------------------------------------------------------
*/
'oauth2_callback' => '/api/oauth2-callback',
/*
|--------------------------------------------------------------------------
| Route for serving assets
|--------------------------------------------------------------------------
*/
'assets' => '/swagger-ui-assets',
/*
|--------------------------------------------------------------------------
| Middleware allows to prevent unexpected access to API documentation
|--------------------------------------------------------------------------
*/
'middleware' => [
'api' => [],
'asset' => [],
'docs' => [],
'oauth2_callback' => [],
],
],
'paths' => [
/*
|--------------------------------------------------------------------------
| Absolute path to location where parsed swagger annotations will be stored
|--------------------------------------------------------------------------
*/
'docs' => storage_path('api-docs'),
/*
|--------------------------------------------------------------------------
| File name of the generated json documentation file
|--------------------------------------------------------------------------
*/
'docs_json' => 'api-docs.json',
/*
|--------------------------------------------------------------------------
| Absolute path to directory containing the swagger annotations are stored.
|--------------------------------------------------------------------------
*/
'annotations' => base_path('app'),
/*
|--------------------------------------------------------------------------
| Absolute path to directories that you would like to exclude from swagger generation
|--------------------------------------------------------------------------
*/
'excludes' => [],
/*
|--------------------------------------------------------------------------
| Edit to set the swagger scan base path
|--------------------------------------------------------------------------
*/
'base' => env('L5_SWAGGER_BASE_PATH', null),
/*
|--------------------------------------------------------------------------
| Absolute path to directory where to export views
|--------------------------------------------------------------------------
*/
'views' => base_path('resources/views/vendor/swagger-lume'),
],
/*
|--------------------------------------------------------------------------
| API security definitions. Will be generated into documentation file.
|--------------------------------------------------------------------------
*/
'security' => [
/*
|--------------------------------------------------------------------------
| Examples of Security definitions
|--------------------------------------------------------------------------
*/
/*
'api_key_security_example' => [ // Unique name of security
'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'A short description for security scheme',
'name' => 'api_key', // The name of the header or query parameter to be used.
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
],
'oauth2_security_example' => [ // Unique name of security
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'A short description for oauth2 security scheme.',
'flow' => 'implicit', // The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode".
'authorizationUrl' => 'http://example.com/auth', // The authorization URL to be used for (implicit/accessCode)
//'tokenUrl' => 'http://example.com/auth' // The authorization URL to be used for (password/application/accessCode)
'scopes' => [
'read:projects' => 'read your projects',
'write:projects' => 'modify projects in your account',
]
],*/
/* Open API 3.0 support
'passport' => [ // Unique name of security
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'Laravel passport oauth2 security.',
'in' => 'header',
'scheme' => 'https',
'flows' => [
"password" => [
"authorizationUrl" => config('app.url') . '/oauth/authorize',
"tokenUrl" => config('app.url') . '/oauth/token',
"refreshUrl" => config('app.url') . '/token/refresh',
"scopes" => []
],
],
],
*/
'api_key' => [ // Unique name of security
'type' => 'apiKey', // Valid values are "basic", "apiKey" or "oauth2".
'description' => "Api Key",
'name' => 'Authorization', // The name of the header or query parameter to be used.
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
],
],
/*
|--------------------------------------------------------------------------
| Turn this off to remove swagger generation on production
|--------------------------------------------------------------------------
*/
'generate_always' => env('SWAGGER_GENERATE_ALWAYS', false),
/*
|--------------------------------------------------------------------------
| Edit to set the swagger version number
|--------------------------------------------------------------------------
*/
'swagger_version' => env('SWAGGER_VERSION', '3.0'),
/*
|--------------------------------------------------------------------------
| Edit to trust the proxy's ip address - needed for AWS Load Balancer
|--------------------------------------------------------------------------
*/
'proxy' => false,
/*
|--------------------------------------------------------------------------
| Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle.
| See more at: https://github.com/swagger-api/swagger-ui#configs-plugin
|--------------------------------------------------------------------------
*/
'additional_config_url' => null,
/*
|--------------------------------------------------------------------------
| Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically),
| 'method' (sort by HTTP method).
| Default is the order returned by the server unchanged.
|--------------------------------------------------------------------------
*/
'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', null),
/*
|--------------------------------------------------------------------------
| Uncomment to pass the validatorUrl parameter to SwaggerUi init on the JS
| side. A null value here disables validation.
|--------------------------------------------------------------------------
*/
'validator_url' => null,
/*
|--------------------------------------------------------------------------
| Add constants which can be used in anotations
|--------------------------------------------------------------------------
*/
'constants' => [
// 'SWAGGER_LUME_CONST_HOST' => env('SWAGGER_LUME_CONST_HOST', 'http://my-default-host.com'),
],
];

13
config/variables.php Normal file
View File

@ -0,0 +1,13 @@
<?php
return [
'accepted_keys' => env('ACCEPTED_KEYS', ''),
'app_env' => env('APP_ENV', 'local'),
'app_debug' => env('APP_DEBUG', true),
'swagger_docs_token' => env('SWAGGER_DOCS_TOKEN', true),
'yoomee_merchant_code' => env('YOOMEE_MERCHANT_CODE', ''),
'yoomee_api_url' => env('YOOMEE_API_URL', ''),
'cinetpay_api_key' => env('CINETPAY_API_KEY', ''),
'cinetpay_secret_key' => env('CINETPAY_SECRET_KEY', ''),
'cinetpay_site_id' => env('CINETPAY_SITE_ID', ''),
'cinetpay_api_url' => env('CINETPAY_API_URL', ''),
];

View File

@ -0,0 +1,29 @@
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = User::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->name,
'email' => $this->faker->unique()->safeEmail,
];
}
}

View File

View File

@ -0,0 +1,54 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('payment_transactions', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('transaction_id')->unique();
$table->integer('aggregator_id')->nullable();
$table->string('aggregator_payment_ref')->nullable();
$table->decimal('amount');
$table->string('currency',3);
$table->string('payment_method');
$table->string('payment_method_exact')->nullable();
$table->string('payment_url')->nullable();
$table->string('payment_token')->nullable();
$table->string('payment_date')->nullable();
$table->string('reason');
$table->string('state');
$table->text('metadata')->nullable();
$table->integer('customer_id');
$table->string('customer_email');
$table->string('customer_name');
$table->string('customer_surname');
$table->string('customer_phone_number');
$table->string('customer_address');
$table->string('customer_city')->nullable();
$table->string('customer_country',2);
$table->string('customer_state',2)->nullable()->comment("Pour les clients USA");
$table->string('customer_zip_code',5)->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('payment_transactions');
}
};

View File

@ -0,0 +1,19 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// $this->call('UsersTableSeeder');
}
}

17
phpunit.xml Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
</php>
</phpunit>

21
public/.htaccess Normal file
View File

@ -0,0 +1,21 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

28
public/index.php Normal file
View File

@ -0,0 +1,28 @@
<?php
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| First we need to get an application instance. This creates an instance
| of the application / container and bootstraps the application so it
| is ready to receive HTTP / Console requests from the environment.
|
*/
$app = require __DIR__.'/../bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/
$app->run();

8
resources/lang/en/errors.php Executable file
View File

@ -0,0 +1,8 @@
<?php
return [
'model_not_found' => 'Does not exist any instance of :model with given id',
'unexpected_error' => 'Unexpected error. Try later',
'validation_error' => 'The field :field has :validation',
'service_unavailable' => 'Service unavailable',
'timeout' => "The server did not receive a complete response within the timeout period. Please Try again later"
];

3
resources/lang/en/messages.php Executable file
View File

@ -0,0 +1,3 @@
<?php
return [
];

View File

@ -0,0 +1,4 @@
<?php
return [
];

4
resources/lang/en/states.php Executable file
View File

@ -0,0 +1,4 @@
<?php
return [
];

View File

@ -0,0 +1,151 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
'accepted' => 'The :attribute must be accepted.',
'active_url' => 'The :attribute is not a valid URL.',
'after' => 'The :attribute must be a date after :date.',
'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
'alpha' => 'The :attribute may only contain letters.',
'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.',
'alpha_num' => 'The :attribute may only contain letters and numbers.',
'array' => 'The :attribute must be an array.',
'before' => 'The :attribute must be a date before :date.',
'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
'between' => [
'numeric' => 'The :attribute must be between :min and :max.',
'file' => 'The :attribute must be between :min and :max kilobytes.',
'string' => 'The :attribute must be between :min and :max characters.',
'array' => 'The :attribute must have between :min and :max items.',
],
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.',
'date' => 'The :attribute is not a valid date.',
'date_equals' => 'The :attribute must be a date equal to :date.',
'date_format' => 'The :attribute does not match the format :format.',
'different' => 'The :attribute and :other must be different.',
'digits' => 'The :attribute must be :digits digits.',
'digits_between' => 'The :attribute must be between :min and :max digits.',
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'The :attribute must be a valid email address.',
'ends_with' => 'The :attribute must end with one of the following: :values',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field must have a value.',
'gt' => [
'numeric' => 'The :attribute must be greater than :value.',
'file' => 'The :attribute must be greater than :value kilobytes.',
'string' => 'The :attribute must be greater than :value characters.',
'array' => 'The :attribute must have more than :value items.',
],
'gte' => [
'numeric' => 'The :attribute must be greater than or equal :value.',
'file' => 'The :attribute must be greater than or equal :value kilobytes.',
'string' => 'The :attribute must be greater than or equal :value characters.',
'array' => 'The :attribute must have :value items or more.',
],
'image' => 'The :attribute must be an image.',
'in' => 'The selected :attribute is invalid.',
'in_array' => 'The :attribute field does not exist in :other.',
'integer' => 'The :attribute must be an integer.',
'ip' => 'The :attribute must be a valid IP address.',
'ipv4' => 'The :attribute must be a valid IPv4 address.',
'ipv6' => 'The :attribute must be a valid IPv6 address.',
'json' => 'The :attribute must be a valid JSON string.',
'lt' => [
'numeric' => 'The :attribute must be less than :value.',
'file' => 'The :attribute must be less than :value kilobytes.',
'string' => 'The :attribute must be less than :value characters.',
'array' => 'The :attribute must have less than :value items.',
],
'lte' => [
'numeric' => 'The :attribute must be less than or equal :value.',
'file' => 'The :attribute must be less than or equal :value kilobytes.',
'string' => 'The :attribute must be less than or equal :value characters.',
'array' => 'The :attribute must not have more than :value items.',
],
'max' => [
'numeric' => 'The :attribute may not be greater than :max.',
'file' => 'The :attribute may not be greater than :max kilobytes.',
'string' => 'The :attribute may not be greater than :max characters.',
'array' => 'The :attribute may not have more than :max items.',
],
'mimes' => 'The :attribute must be a file of type: :values.',
'mimetypes' => 'The :attribute must be a file of type: :values.',
'min' => [
'numeric' => 'The :attribute must be at least :min.',
'file' => 'The :attribute must be at least :min kilobytes.',
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',
'password' => 'The password is incorrect.',
'present' => 'The :attribute field must be present.',
'regex' => 'The :attribute format is invalid.',
'required' => 'The :attribute field is required.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values are present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'same' => 'The :attribute and :other must match.',
'size' => [
'numeric' => 'The :attribute must be :size.',
'file' => 'The :attribute must be :size kilobytes.',
'string' => 'The :attribute must be :size characters.',
'array' => 'The :attribute must contain :size items.',
],
'starts_with' => 'The :attribute must start with one of the following: :values',
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.',
'uuid' => 'The :attribute must be a valid UUID.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap our attribute placeholder
| with something more reader friendly such as "E-Mail Address" instead
| of "email". This simply helps us make our message more expressive.
|
*/
'attributes' => [],
];

8
resources/lang/fr/errors.php Executable file
View File

@ -0,0 +1,8 @@
<?php
return [
'model_not_found' => 'Il n\'existe aucune instance de :model avec l\'id donné',
'unexpected_error' => 'Erreur inattendue. Essayer plus tard',
'validation_error' => 'Le champ :field a :validation',
'service_unavailable' => 'Service non disponible',
'timeout' => "Le serveur n'a pas reçu de réponse complete dans le délai imparti. Essayer plus tard"
];

4
resources/lang/fr/messages.php Executable file
View File

@ -0,0 +1,4 @@
<?php
return [
];

View File

@ -0,0 +1,4 @@
<?php
return [
];

4
resources/lang/fr/states.php Executable file
View File

@ -0,0 +1,4 @@
<?php
return [
];

View File

@ -0,0 +1,168 @@
<?php
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
return [
'accepted' => 'Le champ :attribute doit être accepté.',
'accepted_if' => 'Le champ :attribute doit être accepté quand :other a la valeur :value.',
'active_url' => 'Le champ :attribute n\'est pas une URL valide.',
'after' => 'Le champ :attribute doit être une date postérieure au :date.',
'after_or_equal' => 'Le champ :attribute doit être une date postérieure ou égale au :date.',
'alpha' => 'Le champ :attribute doit contenir uniquement des lettres.',
'alpha_dash' => 'Le champ :attribute doit contenir uniquement des lettres, des chiffres et des tirets.',
'alpha_num' => 'Le champ :attribute doit contenir uniquement des chiffres et des lettres.',
'array' => 'Le champ :attribute doit être un tableau.',
'attached' => ':attribute est déjà attaché(e).',
'before' => 'Le champ :attribute doit être une date antérieure au :date.',
'before_or_equal' => 'Le champ :attribute doit être une date antérieure ou égale au :date.',
'between' => [
'array' => 'Le tableau :attribute doit contenir entre :min et :max éléments.',
'file' => 'La taille du fichier de :attribute doit être comprise entre :min et :max kilo-octets.',
'numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.',
'string' => 'Le texte :attribute doit contenir entre :min et :max caractères.',
],
'boolean' => 'Le champ :attribute doit être vrai ou faux.',
'confirmed' => 'Le champ de confirmation :attribute ne correspond pas.',
'current_password' => 'Le mot de passe est incorrect.',
'date' => 'Le champ :attribute n\'est pas une date valide.',
'date_equals' => 'Le champ :attribute doit être une date égale à :date.',
'date_format' => 'Le champ :attribute ne correspond pas au format :format.',
'declined' => 'Le champ :attribute doit être décliné.',
'declined_if' => 'Le champ :attribute doit être décliné quand :other a la valeur :value.',
'different' => 'Les champs :attribute et :other doivent être différents.',
'digits' => 'Le champ :attribute doit contenir :digits chiffres.',
'digits_between' => 'Le champ :attribute doit contenir entre :min et :max chiffres.',
'dimensions' => 'La taille de l\'image :attribute n\'est pas conforme.',
'distinct' => 'Le champ :attribute a une valeur en double.',
'email' => 'Le champ :attribute doit être une adresse e-mail valide.',
'ends_with' => 'Le champ :attribute doit se terminer par une des valeurs suivantes : :values',
'exists' => 'Le champ :attribute sélectionné est invalide.',
'file' => 'Le champ :attribute doit être un fichier.',
'filled' => 'Le champ :attribute doit avoir une valeur.',
'gt' => [
'array' => 'Le tableau :attribute doit contenir plus de :value éléments.',
'file' => 'La taille du fichier de :attribute doit être supérieure à :value kilo-octets.',
'numeric' => 'La valeur de :attribute doit être supérieure à :value.',
'string' => 'Le texte :attribute doit contenir plus de :value caractères.',
],
'gte' => [
'array' => 'Le tableau :attribute doit contenir au moins :value éléments.',
'file' => 'La taille du fichier de :attribute doit être supérieure ou égale à :value kilo-octets.',
'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :value.',
'string' => 'Le texte :attribute doit contenir au moins :value caractères.',
],
'image' => 'Le champ :attribute doit être une image.',
'in' => 'Le champ :attribute est invalide.',
'in_array' => 'Le champ :attribute n\'existe pas dans :other.',
'integer' => 'Le champ :attribute doit être un entier.',
'ip' => 'Le champ :attribute doit être une adresse IP valide.',
'ipv4' => 'Le champ :attribute doit être une adresse IPv4 valide.',
'ipv6' => 'Le champ :attribute doit être une adresse IPv6 valide.',
'json' => 'Le champ :attribute doit être un document JSON valide.',
'lt' => [
'array' => 'Le tableau :attribute doit contenir moins de :value éléments.',
'file' => 'La taille du fichier de :attribute doit être inférieure à :value kilo-octets.',
'numeric' => 'La valeur de :attribute doit être inférieure à :value.',
'string' => 'Le texte :attribute doit contenir moins de :value caractères.',
],
'lte' => [
'array' => 'Le tableau :attribute doit contenir au plus :value éléments.',
'file' => 'La taille du fichier de :attribute doit être inférieure ou égale à :value kilo-octets.',
'numeric' => 'La valeur de :attribute doit être inférieure ou égale à :value.',
'string' => 'Le texte :attribute doit contenir au plus :value caractères.',
],
'max' => [
'array' => 'Le tableau :attribute ne peut contenir plus de :max éléments.',
'file' => 'La taille du fichier de :attribute ne peut pas dépasser :max kilo-octets.',
'numeric' => 'La valeur de :attribute ne peut être supérieure à :max.',
'string' => 'Le texte de :attribute ne peut contenir plus de :max caractères.',
],
'mimes' => 'Le champ :attribute doit être un fichier de type : :values.',
'mimetypes' => 'Le champ :attribute doit être un fichier de type : :values.',
'min' => [
'array' => 'Le tableau :attribute doit contenir au moins :min éléments.',
'file' => 'La taille du fichier de :attribute doit être supérieure à :min kilo-octets.',
'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :min.',
'string' => 'Le texte :attribute doit contenir au moins :min caractères.',
],
'multiple_of' => 'La valeur de :attribute doit être un multiple de :value',
'not_in' => 'Le champ :attribute sélectionné n\'est pas valide.',
'not_regex' => 'Le format du champ :attribute n\'est pas valide.',
'numeric' => 'Le champ :attribute doit contenir un nombre.',
'password' => 'Le mot de passe est incorrect',
'present' => 'Le champ :attribute doit être présent.',
'prohibited' => 'Le champ :attribute est interdit.',
'prohibited_if' => 'Le champ :attribute est interdit quand :other a la valeur :value.',
'prohibited_unless' => 'Le champ :attribute est interdit à moins que :other est l\'une des valeurs :values.',
'prohibits' => 'Le champ :attribute interdit :other d\'être présent.',
'regex' => 'Le format du champ :attribute est invalide.',
'relatable' => ':attribute n\'est sans doute pas associé(e) avec cette donnée.',
'required' => 'Le champ :attribute est obligatoire.',
'required_if' => 'Le champ :attribute est obligatoire quand la valeur de :other est :value.',
'required_unless' => 'Le champ :attribute est obligatoire sauf si :other est :values.',
'required_with' => 'Le champ :attribute est obligatoire quand :values est présent.',
'required_with_all' => 'Le champ :attribute est obligatoire quand :values sont présents.',
'required_without' => 'Le champ :attribute est obligatoire quand :values n\'est pas présent.',
'required_without_all' => 'Le champ :attribute est requis quand aucun de :values n\'est présent.',
'same' => 'Les champs :attribute et :other doivent être identiques.',
'size' => [
'array' => 'Le tableau :attribute doit contenir :size éléments.',
'file' => 'La taille du fichier de :attribute doit être de :size kilo-octets.',
'numeric' => 'La valeur de :attribute doit être :size.',
'string' => 'Le texte de :attribute doit contenir :size caractères.',
],
'starts_with' => 'Le champ :attribute doit commencer avec une des valeurs suivantes : :values',
'string' => 'Le champ :attribute doit être une chaîne de caractères.',
'timezone' => 'Le champ :attribute doit être un fuseau horaire valide.',
'unique' => 'La valeur du champ :attribute est déjà utilisée.',
'uploaded' => 'Le fichier du champ :attribute n\'a pu être téléversé.',
'url' => 'Le format de l\'URL de :attribute n\'est pas valide.',
'uuid' => 'Le champ :attribute doit être un UUID valide',
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
'attributes' => [
'address' => 'adresse',
'age' => 'âge',
'available' => 'disponible',
'city' => 'ville',
'content' => 'contenu',
'country' => 'pays',
'current_password' => 'mot de passe actuel',
'date' => 'date',
'day' => 'jour',
'description' => 'description',
'email' => 'adresse e-mail',
'excerpt' => 'extrait',
'first_name' => 'prénom',
'gender' => 'genre',
'hour' => 'heure',
'last_name' => 'nom',
'minute' => 'minute',
'mobile' => 'portable',
'month' => 'mois',
'name' => 'nom',
'password' => 'mot de passe',
'password_confirmation' => 'confirmation du mot de passe',
'phone' => 'téléphone',
'second' => 'seconde',
'sex' => 'sexe',
'size' => 'taille',
'time' => 'heure',
'title' => 'titre',
'username' => 'nom d\'utilisateur',
'year' => 'année',
],
];

0
resources/views/.gitkeep Normal file
View File

View File

@ -0,0 +1,101 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{config('swagger-lume.api.title')}}</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{{ swagger_lume_asset('swagger-ui.css') }}" >
<link rel="icon" type="image/png" href="{{ swagger_lume_asset('favicon-32x32.png') }}" sizes="32x32" />
<link rel="icon" type="image/png" href="{{ swagger_lume_asset('favicon-16x16.png') }}" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="{{ swagger_lume_asset('swagger-ui-bundle.js') }}"> </script>
<script src="{{ swagger_lume_asset('swagger-ui-standalone-preset.js') }}"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
dom_id: '#swagger-ui',
url: "{!! $urlToDocs !!}",
operationsSorter: {!! isset($operationsSorter) ? '"' . $operationsSorter . '"' : 'null' !!},
configUrl: {!! isset($additionalConfigUrl) ? '"' . $additionalConfigUrl . '"' : 'null' !!},
validatorUrl: {!! isset($validatorUrl) ? '"' . $validatorUrl . '"' : 'null' !!},
oauth2RedirectUrl: "{{ route('swagger-lume.oauth2_callback') }}",
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>

46
routes/web.php Normal file
View File

@ -0,0 +1,46 @@
<?php
/** @var \Laravel\Lumen\Routing\Router $router */
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/
/**
* Webhooks
*/
$router->addRoute(['GET','POST'],'/yoomee/webhook', ['as' => 'yoomee.webhook' , 'uses' => 'YoomeeController@capturePaymentResult']);
$router->addRoute(['GET','POST'],'/cinetpay/webhook', ['as' => 'cinetpay.webhook' , 'uses' => 'CinetpayController@capturePaymentResult']);
$router->group(['prefix' => '', 'middleware' => 'auth'], function () use ($router) {
/**
* Entry Endpoints
*/
$router->post('pay','PaymentController@pay');
/**
* Yoomee Endpoints
*/
$router->group(['prefix' => 'yoomee'], function () use ($router) {
$router->get('operators','YoomeeController@getOperators');
$router->addRoute(['GET','POST'],'pay', ['as' => 'yoomee.pay', 'uses' => 'YoomeeController@pay']);
});
/**
* CinetPay Endpoints
*/
$router->group(['prefix' => 'cinetpay'], function () use ($router) {
$router->get('operators','CinetpayController@getOperators');
$router->addRoute(['GET','POST'],'pay',['as' => 'cinetpay.pay', 'uses' => 'CinetpayController@pay']);
});
});

2
storage/app/.gitignore vendored Normal file
View File

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

3
storage/framework/cache/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*
!data/
!.gitignore

View File

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

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

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

2
storage/logs/.gitignore vendored Normal file
View File

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

23
tests/ExampleTest.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace Tests;
use Laravel\Lumen\Testing\DatabaseMigrations;
use Laravel\Lumen\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function test_that_base_endpoint_returns_a_successful_response()
{
$this->get('/');
$this->assertEquals(
$this->app->version(), $this->response->getContent()
);
}
}

18
tests/TestCase.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace Tests;
use Laravel\Lumen\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
/**
* Creates the application.
*
* @return \Laravel\Lumen\Application
*/
public function createApplication()
{
return require __DIR__.'/../bootstrap/app.php';
}
}