401 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			401 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
| <?php
 | |
| 
 | |
| namespace App\Http\Controllers;
 | |
| 
 | |
| use App\Enums\PaymentTransactionStatus;
 | |
| use App\Models\PaymentTransaction;
 | |
| use GuzzleHttp\Client;
 | |
| use Illuminate\Http\Request;
 | |
| use Illuminate\Support\Carbon;
 | |
| use Illuminate\Support\Facades\DB;
 | |
| use Illuminate\Support\Facades\Lang;
 | |
| use Illuminate\Support\Facades\Log;
 | |
| use Illuminate\Support\Str;
 | |
| use Throwable;
 | |
| 
 | |
| class YoomeeV2Controller extends Controller
 | |
| {
 | |
|     private $client;
 | |
|     private $timeout = 60; //In seconds
 | |
|     private $isSyncRequest = false ;// Async request
 | |
| 
 | |
|     /**
 | |
|      * 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_v2_url'),
 | |
|             'timeout' => $this->timeout,
 | |
|             'http_errors' => false
 | |
|         ]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @OA\Get(
 | |
|      *   path="/yoomee/v2/methods",
 | |
|      *   summary="Afficher la liste des methodes de paiment de Yoomee",
 | |
|      *   tags={"Yoomee"},
 | |
|      *   security={{"api_key":{}}},
 | |
|      *    @OA\Response(
 | |
|      *      response=200,
 | |
|      *      description="OK",
 | |
|      *       @OA\JsonContent(
 | |
|      *           ref="#/components/schemas/ApiResponse",
 | |
|      *            example = {
 | |
|      *                      "status" : 200,
 | |
|      *                      "response" : {"hasWebview": true, "methods": {"Yoomee": "Yoomee", "MTN": "MTN" , "Orange": "Orange", "EU" : "EU"}},
 | |
|      *                      "error":null
 | |
|      *                  }
 | |
|      *       )
 | |
|      *    )
 | |
|      * )
 | |
|      */
 | |
|     public function getMethods()
 | |
|     {
 | |
|         $response = $this->client->get('providers/v1');
 | |
|         $providers = json_decode($response->getBody()->getContents());
 | |
| //        $providers = ["Yoomee","MTN","Orange","EU"];
 | |
|         $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
 | |
|             ]
 | |
|         );
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /*
 | |
|      * Init payment and provide iLink World checkout page to handle payment
 | |
|      */
 | |
|     public function initPay(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' => 'nullable|string',
 | |
|             'customer_surname' => 'required|string',
 | |
|             'customer_phone_number' => 'required|string',
 | |
|             'customer_address' => 'required|string',
 | |
|             'customer_country' => 'required|string|size:2',
 | |
|             'reason' => 'required|string',
 | |
|         ]);
 | |
| 
 | |
|         $transaction_id = $this->getTransactionID();
 | |
|         $payment_method = $request->input('payment_method');
 | |
|         $customer_phone_number = $request->input('customer_phone_number');
 | |
|         if(str_contains($customer_phone_number,'+237')){
 | |
|             $customer_phone_number = substr($customer_phone_number,4);
 | |
|         }
 | |
| 
 | |
|         do{
 | |
|             $payment_token = Str::random(64);
 | |
|         }while(PaymentTransaction::where('payment_token', $payment_token)->exists());
 | |
| 
 | |
|         $payment_url = route('checkout',['payment_token' => $payment_token]);
 | |
| 
 | |
|         PaymentTransaction::create([
 | |
|             'aggregator_id' => $request->input('aggregator_id'),
 | |
|             "currency" => $request->input('currency'),
 | |
|             "transaction_id" => $transaction_id,
 | |
|             "amount" => $request->input('amount'),
 | |
|             "payment_method" => $payment_method,
 | |
|             "payment_url" => $payment_url,
 | |
|             'payment_token' => $payment_token,
 | |
|             '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" => $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' => 'Payment initiated',
 | |
|             'payment_url' => $payment_url
 | |
|         ]);
 | |
|     }
 | |
| 
 | |
|     public function checkoutPay(Request $request)
 | |
