contacts_events-8.x-1.x-dev/src/Plugin/Commerce/CheckoutPane/BookingPaymentTrait.php

src/Plugin/Commerce/CheckoutPane/BookingPaymentTrait.php
<?php

namespace Drupal\contacts_events\Plugin\Commerce\CheckoutPane;

use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface;
use Drupal\commerce_price\Price;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\contacts_events\Event\BookingCompletionValidationEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Trait for shared code between the booking payment panes.
 */
trait BookingPaymentTrait {

  /**
   * The order.
   *
   * @var \Drupal\commerce_order\Entity\OrderInterface
   */
  protected $order;

  /**
   * The order item tracking service, if available.
   *
   * @var \Drupal\commerce_partial_payments\OrderItemTrackingInterface|null
   */
  protected $paymentTracking;

  /**
   * The booking payment private tempstore.
   *
   * @var \Drupal\Core\TempStore\PrivateTempStore
   */
  protected $tempstore;

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * The element info manager.
   *
   * @var \Drupal\Core\Render\ElementInfoManagerInterface
   */
  protected $elementInfoManager;

  /**
   * Initialise dependencies on the plugin.
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   The container.
   * @param \Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneInterface $plugin
   *   The plugin.
   */
  protected static function init(ContainerInterface $container, CheckoutPaneInterface $plugin) {
    /** @var static $plugin */
    $plugin->tempstore = $container->get('tempstore.private')->get('contacts_events.booking_flow');
    $plugin->eventDispatcher = $container->get('event_dispatcher');
    $plugin->elementInfoManager = $container->get('plugin.manager.element_info');

    if ($container->has('commerce_partial_payments.order_item_tracking')) {
      $plugin->paymentTracking = $container->get('commerce_partial_payments.order_item_tracking');
    }
  }

  /**
   * Get the total deposits required to confirm the booking.
   *
   * @param array|null $tracking
   *   An array to be filled with the payment tracking information.
   *
   * @return \Drupal\commerce_price\Price|null
   *   The deposit amount, or NULL if deposits are not available/there are no
   *   deposits to be paid.
   */
  protected function getDepositTotal(?array &$tracking = []): ?Price {
    $deposit_amount = $this->getDepositAmount();
    if (!$deposit_amount) {
      return NULL;
    }

    // If we have tracking, get the paid amount.
    $paid = $this->paymentTracking ? $this->paymentTracking->getTrackedAmountsForOrder($this->order) : [];

    /** @var \Drupal\commerce_price\Price|null $deposit_total */
    $deposit_total = NULL;
    foreach ($this->order->getItems() as $item) {
      // If this item is not stateful, include it in the deposit. If we have
      // tracking an it has already had the deposit paid, it will be reduced.
      if (!$item->hasField('state') || $item->get('state')->value == 'pending') {
        // The deposit is the lower of the event deposit amount and the item
        // price.
        $item_amount = $item->getAdjustedTotalPrice();
        $item_deposit = $item_amount->lessThan($deposit_amount) ? $item_amount : $deposit_amount;

        // If we have tracking, we should reduce the deposit by what has already
        // been paid.
        // @todo Non stateful items will be required as a deposit even if a
        // deposit has already been paid unless we have tracking.
        if (isset($paid[$item->id()])) {
          $item_deposit = $item_deposit->subtract($paid[$item->id()]);
        }

        // Only consider positive deposit amounts.
        if ($item_deposit->isPositive()) {
          // Set up tracking as per commerce partial payments.
          $tracking[] = ['target_id' => $item->id()] + $item_deposit->toArray();
          $deposit_total = $deposit_total ? $deposit_total->add($item_deposit) : $item_deposit;
        }
      }
    }

    return $deposit_total;
  }

