Setup Cinetpay payout url

This commit is contained in:
Djery-Tom 2023-06-29 09:53:32 +01:00
parent 5384f747e2
commit 8aa95391a9
12 changed files with 472 additions and 21 deletions

View File

@ -34,11 +34,15 @@ YOOMEE_PASSWORD=Ilink@2022
CINETPAY_API_KEY=176445314662bebd39b1b6f8.42908045
CINETPAY_SECRET_KEY=140983310662e79736e8ae45.38146680
CINETPAY_TRANFERT_PASSWORD=gzfmtNjtxfQKl39T
CINETPAY_SITE_ID=862736
CINETPAY_API_URL=https://api-checkout.cinetpay.com/v2/
CINETPAY_TRANSFERT_URL=https://client.cinetpay.com/v1/
RECEIVER_NAME="Commune X"
#STRIPE_KEY=pk_live_51NAILHJ6IfmAiBwqJZ0gkW6n7xz5ofvDYiMXfDmTwDXZdcGGPLPjOPmlL6NtoY58cfDufllT1sPzFvyYFAaMXkvi00UiU4G9K5
#STRIPE_SECRET=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
STRIPE_KEY=pk_test_51NAILHJ6IfmAiBwqd8t8ZL9WjTdcMOSDt46TfLT1DS1VPRTrEY0UC3RDUF0b0woE95FkiUt84JVfIXgHCco4v9MO00DcQ7LkmO
STRIPE_SECRET=sk_test_51NAILHJ6IfmAiBwqgblKnBatWzIt3mtMYyw9Tc2RRFWUJJDVJ2VGKCBo3o0eTPCAigLB8lAbPiDiuvQ9Arwg0fad00fv7zIJdY
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret

View File

