billwerk_subscriptions-1.x-dev/src/Subscriber.php

src/Subscriber.php
<?php

declare(strict_types=1);

namespace Drupal\billwerk_subscriptions;

use Drupal\Component\Serialization\Json;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\billwerk_subscriptions\DataObject\BillwerkContract;
use Drupal\billwerk_subscriptions\DataObject\BillwerkCustomer;
use Drupal\billwerk_subscriptions\Event\SubscriberOrderCreateEvent;
use Drupal\billwerk_subscriptions\Event\SubscriberRefreshUserEvent;
use Drupal\billwerk_subscriptions\Exception\SubscriberException;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * The billwerk subscriber class.
 *
 * Wraps a Drupal user entity and adds subscription-related functionality to
 * this "Subscriber". This makes it easier to interact with subscription
 * functionalities for this user.
 */
class Subscriber {
  use DependencySerializationTrait;

  const USER_FIELD_BILLWERK_CONTRACT_ID = 'field_billwerk_contract_id';

  /**
   * The API service for interacting with Billwerk.
   *
   * @var \Drupal\billwerk_subscriptions\Api
   */
  protected readonly Api $api;

  /**
   * The event dispatcher service for handling events.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected readonly EventDispatcherInterface $eventDispatcher;

  /**
   * The factory service for creating Billwerk data objects.
   *
   * @var \Drupal\billwerk_subscriptions\BillwerkDataObjectFactory
   */
  protected readonly BillwerkDataObjectFactory $billwerkDataObjectFactory;

  /**
   * The helper service for logging.
   *
   * @var \Drupal\billwerk_subscriptions\LogHelper
   */
  protected readonly LogHelper $logHelper;

  /**
   * Constructs a Subscriber object.
   */
  protected function __construct(
    protected readonly UserInterface $user,
    protected ?BillwerkContract $billwerkContract,
  ) {
    // Initialize services (we should, but can't use DI here):
    // @phpstan-ignore-next-line
    $this->api = \Drupal::service("billwerk_subscriptions.api");
    // @phpstan-ignore-next-line
    $this->eventDispatcher = \Drupal::service("event_dispatcher");
    // @phpstan-ignore-next-line
    $this->billwerkDataObjectFactory = \Drupal::service("billwerk_subscriptions.billwerk_data_object_factory");
    // @phpstan-ignore-next-line
    $this->logHelper = \Drupal::service("billwerk_subscriptions.log_helper");
  }

  /**
   * Load the subscriber by the Drupal User object.
   *
   * @param \Drupal\user\UserInterface $user
   *   The Drupal user object.
   *
   * @return \Drupal\billwerk_subscriptions\Subscriber
   *   The subscriber object.
   */
  public static function load(UserInterface $user): self {
    // Lazy-initialize the Subscriber, only initialize the contract if needed.
    // This allows us to use Subscriber for users before they have a
    // subscription and only fetch the subscription data if needed.
    if ($user->isAnonymous()) {
      throw new SubscriberException("The anonymous user may never be a subscriber!");
    }
    return new self($user, NULL);
  }

  /**
   * Load the subscriber by its Drupal UID (ExternalCustomerId at Billwerk).
   *
   * @param int $uid
   *   The Drupal user ID.
   *
   * @return \Drupal\billwerk_subscriptions\Subscriber
   *   The subscriber object.
   */
  public static function loadByDrupalUid(int $uid): ?self {
    $user = User::load($uid);
    if ($user !== NULL) {
      return self::load($user);
    }
    else {
      return NULL;
    }
  }

  /**
   * Load the subscriber by the Drupal user's email address.
   *
   * @param string $mail
   *   The email address.
   *
   * @return Subscriber|null
   *   The subscriber object.
   */
  public static function loadByMail(string $mail): ?self {
    $user = user_load_by_mail($mail);
    if ($user !== NULL) {
      return self::load($user);
    }
    else {
      return NULL;
    }
  }

  /**
   * Load the subscriber by the Billwerk Contract ID.
   *
   * This looks up the Drupal user with the given $billwerkContractId in their
   * user profile.
   *
   * @param mixed $billwerkContractId
   *   The Billwerk Contract ID.
   *
   * @return Subscriber|null
   *   The subscriber object.
   */
  public static function loadByContractId($billwerkContractId): ?self {
    $users = \Drupal::entityTypeManager()
      ->getStorage('user')
      ->loadByProperties([self::USER_FIELD_BILLWERK_CONTRACT_ID => $billwerkContractId]);
    $user = reset($users);

    if (!empty($user)) {
      /** @var \Drupal\user\UserInterface $user */
      return self::load($user);
    }
    else {
      return NULL;
    }
  }