  /**
   * Get the payment amount from the private tempstore.
   *
   * @return \Drupal\commerce_price\Price|null
   *   The payment amount, or NULL if there is none specified.
   */
  protected function getPaymentAmount(): ?Price {
    $amount = $this->tempstore->get("{$this->order->id()}:payment_amount");
    return $amount ? Price::fromArray($amount) : NULL;
  }

  /**
   * Set the payment amount. This is stored in the private tempstore.
   *
   * @param \Drupal\commerce_price\Price|null $amount
   *   The payment amount, or NULL to clear it.
   */
  protected function setPaymentAmount(?Price $amount): void {
    if ($amount) {
      $this->tempstore->set("{$this->order->id()}:payment_amount", $amount->toArray());
    }
    else {
      $this->tempstore->delete("{$this->order->id()}:payment_amount");
    }
  }

  /**
   * Build the payment intro.
   *
   * @param array $complete_form
   *   The complete form.
   *
   * @return array
   *   The payment intro.
   */
  protected function buildPaymentIntro(array $complete_form): array {
    $intro['title'] = [
      '#type' => 'html_tag',
      '#tag' => 'h2',
      '#value' => $this->t('Payment'),
    ];
    $intro['text'] = [
      '#type' => 'html_tag',
      '#tag' => 'p',
    ];

    // Adjust the text based on the next action.
    if (isset($complete_form['actions']['next']) && $complete_form['actions']['next']['#type'] == 'submit') {
      $intro['text']['#value'] = $this->t('To complete your booking, please check the details and click the %label button.', [
        '%label' => $complete_form['actions']['next']['#value'],
      ]);
    }
    else {
      $intro['text']['#value'] = $this->t('Your booking is confirmed and there is nothing to pay.');
    }
    return $intro;
  }

  /**
   * Add a warning if there are pending payments.
   *
   * @param array $pane_form
   *   The pane form.
   */
  protected function addPendingWarning(array &$pane_form): void {
    if ($pane_form['balances']['#has_pending']) {
      $pane_form['pending_warning'] = [
        '#theme' => 'status_messages',
        '#message_list' => [
          'warning' => [$this->t('You have pending payments. Continuing may result in over payment.')],
        ],
      ];
    }
  }

