At the moment I have 'hacked' the paypal module and altered the return page to a custom page we have created. This is so we can fire the rule for completed orders when we check that the IPN is good payment successful and total value is total in order and we have stock in the commerce product key module.

Why? Well many of our users were not returning from paypal, and their orders were never moved to 'processed' or visible in the order list. Created a clone of the that view with opposite filter conditions to find them. We have to move those by hand to the next state and create accounts manually. This really is a pain in any store maintainers behind.

How we get around this, currently.
So in a custom version of my commerce product key module I call this function

/**
 * @see hook_commerce_payment_transaction_presave($transaction)
 */
function commerce_product_key_commerce_payment_transaction_presave($transaction) {
  //only worry about successful transactions
  if ($transaction->status == 'success') {
    $order = commerce_order_load($transaction->order_id);
    $balance = commerce_payment_order_balance($order);
    if (($balance['amount'] >= 0) && ($transaction->amount > 0)) {
      //force the order to completed
      commerce_checkout_complete($order);
        
      //this will only process orders that have been assigned to users.
      if ($order->uid != 0) {
        commerce_product_key_update($order);
        watchdog('commerce_product_key', 'Assigning keys payment was successful order: @order_id', array(
          '@order_id' => $transaction->order_id,),
          WATCHDOG_NOTICE);
      }
    }
    else {
      watchdog('commerce_product_key', 'Payment was NOT successful, not assigning keys order: @order_id, status: @status, amount: @amount, balance: @balance', array(
        '@order_id' => $transaction->order_id,
        '@status' => $transaction->status,
        '@amount' => $transaction->amount,
        '@balance' => $balance['amount']),
        WATCHDOG_ERROR);
    }
  }
}

As you can see from the line commerce_checkout_complete($order); I make that call to fire that rule and process the order to next state.

Then we hade to 'hack' this paypal module to alter the return URL as that URL is now invalid as the commerce check if user has access when hitting it fails. So we created a custom page and returned users to that.

/**
 * Payment method callback: redirect form, a wrapper around the module's general
 *   use function for building a WPS form.
 */
function commerce_paypal_wps_redirect_form($form, &$form_state, $order, $payment_method) {
  // Return an error if the enabling action's settings haven't been configured.
  if (empty($payment_method['settings']['business'])) {
    drupal_set_message(t('PayPal WPS is not configured for use. No PayPal e-mail address has been specified.'), 'error');
    return array();
  }

  $settings = array(
    // Return to the previous page when payment is canceled
    'cancel_return' => url('checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key'], array('absolute' => TRUE)),

    // Return to the payment redirect page for processing successful payments
    //'return' => url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key'], array('absolute' => TRUE)),
    'return' => url('content/order-completed', array('absolute' => TRUE)),

    // Specify the current payment method instance ID in the notify_url
    'payment_method' => $payment_method['instance_id'],
  );

  return commerce_paypal_wps_order_form($form, $form_state, $order, $payment_method['settings'] + $settings);
}

this is the 'hack' 'return' => url('content/order-completed', array('absolute' => TRUE)),

Now what I want to know why I opened this ticket is how should I proceed with this, it been working in production like this for 3 months with no issue. I don't want to keep having to apply this hack with every change to the paypal module. Is there a cleaner way of allowing people to access the payment completed page? Or should I ask for a hook here to alter the return address? and avoid that commerce order completed page... ?

I would prefer to allow people to hit that page, as I can then start using the commerce google analytics module at the moment that module dose not work for me due to this hack. I am sure there will be others as this assumption of landing page is not met with my change.

Thanks in advance any feed back and help overcoming this will be greatly appreciated.

Comments

Devline’s picture

What do you think about some redirection on hook_init() ? I set up the following hook in test, and so far it seems to do the job :

>
function your_module_init() {
  global $user;
  global $base_root;
  global $base_path;
  
  // Redirect  checkout Payment return.
  $search_path_pos = strrpos( $_GET['q'], '/payment/return/');
  if ( $search_path_pos && $search_path_pos > 0) {
    if ( isset( $user->uid)) {

      // The user is logged in, redirect to its orders
      $path = $base_root . $base_path . 'user/' . $user->uid . '/orders';

    } else {

      // The user is not logged in, redirect to home page
      $path = $base_root . $base_path;
    }
    drupal_set_message(t('Your order has been recorded'), 'status');
    drupal_goto( $path, $options = array(), $http_response_code = 302);
  } 
}

Don't forget of course to add the language in the url if you activated the url detection in language settings.

roam2345’s picture

That is at least an option to work around the root of the problem not having access on the page commerce provides after check out. The problem with this solution is we are still routing the use off that page and it is never used hence other hooks that are used on that page are useless. ie. any module implementing hook_commerce_checkout_router would now break.

Thanks for the possible solution not to hack my module but I am still stuck with the problem of not hitting that page...

roam2345’s picture

Here is commerce_checkout_router() it make a call to commerce_checkout_access().. that is returning FALSE and causing the return page not to display since I am calling commerce_checkout_complete($order) because

// Return FALSE if the order does have a uid.
if ($order->uid) { return FALSE; }

my order does... :/

/**
 * Redirects invalid checkout attempts or displays the checkout form if valid.
 */