|     {
 | |
|         $this->validate($request, [
 | |
|             'payment_token' => 'required|string|exists:payment_transactions,payment_token',
 | |
|             'phone_number' => 'required|string',
 | |
|         ]);
 | |
| 
 | |
|         $token = $request->input('payment_token');
 | |
|         $transaction = PaymentTransaction::where('payment_token', $token)
 | |
|             ->where('status', PaymentTransactionStatus::INITIATED)->firstOrFail();
 | |
| 
 | |
| 
 | |
|         $customer_phone_number = $request->input('phone_number');
 | |
|         if(str_contains($customer_phone_number,'+237')){
 | |
|             $customer_phone_number = substr($customer_phone_number,4);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         // Create passport payment
 | |
|         $createResponse = $this->client->post('start/v1', [
 | |
|             'json' => [
 | |
|                 'order_merchant' => config('variables.yoomee_username'),
 | |
|                 'order_merchant_password' => config('variables.yoomee_password'),
 | |
|                 'order_type' => 'MemberAccount.merchantPaymentWithoutFees',
 | |
|                 'order_payer' => $customer_phone_number,
 | |
|                 'order_method' => $transaction->payment_method,
 | |
|                 'order_app_id' => config('variables.yoomee_app_id'),
 | |
|                 'order_ext_id' => $transaction->transaction_id,
 | |
|                 "order_base_amount" => $transaction->amount,
 | |
|                 "order_currency" => $transaction->currency,
 | |
|                 "order_description" => $transaction->reason,
 | |
|                 "order_wait_final_status" => $this->isSyncRequest
 | |
|             ]
 | |
|         ]);
 | |
| 
 | |
| 
 | |
|         if ($createResponse->getStatusCode() == 400) {
 | |
|             $createResponse = json_decode($createResponse->getBody()->getContents());
 | |
| 
 | |
|             if(!empty($createResponse->error)){
 | |
|                 //If error regenerate transaction id to avoid 'Numero de commande deja utilisé'
 | |
|                 $transaction->update([
 | |
|                     'transaction_id' => $this->getTransactionID()
 | |
|                 ]);
 | |
|                 //Convert into single line
 | |
|                 session()->flash('error',str_replace(array("\n", "\r"), '',$createResponse->error_description));
 | |
|                 return redirect()->route('checkout',['payment_token' => $token]);
 | |
|             }
 | |
| 
 | |
|             $transaction->update([
 | |
|                 'aggregator_payment_ref' => $createResponse->transaction_number,
 | |
|                 'payment_date' => $createResponse->transaction_date,
 | |
|                 'status' => strtoupper($createResponse->transaction_status),
 | |
|                 "customer_phone_number" => $customer_phone_number,
 | |
|             ]);
 | |
| 
 | |
|           return redirect()->route('checkout',['payment_token' => $token]);
 | |
|         }
 | |
| 
 | |
|         session()->flash('error',__('errors.unexpected_error'));
 | |
|         return redirect()->route('checkout',['payment_token' => $token]);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     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' => 'nullable|string',
 | |
|             'customer_surname' => 'required|string',
 | |
|             'customer_phone_number' => 'required|string',
 | |
|             'customer_address' => 'required|string',
 | |
|             'customer_country' => 'required|string|size:2',
 | |
|             'reason' => 'required|string',
 | |
|         ]);
 | |
| 
 | |
|         $transaction_id = $this->getTransactionID();
 | |
|         $payment_method = $request->input('payment_method');
 | |
|         $customer_phone_number = $request->input('customer_phone_number');
 | |
|         if(str_contains($customer_phone_number,'+237')){
 | |
|             $customer_phone_number = substr($customer_phone_number,4);
 | |
|         }
 | |
|         // Create passport payment
 | |
|         $createResponse = $this->client->post('start/v1', [
 | |
|             'json' => [
 | |
|                 'order_merchant' => config('variables.yoomee_username'),
 | |
|                 'order_merchant_password' => config('variables.yoomee_password'),
 | |
|                 'order_type' => 'MemberAccount.merchantPaymentWithoutFees',
 | |
|                 'order_payer' => $customer_phone_number,
 | |
|                 'order_method' => $payment_method,
 | |
|                 'order_app_id' => config('variables.yoomee_app_id'),
 | |
|                 'order_ext_id' => $transaction_id,
 | |
|                 "order_base_amount" => $request->input('amount'),
 | |
|                 "order_currency" => $request->input('currency'),
 | |
|                 "order_description" => $request->input('reason'),
 | |
|                 "order_wait_final_status" => $this->isSyncRequest
 | |
|             ]
 | |
|         ]);
 | |
| 
 | |
| 
 | |
