RebelCode/rcmod-eddbk-cart

View on GitHub
src/Module/SubmitBookingOnPaymentHandler.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php

namespace RebelCode\EddBookings\Cart\Module;

use ArrayAccess;
use Dhii\Data\Container\ContainerGetCapableTrait;
use Dhii\Data\Container\ContainerGetPathCapableTrait;
use Dhii\Data\Container\CreateContainerExceptionCapableTrait;
use Dhii\Data\Container\CreateNotFoundExceptionCapableTrait;
use Dhii\Data\Container\NormalizeKeyCapableTrait;
use Dhii\Data\StateAwareFactoryInterface;
use Dhii\Data\TransitionerInterface;
use Dhii\Exception\CreateInvalidArgumentExceptionCapableTrait;
use Dhii\Exception\CreateOutOfRangeExceptionCapableTrait;
use Dhii\I18n\StringTranslatingTrait;
use Dhii\Invocation\InvocableInterface;
use Dhii\Iterator\CountIterableCapableTrait;
use Dhii\Iterator\ResolveIteratorCapableTrait;
use Dhii\Storage\Resource\SelectCapableInterface;
use Dhii\Storage\Resource\UpdateCapableInterface;
use Dhii\Util\Normalization\NormalizeArrayCapableTrait;
use Dhii\Util\Normalization\NormalizeIntCapableTrait;
use Dhii\Util\Normalization\NormalizeIterableCapableTrait;
use Dhii\Util\Normalization\NormalizeStringCapableTrait;
use Dhii\Util\String\StringableInterface as Stringable;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\EventManager\EventInterface;
use RebelCode\EddBookings\Logic\Module\BookingTransitionInterface as T;
use stdClass;
use Traversable;

/**
 * The handler that updates and submits bookings when an EDD payment is complete.
 *
 * @since [*next-version*]
 */
class SubmitBookingOnPaymentHandler implements InvocableInterface
{
    /* @since [*next-version*] */
    use ContainerGetCapableTrait;

    /* @since [*next-version*] */
    use ContainerGetPathCapableTrait;

    /* @since [*next-version*] */
    use CountIterableCapableTrait;

    /* @since [*next-version*] */
    use NormalizeIntCapableTrait;

    /* @since [*next-version*] */
    use NormalizeKeyCapableTrait;

    /* @since [*next-version*] */
    use NormalizeStringCapableTrait;

    /* @since [*next-version*] */
    use NormalizeArrayCapableTrait;

    /* @since [*next-version*] */
    use NormalizeIterableCapableTrait;

    /* @since [*next-version*] */
    use ResolveIteratorCapableTrait;

    /* @since [*next-version*] */
    use CreateContainerExceptionCapableTrait;

    /* @since [*next-version*] */
    use CreateNotFoundExceptionCapableTrait;

    /* @since [*next-version*] */
    use CreateOutOfRangeExceptionCapableTrait;

    /* @since [*next-version*] */
    use CreateInvalidArgumentExceptionCapableTrait;

    /* @since [*next-version*] */
    use StringTranslatingTrait;

    /**
     * The booking transitioner.
     *
     * @since [*next-version*]
     *
     * @var TransitionerInterface
     */
    protected $transitioner;

    /**
     * The factory used for creating state-aware bookings.
     *
     * @since [*next-version*]
     *
     * @var StateAwareFactoryInterface
     */
    protected $stateAwareFactory;

    /**
     * The bookings SELECT resource model.
     *
     * @since [*next-version*]
     *
     * @var SelectCapableInterface
     */
    protected $bookingsSelectRm;

    /**
     * The bookings UPDATE resource model.
     *
     * @since [*next-version*]
     *
     * @var UpdateCapableInterface
     */
    protected $bookingsUpdateRm;

    /**
     * The expression builder.
     *
     * @since [*next-version*]
     *
     * @var object
     */
    protected $exprBuilder;

    /**
     * The cart item data config.
     *
     * @since [*next-version*]
     *
     * @var array|stdClass|ArrayAccess|ContainerInterface
     */
    protected $cartItemConfig;

