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

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

namespace Drupal\contacts_events\Plugin\Commerce\CheckoutPane;

use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface;
use Drupal\commerce_partial_payments\Element\TrackedAmounts;
use Drupal\commerce_payment\Plugin\Commerce\CheckoutPane\PaymentInformation;
use Drupal\commerce_price\Price;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides the review pane.
 *
 * @CommerceCheckoutPane(
 *   id = "booking_payment_information",
 *   label = @Translation("Booking payment information"),
 *   default_step = "review",
 * )
 */
class BookingPaymentInformation extends PaymentInformation implements TrustedCallbackInterface {

  use BookingPaymentTrait;

  /**
   * The currency formatter.
   *
   * @var \CommerceGuys\Intl\Formatter\CurrencyFormatterInterface
   */
  protected $currencyFormatter;

  /**
   * The deposit amount, NULL if not enabled and FALSE if not checked.
   *
   * @var \Drupal\commerce_price\Price|null|false
   */
  protected $depositAmount = FALSE;

  /**
   * The stripe intent order subscriber, if any.
   *
   * @var \Drupal\contacts_events\EventSubscriber\StripeIntentOrderSubscriber|null
   */
  protected $stripeIntentSubscriber;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow = NULL) {
    /** @var static $plugin */
    $plugin = parent::create($container, $configuration, $plugin_id, $plugin_definition, $checkout_flow);
    $plugin->currencyFormatter = $container->get('commerce_price.currency_formatter');
    $plugin->stripeIntentSubscriber = $container->get('commerce_stripe.order_events_subscriber', ContainerInterface::NULL_ON_INVALID_REFERENCE);
    static::init($container, $plugin);
    return $plugin;
  }

  /**
   * {@inheritdoc}
   */
  public function getDisplayLabel() {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function buildPaneSummary() {
    return $this->getSummary();
  }

  /**
   * {@inheritdoc}
   */
  public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
    $pane_form['intro'] = $this->buildPaymentIntro($complete_form);
    $pane_form['balances'] = $this->getSummary();
    $this->addPendingWarning($pane_form);
    $this->runCompletionValidation($complete_form);
    $this->buildPaymentOptions($pane_form);
    $pane_form = parent::buildPaneForm($pane_form, $form_state, $complete_form);
    return $pane_form;
  }

  /**
   * {@inheritdoc}
   */
  public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    parent::validatePaneForm($pane_form, $form_state, $complete_form);

    // Prevent validation errors on the custom selection if we aren't proceeding
    // with custom payment amounts.
    if (isset($pane_form['amount_option_custom']['#parents']) && $this->getAmountOption($pane_form, $form_state) != 'custom') {
      $name_prefix = implode('][', $pane_form['amount_option_custom']['#parents']);
      $errors = $form_state->getErrors();
      $form_state->clearErrors();
      foreach ($errors as $name => $error) {
        if (strpos($name, $name_prefix) !== 0) {
          $form_state->setErrorByName($name, $error);
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    parent::submitPaneForm($pane_form, $form_state, $complete_form);

    /** @var \Drupal\commerce_price\Price|null $amount */
    $amount = NULL;
    switch ($this->getAmountOption($pane_form, $form_state)) {
      // For deposits, get the amount and, if partial payments is available,
      // build the tracking information.
      case 'deposits':
        $amount = $this->getDepositTotal($tracking_values);
        break;

      // For custom (choose per ticket) we need to store the tracking
      // information.
      case 'custom':
        $tracking_values = $form_state->getValue($pane_form['amount_option_custom']['#parents']);
        $tracking_values = TrackedAmounts::extractValues($tracking_values, $amount);
        break;
    }

    // Pass the payment amount onto the process step if it's not the full
    // balance.
    $this->setPaymentAmount($amount);

    // If we have payment tracking, store the information.
    if ($this->paymentTracking && !empty($tracking_values)) {
      $this->paymentTracking->storeTrackingInformation($this->order, $tracking_values);
    }

    // If we have the stripe subscriber, make sure the intent is marked for
    // update.
    if ($this->stripeIntentSubscriber) {
      $this->stripeIntentSubscriber->updateOrder($this->order);
    }
  }

  /**
   * Get the amount option from the form/form state.
   *
   * @param array $pane_form
   *   The pane form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return string
   *   The payment amount option.
   */
  protected function getAmountOption(array $pane_form, FormStateInterface $form_state) {
    if (count($pane_form['amount_option']['#options']) == 1) {
      return array_keys($pane_form['amount_option']['#options'])[0];
    }
    else {
      return $form_state->getValue($pane_form['amount_option']['#parents']);
    }
  }

  /**
   * Get the deposit amount for the event.
   *
   * @return \Drupal\commerce_price\Price|null
   *   The deposit amount for the event, or NULL if deposits are not enabled.
   */
  protected function getDepositAmount(): ?Price {
    if ($this->depositAmount === FALSE) {
      $this->depositAmount = $this->getSettingAmount('payment.deposit');
    }
    return $this->depositAmount;
  }

  /**
   * Get a price amount from an event setting.
   *
   * @param string $setting_key
   *   The event setting key.
   *
   * @return \Drupal\commerce_price\Price|null
   *   A price or NULL if not configured.
   */
  protected function getSettingAmount(string $setting_key): ?Price {
    /** @var \Drupal\contacts_events\Entity\EventInterface|null $event */
    $event = $this->order->get('event')->entity;
    if (!$event) {
      return NULL;
    }

    $amount = $event->getSetting($setting_key);
    return $amount ? Price::fromArray($amount) : NULL;
  }

  /**
   * Process callback for the amount options radios to hide if only one choice.
   *
   * @param array $element
   *   The element array.
   *
   * @return array
   *   The element array.
   */
  public static function hideSingleOption(array $element) {
    if (count($element['#options']) == 1) {
      $element['#access'] = FALSE;
      $element['#required'] = FALSE;
      $element['#value'] = array_keys($element['#options'])[0];
    }
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  protected function buildBillingProfileForm(array $pane_form, FormStateInterface $form_state) {
    // We already have the billing details from a previous step, but the submit
    // handler requires the billing profile in the form array.
    $pane_form = parent::buildBillingProfileForm($pane_form, $form_state);
    $pane_form['billing_information']['#access'] = FALSE;
    return $pane_form;
  }

  /**
   * Format a price object.
   *
   * @param \Drupal\commerce_price\Price $price
   *   The price.
   *
   * @return string
   *   The formatted price.
   */
  protected function formatPrice(Price $price) {
    return $this->currencyFormatter->format($price->getNumber(), $price->getCurrencyCode());
  }

  /**
   * Build the payment options selection.
   *
   * @param array $pane_form
   *   The pane form.
   */
  protected function buildPaymentOptions(array &$pane_form): void {
    // Payment options are only applicable if there is a balance.
    $balance = $this->order->getBalance();
    if (!$balance || !$balance->isPositive()) {
      return;
    }

    $pane_form['amount_option'] = [
      '#type' => 'radios',
      '#title' => $this->t('Payment options'),
      '#required' => TRUE,
      '#process' => $this->elementInfoManager->getInfoProperty('radios', '#process', []),
      '#options' => [
        'full' => $this->t('Pay in full %amount', [
          '%amount' => $this->formatPrice($balance),
        ]),
      ],
      'full' => [
        '#description' => $this->t('Pay all outstanding balances and secure your price now.'),
      ],
    ];
    // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
    $pane_form['amount_option']['#process'][] = [static::class, 'hideSingleOption'];

    /** @var \Drupal\contacts_events\Entity\EventInterface $event */
    $event = $this->order->get('event')->entity;

    // Show the deposit option, if enabled.
    $deposit_amount = $this->getDepositAmount();
    if ($deposit_total = $this->getDepositTotal()) {
      $pane_form['amount_option']['#options']['deposits'] = $this->t('Pay all deposits %amount', [
        '%amount' => $this->formatPrice($deposit_total),
      ]);
      $pane_form['amount_option']['deposits']['#weight'] = -50;
      $pane_form['amount_option']['deposits']['#description'] = $this->t('You can reserve your tickets by paying a deposit of @amount per ticket. The price will go up if not paid in full by the next booking deadline.', [
        '@amount' => $this->formatPrice($deposit_amount),
      ]);
    }

    // Show the installments option, if enabled.
    if ($this->paymentTracking && $event->getSetting('payment.instalments')) {
      $pane_form['amount_option']['#options']['custom'] = $this->t('Choose per ticket');
      $pane_form['amount_option']['custom']['#description'] = $this->t('Pay an instalment on any or all of your tickets.');

      $tracking = $this->paymentTracking->getTrackedAmountsForOrder($this->order);
      $full_tracking = $this->paymentTracking->calculatePaidInFullTracking($this->order);

      // Override the pay in full option amount if the default tracking is not
      // the same as the order balance.
      /** @var \Drupal\commerce_price\Price|null $tracking_total */
      $tracking_total = NULL;
      foreach ($full_tracking as $tracking_item) {
        $item_amount = Price::fromArray($tracking_item);
        $tracking_total = $tracking_total ? $tracking_total->add($item_amount) : $item_amount;
      }
      if (!$tracking_total->equals($balance)) {
        $pane_form['amount_option']['#options']['full'] = $this->t('Pay in full %amount', [
          '%amount' => $this->formatPrice($tracking_total),
        ]);
      }

      // Build our custom amounts option.
      $pane_form['amount_option_custom'] = [
        '#type' => 'commerce_partial_payments_tracked_amount',
        '#tracking' => $tracking,
        '#default_value' => $full_tracking,
        '#items' => [],
        '#header' => [
          'label' => '',
          'status' => $this->t('Status'),
          'price' => $this->t('Price'),
          'balance' => $this->t('Outstanding'),
          'number' => $this->t('Payment'),
        ],
        '#states' => [
          'visible' => [
            ':input[name="booking_payment_information[amount_option]"]' => ['value' => 'custom'],
          ],
        ],
      ];

      foreach ($this->order->getItems() as $item) {
        $item_id = $item->id();
        $price = $item->getAdjustedTotalPrice();
        $is_ticket = $item->bundle() == 'contacts_ticket';

        // Don't show non-ticket items that have no value.
        if (!$is_ticket && (!$price || $price->isZero())) {
          continue;
        }

        /** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface|null $item_state */
        $item_state = $item->hasField('state') ? $item->get('state')->first() : NULL;

        $balance = $price ? $price->subtract($tracking[$item_id]) : NULL;
        /** @var \Drupal\commerce_price\Price|null $balance */
        $has_balance = $balance && $balance->isPositive();

        $pane_form['amount_option_custom']['#items'][$item_id] = [
          'label' => $item->label(),
          'status' => $item_state ? $item_state->getLabel() : NULL,
          'price' => $item->getAdjustedTotalPrice(),
          'number:hide' => !$has_balance,
        ];

        if ($has_balance) {
          // If the item is not stateful or confirmed, we need to set a minimum
          // amount.
          if (!$item_state || $item_state->value == 'pending') {
            // Non stateful items should always be paid in full.
            if (!$item_state) {
              $min_amount = $balance->getNumber();
            }
            // Otherwise, if we have deposits, take the minimum of the deposit
            // and the price.
            elseif ($deposit_amount) {
              if ($deposit_amount->lessThan($price)) {
                $min_amount = $deposit_amount->getNumber();
              }
              else {
                $min_amount = $price->getNumber();
              }
            }
            // Otherwise, just make sure it's non-zero.
            else {
              // Price has a scale of 6, so this enforces non-zero across all
              // currency.
              $min_amount = 0.000001;
            }
            $pane_form['amount_option_custom']['#items'][$item_id]['number:min'] = $min_amount;
          }
          $pane_form['amount_option_custom']['#items'][$item_id]['number:max'] = $balance->getNumber();
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    return ['preRenderAmount'];
  }

}

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

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