  /**
   * Run the completion validation, preventing continuation on error.
   *
   * @param array $complete_form
   *   The complete form array.
   *
   * @return bool
   *   TRUE if there are no errors, FALSE if there are. In either case there may
   *   be warnings.
   */
  protected function runCompletionValidation(array &$complete_form): bool {
    // Validate that we are OK to proceed with completing the checkout process.
    $validation_event = new BookingCompletionValidationEvent($this->order, $this->currentUser->hasPermission('can manage bookings for contacts_events'));
    $this->eventDispatcher->dispatch(BookingCompletionValidationEvent::NAME, $validation_event);

    // Add warnings and messages.
    foreach ($validation_event->getWarnings() as $message) {
      $this->messenger()->addWarning($message);
    }
    foreach ($validation_event->getErrors() as $message) {
      $this->messenger()->addError($message);
    }

    // Completely stop proceeding if we have an error.
    if ($validation_event->hasErrors()) {
      $complete_form['actions']['next']['#access'] = FALSE;
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Ge the payment summary information.
   *
   * @return array
   *   The render array.
   */
  public function getSummary() {
    $build = [
      '#type' => 'html_tag',
      '#tag' => 'dl',
      '#has_pending' => FALSE,
      '#pending_paid_in_full' => FALSE,
      'total' => [
        '#weight' => 0,
        'title' => $this->buildSummaryTitle($this->t('Booking total')),
        'value' => $this->buildSummaryAmount($this->order->getTotalPrice()),
      ],
      'paid' => [
        '#weight' => 50,
        'title' => $this->buildSummaryTitle($this->t('Paid so far')),
        'value' => $this->buildSummaryAmount($this->order->getTotalPaid()),
      ],
      'balance' => [
        '#weight' => 90,
        'title' => $this->buildSummaryTitle($this->t('Outstanding balance')),
        'value' => $this->buildSummaryAmount($this->order->getBalance()),
      ],
    ];

    // Show pending payments so users don't get confused.
    $pending = $this->checkoutFlow->getPendingAmounts();
    if ($pending) {
      $build['#has_pending'] = TRUE;
      $build['#pending_paid_in_full'] = FALSE;
      $build['pending']['#weight'] = 99;
      $balance = $this->order->getBalance();

      // Load the gateways for their labels.
      $gateways = $this->entityTypeManager
        ->getStorage('commerce_payment_gateway')
        ->loadMultiple(array_keys($pending));

      foreach ($pending as $gateway_id => $amount) {
        $build['pending'][$gateway_id]['title'] = $this->buildSummaryTitle($this->t('Pending @label', ['@label' => $gateways[$gateway_id]->label()]));

        /** @var \Drupal\commerce_price\Price $pending_amount */
        foreach ($amount as $pending_amount) {
          if ($balance->getCurrencyCode() === $pending_amount->getCurrencyCode()) {
            $balance = $balance->subtract($pending_amount);
          }
          $build['pending'][$gateway_id]['value'][$pending_amount->getCurrencyCode()] = $this->buildSummaryAmount($pending_amount);
        }
      }

      if (!$balance->isPositive()) {
        $build['#pending_paid_in_full'] = TRUE;
      }
    }

    return $build;
  }

  /**
   * Build the dt for a summary item title.
   *
   * @param string|\Drupal\Component\Render\MarkupInterface|null $title
   *   The title of the item.
   *
   * @return array
   *   The render array for the dt.
   */
  protected function buildSummaryTitle($title): array {
    return [
      '#type' => 'html_tag',
      '#tag' => 'dt',
      '#value' => $title,
    ];
  }

  /**
   * Build the dd for a summary item amount.
   *
   * @param \Drupal\commerce_price\Price|null $amount
   *   The amount to render. If NULL, it will render a zero amount in the
   *   relevant currency.
   * @param string|\Drupal\Component\Render\MarkupInterface|null $prefix
   *   An optional prefix for the amount.
   * @param string|\Drupal\Component\Render\MarkupInterface|null $suffix
   *   An optional suffix for the amount.
   *
   * @return array
   *   The render array for the dd.
   */
  protected function buildSummaryAmount(?Price $amount, $prefix = NULL, $suffix = NULL) {
    // Get a 'zero' if the amount is NULL.
    if (!$amount) {
      $order_total = $this->order->getTotalPrice();
      $default_currency = $order_total ?
        $order_total->getCurrencyCode() :
        $this->order->getStore()->getDefaultCurrencyCode();
      $amount = new Price(0, $default_currency);
    }

    // Use a static to avoid having to compute this multiple times.
    static $pre_render;
    if (!isset($pre_render)) {
      $pre_render = $this->elementInfoManager
        ->getInfoProperty('html_tag', '#pre_render', []);
      array_unshift($pre_render, [$this, 'preRenderAmount']);
    }

    // Build the render array.
    return [
      '#type' => 'html_tag',
      '#tag' => 'dd',
      '#amount' => $amount,
      '#amount_prefix' => $prefix,
      '#amount_suffix' => $suffix,
      '#pre_render' => $pre_render,
    ];
  }

  /**
   * Pre render callback for format an amount.
   *
   * @param array $element
   *   The element with #amount.
   *
   * @return array
   *   The element with #markup set.
   */
  public function preRenderAmount(array $element) {
    if (!isset($element['#value']) && isset($element['#amount'])) {
      $element['#value'] = new FormattableMarkup('@prefix@amount@suffix', [
        '@amount' => $this->formatPrice($element['#amount']),
        '@prefix' => $element['#amount_prefix'] ?? '',
        '@suffix' => $element['#amount_suffix'] ?? '',
      ]);
    }

    return $element;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc