Refactoring revocation bookable rooms reservations
refactored task due a need on registration new feature Change-Id: I658954d8b7132b183595a4bdeb1634e2e681ddec
This commit is contained in:
parent
f25ebdb2bf
commit
037a1420bb
@ -12,6 +12,7 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
use App\Models\Foundation\Summit\Repositories\ISummitRoomReservationRepository;
|
||||
use App\Services\Model\ILocationService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use libs\utils\ITransactionService;
|
||||
use models\summit\SummitRoomReservation;
|
||||
@ -52,29 +53,22 @@ final class SummitRoomReservationRevocationCommand extends Command {
|
||||
|
||||
|
||||
/**
|
||||
* @var ISummitRoomReservationRepository
|
||||
* @var ILocationService
|
||||
*/
|
||||
private $reservations_repository;
|
||||
private $location_service;
|
||||
|
||||
/**
|
||||
* @var ITransactionService
|
||||
*/
|
||||
private $tx_service;
|
||||
|
||||
/**
|
||||
* SummitRoomReservationRevocationCommand constructor.
|
||||
* @param ISummitRoomReservationRepository $reservations_repository
|
||||
* @param ITransactionService $tx_service
|
||||
* @param ILocationService $location_service
|
||||
*/
|
||||
public function __construct
|
||||
(
|
||||
ISummitRoomReservationRepository $reservations_repository,
|
||||
ITransactionService $tx_service
|
||||
ILocationService $location_service
|
||||
)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->reservations_repository = $reservations_repository;
|
||||
$this->tx_service = $tx_service;
|
||||
$this->location_service = $location_service;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,20 +90,7 @@ final class SummitRoomReservationRevocationCommand extends Command {
|
||||
$start = time();
|
||||
$lifetime = intval(Config::get("bookable_rooms.reservation_lifetime", 30));
|
||||
Log::info(sprintf("SummitRoomReservationRevocationCommand: using lifetime of %s ", $lifetime));
|
||||
$this->tx_service->transaction(function() use($lifetime){
|
||||
$filter = new Filter();
|
||||
$filter->addFilterCondition(FilterElement::makeEqual('status', SummitRoomReservation::ReservedStatus));
|
||||
$eol = new \DateTime('now', new \DateTimeZone(SilverstripeBaseModel::DefaultTimeZone));
|
||||
|
||||
$eol->sub(new \DateInterval('PT'.$lifetime.'M'));
|
||||
$filter->addFilterCondition(FilterElement::makeLowerOrEqual('created', $eol->getTimestamp() ));
|
||||
$page = $this->reservations_repository->getAllByPage(new PagingInfo(1, 100), $filter);
|
||||
foreach($page->getItems() as $reservation){
|
||||
Log::warning(sprintf("cancelling reservation %s create at %s", $reservation->getId(), $reservation->getCreated()->format("Y-m-d h:i:sa")));
|
||||
$reservation->cancel();
|
||||
}
|
||||
});
|
||||
|
||||
$this->location_service->revokeBookableRoomsReservedOlderThanNMinutes($lifetime);
|
||||
$end = time();
|
||||
$delta = $end - $start;
|
||||
$this->info(sprintf("execution call %s seconds", $delta));
|
||||
|
@ -11,7 +11,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use models\summit\Summit;
|
||||
use models\summit\SummitRoomReservation;
|
||||
use models\utils\IBaseRepository;
|
||||
@ -19,7 +18,6 @@ use utils\Filter;
|
||||
use utils\Order;
|
||||
use utils\PagingInfo;
|
||||
use utils\PagingResponse;
|
||||
|
||||
/**
|
||||
* Interface ISummitRoomReservationRepository
|
||||
* @package App\Models\Foundation\Summit\Repositories
|
||||
@ -30,7 +28,7 @@ interface ISummitRoomReservationRepository extends IBaseRepository
|
||||
* @param string $payment_gateway_cart_id
|
||||
* @return SummitRoomReservation|null
|
||||
*/
|
||||
public function getByPaymentGatewayCartId(string $payment_gateway_cart_id): ?SummitRoomReservation;
|
||||
public function getByPaymentGatewayCartIdExclusiveLock(string $payment_gateway_cart_id): ?SummitRoomReservation;
|
||||
|
||||
/**
|
||||
* @param Summit $summit
|
||||
@ -40,4 +38,12 @@ interface ISummitRoomReservationRepository extends IBaseRepository
|
||||
* @return PagingResponse
|
||||
*/
|
||||
public function getAllBySummitByPage(Summit $summit, PagingInfo $paging_info, Filter $filter = null, Order $order = null): PagingResponse;
|
||||
|
||||
/**
|
||||
* @param int $minutes
|
||||
* @param int $max
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getAllReservedOlderThanXMinutes(int $minutes, int $max = 100);
|
||||
}
|
@ -26,6 +26,12 @@ interface IBaseRepository
|
||||
*/
|
||||
public function getById($id);
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return IEntity
|
||||
*/
|
||||
public function getByIdExclusiveLock($id);
|
||||
|
||||
/**
|
||||
* @param IEntity $entity
|
||||
* @param bool $sync
|
||||
|
@ -31,11 +31,23 @@ use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
abstract class DoctrineRepository extends EntityRepository implements IBaseRepository
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return IEntity|null|object
|
||||
*/
|
||||
public function getById($id)
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return IEntity|null|object
|
||||
*/
|
||||
public function getByIdExclusiveLock($id){
|
||||
return $this->find($id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $entity
|
||||
* @param bool $sync
|
||||
|
@ -16,6 +16,7 @@ use App\Repositories\SilverStripeDoctrineRepository;
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use models\summit\Summit;
|
||||
use models\summit\SummitRoomReservation;
|
||||
use models\utils\SilverstripeBaseModel;
|
||||
use utils\DoctrineFilterMapping;
|
||||
use utils\DoctrineJoinFilterMapping;
|
||||
use utils\Filter;
|
||||
@ -26,7 +27,7 @@ use utils\PagingResponse;
|
||||
* Class DoctrineSummitRoomReservationRepository
|
||||
* @package App\Repositories\Summit
|
||||
*/
|
||||
class DoctrineSummitRoomReservationRepository
|
||||
final class DoctrineSummitRoomReservationRepository
|
||||
extends SilverStripeDoctrineRepository
|
||||
implements ISummitRoomReservationRepository
|
||||
{
|
||||
@ -156,10 +157,42 @@ class DoctrineSummitRoomReservationRepository
|
||||
* @param string $payment_gateway_cart_id
|
||||
* @return SummitRoomReservation|null
|
||||
*/
|
||||
public function getByPaymentGatewayCartId(string $payment_gateway_cart_id):?SummitRoomReservation
|
||||
public function getByPaymentGatewayCartIdExclusiveLock(string $payment_gateway_cart_id):?SummitRoomReservation
|
||||
{
|
||||
return $this->findOneBy(["payment_gateway_cart_id" => trim($payment_gateway_cart_id)]);
|
||||
$query = $this->getEntityManager()
|
||||
->createQueryBuilder()
|
||||
->select("e")
|
||||
->from($this->getBaseEntity(), "e")
|
||||
->where("e.payment_gateway_cart_id = payment_gateway_cart_id");
|
||||
|
||||
$query->setParameter("payment_gateway_cart_id", trim($payment_gateway_cart_id));
|
||||
|
||||
return $query->getQuery()->setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $minutes
|
||||
* @param int $max
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getAllReservedOlderThanXMinutes(int $minutes, int $max = 100)
|
||||
{
|
||||
$eol = new \DateTime('now', new \DateTimeZone(SilverstripeBaseModel::DefaultTimeZone));
|
||||
$eol->sub(new \DateInterval('PT' . $minutes . 'M'));
|
||||
|
||||
$query = $this->getEntityManager()
|
||||
->createQueryBuilder()
|
||||
->select("e")
|
||||
->from($this->getBaseEntity(), "e")
|
||||
->where("e.created <= :eol")
|
||||
->andWhere("e.status = :status");
|
||||
|
||||
$query->setParameter("eol", $eol);
|
||||
$query->setParameter("status", SummitRoomReservation::ReservedStatus);
|
||||
|
||||
return $query->getQuery()->setMaxResults($max)->getResult();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -12,6 +12,15 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Illuminate\Http\Request as LaravelRequest;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class CartAlreadyPaidException
|
||||
* @package App\Services\Apis
|
||||
*/
|
||||
class CartAlreadyPaidException extends Exception {
|
||||
|
||||
}
|
||||
/**
|
||||
* Interface IPaymentGatewayAPI
|
||||
* @package App\Services\Apis
|
||||
@ -49,4 +58,29 @@ interface IPaymentGatewayAPI
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function refundPayment(string $cart_id, float $amount, string $currency): void;
|
||||
|
||||
/**
|
||||
* @param string $cart_id
|
||||
* @return mixed|void
|
||||
* @throws CartAlreadyPaidException
|
||||
*/
|
||||
public function abandonCart(string $cart_id);
|
||||
|
||||
/**
|
||||
* @param string $status
|
||||
* @return bool
|
||||
*/
|
||||
public function canAbandon(string $status):bool;
|
||||
|
||||
/**
|
||||
* @param string $cart_id
|
||||
* @return string
|
||||
*/
|
||||
public function getCartStatus(string $cart_id):string;
|
||||
|
||||
/**
|
||||
* @param string $status
|
||||
* @return bool
|
||||
*/
|
||||
public function isSucceeded(string $status):bool;
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use App\Services\Apis\CartAlreadyPaidException;
|
||||
use App\Services\Apis\IPaymentGatewayAPI;
|
||||
use Illuminate\Http\Request as LaravelRequest;
|
||||
use models\exceptions\ValidationException;
|
||||
@ -245,4 +246,70 @@ final class StripeApi implements IPaymentGatewayAPI
|
||||
}
|
||||
$charge->refund($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cart_id
|
||||
* @return mixed|void
|
||||
* @throws CartAlreadyPaidException
|
||||
*/
|
||||
public function abandonCart(string $cart_id)
|
||||
{
|
||||
if(empty($this->api_key))
|
||||
throw new \InvalidArgumentException();
|
||||
|
||||
Stripe::setApiKey($this->api_key);
|
||||
$intent = PaymentIntent::retrieve($cart_id);
|
||||
|
||||
if(is_null($intent))
|
||||
throw new \InvalidArgumentException();
|
||||
|
||||
if(!in_array($intent->status,[ PaymentIntent::STATUS_REQUIRES_PAYMENT_METHOD,
|
||||
PaymentIntent::STATUS_REQUIRES_CAPTURE,
|
||||
PaymentIntent::STATUS_REQUIRES_CONFIRMATION,
|
||||
PaymentIntent::STATUS_REQUIRES_ACTION
|
||||
]))
|
||||
throw new CartAlreadyPaidException(sprintf("cart id %s has status %s", $cart_id, $intent->status));
|
||||
|
||||
$intent->cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $status
|
||||
* @return bool
|
||||
*/
|
||||
public function canAbandon(string $status): bool
|
||||
{
|
||||
return in_array($status,[
|
||||
PaymentIntent::STATUS_REQUIRES_PAYMENT_METHOD,
|
||||
PaymentIntent::STATUS_REQUIRES_CAPTURE,
|
||||
PaymentIntent::STATUS_REQUIRES_CONFIRMATION,
|
||||
PaymentIntent::STATUS_REQUIRES_ACTION
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $status
|
||||
* @return bool
|
||||
*/
|
||||
public function isSucceeded(string $status):bool {
|
||||
return $status == PaymentIntent::STATUS_SUCCEEDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cart_id
|
||||
* @return string
|
||||
*/
|
||||
public function getCartStatus(string $cart_id): string
|
||||
{
|
||||
if(empty($this->api_key))
|
||||
throw new \InvalidArgumentException();
|
||||
|
||||
Stripe::setApiKey($this->api_key);
|
||||
$intent = PaymentIntent::retrieve($cart_id);
|
||||
|
||||
if(is_null($intent))
|
||||
throw new \InvalidArgumentException();
|
||||
|
||||
return $intent->status;
|
||||
}
|
||||
}
|
@ -329,4 +329,9 @@ interface ILocationService
|
||||
* @return SummitVenueRoom
|
||||
*/
|
||||
public function removeRoomImage(Summit $summit, int $venue_id, int $room_id):SummitVenueRoom;
|
||||
|
||||
/**
|
||||
* @param int $minutes
|
||||
*/
|
||||
public function revokeBookableRoomsReservedOlderThanNMinutes(int $minutes):void;
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\Events\CreatedBookableRoomReservation;
|
||||
use App\Events\FloorDeleted;
|
||||
use App\Events\FloorInserted;
|
||||
@ -55,6 +56,7 @@ use models\summit\SummitRoomReservation;
|
||||
use models\summit\SummitVenue;
|
||||
use models\summit\SummitVenueFloor;
|
||||
use models\summit\SummitVenueRoom;
|
||||
|
||||
/**
|
||||
* Class SummitLocationService
|
||||
* @package App\Services\Model
|
||||
@ -1710,14 +1712,14 @@ final class SummitLocationService
|
||||
throw new EntityNotFoundException('member not found');
|
||||
}
|
||||
|
||||
if($owner->getReservationsCountBySummit($summit) >= $summit->getMeetingRoomBookingMaxAllowed())
|
||||
throw new ValidationException(sprintf("member %s already reached maximun quantity of reservations (%s)", $owner->getId(), $summit->getMeetingRoomBookingMaxAllowed() ));
|
||||
if ($owner->getReservationsCountBySummit($summit) >= $summit->getMeetingRoomBookingMaxAllowed())
|
||||
throw new ValidationException(sprintf("member %s already reached maximun quantity of reservations (%s)", $owner->getId(), $summit->getMeetingRoomBookingMaxAllowed()));
|
||||
|
||||
$payload['owner'] = $owner;
|
||||
|
||||
$currency = trim($payload['currency']);
|
||||
|
||||
if($room->getCurrency() != $currency){
|
||||
if ($room->getCurrency() != $currency) {
|
||||
throw new ValidationException
|
||||
(
|
||||
sprintf
|
||||
@ -1731,7 +1733,7 @@ final class SummitLocationService
|
||||
|
||||
$amount = intval($payload['amount']);
|
||||
|
||||
if($room->getTimeSlotCost() != $amount){
|
||||
if ($room->getTimeSlotCost() != $amount) {
|
||||
throw new ValidationException
|
||||
(
|
||||
sprintf
|
||||
@ -1751,20 +1753,20 @@ final class SummitLocationService
|
||||
$result = $this->payment_gateway->generatePayment
|
||||
(
|
||||
[
|
||||
"amount" => $reservation->getAmount(),
|
||||
"currency" => $reservation->getCurrency(),
|
||||
"amount" => $reservation->getAmount(),
|
||||
"currency" => $reservation->getCurrency(),
|
||||
"receipt_email" => $reservation->getOwner()->getEmail(),
|
||||
"metadata" => [
|
||||
"type" => "bookable_room_reservation",
|
||||
"metadata" => [
|
||||
"type" => "bookable_room_reservation",
|
||||
"room_id" => $room->getId(),
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
if(!isset($result['cart_id']))
|
||||
if (!isset($result['cart_id']))
|
||||
throw new ValidationException("payment gateway error");
|
||||
|
||||
if(!isset($result['client_token']))
|
||||
if (!isset($result['client_token']))
|
||||
throw new ValidationException("payment gateway error");
|
||||
|
||||
$reservation->setPaymentGatewayCartId($result['cart_id']);
|
||||
@ -1782,7 +1784,7 @@ final class SummitLocationService
|
||||
{
|
||||
$this->tx_service->transaction(function () use ($payload) {
|
||||
|
||||
$reservation = $this->reservation_repository->getByPaymentGatewayCartId($payload['cart_id']);
|
||||
$reservation = $this->reservation_repository->getByPaymentGatewayCartIdExclusiveLock($payload['cart_id']);
|
||||
|
||||
if (is_null($reservation)) {
|
||||
throw new EntityNotFoundException(sprintf("there is no reservation with cart_id %s", $payload['cart_id']));
|
||||
@ -1794,8 +1796,7 @@ final class SummitLocationService
|
||||
$reservation->setPaid();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ValidationException $ex){
|
||||
} catch (ValidationException $ex) {
|
||||
Log::error($ex);
|
||||
Log::warning("doing refund of cancelled reservation");
|
||||
$reservation->setStatus(SummitRoomReservation::RequestedRefundStatus);
|
||||
@ -1860,9 +1861,9 @@ final class SummitLocationService
|
||||
throw new EntityNotFoundException();
|
||||
}
|
||||
|
||||
$status = $reservation->getStatus();
|
||||
$status = $reservation->getStatus();
|
||||
$validStatuses = [SummitRoomReservation::RequestedRefundStatus, SummitRoomReservation::PayedStatus];
|
||||
if(!in_array($status, $validStatuses))
|
||||
if (!in_array($status, $validStatuses))
|
||||
throw new ValidationException
|
||||
(
|
||||
sprintf
|
||||
@ -1872,18 +1873,17 @@ final class SummitLocationService
|
||||
)
|
||||
);
|
||||
|
||||
if($amount <= 0){
|
||||
if ($amount <= 0) {
|
||||
throw new ValidationException("can not refund an amount lower than zero!");
|
||||
}
|
||||
|
||||
if($amount > intval($reservation->getAmount())){
|
||||
if ($amount > intval($reservation->getAmount())) {
|
||||
throw new ValidationException("can not refund an amount greater than paid one!");
|
||||
}
|
||||
|
||||
try{
|
||||
try {
|
||||
$this->payment_gateway->refundPayment($reservation->getPaymentGatewayCartId(), $amount, $reservation->getCurrency());
|
||||
}
|
||||
catch (\Exception $ex){
|
||||
} catch (\Exception $ex) {
|
||||
throw new ValidationException($ex->getMessage());
|
||||
}
|
||||
|
||||
@ -2153,7 +2153,7 @@ final class SummitLocationService
|
||||
if (!$venue instanceof SummitVenue) {
|
||||
throw new EntityNotFoundException
|
||||
(
|
||||
"venue not found"
|
||||
"venue not found"
|
||||
);
|
||||
}
|
||||
|
||||
@ -2277,7 +2277,7 @@ final class SummitLocationService
|
||||
if (is_null($room)) {
|
||||
throw new EntityNotFoundException
|
||||
(
|
||||
'room not found'
|
||||
'room not found'
|
||||
);
|
||||
}
|
||||
|
||||
@ -2296,7 +2296,7 @@ final class SummitLocationService
|
||||
throw new ValidationException(sprintf("file exceeds max_file_size (%s MB).", ($max_file_size / 1024) / 1024));
|
||||
}
|
||||
|
||||
$image = $this->file_uploader->build($file, sprintf('summits/%s/locations/%s/rooms', $summit->getId(), $venue_id ), true);
|
||||
$image = $this->file_uploader->build($file, sprintf('summits/%s/locations/%s/rooms', $summit->getId(), $venue_id), true);
|
||||
$room->setImage($image);
|
||||
|
||||
return $image;
|
||||
@ -2331,7 +2331,7 @@ final class SummitLocationService
|
||||
);
|
||||
}
|
||||
|
||||
if(!$room->hasImage())
|
||||
if (!$room->hasImage())
|
||||
throw new ValidationException("room has no image set");
|
||||
|
||||
$room->clearImage();
|
||||
@ -2339,4 +2339,43 @@ final class SummitLocationService
|
||||
return $room;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $minutes
|
||||
*/
|
||||
public function revokeBookableRoomsReservedOlderThanNMinutes(int $minutes): void
|
||||
{
|
||||
// this is done in this way to avoid db lock contentions
|
||||
$reservations = $this->tx_service->transaction(function () use ($minutes) {
|
||||
return $this->reservation_repository->getAllReservedOlderThanXMinutes($minutes);
|
||||
});
|
||||
|
||||
foreach ($reservations as $reservation) {
|
||||
|
||||
$this->tx_service->transaction(function () use ($reservation) {
|
||||
|
||||
try {
|
||||
$reservation = $this->reservation_repository->getByIdExclusiveLock($reservation->getId());
|
||||
if (!$reservation instanceof SummitRoomReservation) return;
|
||||
|
||||
Log::warning(sprintf("cancelling reservation %s created at %s", $reservation->getId(), $reservation->getCreated()->format("Y-m-d h:i:sa")));
|
||||
$status = $this->payment_gateway->getCartStatus($reservation->getPaymentGatewayCartId());
|
||||
if (!$this->payment_gateway->canAbandon($status)) {
|
||||
Log::warning(sprintf("reservation %s created at %s can not be cancelled external status %s", $reservation->getId(), $reservation->getCreated()->format("Y-m-d h:i:sa"), $status));
|
||||
if($this->payment_gateway->isSucceeded($status)){
|
||||
$reservation->setPaid();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$this->payment_gateway->abandonCart($reservation->getPaymentGatewayCartId());
|
||||
|
||||
$reservation->cancel();
|
||||
} catch (\Exception $ex) {
|
||||
Log::warning($ex);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user