  /**
   * Refresh the user account details from their Billwerk Contract Subscription.
   *
   * If the user has no Billwerk Contract ID assigned, no refresh will be
   * performed and the Event won't be called.
   * Use Subscriber::hasUserBillwerkContractId() before to check if the user
   * has a Billwerk Contract ID set, if needed.
   *
   * Doesn't change anything on their own (to not make assumptions), but instead
   * dispatches the  SubscriberRefreshFromBillwerkContractSubscriptionEvent
   * so that handlers can implement what happens if the subscription needs
   * to be updated.
   */
  public function refreshFromBillwerkContractSubscription(): void {
    if (!$this->hasUserBillwerkContractId()) {
      // Only process users with a Billwerk Contract ID set. Skip others!
      return;
    }

    // Simply trigger the event to let handlers implement what should happen
    // and pass the subscriber object:
    $subscriberRefreshSubscriptionEvent = new SubscriberRefreshUserEvent($this);
    $this->eventDispatcher->dispatch($subscriberRefreshSubscriptionEvent);
  }

  /**
   * Creates a new Billwerk Contract and Customer by an order.
   *
   * @throws \Drupal\billwerk_subscriptions\Exception\SubscriberException
   *
   * @return \Drupal\billwerk_subscriptions\Subscriber
   *   The subscriber object.
   */
  public function billwerkCreateContract(): Subscriber {
    // Prefill the $orderData with typical defaults:
    // They can be altered in the Event individually.
    $email = $this->getUser()->getEmail();
    $uid = $this->getUser()->id();
    $language = $this->getUser()->getPreferredLangcode();
    $orderData = [
      'Cart' => [
        // This needs to be overwritten by the subscriber!
        'planVariantId' => NULL,
      ],
      'Customer' => [
        // Other example values:
        // FirstName: response.firstName,
        // LastName: response.lastName,
        // Language parameter seems deprecated in favor of Locale?:
        // 'Language' => $language,.
        'EmailAddress' => $email,
        // @codingStandardsIgnoreStart
        // AdditionalEmailAddresses: [],
        // Address: {
        //   Street: 'TRIAL',
        //   HouseNumber: 'TRIAL',
        //   PostalCode: 'TRIAL',
        //   City: 'TRIAL',
        //   CompanyName: 'TRIAL',
        //   Country: 'DE',
        // },
        // AdditionalAddresses: [],.
        // @codingStandardsIgnoreEnd
        'Locale' => $language,
        'DefaultBearerMedium' => 'Email',
        'ExternalCustomerId' => $uid,
        // Leave a note in Billwerk to show the origin of this user:
        'Note' => 'Created by Drupal billwerk_subscriptions module on Drupal User #' . $uid . ' creation at ' . date("c"),
      ],
    ];

    // The SubscriberOrderCreateEvent can be used to modify the order data
    // individually.
    $subscriberOrderCreateEvent = new SubscriberOrderCreateEvent(SubscriberOrderCreateEvent::ORDER_TYPE_CREATE_CUSTOMER_CONTRACT, $orderData, $this, FALSE, TRUE);
    $this->eventDispatcher->dispatch($subscriberOrderCreateEvent);

    // Retrieve the possibly modified order data:
    $orderData = $subscriberOrderCreateEvent->getOrderData();
    if ($subscriberOrderCreateEvent->getPreviewOrder()) {
      $order = $this->api->createOrderPreview($orderData);
      // Order is returned as subarray in preview:
      $order = $order->Order;
    }
    else {
      $order = $this->api->createOrder($orderData);
    }

    if ($subscriberOrderCreateEvent->getCommitOrder()) {
      $committedOrder = $this->api->commitOrder($order->Id, []);
    }
    $contractId = $committedOrder->Id;
    if (!empty($contractId)) {
      $this->setUserBillwerkContractId($contractId);
    }
    else {
      $this->logHelper->error('Registration for %email was not possible, Billwerk returned the following response, without the required order ID: %response', [
        '%email' => $email,
        '%response' => Json::encode($order),
      ]);
      throw new SubscriberException('Contract ID could not be determined for committed order. Contract was created, but not assigned to the user "' . $this->getUser()->id() . '"');
    }
    return $this;
  }

