File: /var/www/vhost/disk-apps/demo.sports-crowd.com/app/Core/Payment/Methods/PayU/PayUStrategy.php
<?php
declare(strict_types=1);
namespace App\Core\Payment\Methods\PayU;
use App\Core\Payment\Application\PaymentTransactionService;
use App\Core\Payment\Entities\Payment;
use App\Core\Payment\Entities\PaymentIntentResponse;
use App\Core\Payment\Entities\PaymentRetrieveResponse;
use App\Core\Payment\PaymentMethodInterface;
use App\Core\Payment\PaymentStatusEnum;
use DateTime;
use DateTimeZone;
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use Illuminate\Http\Request;
class PayUStrategy implements PaymentMethodInterface
{
const PAYMENT_STATUS = [
"APPROVED" => PaymentStatusEnum::CONFIRMED,
"DECLINED" => PaymentStatusEnum::DECLINED,
"PENDING" => PaymentStatusEnum::PENDING,
"REJECTED" => PaymentStatusEnum::REJECTED
];
private $parameters;
public function __construct(
$parameters
) {
$this->parameters = $parameters;
}
public function pay(Payment $payment): PaymentIntentResponse
{
$paymentIntentResponse = new PaymentIntentResponse(
PaymentIntentResponse::ACTION_SHOW,
null
);
// add timestamp to reference
$editedPayment = $this->editReference($payment);
$data = $this->createSessionRequest($editedPayment);
$paymentIntentResponse->setView('payu.webcheckout');
$paymentIntentResponse->setData($data);
return $paymentIntentResponse;
$paymentIntentResponse->setRedirectUrl($paymentIntent->processUrl());
}
public function retrieve(Payment $payment): PaymentRetrieveResponse
{
try {
$baseUrl = "https://sandbox.api.payulatam.com";
if ($this->parameters->is_productive) {
$baseUrl = "https://api.payulatam.com";
}
$baseUrl .= '/reports-api/4.0/service.cgi';
$client = new Client();
$response = $client->post(
$baseUrl,
[
'auth' => [
$this->parameters->client_id,
$this->parameters->api_key,
],
'headers' => [
'Accept' => 'application/json'
],
RequestOptions::JSON => [
"test" => $this->parameters->is_productive,
"language" => "es",
"command" => "ORDER_DETAIL_BY_REFERENCE_CODE",
"merchant" => [
"apiLogin" => $this->parameters->api_login,
"apiKey" => $this->parameters->api_key
],
"details" => [
"referenceCode" => $payment->reference()
]
]
]
);
$body = $response->getBody()->getContents();
$data = json_decode($body);
if ($data->code == "SUCCESS") {
$responsePayload = $data->result->payload;
foreach ($responsePayload as $responseData) {
foreach ($responseData->transactions as $transaction) {
$status = self::PAYMENT_STATUS[$transaction->transactionResponse->state];
if ($transaction->transactionResponse->state == 'APPROVED') {
$payment->setPaymentGatewayTxId($transaction->id);
break 2;
}
}
}
}
$retrieveResponse = new PaymentRetrieveResponse(
$payment->paymentGatewayTxId(),
$status,
"Transacción procesada correctamente"
);
$retrieveResponse->setRawData($data);
return $retrieveResponse;
} catch (\Throwable $th) {
return new PaymentRetrieveResponse(
$payment->paymentGatewayTxId(),
PaymentStatusEnum::PENDING,
$th->getMessage()
);
}
}
private function createSessionRequest(Payment $payment)
{
return [
'gateway_data' => [
'url' => $this->parameters->is_productive ? $this->parameters->gw_url_prd : $this->parameters->gw_url_sandbox,
'merchantId' => $this->parameters->merchant_id,
'accountId' => $this->parameters->account_id,
'isProductive' => (int) $this->parameters->is_productive
],
'payment' => [
'reference' => $payment->reference(),
'description' => "PayU payment: {$payment->reference()}",
'amount' => [
'currency' => $this->parameters->currency,
'total' => $payment->amount()->total()
],
],
'buyer' => [
'email' => $payment->customer()->email(),
],
'signature' => $this->generateSignature($payment),
'confirmationUrl' => $this->buildConfirmationUrl($payment),
'responseUrl' => $this->buildReturnUrl($payment),
];
}
private function buildReturnUrl($payment)
{
$url = config('app.url') . '/store/payment';
$queryString = [
'paymentGatewayId' => $this->parameters->id,
'reference' => $payment->reference()
];
$queryString = array_merge($queryString, $this->parameters->extraConfirmUrlData);
return Request::create($url)->fullUrlWithQuery($queryString);
}
private function buildConfirmationUrl($payment)
{
$url = config('app.url') . '/store/webhooksListener';
$queryString = [
'paymentGatewayId' => $this->parameters->id
];
return Request::create($url)->fullUrlWithQuery($queryString);
}
private function generateSignature(Payment $payment)
{
return md5($this->parameters->api_key . '~' . $this->parameters->merchant_id . '~' . $payment->reference() . '~' . $payment->amount()->total() . '~' . $this->parameters->currency);
}
private function editReference(Payment $payment)
{
$paymentTransactionService = new PaymentTransactionService();
$paymentTransaction = $paymentTransactionService->getById($payment->id());
$newReference = $this->payUReference($payment->reference());
$paymentTransactionService->setReference($paymentTransaction, $newReference);
return $paymentTransactionService->buildPayment($paymentTransaction);
}
public static function payUReference(string $reference): string
{
if (self::checkTimestampReference($reference)['valid'] && self::checkTimestampReference($reference)['is_recent']) {
$referenceArray = explode('_', $reference);
array_pop($referenceArray);
return implode('_', $referenceArray) . "_" . time();
} else {
return $reference . "_" . time();
}
}
public static function checkTimestampReference(string $reference, $recentDays = 365) {
$parts = explode("_", $reference);
$lastPart = end($parts);
if (!ctype_digit($lastPart)) {
return ["valid" => false, "reason" => "Last part is not numeric"];
}
$timestamp = (int)$lastPart;
if ($timestamp < 946684800 || $timestamp > 4102444800) {
return ["valid" => false, "reason" => "Not a valid Unix timestamp"];
}
$date = (new DateTime())->setTimestamp($timestamp);
$now = new DateTime("now", new DateTimeZone("UTC"));
$diffSeconds = abs($now->getTimestamp() - $timestamp);
$isRecent = $diffSeconds < ($recentDays * 86400);
return [
"valid" => true,
"timestamp" => $timestamp,
"datetime" => $date->format(DateTime::ATOM),
"is_recent" => $isRecent,
"age_seconds" => $diffSeconds
];
}
}