|         if ($createResponse->getStatusCode() == 400) {
 | |
|             $createResponse = json_decode($createResponse->getBody()->getContents());
 | |
| 
 | |
|             if(!empty($createResponse->error)){
 | |
|                 return $this->errorResponse($createResponse->error_description);
 | |
|             }
 | |
| 
 | |
|             $transaction = PaymentTransaction::create([
 | |
|                 'aggregator_id' => $request->input('aggregator_id'),
 | |
|                 "currency" => $request->input('currency'),
 | |
|                 'aggregator_payment_ref' => $createResponse->transaction_number,
 | |
|                 "transaction_id" => $transaction_id,
 | |
|                 "amount" => $createResponse->transaction_amount,
 | |
|                 'payment_date' => $createResponse->transaction_date,
 | |
|                 "payment_method" => $payment_method,
 | |
|                 'payment_token' => Str::random(64),
 | |
|                 'status' => strtoupper($createResponse->transaction_status),
 | |
|                 "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" => $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'),
 | |
|             ]);
 | |
| 
 | |
|             if($transaction->status == PaymentTransactionStatus::PENDING){
 | |
|                 return $this->successResponse([
 | |
|                     'verification_url' => route('yoomee.v2.webhook',['transaction_id' => $transaction_id])
 | |
|                 ]);
 | |
|             }else{
 | |
|                 return $this->successResponse([
 | |
|                     'transaction_id' => $transaction_id,
 | |
|                     'token' => $transaction->payment_token
 | |
|                 ]);
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         return $this->errorResponse(__('errors.unexpected_error'));
 | |
|     }
 | |
| 
 | |
|     public function capturePaymentResult(Request $request)
 | |
|     {
 | |
|         $this->validate($request, [
 | |
|             'transaction_id' => 'required|string|exists:payment_transactions,transaction_id'
 | |
|         ]);
 | |
| 
 | |
|         $transaction = PaymentTransaction::where('transaction_id',$request->input('transaction_id'))->first();
 | |
|         return $this->getPaymentStatus($transaction);
 | |
|     }
 | |
| 
 | |
|     public function getPaymentStatus(Request $request)
 | |
|     {
 | |
|         $this->validate($request, [
 | |
|             'transaction_id' => 'required|string|exists:payment_transactions,transaction_id',
 | |
|             'verify_btn' => 'nullable|boolean'
 | |
|         ]);
 | |
| 
 | |
|         $transaction = PaymentTransaction::where('transaction_id',$request->input('transaction_id'))->first();
 | |
|         $verify_btn = $request->input('verify_btn');
 | |
| 
 | |
|         try {
 | |
| 
 | |
|             // Si le paiement fait plus de 5 min on l'annule
 | |
|             if($transaction->status == PaymentTransactionStatus::PENDING && $transaction->created_at->diffInMinutes(Carbon::now()) > 5){
 | |
|                 $transaction->update([
 | |
|                     'status' => PaymentTransactionStatus::CANCELLED
 | |
|                 ]);
 | |
|             }
 | |
|             $response = $this->client->post('status/v1', [
 | |
|                 'json' => [
 | |
|                     'order_merchant' => config('variables.yoomee_username'),
 | |
|                     'order_merchant_password' => config('variables.yoomee_password'),
 | |
|                     'order_method' => $transaction->payment_method,
 | |
|                     'order_app_id' => config('variables.yoomee_app_id'),
 | |
|                     'order_ext_id' => $transaction->transaction_id,
 | |
|                     "order_wait_final_status" => $this->isSyncRequest
 | |
|                 ]
 | |
|             ]);
 | |
| 
 | |
|             $responseData = json_decode($response->getBody()->getContents());
 | |
|             $responseCode = $response->getStatusCode();
 | |
| 
 | |
|             if ($responseCode == 400) {
 | |
| 
 | |
|                 $status = strtoupper($responseData->transaction_status);
 | |
|                 if($status == 'SUCCESS'){
 | |
|                     $status = PaymentTransactionStatus::ACCEPTED;
 | |
|                 }else{
 | |
|                     if(str_starts_with($status,'C')){
 | |
|                         $status = PaymentTransactionStatus::REFUSED;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 $transaction->update([
 | |
|                     'status' => $status,
 | |
|                     'payment_date' => $responseData->transaction_date ?? null,
 | |
|                 ]);
 | |
|             }
 | |
| 
 | |
|         } catch (Throwable $e) {
 | |
|             Log::info("Get Yoomee Payment Status Error");
 | |
|             Log::info($e->getMessage());
 | |
|             $transaction->update([
 | |
|                 'status' => PaymentTransactionStatus::REFUSED
 | |
|             ]);
 | |
| 
 | |
|         }
 | |
| 
 | |
|         if($verify_btn){
 | |
|             return redirect()->route('checkout',['payment_token' => $transaction->payment_token]);
 | |
|         }else {
 | |
|             if ($transaction->status == PaymentTransactionStatus::ACCEPTED) {
 | |
|                 return [
 | |
|                     'message' => "Payment accepted",
 | |
|                     'status' => 1,
 | |
|                     'refresh' => 1,
 | |
|                 ];
 | |
|             } else {
 | |
|                 return [
 | |
|                     'message' => "Payment failed",
 | |
|                     'status' => 0
 | |
|                 ];
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     public function merchantRedirect(Request $request)
 | |
|     {
 | |
| 
 | |
|         $this->validate($request, [
 | |
|             'transaction_id' => 'required|string|exists:payment_transactions,transaction_id'
 | |
|         ]);
 | |
| 
 | |
|         $transaction = PaymentTransaction::where('transaction_id',$request->input('transaction_id'))->first();
 | |
| 
 | |
|         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
 | |
|             ]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
| }
 |