client = new Client([ 'base_uri' => config('variables.cinetpay_api_url') ]); } /** * @OA\Get( * path="/cinetpay/methods", * summary="Afficher la liste des methodes de Cinetpay", * tags={"Cinetpay"}, * 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', // 'payment_method' => 'nullable|string|in:ALL,MOBILE_MONEY,CREDIT_CARD,WALLET', '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_city' => 'required_if:payment_method,CREDIT_CARD|string', 'customer_country' => 'required|string|size:2', 'customer_state' => 'required_if:payment_method,CREDIT_CARD|string', //Etat du pays dans lequel se trouve le client. Cette valeur est obligatoire si le client se trouve au États Unis d’Amérique (US) ou au Canada (CA) 'customer_zip_code' => 'required_if:payment_method,CREDIT_CARD|string|size:5', 'reason' => 'required|string' ]); $aggregator = PaymentAggregator::where('name','like','%cinetpay%')->firstOrFail(); $transaction_id = $this->getTransactionID(); $payment_method = 'ALL'; $amount = $request->input('amount'); $currency = $request->input('currency'); if ($currency != 'USD') { // Convertir en multiple de 5 $amount = $this->roundUpToAny($amount); } // Init payment $createResponse = $this->client->post('payment', [ 'json' => [ "api_key" => config('variables.cinetpay_api_key'), "site_id" => config('variables.cinetpay_site_id'), "transaction_id" => $transaction_id, "amount" => $amount, "currency" => $request->input('currency'), "description" => $request->input('reason'), "notify_url" => route('cinetpay.webhook'), "return_url" => route('cinetpay.webhook'), "channels" => $payment_method, 'lang' => app()->getLocale(), "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'), ], '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" => $payment_method, "payment_token" => $responseData->data->payment_token, "payment_url" => $responseData->data->payment_url, '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->payment_url ], ResponseAlias::HTTP_MOVED_PERMANENTLY); }else{ return $this->errorResponse($responseData->error ?? trans('errors.unexpected_error'),$responseCode); } } 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' ]); $aggregator = PaymentAggregator::where('name','like','%cinetpay%')->firstOrFail(); try{ $customer_surname = $request->input('customer_surname'); $customer_name = $request->input('customer_name') ?? $customer_surname; $customer_email = $request->input('customer_email'); $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()); $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' => $customer_name, 'surname' => $customer_surname, 'email' => $customer_email ] ] ), ], 'timeout' => $this->timeout, 'http_errors' => false ]); $responseCode = $contactResponse->getStatusCode(); $responseData = json_decode($contactResponse->getBody()->getContents()); 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" => $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 = $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][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 Cinetpay 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 CinetPay 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) { $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"); } } private function convertTransfertStatus($incomingStatus): string { return match ($incomingStatus) { 'NEW', 'REC' => PaymentTransactionStatus::PENDING, 'VAL' => PaymentTransactionStatus::ACCEPTED, 'REJ' => PaymentTransactionStatus::REFUSED, 'NOS' => PaymentTransactionStatus::PENDING_OTP, default => PaymentTransactionStatus::INITIATED, }; } public function capturePaymentResult(Request $request) { $this->validate($request, [ 'cpm_site_id' => 'nullable|string', 'cpm_trans_id' => 'nullable|string|exists:payment_transactions,transaction_id' ]); if($request->has('cpm_trans_id')) { $data = $request->input('cpm_site_id') . $request->input('cpm_trans_id') . $request->input('cpm_trans_date') . $request->input('cpm_amount') . $request->input('cpm_currency') . $request->input('signature') . $request->input('payment_method') . $request->input('cel_phone_num') . $request->input('cpm_phone_prefixe') . $request->input('cpm_language') . $request->input('cpm_version') . $request->input('cpm_payment_config') . $request->input('cpm_page_action') . $request->input('cpm_custom') . $request->input('cpm_designation') . $request->input('cpm_error_message'); $generated_token = hash_hmac('SHA256', $data, config('variables.cinetpay_secret_key')); $received_token = $request->header('x-token'); if(hash_equals($received_token, $generated_token)) { $transaction = PaymentTransaction::where('transaction_id',$request->input('cpm_trans_id'))->firstOrFail(); return $this->getPaymentStatus($transaction); }else{ return $this->errorResponse("Invalid token"); } }else if($request->has('transaction_id')){ $transaction = PaymentTransaction::where('transaction_id',$request->input('transaction_id'))->firstOrFail(); return $this->getPaymentStatus($transaction); }else{ return response("OK"); } } private function getPaymentStatus(PaymentTransaction $transaction) { try { $response = $this->client->post('payment/check', [ 'json' => [ "apikey" => config('variables.cinetpay_api_key'), "site_id" => config('variables.cinetpay_site_id'), 'transaction_id' => $transaction->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, 'aggregator_payment_ref' => $responseData->data->operator_id ?? null, 'payment_date' => $responseData->data->payment_date ?? 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 checkBalance(Request $request) { $this->validate($request, [ 'country_id' => 'required|integer|exists:countries,id', 'amount' => 'required|numeric', 'payment_channel' => 'nullable|string', ]); $amount = $request->input('amount'); $paymentChannel = $request->input('payment_channel'); $countryId = $request->input('country_id'); $country = Country::where('id', $countryId)->firstOrFail(); $countryCode = $country->code_country; $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 $balanceResponse = $client->get('transfer/check/balance', [ 'query' => [ 'token' => $token ], 'timeout' => $this->timeout, 'http_errors' => false ]); $responseCode = $balanceResponse->getStatusCode(); $responseData = json_decode($balanceResponse->getBody()->getContents()); if ($responseCode == 200) { $amountAvailable = $responseData?->data?->countryBalance?->{$countryCode}?->available ?? 0; $fees = 0; $aggregator = PaymentAggregator::where('name','like','%cinetpay%')->first(); if(!empty($aggregator)){ $baseQuery = $aggregator->rates()->where('type', PaymentType::CASH_IN) ->where('method', PaymentMethod::WALLET) ->when($paymentChannel, function ($q) use($paymentChannel){ return $q->where('channel',$paymentChannel); }); $rate = (clone $baseQuery)->where('country', $countryCode)->first(); if(empty($rate)){ $rate = (clone $baseQuery)->where('country','ALL')->first(); } if(!empty($rate)){ if(!empty($rate->fixed_fees)){ $targetCurrency = $country->currency->code; $sourceCurrency = $targetCurrency; if(!empty($rate->fixed_fees_currency)){ $sourceCurrency = $rate->fixed_fees_currency; } $fixed_fees = $this->toMoneyAmount($rate->fixed_fees, $sourceCurrency, $targetCurrency); $fees = (($amount - $fixed_fees) * $rate->rate / 100 ) + $fixed_fees; }else{ $fees = $amount * $rate->rate / 100; } } } $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')); } }