@ -3,11 +3,14 @@
namespace App\Http\Controllers;
use App\Enums\PaymentTransactionStatus;
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 Propaganistas\LaravelPhone\PhoneNumber;
use Propaganistas\LaravelPhone\Rules\Phone;
use Throwable;
class CinetpayController extends Controller
@ -164,6 +167,229 @@ class CinetpayController extends Controller
}
}
public function payOut(Request $request)
{
$this->validate($request, [
// 'aggregator_id' => 'required|integer',
'amount' => 'required|numeric|min:5',
'currency' => 'required|string|size:3',
'customer_id' => 'nullable',
// 'payment_method' => 'required|string|in:WALLET',
'customer_email' => 'required|email',
'customer_name' => 'nullable|string',
'customer_surname' => 'required|string',
'customer_phone_number' => ['nullable','string',(new Phone())->country(['CI','SN','ML','CM','TG','BF','CD','GN','BJ'])],
'customer_address' => 'required|string',
'customer_city' => 'required|string',
'customer_country' => 'required|string|size:2|in:CI,SN,ML,CM,TG,BF,CD,GN,BJ',
'reason' => 'required|string'
]);
$aggregator = PaymentAggregator::where('name','like','%cinetpay%')->firstOrFail();
try{
$country_code = $request->input('customer_country');
$phoneNumber = str_replace(' ','',$request->input('customer_phone_number'));
$phone = new PhoneNumber($phoneNumber, $country_code);
$nationalPhone = str_replace(' ','',$phone->formatNational());
$phonePrefix = substr($phoneNumber, 1, strlen($phoneNumber) - strlen($nationalPhone) - 1);
$amount = $request->input('amount');
$payment_method = 'WALLET';
if($amount < 500){
return $this->errorResponse('Minimun amount is 500');
}
$client = new Client([
'base_uri' => config('variables.cinetpay_transfert_url')
]);
// Login
$loginResponse = $client->post('auth/login', [
'form_params' => [
"apikey" => config('variables.cinetpay_api_key'),
"password" => config('variables.cinetpay_transfert_password'),
],
'timeout' => $this->timeout,
'http_errors' => false
]);
$responseData = json_decode($loginResponse->getBody()->getContents());
$token = $responseData->data->token;
$responseCode = $loginResponse->getStatusCode();
if ($responseCode == 200 && !empty($token)) {
// Add Contact
$contactResponse = $client->post('transfer/contact', [
'query' => [
'token' => $token
],
'form_params' => [
"data" => json_encode(
[
[
'prefix' => $phonePrefix,
'phone' => $nationalPhone,
'name' => $request->input('customer_name'),
'surname' => $request->input('customer_surname'),
'email' => $request->input('customer_email')
]
]
),
],
'timeout' => $this->timeout,
'http_errors' => false
]);
$responseCode = $contactResponse->getStatusCode();
if ($responseCode == 200) {
$transactionId = $this->getTransactionID();
$transaction = PaymentTransaction::create([
'aggregator_id' => $aggregator->id,
"currency" => $request->input('currency'),
"transaction_id" => $transactionId,
"amount" => $amount,
"payment_method" => $payment_method,
'status' => PaymentTransactionStatus::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" => $phoneNumber,
"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'),
]);
// Transfert Fund
$transfertResponse = $client->post('transfer/money/send/contact', [
'query' => [
'token' => $token
],
'form_params' => [
"data" => json_encode(
[
[
'client_transaction_id' => $transactionId,
'amount' => $amount,
'notify_url' => route('cinetpay.transfert.webhook'),
'prefix' => $phonePrefix,
'phone' => $nationalPhone,
]
]
),
],
'timeout' => $this->timeout,
'http_errors' => false
]);
$responseData = json_decode($transfertResponse->getBody()->getContents());
$responseCode = $transfertResponse->getStatusCode();
if ($responseCode == 200) {
$transaction->update([
'aggregator_payment_ref' => $responseData->data[0]?->transaction_id,
'status' => PaymentTransactionStatus::PENDING,
]);
return $this->successResponse([
'message' => 'Transfert is pending',
'transaction_id' => $transactionId,
'transaction_status' => $transaction->status
]);
}
}
}
$errorMessage = $responseData?->description ?? $responseData?->message;
}catch (Throwable $e){
Log::error("Error CinetPay transfert payment");
$errorMessage = $e->getMessage();
Log::error($errorMessage);
}
return $this->errorResponse($errorMessage ?? __('errors.unexpected_error'));
}
public function captureTransfertResult(Request $request)
{
$this->validate($request, [
'transaction_id' => 'nullable|string',
'client_transaction_id' => 'nullable|string|exists:payment_transactions,transaction_id'
]);
if($request->has('transaction_id') && $request->has('client_transaction_id')){
$transaction = PaymentTransaction::where('transaction_id',$request->input('client_transaction_id'))->firstOrFail();
try {
$client = new Client([
'base_uri' => config('variables.cinetpay_transfert_url')
]);
// Login
$loginResponse = $client->post('auth/login', [
'form_params' => [
"apikey" => config('variables.cinetpay_api_key'),
"password" => config('variables.cinetpay_transfert_password'),
],
'timeout' => $this->timeout
]);
$responseData = json_decode($loginResponse->getBody()->getContents());
$token = $responseData->data->token;
$responseCode = $loginResponse->getStatusCode();
if ($responseCode == 200 && !empty($token)) {
$response = $client->get('transfer/check/money', [
'query' => [
'token' => $token,
'transaction_id' => $request->input('transaction_id')
],
]);
$responseData = json_decode($response->getBody()->getContents());
$responseCode = $response->getStatusCode();
if ($responseCode == 200) {
$transaction->update([
'status' => $responseData->data->status,
'payment_method_exact' => $responseData->data->payment_method ?? null,
'payment_date' => $responseData->data->payment_date ?? null,
]);
}
}
} catch (Throwable $e) {
Log::info("Get Cinetpay Transfert Status Error");
$errorMessage = $e->getMessage();
Log::info($errorMessage);
$transaction->update([
'status' => PaymentTransactionStatus::REFUSED
]);
}
return $this->errorResponse($errorMessage ?? __('errors.unexpected_error'));
}else{
return response("OK");
}
}
public function capturePaymentResult(Request $request)
{
$this->validate($request, [

View File

@ -67,6 +67,31 @@ class PaymentController extends Controller
}
}
public function payOut(Request $request)
{
$this->validate($request, [
'network_name' => 'nullable|string',
'payment_method' => 'required|string|in:WALLET,CARD',
]);
$paymentMethod = $request->input('payment_method');
if($paymentMethod === 'WALLET'){
$networkName = strtolower($request->input('network_name'));
if(str_contains($networkName,'orange') || str_contains($networkName,'mtn')){
return app(CinetpayController::class)->payOut($request);
}
}
if($paymentMethod == 'CARD'){
return app(StripeController::class)->payOut($request);
}
return $this->errorResponse(__('errors.network_not_taken'));
}
public function paymentResult(Request $request)
{
if($request->has('transaction_id')){

View File

@ -83,11 +83,6 @@ class StripeController extends Controller
$amount = $request->input('amount');
$currency = $request->input('currency');
if ($currency != 'USD') {
// Convertir en multiple de 5
$amount = $this->roundUpToAny($amount);
}
if($amount < 325 && $currency == 'XAF'){
$amount = 325;
}
@ -256,8 +251,8 @@ class StripeController extends Controller
'customer_address' => 'required|string',
'customer_city' => 'required|string',
'customer_country' => 'required|string|size:2',
// 'customer_state' => 'required_if:customer_country,US|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:customer_country,US|string|size:5',
'customer_state' => 'required_if:customer_country,US|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:customer_country,US|string|size:5',
'reason' => 'required|string'
]);
@ -269,11 +264,6 @@ class StripeController extends Controller
$amount = $request->input('amount');
$currency = $request->input('currency');
if ($currency != 'USD') {
// Convertir en multiple de 5
$amount = $this->roundUpToAny($amount);
}
if($amount < 325 && $currency == 'XAF'){
$amount = 325;
}
@ -389,8 +379,8 @@ class StripeController extends Controller
'customer_address' => 'required|string',
'customer_city' => 'required|string',
'customer_country' => 'required|string|size:2',
// 'customer_state' => 'required_if:customer_country,US|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:customer_country,US|string|size:5',
'customer_state' => 'required_if:customer_country,US|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:customer_country,US|string|size:5',
'reason' => 'required|string'
]);

