508 lines
20 KiB
PHP
508 lines
20 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Enums\PaymentMethod;
|
|
use App\Enums\PaymentTransactionStatus;
|
|
use App\Enums\PaymentType;
|
|
use App\Models\Country;
|
|
use App\Models\PaymentAggregator;
|
|
use App\Models\PaymentTransaction;
|
|
use DateTime;
|
|
use GuzzleHttp\Client;
|
|
use GuzzleHttp\Exception\GuzzleException;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Lang;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Propaganistas\LaravelPhone\PhoneNumber;
|
|
use Propaganistas\LaravelPhone\Rules\Phone;
|
|
use Symfony\Component\HttpFoundation\Response as ResponseAlias;
|
|
use Throwable;
|
|
|
|
class FlutterwaveController extends Controller
|
|
{
|
|
private $timeout = 60; //In seconds
|
|
|
|
/**
|
|
* Create a new controller instance.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct()
|
|
{
|
|
|
|
}
|
|
|
|
/**
|
|
* @OA\Get(
|
|
* path="/flutterwave/methods",
|
|
* summary="Afficher la liste des methodes de Flutterwave",
|
|
* tags={"Flutterwave"},
|
|
* security={{"api_key":{}}},
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="OK",
|
|
* @OA\JsonContent(
|
|
* ref="#/components/schemas/ApiResponse",
|
|
* example = {
|
|
* "status" : 200,
|
|
* "response" : {"hasWebview": true, "methods": { "MOBILE_MONEY": "Mobile Money", "CREDIT_CARD": "Carte de crédit" }},
|
|
* "error":null
|
|
* }
|
|
* )
|
|
* )
|
|
* )
|
|
*/
|
|
public function getMethods()
|
|
{
|
|
$providers = [
|
|
// 'ALL',
|
|
'MOBILE_MONEY',
|
|
// 'CREDIT_CARD',
|
|
];
|
|
$methods = [];
|
|
foreach ($providers as $provider) {
|
|
$key = 'providers.' . $provider;
|
|
$method['title'] = Lang::has($key) ? __($key) : $provider;
|
|
$method['value'] = $provider;
|
|
$methods[] = $method;
|
|
}
|
|
return $this->successResponse([
|
|
'hasWebview' => true,
|
|
'methods' => $methods,
|
|
]);
|
|
}
|
|
|
|
//
|
|
public function pay(Request $request)
|
|
{
|
|
$this->validate($request, [
|
|
// 'aggregator_id' => 'required|integer',
|
|
'amount' => 'required|numeric|min:5',
|
|
'currency' => 'required|string|size:3',
|
|
'customer_id' => 'required|integer',
|
|
'customer_email' => 'required|email',
|
|
'customer_name' => 'nullable|string',
|
|
'customer_surname' => 'required|string',
|
|
'customer_phone_number' => 'required|string',
|
|
'customer_address' => 'required|string',
|
|
'reason' => 'required|string'
|
|
]);
|
|
|
|
|
|
$aggregator = PaymentAggregator::where('name','like','%flutterwave%')->firstOrFail();
|
|
|
|
$transaction_id = $this->getTransactionID();
|
|
$amount = $request->input('amount');
|
|
$currency = $request->input('currency');
|
|
|
|
if ($currency != 'USD') {
|
|
// Convertir en multiple de 5
|
|
$amount = $this->roundUpToAny($amount);
|
|
}
|
|
|
|
// Init payment
|
|
$createResponse = (new Client())->post('https://api.flutterwave.com/v3/payments', [
|
|
'headers' => [
|
|
"Authorization" => 'Bearer '.config('variables.flw_secret_key')
|
|
],
|
|
'json' => [
|
|
"tx_ref" => $transaction_id,
|
|
"amount" => $amount,
|
|
"currency" => $request->input('currency'),
|
|
"payment_options" => "mobilemoneyfranco",
|
|
"redirect_url" => route('flutterwave.webhook'),
|
|
"customer" => [
|
|
"email" => $request->input('customer_email'),
|
|
"phonenumber" => $request->input('customer_phone_number'),
|
|
"name" => $request->input('customer_surname').' '.$request->input('customer_name')
|
|
],
|
|
"customizations" => [
|
|
"title" => $request->input('reason'),
|
|
"logo" => 'https://ilink-app.com/backoffice/images/logo_blueback.png'
|
|
],
|
|
"meta" => [
|
|
"customer_id" => $request->input('customer_id'),
|
|
"customer_address" => $request->input('customer_address')
|
|
]
|
|
],
|
|
'timeout' => $this->timeout
|
|
]);
|
|
|
|
$responseData = json_decode($createResponse->getBody()->getContents());
|
|
$responseCode = $createResponse->getStatusCode();
|
|
|
|
if ($responseCode == 200) {
|
|
|
|
PaymentTransaction::create([
|
|
'aggregator_id' => $aggregator->id,
|
|
"currency" => $request->input('currency'),
|
|
"transaction_id" => $transaction_id,
|
|
"amount" => $amount,
|
|
"payment_method" => "ALL",
|
|
"payment_url" => $responseData->data->link,
|
|
'status' => PaymentTransactionStatus::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->link
|
|
], ResponseAlias::HTTP_MOVED_PERMANENTLY);
|
|
|
|
}else{
|
|
return $this->errorResponse($responseData->error->message ?? trans('errors.unexpected_error'),$responseCode);
|
|
}
|
|
}
|
|
|
|
|
|
public function capturePaymentResult(Request $request)
|
|
{
|
|
$this->validate($request, [
|
|
'transaction_id' => 'nullable|string',
|
|
'tx_ref' => 'nullable|string|exists:payment_transactions,transaction_id',
|
|
'status' => 'nullable|string'
|
|
]);
|
|
|
|
Log::info(json_encode($request->all()));
|
|
if($request->has('tx_ref')){
|
|
$transaction = PaymentTransaction::where('transaction_id',$request->input('tx_ref'))->firstOrFail();
|
|
return $this->getPaymentStatus($transaction, $request->input('transaction_id'));
|
|
}else{
|
|
return response("OK");
|
|
}
|
|
}
|
|
|
|
private function getPaymentStatus(PaymentTransaction $transaction, $flwTransactionId)
|
|
{
|
|
try {
|
|
|
|
// Create a client with a base URI
|
|
$response = (new Client())->get('https://api.flutterwave.com/v3/transactions/'.$flwTransactionId.'/verify', [
|
|
'headers' => [
|
|
"Authorization" => config('variables.flw_secret_key')
|
|
],
|
|
]);
|
|
|
|
$responseData = json_decode($response->getBody()->getContents());
|
|
$responseCode = $response->getStatusCode();
|
|
|
|
if ($responseCode == 200 && $responseData?->data?->status == 'successful'
|
|
&& $responseData?->data?->tx_ref == $transaction->transaction_id) {
|
|
|
|
$transaction->update([
|
|
'status' => PaymentTransactionStatus::ACCEPTED,
|
|
'payment_method_exact' => $responseData?->data?->payment_type ?? null,
|
|
'aggregator_payment_ref' => $responseData?->data?->flw_ref ?? null,
|
|
'payment_date' => $responseData?->data?->created_at != null ? new DateTime($responseData?->data?->created_at) : null,
|
|
]);
|
|
|
|
}
|
|
|
|
} catch (Throwable $e) {
|
|
Log::info("Get Payment Status Error");
|
|
Log::info($e->getMessage());
|
|
$transaction->update([
|
|
'status' => PaymentTransactionStatus::REFUSED
|
|
]);
|
|
|
|
}
|
|
|
|
if($transaction->status == PaymentTransactionStatus::ACCEPTED){
|
|
return redirect()->route('paymentResult',[
|
|
'transaction_id' => $transaction->transaction_id,
|
|
'token' => $transaction->payment_token,
|
|
'status' => 1
|
|
]);
|
|
}else{
|
|
return redirect()->route('paymentResult',[
|
|
'message' => "Payment failed",
|
|
'status' => 0
|
|
]);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
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' => 'nullable|string',
|
|
'customer_city' => 'nullable|string',
|
|
'customer_country' => 'required|string|size:2|in:CI,SN,ML,CM,TG,BF,CD,GN,BJ',
|
|
'reason' => 'required|string',
|
|
'network_name' => 'required|string',
|
|
]);
|
|
|
|
$aggregator = PaymentAggregator::where('name','like','%flutterwave%')->firstOrFail();
|
|
|
|
try{
|
|
|
|
$customer_surname = $request->input('customer_surname');
|
|
$customer_name = $request->input('customer_name') ?? $customer_surname;
|
|
$customer_email = $request->input('customer_email');
|
|
$networkName = strtolower($request->input('network_name'));
|
|
|
|
$country_code = $request->input('customer_country');
|
|
$phoneNumber = str_replace(' ','',$request->input('customer_phone_number'));
|
|
|
|
$phone = new PhoneNumber($phoneNumber, $country_code);
|
|
$phoneNumber = str_replace(' ','',$phone->formatInternational());
|
|
$accountNumber = substr($phoneNumber,1);
|
|
|
|
$amount = $request->input('amount');
|
|
$payment_method = 'WALLET';
|
|
|
|
// if($amount < 500){
|
|
// return $this->errorResponse('Minimun amount is 500');
|
|
// }
|
|
|
|
|
|
$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" => $customer_name,
|
|
"customer_surname" => $customer_surname,
|
|
"customer_email" => $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 = (new Client())->post('https://api.flutterwave.com/v3/transfers', [
|
|
'headers' => [
|
|
"Authorization" => 'Bearer '.config('variables.flw_secret_key')
|
|
],
|
|
'json' => [
|
|
"account_bank" => str_contains($networkName,'orange') ? 'orangemoney' : 'MTN',
|
|
"account_number" => $accountNumber,
|
|
'beneficiary_name' => $customer_surname.' '.$customer_name,
|
|
'amount' => $amount,
|
|
'narration' => $request->input('reason'),
|
|
'currency' => $request->input('currency'),
|
|
'reference' => $transactionId,
|
|
'callback_url' => route('cinetpay.transfert.webhook'),
|
|
'debit_currency' => $request->input('currency'),
|
|
],
|
|
'timeout' => $this->timeout,
|
|
'http_errors' => false
|
|
]);
|
|
|
|
$responseData = json_decode($transfertResponse->getBody()->getContents());
|
|
$responseCode = $transfertResponse->getStatusCode();
|
|
|
|
if ($responseCode == 200) {
|
|
|
|
Log::info("Response of transfert");
|
|
Log::info(json_encode($responseData));
|
|
// $transaction->update([
|
|
// 'aggregator_payment_ref' => $responseData->data[0][0]?->transaction_id,
|
|
// 'status' => $this->convertTransfertStatus($responseData->data[0][0]?->treatment_status) ,
|
|
// ]);
|
|
|
|
return $this->successResponse([
|
|
'message' => 'Transfert is pending',
|
|
'transaction_id' => $transactionId,
|
|
'transaction_status' => $transaction->status
|
|
]);
|
|
}else{
|
|
Log::error("Error Flutterwave make transfert payment");
|
|
Log::error(json_encode($responseData));
|
|
return $this->errorResponse(__('errors.service_unavailable_try_later'));
|
|
}
|
|
|
|
// $errorMessage = $responseData?->description ?? $responseData?->message;
|
|
|
|
}catch (Throwable $e){
|
|
Log::error("Error Flutterwave transfert payment");
|
|
$errorMessage = $e->getMessage();
|
|
Log::error("Response data :: ".json_encode($responseData ?? ''));
|
|
Log::error($errorMessage);
|
|
}
|
|
|
|
return $this->errorResponse($errorMessage ?? __('errors.unexpected_error'));
|
|
}
|
|
|
|
public function captureTransfertResult(Request $request)
|
|
{
|
|
Log::info("Transfert wevbook");
|
|
Log::info(json_encode($request->all()));
|
|
$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([
|
|
// 'aggregator_payment_ref' => $responseData->data[0]?->transaction_id,
|
|
// 'status' => $this->convertTransfertStatus($responseData->data[0]?->treatment_status),
|
|
// ]);
|
|
//
|
|
// }
|
|
// }
|
|
//
|
|
// } catch (Throwable $e) {
|
|
// Log::info("Get Cinetpay Transfert Status Error");
|
|
// $errorMessage = $e->getMessage();
|
|
// Log::error("Response data :: ".json_encode($responseData ?? ''));
|
|
// Log::info($errorMessage);
|
|
// $transaction->update([
|
|
// 'status' => PaymentTransactionStatus::REFUSED
|
|
// ]);
|
|
// }
|
|
//
|
|
// return $this->errorResponse($errorMessage ?? __('errors.unexpected_error'));
|
|
// }else{
|
|
return response("OK");
|
|
// }
|
|
}
|
|
|
|
// Check Balance for payout or transfer
|
|
|
|
/**
|
|
* @throws GuzzleException
|
|
*/
|
|
public function checkBalance(Request $request)
|
|
{
|
|
$this->validate($request, [
|
|
'country_id' => 'required|integer|exists:countries,id',
|
|
'amount' => 'required|numeric',
|
|
'type' => 'nullable|string' // mobilemoney,account
|
|
]);
|
|
|
|
|
|
$amount = floatval($request->input('amount'));
|
|
$type = $request->input('type', 'mobilemoney');
|
|
$countryId = $request->input('country_id');
|
|
$country = Country::with('currency')->where('id', $countryId)->firstOrFail();
|
|
$currencyCode = $country->currency->code;
|
|
|
|
|
|
// https://flutterwavedoc.readme.io/reference/get-all-wallet-balances
|
|
$balanceResponse = (new Client())->get('https://api.flutterwave.com/v3/balances/'.$currencyCode, [
|
|
'headers' => [
|
|
"Authorization" => 'Bearer '.config('variables.flw_secret_key')
|
|
],
|
|
'timeout' => $this->timeout,
|
|
'http_errors' => false
|
|
]);
|
|
|
|
$responseCode = $balanceResponse->getStatusCode();
|
|
$responseData = json_decode($balanceResponse->getBody()->getContents());
|
|
if ($responseCode == 200) {
|
|
$amountAvailable = $responseData?->data?->available_balance ?? 0;
|
|
|
|
// https://flutterwavedoc.readme.io/reference/get-transfer-fee
|
|
$fees = 0;
|
|
$feesResponse = (new Client())->get('https://api.flutterwave.com/v3/transfers/fee', [
|
|
'headers' => [
|
|
"Authorization" => 'Bearer '.config('variables.flw_secret_key')
|
|
],
|
|
'query' => [
|
|
'amount' => $amount,
|
|
'currency' => $currencyCode,
|
|
'type' => $type
|
|
],
|
|
'timeout' => $this->timeout,
|
|
'http_errors' => false
|
|
]);
|
|
|
|
$responseCode = $feesResponse->getStatusCode();
|
|
$responseData = json_decode($feesResponse->getBody()->getContents());
|
|
if ($responseCode == 200) {
|
|
$data = $responseData?->data;
|
|
if(!empty($data)){
|
|
$fee = $data[0]->fee;
|
|
$feeType = $data[0]->fee_type;
|
|
|
|
if($feeType == 'value'){
|
|
$fees = $fee;
|
|
}
|
|
|
|
if($feeType == 'percentage'){
|
|
$fees = $amount * $fee;
|
|
}
|
|
|
|
$amount += $fees;
|
|
|
|
if ($amountAvailable >= $amount){
|
|
return $this->successResponse("Solde disponible");
|
|
}else{
|
|
|
|
Log::error("Solde insuffisant :: ".$amountAvailable);
|
|
return $this->errorResponse(__('errors.service_unavailable_try_later'));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$errorMessage = $responseData?->description ?? $responseData?->message;
|
|
Log::error("Error CinetPay check balance");
|
|
Log::error("Response data :: ".json_encode($responseData ?? ''));
|
|
return $this->errorResponse($errorMessage ?? __('errors.unexpected_error'));
|
|
|
|
}
|
|
}
|