Skip to content

Commit

Permalink
1. Small improving validation of order pay input data.
Browse files Browse the repository at this point in the history
2. Implementing pay order, logic with http request
3. Fixing core bug of updating entity
4. Unit tests for controller pay action and order pay service
  • Loading branch information
antonkanevsky committed Sep 26, 2019
1 parent a52d678 commit c1551ab
Show file tree
Hide file tree
Showing 11 changed files with 468 additions and 44 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"symfony/yaml": "^4.3",
"symfony/dependency-injection": "^4.3",
"symfony/config": "^4.3",
"symfony/expression-language": "^4.3"
"symfony/expression-language": "^4.3",
"guzzlehttp/guzzle": "^6.3"
},
"require-dev": {
"phpunit/phpunit": "^7.5",
Expand Down
10 changes: 10 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
imports:
- { resource: parameters/db.yaml }

parameters:
order.pay_check_url: 'http://ya.ru'

services:
_defaults:
autowire: true
Expand Down Expand Up @@ -32,4 +35,11 @@ services:

App\Service\CreateOrderServiceInterface: '@App\Service\CreateOrderService'

App\Service\OrderPayService:
arguments:
$payCheckURL: '%order.pay_check_url%'

App\Service\OrderPayServiceInterface: '@App\Service\OrderPayService'

GuzzleHttp\ClientInterface:
class: GuzzleHttp\Client
3 changes: 3 additions & 0 deletions config/services_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ services:
test.create_order_service:
alias: 'App\Service\CreateOrderService'
public: true
test.pay_order_service:
alias: 'App\Service\OrderPayService'
public: true

App\Repository\OrderRepository:
public: true
Expand Down
1 change: 0 additions & 1 deletion installer/db_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,3 @@ CREATE TABLE order_item
CONSTRAINT order_item_pkey
PRIMARY KEY (order_id, item_id)
);

72 changes: 41 additions & 31 deletions src/Controller/OrderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
namespace App\Controller;

use App\Core\RequestAwareInterface;
use App\Entity\Order;
use App\Service\CreateOrderServiceInterface;
use App\Service\Exception\APIServiceException;
use App\Service\OrderPayServiceInterface;
use App\Validator\ValidatorException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

Expand Down Expand Up @@ -93,44 +93,20 @@ public function createOrder(): JsonResponse
/**
* Оплата заказа
*
* TODO Вынести валидацию входящих данных в слой формы
*
* @return JsonResponse
*/
public function payOrder(): JsonResponse
{
$input = json_decode($this->request->getContent(), true);
$orderId = $input['id'] ?? null;
if (null === $orderId) {
return new JsonResponse(null, JsonResponse::HTTP_BAD_REQUEST);
}

$amount = $input['amount'] ?? null;
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT);
if (false === $amount) {
return new JsonResponse(null, JsonResponse::HTTP_BAD_REQUEST);
}

try {
$order = $this->orderPayService->payOrder($orderId, $amount);
if (Order::STATUS_PAID === $order->getStatus()) {
return new JsonResponse(
[
'success' => true,
]
);
}

/*
* Случай когда неполная оплата или HTTP запрос на ya.ru не удался
*/
list($orderId, $paymentSum) = $this->validatePayOrderInputData();
$this->orderPayService->payOrder($orderId, $paymentSum);

return new JsonResponse(
[
'error' => 'Partial payment or HTTP request from API server not OK',
],
JsonResponse::HTTP_BAD_REQUEST
'success' => true,
]
);
} catch (APIServiceException $e) {
} catch (APIServiceException | ValidatorException $e) {
return new JsonResponse(
[
'error' => $e->getMessage(),
Expand All @@ -139,4 +115,38 @@ public function payOrder(): JsonResponse
);
}
}

