You are here

class PayPalController in Ubercart 8.4

Returns responses for PayPal routes.

Hierarchy

Expanded class hierarchy of PayPalController

File

payment/uc_paypal/src/Controller/PayPalController.php, line 21

Namespace

Drupal\uc_paypal\Controller
View source
class PayPalController extends ControllerBase implements ContainerInjectionInterface {

  /**
   * The payment method manager.
   *
   * @var \Drupal\uc_payment\Plugin\PaymentMethodManager
   */
  protected $paymentMethodManager;

  /**
   * The session.
   *
   * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
   */
  protected $session;

  /**
   * The datetime.time service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $dateTime;

  /**
   * The database service.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * Constructs a CheckoutController.
   *
   * @param \Drupal\uc_payment\Plugin\PaymentMethodManager $payment_method_manager
   *   The payment method manager.
   * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
   *   The session.
   * @param \Drupal\Component\Datetime\TimeInterface $date_time
   *   The datetime.time service.
   * @param \Drupal\Core\Database\Connection $database
   *   A database connection.
   */
  public function __construct(PaymentMethodManager $payment_method_manager, SessionInterface $session, TimeInterface $date_time, Connection $database) {
    $this->paymentMethodManager = $payment_method_manager;
    $this->session = $session;
    $this->dateTime = $date_time;
    $this->database = $database;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('plugin.manager.uc_payment.method'), $container
      ->get('session'), $container
      ->get('datetime.time'), $container
      ->get('database'));
  }

  /**
   * Processes the IPN HTTP request.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request of the page.
   *
   * @return \Symfony\Component\HttpFoundation\Response
   *   An empty Response with HTTP status code 200.
   */
  public function ipn(Request $request) {
    $this
      ->processIpn($request->request
      ->all());
    return new Response();
  }

  /**
   * Processes Instant Payment Notifications from PayPal.
   *
   * @param array $ipn
   *   The IPN data.
   */
  protected function processIpn(array $ipn) {
    $amount = $ipn['mc_gross'];
    $email = !empty($ipn['business']) ? $ipn['business'] : $ipn['receiver_email'];
    $txn_id = $ipn['txn_id'];
    if (!isset($ipn['invoice'])) {
      $this
        ->getLogger('uc_paypal')
        ->error('IPN attempted with invalid order ID.');
      return;
    }

    // Extract order and cart IDs.
    $order_id = $ipn['invoice'];
    if (strpos($order_id, '-') > 0) {
      list($order_id, $cart_id) = explode('-', $order_id);
      $this->session
        ->set('uc_cart_id', $cart_id);
    }
    $order = Order::load($order_id);
    if (!$order) {
      $this
        ->getLogger('uc_paypal')
        ->error('IPN attempted for non-existent order @order_id.', [
        '@order_id' => $order_id,
      ]);
      return;
    }

    // @todo Send method name and order ID in the IPN URL?
    $config = $this->paymentMethodManager
      ->createFromOrder($order)
      ->getConfiguration();

    // Optionally log IPN details.
    if (!empty($config['wps_debug_ipn'])) {
      $this
        ->getLogger('uc_paypal')
        ->notice('Receiving IPN at URL for order @order_id. <pre>@debug</pre>', [
        '@order_id' => $order_id,
        '@debug' => print_r($ipn, TRUE),
      ]);
    }

    // Express Checkout IPNs may not have the WPS email stored. But if it is,
    // make sure that the right account is being paid.
    if (!empty($config['wps_email']) && mb_strtolower($email) != mb_strtolower($config['wps_email'])) {
      $this
        ->getLogger('uc_paypal')
        ->error('IPN for a different PayPal account attempted.');
      return;
    }

    // Determine server.
    if (empty($ipn['test_ipn'])) {
      $host = 'https://ipnpb.paypal.com/cgi-bin/webscr';
    }
    else {
      $host = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
    }

    // POST IPN data back to PayPal to validate.
    try {
      $response = \Drupal::httpClient()
        ->request('POST', $host, [
        'form_params' => [
          'cmd' => '_notify-validate',
        ] + $ipn,
      ]);
    } catch (TransferException $e) {
      $this
        ->getLogger('uc_paypal')
        ->error('IPN validation failed with HTTP error %error.', [
        '%error' => $e
          ->getMessage(),
      ]);
      return;
    }

    // Check IPN validation response to determine if the IPN was valid..
    if ($response
      ->getBody() != 'VERIFIED') {
      $this
        ->getLogger('uc_paypal')
        ->error('IPN transaction failed verification.');
      uc_order_comment_save($order_id, 0, $this
        ->t('An IPN transaction failed verification for this order.'), 'admin');
      return;
    }

    // Check for a duplicate transaction ID.
    $duplicate = (bool) $this->database
      ->queryRange('SELECT 1 FROM {uc_payment_paypal_ipn} WHERE txn_id = :id AND status <> :status', 0, 1, [
      ':id' => $txn_id,
      ':status' => 'Pending',
    ])
      ->fetchField();
    if ($duplicate) {
      if ($order
        ->getPaymentMethodId() != 'credit') {
        $this
          ->getLogger('uc_paypal')
          ->notice('IPN transaction ID has been processed before.');
      }
      return;
    }
    $this->database
      ->insert('uc_payment_paypal_ipn')
      ->fields([
      'order_id' => $order_id,
      'txn_id' => $txn_id,
      'txn_type' => $ipn['txn_type'],
      'mc_gross' => $amount,
      'status' => $ipn['payment_status'],
      'receiver_email' => $email,
      'payer_email' => $ipn['payer_email'],
      'received' => $this->dateTime
        ->getRequestTime(),
    ])
      ->execute();
    switch ($ipn['payment_status']) {
      case 'Canceled_Reversal':
        uc_order_comment_save($order_id, 0, $this
          ->t('PayPal has canceled the reversal and returned @amount @currency to your account.', [
          '@amount' => uc_currency_format($amount, FALSE),
          '@currency' => $ipn['mc_currency'],
        ]), 'admin');
        break;
      case 'Completed':
        if (abs($amount - $order
          ->getTotal()) > 0.01) {
          $this
            ->getLogger('uc_paypal')
            ->warning('Payment @txn_id for order @order_id did not equal the order total.', [
            '@txn_id' => $txn_id,
            '@order_id' => $order
              ->id(),
            'link' => Link::createFromRoute($this
              ->t('view'), 'entity.uc_order.canonical', [
              'uc_order' => $order
                ->id(),
            ])
              ->toString(),
          ]);
        }
        $comment = $this
          ->t('PayPal transaction ID: @txn_id', [
          '@txn_id' => $txn_id,
        ]);
        uc_payment_enter($order_id, 'paypal_wps', $amount, $order
          ->getOwnerId(), NULL, $comment);
        uc_order_comment_save($order_id, 0, $this
          ->t('PayPal IPN reported a payment of @amount @currency.', [
          '@amount' => uc_currency_format($amount, FALSE),
          '@currency' => $ipn['mc_currency'],
        ]));
        break;
      case 'Denied':
        uc_order_comment_save($order_id, 0, $this
          ->t("You have denied the customer's payment."), 'admin');
        break;
      case 'Expired':
        uc_order_comment_save($order_id, 0, $this
          ->t('The authorization has failed and cannot be captured.'), 'admin');
        break;
      case 'Failed':
        uc_order_comment_save($order_id, 0, $this
          ->t("The customer's attempted payment from a bank account failed."), 'admin');
        break;
      case 'Pending':
        $order
          ->setStatusId('paypal_pending')
          ->save();
        uc_order_comment_save($order_id, 0, $this
          ->t('Payment is pending at PayPal: @reason', [
          '@reason' => $this
            ->pendingMessage($ipn['pending_reason']),
        ]), 'admin');
        break;

      // You, the merchant, refunded the payment.
      case 'Refunded':
        $comment = $this
          ->t('PayPal transaction ID: @txn_id', [
          '@txn_id' => $txn_id,
        ]);
        uc_payment_enter($order_id, 'paypal_wps', $amount, $order
          ->getOwnerId(), NULL, $comment);
        break;
      case 'Reversed':
        $this
          ->getLogger('uc_paypal')
          ->error('PayPal has reversed a payment!');
        uc_order_comment_save($order_id, 0, $this
          ->t('Payment has been reversed by PayPal: @reason', [
          '@reason' => $this
            ->reversalMessage($ipn['reason_code']),
        ]), 'admin');
        break;
      case 'Processed':
        uc_order_comment_save($order_id, 0, $this
          ->t('A payment has been accepted.'), 'admin');
        break;
      case 'Voided':
        uc_order_comment_save($order_id, 0, $this
          ->t('The authorization has been voided.'), 'admin');
        break;
    }
  }

  /**
   * Returns a message for the pending reason of a PayPal payment.
   */
  protected function pendingMessage($reason) {
    switch ($reason) {
      case 'address':
        return $this
          ->t('The payment is pending because your customer did not include a confirmed shipping address and your Payment Receiving Preferences is set to allow you to manually accept or deny each of these payments.');
      case 'authorization':
        return $this
          ->t('The payment is pending because you set the payment action to Authorization and have not yet captured funds.');
      case 'echeck':
        return $this
          ->t('The payment is pending because it was made by an eCheck that has not yet cleared.');
      case 'intl':
        return $this
          ->t('The payment is pending because you hold a non-U.S. account and do not have a withdrawal mechanism. You must manually accept or deny this international payment from your Account Overview.');
      case 'multi_currency':
        return $this
          ->t('The payment is pending because you do not have a balance in the currency sent, and you do not have your Payment Receiving Preferences set to automatically convert and accept this payment. You must manually accept or deny a payment of this currency from your Account Overview.');
      case 'order':
        return $this
          ->t('The payment is pending because you set the payment action to Order and have not yet captured funds.');
      case 'paymentreview':
        return $this
          ->t('The payment is pending while it is being reviewed by PayPal for risk.');
      case 'unilateral':
        return $this
          ->t('The payment is pending because it was made to an e-mail address that is not yet registered or confirmed.');
      case 'upgrade':
        return $this
          ->t('The payment is pending because it was either made via credit card and you do not have a Business or Premier account or you have reached the monthly limit for transactions on your account.');
      case 'verify':
        return $this
          ->t('The payment is pending because you are not yet a verified PayPal member. Please verify your account.');
      case 'other':
        return $this
          ->t('The payment is pending for a reason other than those listed above. For more information, contact PayPal Customer Service.');
      default:
        return $this
          ->t('Reason "@reason" unknown; contact PayPal Customer Service for more information.', [
          '@reason' => $reason,
        ]);
    }
  }

  /**
   * Returns a message for the reason code of a PayPal reversal.
   */
  protected function reversalMessage($reason) {
    switch ($reason) {
      case 'chargeback':
        return $this
          ->t('The customer has initiated a chargeback.');
      case 'guarantee':
        return $this
          ->t('The customer triggered a money-back guarantee.');
      case 'buyer-complaint':
        return $this
          ->t('The customer filed a complaint about the transaction.');
      case 'refund':
        return $this
          ->t('You gave the customer a refund.');
      default:
        return $this
          ->t('Reason "@reason" unknown; contact PayPal Customer Service for more information.', [
          '@reason' => $reason,
        ]);
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 1
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityManager protected property The entity manager.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 2
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 2
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::currentUser protected function Returns the current user. 1
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityManager Deprecated protected function Retrieves the entity manager service.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 2
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 2
ControllerBase::redirect protected function Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait::redirect
ControllerBase::state protected function Returns the state storage service.
LinkGeneratorTrait::$linkGenerator protected property The link generator. 1
LinkGeneratorTrait::getLinkGenerator Deprecated protected function Returns the link generator.
LinkGeneratorTrait::l Deprecated protected function Renders a link to a route given a route name and its parameters.
LinkGeneratorTrait::setLinkGenerator Deprecated public function Sets the link generator service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PayPalController::$database protected property The database service.
PayPalController::$dateTime protected property The datetime.time service.
PayPalController::$paymentMethodManager protected property The payment method manager.
PayPalController::$session protected property The session.
PayPalController::create public static function Instantiates a new instance of this class. Overrides ControllerBase::create
PayPalController::ipn public function Processes the IPN HTTP request.
PayPalController::pendingMessage protected function Returns a message for the pending reason of a PayPal payment.
PayPalController::processIpn protected function Processes Instant Payment Notifications from PayPal.
PayPalController::reversalMessage protected function Returns a message for the reason code of a PayPal reversal.
PayPalController::__construct public function Constructs a CheckoutController.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
UrlGeneratorTrait::$urlGenerator protected property The url generator.
UrlGeneratorTrait::getUrlGenerator Deprecated protected function Returns the URL generator service.
UrlGeneratorTrait::setUrlGenerator Deprecated public function Sets the URL generator service.
UrlGeneratorTrait::url Deprecated protected function Generates a URL or path for a specific route based on the given parameters.