  /**
   * Changes the active subscription.
   *
   * See https://billwerk.readme.io/reference/orders_postorder_orderdto_post
   * for details and the returned BillwerkOrderDTO.
   *
   * @param string $newPlanVariantId
   *   The new Billwerk Plan Variant ID to upgrade / downgrade to.
   *   Provide the current Billwerk Plan Variant ID to keep the existing
   *   contract.
   * @param array $newComponentIds
   *   An array of Billwerk Component IDs to book additionally.
   * @param array $endComponentIds
   *   An array of Billwerk Component IDs to end.
   * @param string $couponCode
   *   The coupon code to use.
   * @param bool $changeImmediately
   *   Determines if the subscription change should happen immediately
   *   (typically for upgrades) or at the end of the contract period
   *   (typically for downgrades or cancellation).
   * @param bool $previewOrder
   *   Preview the order (do not prepare a committable order).
   * @param bool $commitOrder
   *   Commit the order (otherwise just prepare it to be committed later!).
   *
   * @return \stdClass
   *   The Billwerk Order data object (dynamically created from JSON).
   */
  public function billwerkChangeSubscription(?string $newPlanVariantId = NULL, array $newComponentIds = [], array $endComponentIds = [], ?string $couponCode = NULL, bool $changeImmediately = FALSE, bool $previewOrder = FALSE, bool $commitOrder = FALSE): \stdClass {
    $orderData = [
      // "To instantly bill these fees you can trigger an interim billing in the
      // order by setting TriggerInterimBilling to true, or by triggering a
      // separate interim billing."
      // @see https://docu.billwerk.plus/en/use-cases/components/component-subscriptions.html
      'TriggerInterimBilling' => TRUE,
      // 'PreviewAfterTrial' => FALSE,
      'Cart' => [
        "InheritStartDate" => FALSE,
      ],
      // Should only be given, if a new contract is created, for up/downgrades
      // only contract ID should be given.
      // 'CustomerId' => $this->getBillwerkCustomer()->getId(),
      'ContractId' => $this->getBillwerkContract()->getId(),
    ];

    // "Negative" subscription changes like downgrades and cancellation should
    // typically not happen immediately, but at the end of the contract period.
    // "Positive" subscription changes like upgrades should typically happen
    // immediately. Billwerk requires us to set the ChangeDate to the next
    // end date of the subscription which needs to be determined from the API.
    // @see https://developer.billwerk.io/docs/useCases/contracts/upDowngradingToPlan
    if (!$changeImmediately) {
      if (!empty($newPlanVariantId)) {
        // We're handling a plan variant change here.
        // Changes should apply to the next enddate, if not immediately:
        $orderData['ChangeDate'] = $this->billwerkGetContractNextEndDate();
      }
    }

    // @todo Should we move this logic out into the default handler via event?
    // @improve Should we first validate the values against our entities here?
    if (!empty($newPlanVariantId)) {
      // @todo Awaiting Billwerk Support response: This will change the plan immediately! What can we do?
      $orderData['Cart']['PlanVariantId'] = $newPlanVariantId;
    }

    if (!empty($newComponentIds)) {
      foreach ($newComponentIds as $newComponentSubscriptionId) {
        $orderData['Cart']['ComponentSubscriptions'][] = [
          'ComponentId' => $newComponentSubscriptionId,
          // Required:
          // Currently we don't support other quantities!
          'Quantity' => 1,
          // #20250515: Do NOT set a start date for component subscriptions
          // @see https://www.drupal.org/project/billwerk_subscriptions/issues/3524721
          // Round up to next minute to have proper amounts:
          // 'StartDate' => Api::billwerkDateFormat(time(), NULL, NULL, TRUE),
          // @codingStandardsIgnoreStart
          // The following should typically NOT be needed, just kept if we run into issues and have to fix it somehow like this:
          // IMPORTANT: For "BilledUntil" to work, the component setting "Independent billing" needs to be enabled!
          // Otherwise this value has NO effect!
          // If this should also work for Trials, also "Bill in trial" needs to be enabled!
          // Round up to next minute to have proper amounts:
          // 'BilledUntil' => Api::billwerkDateFormat(strtotime("+1 month", time()), NULL, $this->billwerkGetContractNextEndDate(), TRUE),
          // 'EndDate' => $this->api->billwerkDateFormat(strtotime("+1 month", time()), NULL, $this->billwerkGetContractNextEndDate(), TRUE),.
          // @codingStandardsIgnoreEnd
        ];
      }
      // We definitely want to trigger interim billing in this case:
      $orderData['TriggerInterimBilling'] = TRUE;
    }

    if (!empty($endComponentIds)) {
      if (count($endComponentIds) > 1) {
        // We can not handle more than one component subscription at once,
        // as the order method only allows one ChangeDate and we need to
        // set it on our own, because an empty value would not end the
        // component to the end of the billing period, but instead immediately
        // and refund the amount.
        throw new \UnexpectedValueException("We don't support ending multiple add-on subscriptions at once.");
      }
      // So we only pick the first one:
      $endComponentId = reset($endComponentIds);

      // Get additional information about the active subscriptions of contract:
      $contractActiveSubscriptions = $this->getContractSubscriptionsActive();
      $contractActiveComponentSubscriptions = $contractActiveSubscriptions['ComponentSubscriptions'];

      // Look up details of the subscription to end in the list of contract
      // component subscriptions:
      foreach ($contractActiveComponentSubscriptions as /*$componentSubscriptionId =>*/ $activeComponentSubscription) {
        if ($activeComponentSubscription['ComponentId'] === $endComponentId) {
          $orderData['Cart']['EndComponentSubscriptions'][] = $endComponentId;
          if (!empty($activeComponentSubscription['BilledUntil'])) {
            $orderData['ChangeDate'] = $activeComponentSubscription['BilledUntil'];
          }
          // We definitely want to trigger interim billing in this case:
          $orderData['TriggerInterimBilling'] = TRUE;
        }
      }
    }

    if (!empty($couponCode)) {
      $orderData['Cart']['CouponCode'] = $couponCode;
    }

    $subscriberOrderCreateEvent = new SubscriberOrderCreateEvent(SubscriberOrderCreateEvent::ORDER_TYPE_UPDATE_SUBSCRIPTION, $orderData, $this, $previewOrder, $commitOrder);
    $this->eventDispatcher->dispatch($subscriberOrderCreateEvent);

    $orderData = $subscriberOrderCreateEvent->getOrderData();
    if ($subscriberOrderCreateEvent->getPreviewOrder()) {
      $order = $this->api->createOrderPreview($orderData);
      // Order is returned as subarray in preview:
      $order = $order->Order;
    }
    else {
      $order = $this->api->createOrder($orderData);
    }

    if ($subscriberOrderCreateEvent->getCommitOrder()) {
      $order = $this->api->commitOrder($order->Id, []);
    }

    return $order;
  }

