diff --git a/app/src/Controller/API/Admin/Users.php b/app/src/Controller/API/Admin/Users.php index 10b2b17..4805696 100644 --- a/app/src/Controller/API/Admin/Users.php +++ b/app/src/Controller/API/Admin/Users.php @@ -1,11 +1,13 @@ withJson($response, $output); } + public function edit(ServerRequestInterface $request, ResponseInterface $response, + Service\Login $loginService, int $user_id): ResponseInterface + { + $input = $request->getParsedBody(); + $output = [ + 'input' => array_filter($input, fn($key) => $key !== 'password', ARRAY_FILTER_USE_KEY), + 'success' => false, + 'user' => null + ]; + try { + $user = $loginService->editUser($user_id, $input); + $output['success'] = true; + $output['user'] = $user; + } catch (Read|Update) {} + return $this->withJson($response, $output); + } + public function delete(ServerRequestInterface $request, ResponseInterface $response, + Service\Login $loginService, int $user_id): ResponseInterface + { + $output = [ + 'success' => false, + 'user' => null + ]; + try { + $user = $loginService->deleteUser($user_id); + $output['success'] = true; + $output['user'] = $user; + } catch (Read|Delete) {} + return $this->withJson($response, $output); + } } diff --git a/app/src/Controller/API/Login.php b/app/src/Controller/API/Login.php index b7ebeac..dd4e5f2 100644 --- a/app/src/Controller/API/Login.php +++ b/app/src/Controller/API/Login.php @@ -1,14 +1,14 @@ login($user); $output['token'] = $loginService->getToken(); } - } catch (EmptyResult) {} + } catch (EmptyResult $exception) { + $output['error'] = [ + 'code' => $exception->getCode(), + 'message' => $exception->getMessage(), + 'stackTrace' => $exception->getTraceAsString() + ]; + } return $this->withJson($response, $output); } } diff --git a/app/src/Controller/API/Ventas/MediosPago/Toku.php b/app/src/Controller/API/Ventas/MediosPago/Toku.php index c07e62d..8e1ee17 100644 --- a/app/src/Controller/API/Ventas/MediosPago/Toku.php +++ b/app/src/Controller/API/Ventas/MediosPago/Toku.php @@ -44,7 +44,7 @@ class Toku extends Controller $body = $request->getBody(); $input = json_decode($body->getContents(), true); try { - if ($tokuService->updatePago($input['payment_intent'])) { + if ($tokuService->successEvent($input)) { return $responseFactory->createResponse(204); } return $responseFactory->createResponse(409, 'Payment could not be updated'); diff --git a/app/src/Middleware/API.php b/app/src/Middleware/API.php index 7576ace..eefa1bb 100644 --- a/app/src/Middleware/API.php +++ b/app/src/Middleware/API.php @@ -27,7 +27,7 @@ class API } try { $key = $this->apiService->getKey($request); - } catch (MissingAuthorizationHeader $exception) { + } catch (MissingAuthorizationHeader) { return $this->responseFactory->createResponse(401); } if ($this->validateSimpleKey($request, $key)) { diff --git a/app/src/Service/Job.php b/app/src/Service/Job.php index c72523a..a28b641 100644 --- a/app/src/Service/Job.php +++ b/app/src/Service/Job.php @@ -38,7 +38,7 @@ class Job extends Ideal\Service public function getPending(): array { try { - return array_merge([$this, 'process'],$this->jobRepository->fetchPending()); + return array_map([$this, 'process'],$this->jobRepository->fetchPending()); } catch (EmptyResult $exception) { throw new Read(__CLASS__, $exception); } diff --git a/app/src/Service/Login.php b/app/src/Service/Login.php index c4d98fe..88f3017 100644 --- a/app/src/Service/Login.php +++ b/app/src/Service/Login.php @@ -6,6 +6,10 @@ use DateTimeImmutable; use DateInterval; use Exception; use PDOException; +use Incoviba\Exception\ServiceAction\Create; +use Incoviba\Exception\ServiceAction\Delete; +use Incoviba\Exception\ServiceAction\Read; +use Incoviba\Exception\ServiceAction\Update; use Incoviba\Common\Implement\Exception\EmptyResult; use Incoviba\Repository; use Incoviba\Model; @@ -40,6 +44,11 @@ class Login } catch (PDOException|EmptyResult) {} return false; } + + /** + * @return Model\User + * @throws Exception + */ public function getUser(): Model\User { $login = $this->repository->fetchActiveBySelector($this->selector); @@ -57,20 +66,90 @@ class Login return $this->cookie_separator; } + /** + * @param array $data + * @return Model\User + * @throws Create + * @throws Read + */ public function addUser(array $data): Model\User { try { return $this->userRepository->fetchByName($data['name']); } catch (EmptyResult) { list($passphrase, $encrypted) = $this->splitPassword($data['password']); - $password = $this->cryptoJs_aes_decrypt($encrypted, $passphrase); + try { + $password = $this->cryptoJs_aes_decrypt($encrypted, $passphrase); + } catch (Exception $exception) { + throw new Read(__CLASS__, $exception); + } $password = password_hash($password, PASSWORD_DEFAULT); $user = $this->userRepository->create([ 'name' => $data['name'], 'password' => $password, 'enabled' => $data['enabled'] ?? 1 ]); - return $this->userRepository->save($user); + try { + return $this->userRepository->save($user); + } catch (PDOException $exception) { + throw new Create(__CLASS__, $exception); + } + } + } + + /** + * @param int $user_id + * @param array $data + * @return Model\User + * @throws Read + * @throws Update + */ + public function editUser(int $user_id, array $data): Model\User + { + try { + $user = $this->userRepository->fetchById($user_id); + } catch (EmptyResult $exception) { + throw new Read(__CLASS__, $exception); + } + if (!isset($data['force']) and !$user->validate($data['old_password'])) { + throw new Read(__CLASS__); + } + list($passphrase, $encrypted) = $this->splitPassword($data['password']); + try { + $password = $this->cryptoJs_aes_decrypt($encrypted, $passphrase); + } catch (Exception $exception) { + throw new Read(__CLASS__, $exception); + } + $password = password_hash($password, PASSWORD_DEFAULT); + $userData = [ + 'password' => $password + ]; + try { + $user = $this->userRepository->edit($user, $userData); + } catch (PDOException | EmptyResult $exception) { + throw new Update(__CLASS__, $exception); + } + return $user; + } + + /** + * @param int $user_id + * @return Model\User + * @throws Delete + * @throws Read + */ + public function deleteUser(int $user_id): Model\User + { + try { + $user = $this->userRepository->fetchById($user_id); + } catch (EmptyResult $exception) { + throw new Read(__CLASS__, $exception); + } + try { + $this->userRepository->remove($user); + return $user; + } catch (PDOException | EmptyResult $exception) { + throw new Delete(__CLASS__, $exception); } } public function validateUser(Model\User $user, string $encryptedPassword): bool @@ -88,7 +167,8 @@ class Login try { $login = $this->repository->fetchActiveByUser($user->id); $this->logout($login->user); - } catch (PDOException|EmptyResult) { + } catch (PDOException | EmptyResult $exception) { + error_log($exception, 3, '/logs/exception.log'); } try { @@ -104,7 +184,8 @@ class Login $this->repository->save($login); $this->saveCookie($selector, $token, $login->dateTime->add(new DateInterval("PT{$this->max_login_time}H"))); return true; - } catch (PDOException|Exception) { + } catch (PDOException | Exception $exception) { + error_log($exception, 3, '/logs/exception.log'); return false; } } diff --git a/app/src/Service/Queue.php b/app/src/Service/Queue.php index 0630319..042ce39 100644 --- a/app/src/Service/Queue.php +++ b/app/src/Service/Queue.php @@ -44,7 +44,7 @@ class Queue extends Ideal\Service return false; } - $status = true; + $errors = []; foreach ($jobs as $job) { $type = 'default'; if (isset($job->configuration['type'])) { @@ -57,13 +57,15 @@ class Queue extends Ideal\Service $worker = $this->workers[$type]; try { - $status &= $worker->run($job); + if (!$worker->execute($job)) { + $errors []= $job->id; + } } catch (Exception $exception) { $final = new Exception("Could not run job", 0, $exception); $this->logger->warning($final); - $status &= false; + $errors []= $job->id; } } - return $status; + return count($errors) === 0; } } diff --git a/app/src/Service/Venta/MediosPago/Toku.php b/app/src/Service/Venta/MediosPago/Toku.php index 67a805e..652e565 100644 --- a/app/src/Service/Venta/MediosPago/Toku.php +++ b/app/src/Service/Venta/MediosPago/Toku.php @@ -131,12 +131,36 @@ class Toku extends Ideal\Service return $invoices; } + /** + * @param array $input + * @return bool + * @throws InvalidResult + */ + public function successEvent(array $input): bool + { + $validEvents = ['payment_intent.succeeded', 'payment.succeeded', 'transaction.success', + 'transaction.bulk_success', 'payment_intent.succeeded_batch']; + if (!in_array($input['event_type'], $validEvents)) { + throw new InvalidResult("{$input['event_type']} is not a valid event", 422); + } + switch ($input['event_type']) { + case 'payment_intent.succeeded_batch': + case 'transaction.bulk_success': + return $this->successBulk($input); + case 'transaction.success': + return $this->successTransaction($input); + default: + $paymentData = $this->mapEventData($input); + return $this->updatePago($paymentData); + } + } + /** * @param array $request * @return bool * @throws InvalidResult */ - public function updatePago(array $request): bool + protected function updatePago(array $request): bool { # If $customer is not found, it will throw an exception and stop $customer = $this->customer->getByExternalId($request['customer']); @@ -144,4 +168,104 @@ class Toku extends Ideal\Service return $this->invoice->update($invoice['id'], $request); } + protected function successTransaction(array $input): bool + { + $intents = $this->mapMultiplePaymentIntentsData($input); + $errors = []; + foreach ($intents as $intent) { + if (!$this->updatePago($intent)) { + $errors []= $intent; + } + } + if (array_key_exists('wallet_movements', $input)) { + foreach ($input['wallet_movements'] as $walletMovement) { + if (array_key_exists('type', $walletMovement) and $walletMovement['type'] === 'SURPLUS') { + $this->logger->alert('Revisar el envío de cuotas de la Venta ' . $walletMovement['product_id']); + } + } + } + return count($errors) === 0; + } + /** + * @param array $input + * @return bool + * @throws InvalidResult + */ + protected function successBulk(array $input): bool + { + return match($input['event_type']) { + 'payment_intent.succeeded_batch' => $this->successBulkPaymentIntent($input), + 'transaction.bulk_success' => $this->successBulkTransaction($input), + default => false + }; + } + /** + * @param array $input + * @return bool + * @throws InvalidResult + */ + protected function successBulkTransaction(array $input): bool + { + $errors = []; + foreach($input['events'] as $event) { + $event['event_type'] = 'transaction.success'; + if (!$this->successEvent($event)) { + $errors []= $event; + } + } + return count($errors) === 0; + } + /** + * @param array $input + * @return bool + * @throws InvalidResult + */ + protected function successBulkPaymentIntent(array $input): bool + { + $errors = []; + foreach($input['payment_intent'] as $intent) { + $intent['event_type'] = 'payment_intent.succeeded'; + $intent['payment_intent'] = $input['payment_intents']; + unset($intent['payment_intents']); + if (!$this->successEvent($intent)) { + $errors []= $intent; + } + } + return count($errors) === 0; + } + protected function mapEventData(array $input): array + { + return match ($input['event_type']) { + 'payment_intent.succeeded' => $this->mapPaymentIntentData($input), + 'payment.succeeded' => $this->mapPaymentEventData($input), + default => [], + }; + } + protected function mapMultiplePaymentIntentsData(array $input): array + { + $output = []; + foreach ($input['payment_intents'] as $intent) { + $intent['transaction_date'] = $input['transaction']['transaction_date']; + $intent['customer'] = $input['customer']['id']; + $intent['invoice'] = $intent['id_invoice']; + $intent['subscription'] = $intent['id_subscription']; + $intent['cuota_id'] = $intent['invoice_external_id']; + $o = $this->mapPaymentIntentData($intent); + $output []= $o; + } + return $output; + } + protected function mapPaymentEventData(array $input): array + { + $data = $input['payment']; + $data['status'] = 'AUTHORIZED'; + $data['date'] = $data['payment_date']; + return $data; + } + protected function mapPaymentIntentData(array $input): array + { + $data = $input['payment_intent']; + $data['date'] = $data['transaction_date']; + return $data; + } } diff --git a/app/src/Service/Venta/MediosPago/Toku/Invoice.php b/app/src/Service/Venta/MediosPago/Toku/Invoice.php index cc1ef40..eac203c 100644 --- a/app/src/Service/Venta/MediosPago/Toku/Invoice.php +++ b/app/src/Service/Venta/MediosPago/Toku/Invoice.php @@ -66,14 +66,15 @@ class Invoice extends AbstractEndPoint if ($data['status'] !== 'AUTHORIZED') { throw new InvalidResult("Pago no autorizado", 422); } + $dateString = $data['date']; try { - $date = new DateTimeImmutable($data['transaction_date']); + $date = new DateTimeImmutable($dateString); } catch (DateMalformedStringException $exception) { - throw new InvalidResult("Fecha no válida: {$data['transaction_date']}", 422, $exception); + throw new InvalidResult("Fecha no válida: {$dateString}", 422, $exception); } $uf = $this->ufService->get($date); if ($uf === 0.0) { - throw new InvalidResult("No hay UF para la fecha: {$data['transaction_date']}", 422); + throw new InvalidResult("No hay UF para la fecha: {$dateString}", 422); } $valor = $data['amount'] / $uf; if (abs($valor - $invoice->cuota->pago->valor()) >= 0.0001) { diff --git a/app/src/Service/Worker/Request.php b/app/src/Service/Worker/Request.php index 9619cac..0b1c791 100644 --- a/app/src/Service/Worker/Request.php +++ b/app/src/Service/Worker/Request.php @@ -2,13 +2,14 @@ namespace Incoviba\Service\Worker; use Incoviba\Common\Implement\Exception\EmptyResponse; +use Incoviba\Service\Worker; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; use Psr\Log\LoggerInterface; use Incoviba\Common\Ideal; use Incoviba\Model; -class Request extends Ideal\Service +class Request extends Ideal\Service implements Worker { public function __construct(LoggerInterface $logger, protected ClientInterface $client) {