feat: enable flutterwave payment

This commit is contained in:
Djery-Tom 2023-08-30 15:14:37 +01:00
parent 5bdc61414f
commit 4de520e861
9 changed files with 252 additions and 5 deletions

View File

@ -47,3 +47,5 @@ STRIPE_KEY=pk_test_51NAILHJ6IfmAiBwqd8t8ZL9WjTdcMOSDt46TfLT1DS1VPRTrEY0UC3RDUF0b
STRIPE_SECRET=sk_test_51NAILHJ6IfmAiBwqgblKnBatWzIt3mtMYyw9Tc2RRFWUJJDVJ2VGKCBo3o0eTPCAigLB8lAbPiDiuvQ9Arwg0fad00fv7zIJdY
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
STRIPE_ACCOUNT=acct_1NAILHJ6IfmAiBwq
FLW_SECRET_KEY=FLWSECK_TEST-8bf06ec7ac19eb8f045e9f3ebe9dfb97-X

View File

@ -111,11 +111,10 @@ class Handler extends ExceptionHandler
}
if ($exception instanceof ClientException) {
$message = $exception->getResponse()->getBody()->getContents();
$response = json_decode($exception->getResponse()->getBody()->getContents());
$code = $exception->getCode();
Log::error(json_encode($message));
return $this->errorResponse($message, $code);
return $this->errorResponse($response->message ?? $response , $code);
}
if($exception instanceof ConnectException){

View File

@ -0,0 +1,194 @@
<?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 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()
{
}
//
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" => config('variables.flw_secret_key')
],
'json' => [
"tx_ref" => $transaction_id,
"amount" => $amount,
"currency" => $request->input('currency'),
"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
]);
}
}
}

View File

@ -68,6 +68,8 @@ class PaymentController extends Controller
return app(YoomeeV2Controller::class)->initPay($request);
case 'cinetpay':
return app(CinetpayController::class)->pay($request);
case 'flutterwave':
return app(FlutterwaveController::class)->pay($request);
case 'stripe':
return app(StripeController::class)->payIn($request);
default:

View File

@ -223,7 +223,7 @@ class StripeController extends Controller
}
return $this->errorResponse($errorMessage ?? __('errors.unexpected_error'));
return $this->errorResponse(__('errors.unexpected_error'));
}
@ -459,7 +459,7 @@ class StripeController extends Controller
}
}
return $this->errorResponse($errorMessage ?? __('errors.unexpected_error'));
return $this->errorResponse(__('errors.unexpected_error'));
}

View File

@ -8,4 +8,8 @@ class PaymentTransaction extends Model
{
protected $table = 'payment_transactions';
protected $guarded = ['id'];
protected $dates = [
'payment_date'
];
}

View File

@ -22,4 +22,5 @@ return [
'stripe_secret' => env('STRIPE_SECRET', ''),
'stripe_webhook_secret' => env('STRIPE_WEBHOOK_SECRET', ''),
'stripe_account' => env('STRIPE_ACCOUNT', ''),
'flw_secret_key' => env('FLW_SECRET_KEY', '')
];

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('payment_transactions', function (Blueprint $table) {
$table->dateTime('payment_date')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('payment_transactions', function (Blueprint $table) {
$table->string('payment_date')->nullable()->change();
});
}
};

View File

@ -41,6 +41,8 @@ $router->addRoute(['GET','POST'],'/yoomee/v2/webhook', ['as' => 'yoomee.v2.webho
$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'],'/flutterwave/webhook', ['as' => 'flutterwave.webhook' , 'uses' => 'FlutterwaveController@capturePaymentResult']);
$router->addRoute(['GET','POST'],'/paymentResult', ['as' => 'paymentResult' , 'uses' => 'PaymentController@paymentResult']);
@ -79,6 +81,15 @@ $router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('checkBalance',['as' => 'cinetpay.check-balance', 'uses' => 'CinetpayController@checkBalance']);
});
/**
* Flutterwave Endpoints
*/
$router->group(['prefix' => 'flutterwave'], function () use ($router) {
$router->addRoute(['GET','POST'],'pay',['as' => 'flutterwave.pay', 'uses' => 'FlutterwaveController@pay']);
$router->post('payOut',['as' => 'flutterwave.payout', 'uses' => 'FlutterwaveController@payOut']);
$router->get('checkBalance',['as' => 'flutterwave.check-balance', 'uses' => 'FlutterwaveController@checkBalance']);
});
/**
* Stripe Endpoints
*/