You are here

protected function PayPalController::processIpn in Ubercart 8.4

Processes Instant Payment Notifications from PayPal.

Parameters

array $ipn: The IPN data.

1 call to PayPalController::processIpn()
PayPalController::ipn in payment/uc_paypal/src/Controller/PayPalController.php
Processes the IPN HTTP request.

File

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

Class

PayPalController
Returns responses for PayPal routes.

Namespace

Drupal\uc_paypal\Controller

Code

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;
  }
}