  /**
   * Helper method to get further information about the active contract.
   *
   * Returns details on the contract and especially its state.
   * Contains information about the phase and component / discount
   * subscriptions, in a better structured way than Billwerk does.
   *
   * @return array
   *   The active contract subscriptions.
   */
  protected function getContractSubscriptionsActive(): array {
    // Get the active subscriptions information from the contract:
    // @see https://docs.frisbii-transform.com/reference/contracts_getsubscriptions_id_timestamp_get
    $contractSubscriptions = $this->api->getContractSubscriptions($this->getBillwerkContract()->getId());
    // Create improved array:
    $activeContractSubscriptions = $contractSubscriptions;
    // Index component subscriptions by ID:
    foreach ($activeContractSubscriptions['ComponentSubscriptions'] as $activeComponentSubscriptions) {
      $activeContractSubscriptions['ComponentSubscriptions'][$activeComponentSubscriptions['Id']] = $activeComponentSubscriptions;
    }
    // Index discount subscriptions by ID:
    foreach ($activeContractSubscriptions['DiscountSubscriptions'] as $activeDiscountSubscriptions) {
      $activeContractSubscriptions['DiscountSubscriptions'][$activeDiscountSubscriptions['Id']] = $activeDiscountSubscriptions;
    }

    return $contractSubscriptions;
  }

  /**
   * Looks up the customer at Billwerk by ExternalId = Drupal user id (UID).
   *
   * Especially useful after importing customers and contracts to Billwerk
   * with the ExternalId set to the Drupal user ID. Can then be used
   * to get the customers primary contract id for the user profile.
   *
   * Example use case: BillwerkContractIdsFetchAndAssignAction
   *
   * @return ?BillwerkCustomer
   *   The BillwerkCustomer object.
   */
  public function billwerkLookupCustomerByUid(): ?BillwerkCustomer {
    $customerData = $this->api->getCustomerByExternalId((string) $this->getUser()->id());
    if (empty($customerData) || !isset($customerData['Id'])) {
      // No customer with this ExternalId (=uid) found!
      return NULL;
    }
    /** @var BillwerkDataObjectFactory $billwerkDataObjectFactory */
    // @phpstan-ignore-next-line
    $billwerkDataObjectFactory = \Drupal::service('billwerk_subscriptions.billwerk_data_object_factory');
    return $billwerkDataObjectFactory->billwerkLoadBillwerkCustomer($customerData['Id']);
  }