/**
* Валидация входящих данных для метода оплаты заказа
* TODO Вынести валидацию входящих данных в отдельный класс
*
* @return array
*
* @throws ValidatorException
*/
private function validatePayOrderInputData(): array
{
$input = json_decode($this->request->getContent(), true);
$orderId = $input['id'] ?? null;
$amount = $input['amount'] ?? null;

if (null === $orderId || null === $amount) {
throw new ValidatorException('Required fields are expected');
}

$amount = filter_var($amount, FILTER_VALIDATE_FLOAT);
$orderId = filter_var(
$orderId,
FILTER_VALIDATE_REGEXP,
[
'options' => ['regexp' => '/^\d+$/']
]
);

if (false === $orderId || false === $amount) {
throw new ValidatorException('Invalid input data');
}

return [$orderId, $amount];
}
}
14 changes: 8 additions & 6 deletions src/Core/BaseRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ public function save(EntityInterface $entity)
$data[$field] = $this->formatValueToDBFormat($field, $value);
}

$id = $data[static::PRIMARY_KEY] ?? null;
if (empty($id)) {
unset($data[static::PRIMARY_KEY]);
$id = $data[static::PRIMARY_KEY];
unset($data[static::PRIMARY_KEY]);

if (empty($id)) {
$id = $this->insertRow($data);
// TODO сделать проставление св-ва id через ReflectionProperty
$idSetter = 'set'.ucfirst(static::PRIMARY_KEY);
Expand Down Expand Up @@ -177,12 +177,14 @@ private function insertRow(array $data)
*/
private function updateRow(int $id, array $data)
{
$setPart = implode(' = ?,', array_map(function ($field) {
return $this->camelToSnakeCase($field);
}, array_keys($data))) . ' = ?';

$sql = sprintf(
'UPDATE "%s" SET %s WHERE %s',
static::TABLE_NAME,
implode(' = ?,', array_map(function ($field) {
return $this->camelToSnakeCase($field);
}, array_keys($data))),
$setPart,
static::PRIMARY_KEY . ' = ?'
);

Expand Down
61 changes: 56 additions & 5 deletions src/Service/OrderPayService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use App\Entity\Order;
use App\Repository\OrderRepository;
use App\Service\Exception\APIServiceException;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;

/**
* Сервис оплаты заказа
Expand All @@ -20,14 +22,35 @@ class OrderPayService implements OrderPayServiceInterface
*/
private $orderRepository;

/**
* HTTP клиент
*
* @var ClientInterface
*/
private $httpClient;

/**
* URL для http запроса
*
* @var string
*/
private $payCheckURL;

/**
* Конструктор
*
* @param OrderRepository $orderRepository
* @param ClientInterface $httpClient
* @param string $payCheckURL
*/
public function __construct(OrderRepository $orderRepository)
{
public function __construct(
OrderRepository $orderRepository,
ClientInterface $httpClient,
string $payCheckURL
) {
$this->orderRepository = $orderRepository;
$this->httpClient = $httpClient;
$this->payCheckURL = $payCheckURL;
}

/**
Expand All @@ -42,17 +65,45 @@ public function payOrder($orderId, float $paymentSum): Order
{
$order = $this->orderRepository->findById($orderId);
if (null === $order) {
throw new APIServiceException('Invalid input data');
throw new APIServiceException('Order not found');
}

if (Order::STATUS_NEW !== $order->getStatus()) {
throw new APIServiceException('Incorrect order status');
}

if ($order->getAmount() !== $paymentSum) {
return $order;
if ($paymentSum < $order->getAmount()) {
throw new APIServiceException('Partial payment is not implemented');
}

if (!$this->httpRequestIsOK()) {
throw new APIServiceException('Http request to check payment is not OK');
}

$order->setStatus(Order::STATUS_PAID);
$this->orderRepository->save($order);

return $order;
}

/**
* Проверяет успешность http запроса
*
* @return bool
*/
private function httpRequestIsOK(): bool
{
try {
$response = $this->httpClient->request(
'GET',
$this->payCheckURL
);

return $response->getStatusCode() === 200;
} catch (RequestException $e) {
// Умышленно тушим ошибки реквеста
}

return false;
}
}
13 changes: 13 additions & 0 deletions src/Validator/ValidatorException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types = 1);

namespace App\Validator;

/**
* Исключения валидации данных
*/
class ValidatorException extends \RuntimeException
{

}
Loading

0 comments on commit c1551ab

Please sign in to comment.