    /**
     * Constructor.
     *
     * @since [*next-version*]
     *
     * @param TransitionerInterface                         $transitioner      The booking transitioner.
     * @param StateAwareFactoryInterface                    $stateAwareFactory The factory to use for creating
     *                                                                         state-aware bookings.
     * @param SelectCapableInterface                        $bookingsSelectRm  The bookings SELECT resource model.
     * @param UpdateCapableInterface                        $bookingsUpdateRm  The bookings UPDATE resource model.
     * @param object                                        $exprBuilder       The expression builder.
     * @param array|stdClass|ArrayAccess|ContainerInterface $cartItemConfig    The cart item data config.
     */
    public function __construct(
        TransitionerInterface $transitioner,
        StateAwareFactoryInterface $stateAwareFactory,
        SelectCapableInterface $bookingsSelectRm,
        UpdateCapableInterface $bookingsUpdateRm,
        $exprBuilder,
        $cartItemConfig
    ) {
        $this->transitioner      = $transitioner;
        $this->stateAwareFactory = $stateAwareFactory;
        $this->bookingsSelectRm  = $bookingsSelectRm;
        $this->bookingsUpdateRm  = $bookingsUpdateRm;
        $this->exprBuilder       = $exprBuilder;
        $this->cartItemConfig    = $cartItemConfig;
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    public function __invoke()
    {
        // Get the event instance and ensure it implements the interface
        $event = func_get_arg(0);
        if (!($event instanceof EventInterface)) {
            throw $this->_createInvalidArgumentException(
                $this->__('Argument is not an event instance'), null, null, $event
            );
        }

        // Get the event params
        $paymentId = $event->getParam(0);
        $newStatus = $event->getParam(1);
        $oldStatus = $event->getParam(2);

        // Ensure payment was not already completed before. If it was, stop here.
        if ($oldStatus === 'publish' || $oldStatus === 'complete') {
            return;
        }
        // Check if the payment is being updated to a status of "completion". If not, stop here.
        if ($newStatus !== 'publish' && $newStatus !== 'complete') {
            return;
        }

        // Get the payment meta
        $paymentMeta = $this->_getPaymentMetaData($paymentId);
        // Get the items that were in the cart for this payment
        $items = $this->_containerGet($paymentMeta, 'downloads');

        // Get the cart item data keys from the cart item config
        $dataKey       = $this->_containerGetPath($this->cartItemConfig, ['data', 'key']);
        $eddBkKey      = $this->_containerGetPath($this->cartItemConfig, ['data', 'eddbk_key']);
        $bookingIdKey  = $this->_containerGetPath($this->cartItemConfig, ['data', 'booking_id_key']);
        $bookingIdPath = [$dataKey, $eddBkKey, $bookingIdKey];

        // Alias expression builder
        $b = $this->exprBuilder;

        foreach ($items as $_item) {
            try {
                // Get the booking ID from the additional item data
                $_bookingId = $this->_containerGetPath($_item, $bookingIdPath);
            } catch (NotFoundExceptionInterface $exception) {
                continue;
            }

            // Fetch the corresponding booking from storage
            $_condition = $b->eq($b->var('id'), $b->lit($_bookingId));
            $_bookings  = $this->bookingsSelectRm->select($_condition);
            // Stop if no booking was found, or if multiple bookings matched the ID (for some reason?)
            if ($this->_countIterable($_bookings) !== 1) {
                continue;
            }

            // Get the booking data
            $_bookingData = reset($_bookings);
            // Create the booking
            $_booking = $this->stateAwareFactory->make([
                StateAwareFactoryInterface::K_DATA => $_bookingData,
            ]);

            // Transition the booking
            $_booking = $this->transitioner->transition($_booking, T::TRANSITION_SUBMIT);

            // Prepare the change set
            $_bookingData = $_booking->getState();
            $_paymentData = [
                'payment_id' => $paymentId,
                'client_id'  => $this->_getPaymentCustomerId($paymentId),
            ];
            $_changeSet   = $this->_patchBookingData($_bookingData, $_paymentData);

            // Update the booking
            $this->bookingsUpdateRm->update($_changeSet, $_condition);
        }

        return;
    }

    /**
     * Patches the given booking data with a change set.
     *
     * @since [*next-version*]
     *
     * @param array|stdClass|Traversable $bookingData The booking data.
     * @param array|stdClass|Traversable $changeSet   The change set.
     *
     * @return array|stdClass|Traversable The patched data.
     */
    protected function _patchBookingData($bookingData, $changeSet)
    {
        $patched = $this->_normalizeArray($bookingData);

        foreach ($changeSet as $_key => $_val) {
            $patched[$_key] = $_val;
        }

        return $patched;
    }

    /**
     * Retrieves the meta data for a payment, given by its ID.
     *
     * @since [*next-version*]
     *
     * @param int|string $paymentId The payment ID.
     *
     * @return array|stdClass|ArrayAccess|ContainerInterface
     */
    protected function _getPaymentMetaData($paymentId)
    {
        return \edd_get_payment_meta($paymentId);
    }

    /**
     * Retrieves the customer ID for a payment, given by its ID.
     *
     * @since [*next-version*]
     *
     * @param int|string $paymentId The payment ID.
     *
     * @return int|string|Stringable The customer ID.
     */
    protected function _getPaymentCustomerId($paymentId)
    {
        return \edd_get_payment_customer_id($paymentId);
    }
}