  /**
   * Locks the customer account at Billwerk.
   *
   * @throws \Drupal\billwerk_subscriptions\Exception\SubscriberException
   */
  public function billwerkLockCustomer(): void {
    $billwerkCustomerId = $this->getBillwerkCustomer()->getId();
    if (!empty($billwerkCustomerId)) {
      $this->api->customerUpdateLockedState($billwerkCustomerId, TRUE);
    }
    else {
      throw new SubscriberException("Customer ID could not be determined for User #{$this->getUser()->id()}");
    }
  }

  /**
   * Unlocks the customer account at Billwerk.
   *
   * @throws \Drupal\billwerk_subscriptions\Exception\SubscriberException
   */
  public function billwerkUnlockCustomer(): void {
    $billwerkCustomerId = $this->getBillwerkCustomer()->getId();
    if (!empty($billwerkCustomerId)) {
      $this->api->customerUpdateLockedState($billwerkCustomerId, FALSE);
    }
    else {
      throw new SubscriberException("Customer ID could not be determined for User #{$this->getUser()->id()}");
    }
  }

  /**
   * Deletes the customer account and its contracts at Billwerk entirely.
   *
   * @throws \Drupal\billwerk_subscriptions\Exception\SubscriberException
   */
  public function billwerkDeleteCustomer(): void {
    $billwerkCustomerId = $this->getBillwerkCustomer()->getId();
    if (!empty($billwerkCustomerId)) {
      $this->api->deleteCustomer($billwerkCustomerId);
      $this->setUserBillwerkContractId('');
    }
    else {
      throw new SubscriberException("Customer ID could not be determined for User #{$this->getUser()->id()}");
    }
  }

  /**
   * Sets the Billwerk Customer ExternalCustomerId to the Subscriber's user ID.
   *
   * @throws \Drupal\billwerk_subscriptions\Exception\SubscriberException
   */
  public function billwerkSetCustomerExternalCustomerId(): void {
    if (empty($this->getBillwerkCustomer())) {
      throw new SubscriberException("Customer could not be loaded for User #{$this->getUser()->id()}");
    }
    if ($this->getBillwerkCustomer()->getExternalCustomerId() == $this->getUser()->id()) {
      // The external customer ID already equals the Drupal user id.
      return;
    }
    // Set the Billwerk ExternalCustomerId to the user ID.
    $this->api->patchCustomer($this->getBillwerkCustomer()->getId(), ['ExternalCustomerId' => $this->getUser()->id()]);
  }

  /**
   * Returns the Subscriber's Billwerk self service token.
   *
   * @return string
   *   The self service token.
   */
  public function billwerkGetSelfserviceToken(): string {
    $contractId = $this->getUserBillwerkContractId();
    return $this->api->getSelfserviceToken($contractId);
  }

  /**
   * Get the contract details by user billwerk contract id.
   *
   * @return string
   *   The contract details.
   */
  public function billwerkGetContractDetails(): array {
    $contractId = $this->getUserBillwerkContractId();
    return $this->api->getContract($contractId);
  }

  /**
   * Returns true if the user has pending (future) contract phases.
   *
   * This helper method is needed, as Billwerk has no other way to determine
   * that the contract has already been changed to a new phase.
   * We for example need that information to prevent a user from changing
   * their contract again and again.
   *
   * @return bool
   *   Whether the user has pending contract phases.
   */
  public function billwerkHasPendingContractPhases(): bool {
    $contract = $this->getBillwerkContract();
    if (empty($contract)) {
      // No contract.
      // @improve: Should we instead throw an exception here?
      return FALSE;
    }
    if ($contract->getLifecycleStatus() == BillwerkContract::LIFECYCLE_STATUS_INTRIAL) {
      // Never treat pending contract phases in trial as pending:
      return FALSE;
    }
    $contractDetails = $this->billwerkGetContractDetails();
    if (!empty($contractDetails['Phases'])) {
      $contractPhases = $contractDetails['Phases'];
      $lastContractPhase = end($contractPhases);
      return $lastContractPhase['StartDate'] != $contractDetails['CurrentPhase']['StartDate'];
    }
    return FALSE;
  }

