paymentservice/app/Http/Controllers/YoomeeV2Controller.php

402 lines
16 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Enums\PaymentTransactionState;
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' => 'required|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,
'state' => PaymentTransactionState::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('state', PaymentTransactionState::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,
'state' => strtoupper($createResponse->transaction_status),
"customer_phone_number" => $customer_phone_number,
]);
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' => 'required|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),
'state' => 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->state == PaymentTransactionState::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->state == PaymentTransactionState::PENDING && $transaction->created_at->diffInMinutes(Carbon::now()) > 5){
$transaction->update([
'state' => PaymentTransactionState::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) {
$state = strtoupper($responseData->transaction_status);
if($state == 'SUCCESS'){
$state = PaymentTransactionState::ACCEPTED;
}else{
if(str_starts_with($state,'C')){
$state = PaymentTransactionState::REFUSED;
}
}
$transaction->update([
'state' => $state,
'payment_date' => $responseData->transaction_date ?? null,
]);
}
} catch (Throwable $e) {
Log::info("Get Yoomee Payment Status Error");
Log::info($e->getMessage());
$transaction->update([
'state' => PaymentTransactionState::REFUSED
]);
}
if ($transaction->state == PaymentTransactionState::ACCEPTED) {
return [
'message' => "Payment accepted",
'status' => 1,
'refresh' => 1,
];
} else {
if($verify_btn){
return redirect()->route('checkout',['payment_token' => $transaction->payment_token]);
}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->state == PaymentTransactionState::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
]);
}
}
}