From 4de520e86112ae6261390f4ea7bd47ce10041880 Mon Sep 17 00:00:00 2001 From: Djery-Tom Date: Wed, 30 Aug 2023 15:14:37 +0100 Subject: [PATCH] feat: enable flutterwave payment --- .env.example | 2 + app/Exceptions/Handler.php | 5 +- .../Controllers/FlutterwaveController.php | 194 ++++++++++++++++++ app/Http/Controllers/PaymentController.php | 2 + app/Http/Controllers/StripeController.php | 4 +- app/Models/PaymentTransaction.php | 4 + config/variables.php | 1 + ...r_payment_date_in_payment_transactions.php | 34 +++ routes/web.php | 11 + 9 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 app/Http/Controllers/FlutterwaveController.php create mode 100644 database/migrations/2023_08_30_135610_set_date_type_for_payment_date_in_payment_transactions.php diff --git a/.env.example b/.env.example index ff278db..a415a75 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 2963139..3bed6c8 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -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){ diff --git a/app/Http/Controllers/FlutterwaveController.php b/app/Http/Controllers/FlutterwaveController.php new file mode 100644 index 0000000..8d451dc --- /dev/null +++ b/app/Http/Controllers/FlutterwaveController.php @@ -0,0 +1,194 @@ +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 + ]); + } + + } + +} diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 8de448b..de5d791 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -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: diff --git a/app/Http/Controllers/StripeController.php b/app/Http/Controllers/StripeController.php index 491ab37..6ec4b9f 100644 --- a/app/Http/Controllers/StripeController.php +++ b/app/Http/Controllers/StripeController.php @@ -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')); } diff --git a/app/Models/PaymentTransaction.php b/app/Models/PaymentTransaction.php index aa98552..9ce6734 100644 --- a/app/Models/PaymentTransaction.php +++ b/app/Models/PaymentTransaction.php @@ -8,4 +8,8 @@ class PaymentTransaction extends Model { protected $table = 'payment_transactions'; protected $guarded = ['id']; + + protected $dates = [ + 'payment_date' + ]; } diff --git a/config/variables.php b/config/variables.php index 3bc8ff3..cad8884 100644 --- a/config/variables.php +++ b/config/variables.php @@ -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', '') ]; diff --git a/database/migrations/2023_08_30_135610_set_date_type_for_payment_date_in_payment_transactions.php b/database/migrations/2023_08_30_135610_set_date_type_for_payment_date_in_payment_transactions.php new file mode 100644 index 0000000..d08fbdc --- /dev/null +++ b/database/migrations/2023_08_30_135610_set_date_type_for_payment_date_in_payment_transactions.php @@ -0,0 +1,34 @@ +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(); + }); + } +}; diff --git a/routes/web.php b/routes/web.php index 9b6cd31..04fdeea 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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 */