  /**
   * Returns the next end date of the contract.
   *
   * @return string
   *   The next end date.
   */
  public function billwerkGetContractNextEndDate(): string {
    // https://developer.billwerk.io/docs/useCases/contracts/terminateContractWithNotice
    $contractId = $this->getUserBillwerkContractId();
    $cancellationPreview = $this->api->getContractCancellationPreview($contractId);
    if (empty($cancellationPreview['EndDate'])) {
      throw new \UnexpectedValueException("Contract next end date could not be determined, but is required.");
    }
    return $cancellationPreview['EndDate'];
  }

  /**
   * Returns the next billing date of the contract.
   *
   * @return string
   *   The next billing date.
   */
  public function billwerkGetContractNextBillingDate(): string {
    // https://developer.billwerk.io/docs/useCases/contracts/upDowngradingToPlan
    $contractId = $this->getUserBillwerkContractId();
    $contract = $this->api->getContract($contractId);
    if (empty($contract['NextBillingDate'])) {
      throw new \UnexpectedValueException("Contract next billing date could not be determined, but is required.");
    }
    return $contract['NextBillingDate'];
  }

  /**
   * Returns the contract billed until date.
   *
   * @return string
   *   The billed until date.
   */
  public function billwerkGetContractBilledUntilDate(): string {
    // https://developer.billwerk.io/docs/useCases/contracts/upDowngradingToPlan
    $contractId = $this->getUserBillwerkContractId();
    $contract = $this->api->getContract($contractId);
    if (empty($contract['BilledUntil'])) {
      throw new \UnexpectedValueException("Contract billed until date could not be determined, but is required.");
    }
    return $contract['BilledUntil'];
  }

  /**
   * Returns the BillwerkContract with lazy-loading.
   *
   * @return \Drupal\billwerk_subscriptions\DataObject\BillwerkContract
   *   The BillwerkContract object.
   */
  public function getBillwerkContract(): ?BillwerkContract {
    if ($this->billwerkContract !== NULL) {
      return $this->billwerkContract;
    }
    else {
      if ($this->hasUserBillwerkContractId()) {
        $billwerkContractId = $this->getUserBillwerkContractId();
        $this->billwerkContract = $this->billwerkDataObjectFactory->billwerkLoadBillwerkContract($billwerkContractId);
      }
    }

    return $this->billwerkContract;
  }

  /**
   * Summary of getCustomerData.
   *
   * @return ?\Drupal\billwerk_subscriptions\DataObject\BillwerkCustomer
   *   The BillwerkCustomer object.
   */
  public function getBillwerkCustomer(): ?BillwerkCustomer {
    return $this->getBillwerkContract()->getBillwerkCustomer();
  }

  /**
   * Returns the user.
   *
   * @return \Drupal\user\UserInterface
   *   The user object.
   */
  public function getUser(): UserInterface {
    // Safety net to never accidentally return the anonymous user:
    if ($this->user->isAnonymous()) {
      throw new SubscriberException('Subscriber should never be anonymous! Something is wrong.');
    }
    return $this->user;
  }

  /**
   * Sets the Billwerk Contract ID in the Drupal user profile.
   *
   * @param string $billwerkContractId
   *   The Billwerk Contract ID.
   * @param bool $save
   *   Whether to save the user profile.
   */
  public function setUserBillwerkContractId(string $billwerkContractId, bool $save = TRUE): void {
    $this->user->set(self::USER_FIELD_BILLWERK_CONTRACT_ID, $billwerkContractId);
    if ($save) {
      $this->user->save();
    }
  }

  /**
   * Returns the Billwerk Contract ID stored in the Drupal user profile.
   *
   * If none ist stored, returns NULL.
   *
   * @return ?string
   *   The Billwerk Contract ID.
   */
  public function getUserBillwerkContractId(): ?string {
    return $this->user->get(self::USER_FIELD_BILLWERK_CONTRACT_ID)->getString() ?: NULL;
  }

  /**
   * Returns true if the user has a Billwerk Contract ID stored, else false.
   *
   * @return bool
   *   Whether the user has a Billwerk Contract ID stored.
   */
  public function hasUserBillwerkContractId(): bool {
    $userBillwerkContractId = $this->getUserBillwerkContractId();
    return !empty($userBillwerkContractId);
  }

}

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

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