View File

@ -14,6 +14,7 @@
"guzzlehttp/guzzle": "^7.4",
"illuminate/session": "^9.52",
"laravel/lumen-framework": "^9.0",
"propaganistas/laravel-phone": "^5.0",
"stripe/stripe-php": "^10.13"
},
"require-dev": {

200
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "f0ebadd8749412a4cd14d3ae29e1d742",
"content-hash": "81090437c0e646c7e1df7fd291f0a3ac",
"packages": [
{
"name": "brick/math",
@ -1095,6 +1095,132 @@
],
"time": "2022-02-20T15:07:15+00:00"
},
{
"name": "giggsey/libphonenumber-for-php",
"version": "8.13.15",
"source": {
"type": "git",
"url": "https://github.com/giggsey/libphonenumber-for-php.git",
"reference": "b294846e26ea985e6b1fbdfaf15387daca60c2de"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/b294846e26ea985e6b1fbdfaf15387daca60c2de",
"reference": "b294846e26ea985e6b1fbdfaf15387daca60c2de",
"shasum": ""
},
"require": {
"giggsey/locale": "^1.7|^2.0",
"php": ">=5.3.2",
"symfony/polyfill-mbstring": "^1.17"
},
"require-dev": {
"pear/pear-core-minimal": "^1.9",
"pear/pear_exception": "^1.0",
"pear/versioncontrol_git": "^0.5",
"phing/phing": "^2.7",
"php-coveralls/php-coveralls": "^1.0|^2.0",
"symfony/console": "^2.8|^3.0|^v4.4|^v5.2",
"symfony/phpunit-bridge": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "8.x-dev"
}
},
"autoload": {
"psr-4": {
"libphonenumber\\": "src/"
},
"exclude-from-classmap": [
"/src/data/",
"/src/carrier/data/",
"/src/geocoding/data/",
"/src/timezone/data/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Joshua Gigg",
"email": "giggsey@gmail.com",
"homepage": "https://giggsey.com/"
}
],
"description": "PHP Port of Google's libphonenumber",
"homepage": "https://github.com/giggsey/libphonenumber-for-php",
"keywords": [
"geocoding",
"geolocation",
"libphonenumber",
"mobile",
"phonenumber",
"validation"
],
"support": {
"issues": "https://github.com/giggsey/libphonenumber-for-php/issues",
"source": "https://github.com/giggsey/libphonenumber-for-php"
},
"time": "2023-06-23T07:47:45+00:00"
},
{
"name": "giggsey/locale",
"version": "2.4",
"source": {
"type": "git",
"url": "https://github.com/giggsey/Locale.git",
"reference": "a6b33dfc9e8949b7e28133c4628b29cd9f1850bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/giggsey/Locale/zipball/a6b33dfc9e8949b7e28133c4628b29cd9f1850bb",
"reference": "a6b33dfc9e8949b7e28133c4628b29cd9f1850bb",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"require-dev": {
"ext-json": "*",
"pear/pear-core-minimal": "^1.9",
"pear/pear_exception": "^1.0",
"pear/versioncontrol_git": "^0.5",
"phing/phing": "^2.7",
"php-coveralls/php-coveralls": "^2.0",
"phpunit/phpunit": "^8.5|^9.5",
"symfony/console": "^5.0|^6.0",
"symfony/filesystem": "^5.0|^6.0",
"symfony/finder": "^5.0|^6.0",
"symfony/process": "^5.0|^6.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Giggsey\\Locale\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Joshua Gigg",
"email": "giggsey@gmail.com",
"homepage": "https://giggsey.com/"
}
],
"description": "Locale functions required by libphonenumber-for-php",
"support": {
"issues": "https://github.com/giggsey/Locale/issues",
"source": "https://github.com/giggsey/Locale/tree/2.4"
},
"time": "2023-04-13T07:40:58+00:00"
},
{
"name": "graham-campbell/result-type",
"version": "v1.1.1",
@ -3787,6 +3913,78 @@
],
"time": "2023-02-25T19:38:58+00:00"
},
{
"name": "propaganistas/laravel-phone",
"version": "5.0.3",
"source": {
"type": "git",
"url": "https://github.com/Propaganistas/Laravel-Phone.git",
"reference": "32aa5ed857a9a6c1a80a0ebdbff4676f2f8e37cc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Propaganistas/Laravel-Phone/zipball/32aa5ed857a9a6c1a80a0ebdbff4676f2f8e37cc",
"reference": "32aa5ed857a9a6c1a80a0ebdbff4676f2f8e37cc",
"shasum": ""
},
"require": {
"giggsey/libphonenumber-for-php": "^7.0|^8.0",
"illuminate/contracts": "^9.0|^10.0",
"illuminate/support": "^9.0|^10.0",
"illuminate/validation": "^9.0|^10.0",
"php": "^8.0"
},
"require-dev": {
"laravel/pint": "^1.4",
"nunomaduro/larastan": "^2.4",
"orchestra/testbench": "*",
"phpunit/phpunit": "^9.5.10"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Propaganistas\\LaravelPhone\\PhoneServiceProvider"
]
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Propaganistas\\LaravelPhone\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Propaganistas",
"email": "Propaganistas@users.noreply.github.com"
}
],
"description": "Adds phone number functionality to Laravel based on Google's libphonenumber API.",
"keywords": [
"laravel",
"libphonenumber",
"phone",
"validation"
],
"support": {
"issues": "https://github.com/Propaganistas/Laravel-Phone/issues",
"source": "https://github.com/Propaganistas/Laravel-Phone/tree/5.0.3"
},
"funding": [
{
"url": "https://github.com/Propaganistas",
"type": "github"
}
],
"time": "2023-02-04T16:13:42+00:00"
},
{
"name": "psr/cache",
"version": "3.0.0",

View File

@ -15,7 +15,9 @@ return [
'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', ''),
'cinetpay_transfert_password' => env('CINETPAY_TRANFERT_PASSWORD', ''),
'cinetpay_transfert_url' => env('CINETPAY_TRANSFERT_URL', ''),
'stripe_key' => env('STRIPE_KEY', ''),
'stripe_secret' => env('STRIPE_SECRET', ''),
'stripe_webhook_secret' => env('STRIPE_WEBHOOK_SECRET', ''),

View File

@ -4,5 +4,6 @@ return [
'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"
'timeout' => "The server did not receive a complete response within the timeout period. Please Try again later",
'network_not_taken' => "Network not taken into account"
];

View File

@ -117,6 +117,7 @@ return [
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.',
'uuid' => 'The :attribute must be a valid UUID.',
'phone' => 'The :attribute field must be a valid number.',
/*
|--------------------------------------------------------------------------

View File

@ -4,5 +4,6 @@ return [
'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"
'timeout' => "Le serveur n'a pas reçu de réponse complete dans le délai imparti. Essayer plus tard",
'network_not_taken' => "Réseau non pris en compte"
];

View File

@ -132,7 +132,7 @@ return [
'rule-name' => 'custom-message',
],
],
'phone' => 'Le champ :attribute doit être un numéro valide.',
'attributes' => [
'address' => 'adresse',
'age' => 'âge',

View File

@ -39,6 +39,7 @@ $router->group(['middleware' => 'session'], function () use ($router) {
*/
$router->addRoute(['GET','POST'],'/yoomee/v2/webhook', ['as' => 'yoomee.v2.webhook' , 'uses' => 'YoomeeV2Controller@capturePaymentResult']);
$router->addRoute(['GET','POST'],'/cinetpay/webhook', ['as' => 'cinetpay.webhook' , 'uses' => 'CinetpayController@capturePaymentResult']);
$router->addRoute(['GET','POST'],'/cinetpay/transfert/webhook', ['as' => 'cinetpay.transfert.webhook' , 'uses' => 'CinetpayController@captureTransfertResult']);
$router->addRoute(['GET','POST'],'/stripe/webhook', ['as' => 'stripe.webhook' , 'uses' => 'StripeController@capturePaymentResult']);
$router->addRoute(['GET','POST'],'/paymentResult', ['as' => 'paymentResult' , 'uses' => 'PaymentController@paymentResult']);
@ -51,7 +52,7 @@ $router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('methods','PaymentController@getMethods');
$router->post('pay','PaymentController@pay');
$router->get('checkStatus/{transaction_id}','PaymentController@checkStatus');
$router->post('payOut','PaymentController@payOut');
/**
* Yoomee Endpoints
@ -72,6 +73,7 @@ $router->group(['middleware' => 'auth'], function () use ($router) {
$router->group(['prefix' => 'cinetpay'], function () use ($router) {
$router->get('methods',['as' => 'cinetpay.methods', 'uses' => 'CinetpayController@getMethods']);
$router->addRoute(['GET','POST'],'pay',['as' => 'cinetpay.pay', 'uses' => 'CinetpayController@pay']);
$router->post('payOut',['as' => 'cinetpay.payout', 'uses' => 'CinetpayController@payOut']);
});
/**
@ -82,7 +84,7 @@ $router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('methods',['as' => 'stripe.methods', 'uses' => 'StripeController@getMethods']);
$router->post('pay',['as' => 'stripe.pay', 'uses' => 'StripeController@pay']);
$router->post('payIn',['as' => 'stripe.submit', 'uses' => 'StripeController@payIn']);
$router->post('payOut',['as' => 'stripe.submit', 'uses' => 'StripeController@payOut']);
$router->post('payOut',['as' => 'stripe.payout', 'uses' => 'StripeController@payOut']);
});
});