function commerce_checkout_router($order, $checkout_page = NULL) {
  $checkout_pages = commerce_checkout_pages();

  // If no checkout page is specified, default to the first one.
  if (empty($checkout_page)) {
    $checkout_page = reset($checkout_pages);
  }

  // If the user does not have access to checkout the order, return a 404. We
  // could return a 403, but then the user would know they've identified a
  // potentially valid checkout URL.
  if (!commerce_checkout_access($order, $checkout_page)) {
    return drupal_not_found();
  }

  // If there are no line items on the order, redirect away.
  $wrapper = entity_metadata_wrapper('commerce_order', $order);

  if (commerce_line_items_quantity($wrapper->commerce_line_items, 'product') == 0) {
    drupal_goto('<front>');
  }

  // Prior to displaying the checkout form, allow other modules to route the
  // checkout form.
  module_invoke_all('commerce_checkout_router', $order, $checkout_page);

  // Update the page title if specified.
  if (!empty($checkout_page['title'])) {
    drupal_set_title($checkout_page['title']);
  }

  return drupal_get_form('commerce_checkout_form_' . $checkout_page['page_id'], $order, $checkout_page);
}
function commerce_checkout_access($order, $checkout_page, $account = NULL) {
  global $user;

  // Default to the current user as the account whose access we're checking.
  if (empty($account)) {
    $account = clone($user);
  }

  // First, if this order doesn't belong to the account return FALSE.
  if ($account->uid) {
    if ($account->uid != $order->uid ) {
      return FALSE;
    }
  }
  elseif (empty($_SESSION['commerce_cart_completed_orders']) ||
    !in_array($order->order_id, $_SESSION['commerce_cart_completed_orders'])) {
    // Return FALSE if the order does have a uid.
    if ($order->uid) {
      return FALSE;
    }

    // And then return FALSE if the anonymous user's session doesn't specify
    // this order ID.
    if (empty($_SESSION['commerce_cart_orders']) || !in_array($order->order_id, $_SESSION['commerce_cart_orders'])) {
      return FALSE;
    }
  }

  // Load the order status object for the current order.
  $order_status = commerce_order_status_load($order->status);

  // If the order is not in a checkout status, return FALSE for any page but the
  // completion page unless the order is still a shopping cart.
  if ($order_status['state'] != 'checkout' && $checkout_page['page_id'] != 'complete') {
    if ($order_status['state'] == 'cart') {
      $checkout_pages = commerce_checkout_pages();
      $first_page = key($checkout_pages);

      if ($checkout_page['page_id'] != $first_page) {
        return FALSE;
      }
    }
    else {
      return FALSE;
    }
  }

  // If the order is still in checkout, only allow access to pages that it is
  // currently on or has previously completed.
  if ($order_status['state'] == 'checkout') {
    $status_checkout_page = commerce_checkout_page_load($order_status['checkout_page']);

    // However, if buttons aren't present on the status's checkout page, don't
    //  allow access unless the order status matches the page.
    if (!$status_checkout_page['buttons'] && $status_checkout_page['page_id'] != $checkout_page['page_id']) {
      // The exception here for the checkout completion page allows customers to
      // access this page for any order status the order might be moved to when
      // the order is completed.
      if ($checkout_page['page_id'] != 'complete') {
        return FALSE;
      }
    }
    else {
      // Compare the weights of the currently requested page against the weight
      // of the order status's page and return FALSE if it's greater.
      if ($checkout_page['weight'] > $status_checkout_page['weight']) {
        return FALSE;
      }
    }
  }
  // We've now handled above cases where the user is trying to access a checkout
  // page other than the completion page for an order that is not in a checkout
  // status.  We then handled cases where the user is trying to access any
  // checkout page for orders in a checkout status.  We now turn to cases where
  // the user is accessing the complete page for any other order state.
  elseif ($checkout_page['page_id'] == 'complete') {
    // Don't allow completion page access for orders in the cart or canceled states.
    if (in_array($order_status['state'], array('canceled', 'cart'))) {
      return FALSE;
    }
  }

  return TRUE;
}
c4rl’s picture

Subscribe

chaloum’s picture

has this been sorted out or is it still work in progress?

roam2345’s picture

I am still awaiting feed back from the commerce guys...

dkgiles’s picture

subscribing

kiwimind’s picture

Stop subscribing, start following. http://drupal.org/node/1306444

andyg5000’s picture

Status: Active » Closed (duplicate)

The patch in #1561594: Allow PayPal WPS order form data to be altered prior to form creation allows you to alter the $data array and change the return url . It should be committed very soon. I'm closing this issue as a dupe in favor of the other. Feel free to re-open if altering the $data array doesn't provide the functionality you're looking for.

Zem’s picture

Bumping this issue in hope it is going to be resolved because I really want the development of the Commerce Product Key module http://drupal.org/project/commerce_product_key to be able to continue.

duvalbruno’s picture

+1 for #10,

It will be nice as well to have the Commerce feature exposed to Services 3.0. There is a sandbox project available http://drupal.org/sandbox/drupalista-br/1283494

thanks

Malty’s picture

+1 for #10 & #11
Hello, I really like this resolved as I too would like more development of the Product Key module!!!
http://drupal.org/project/commerce_product_key

drugan’s picture

Hope it will be useful for you to review this post:
Add "Checkout complete page is viewed" event