10 replies [Last post]
jnasharu's picture
Offline
Joined: 03/09/2010
Juice: 18
Was this information Helpful?

I'd like to be able to disable checkout panes based on payment method chosen.

For example, if a user chooses to pay by check (payment pack), I would get rid of the billing fieldset and allow them to checkout without having to fill that information in.

I've tried adding some callbacks to try to alter the checkout form via ajax, but the checkout form doesn't seem to be cached so I can't grab it from there to alter. If I force the checkout form to cache I get strange behavior on an order cancel. And I don't think I should be doing that anyway.

I've also been trying to just hide the fields and then hi-jack the validate and submit handlers for the checkout form to allow the FAPI to ignore the required fields in the billing pane.

None of these have worked. Has anyone ever done anything like this?

Any help is greatly appreciated.

Cheers!
-nash

do username = jimmynash

jnasharu's picture
Offline
Joined: 03/09/2010
Juice: 18
My solution

A huge thanks to longwave on the #drupal-ubercart irc channel!

This is what I did to handle this:

Used hook_form_alter on the form id of "uc_cart_checkout_form"
Removed the requirement for the billing fields and added a custom submit handler in front of the regular submit handler:

<?php
         
//take requirement off of billing fields
         
foreach($form['panes']['billing'] as $key => $val) {
            if(
strpos($key, '#') !== 0 ){
             
$form['panes']['billing'][$key]['#required'] = FALSE;
            }
          }
         
         
//add custom submit handler
         
array_unshift($form['#validate'], 'my_module_checkout_form_validate');
?>

I modified the ubercart/payment/uc_payment/uc_payment.js to hide the billing fieldset if the user selects the payment option of "Check"
There is probably a better way to do this in my own main script file (never hack core, sometimes hack contrib as someone I know once said).

<?php
 
// Make the post to get the details for the chosen payment method.
 
$.post(path, data,
    function(
details) {
      if (
this_update.getTime() == payment_update) {
       
// If the response was empty, throw up the default message.
       
if (details == '') {
          $(
'#payment_details').empty().html(Drupal.settings.defPaymentMsg);
        }
       
// Otherwise display the returned details.
       
else {
          $(
'#payment_details').empty().append(details);
         
//HACK hide the billing pane if check is selected
         
var pm_check = path.indexOf('/payment_details/check');
          if(
pm_check != -1){
            $(
'#billing-pane').hide();
          }else{
            $(
'#billing-pane').show();
          }
         
//end HACK additions
       
}
      }
?>

Used jQuery to add the "required" asterisks back onto the field labels since Drupal no longer generates them.

<?php
 
$('fieldset#billing-pane td.field-label').each(function(){
    if($(
this).text() != 'Company:' && $(this).html() != ' '){
      $(
this).html('<span class="form-required">*</span>' + $(this).text())
    }
  });
?>

Overrode the theme_uc_cart_checkout_review() function from ubercart/uc_cart/uc_cart_pages.inc to get rid of the billing pane display if "Check" was the chosen payment method. This was done in the template.php file. There might be a better way to figure out the payment method, but it wasn't obvious to me at the time.

<?php
function my_theme_uc_cart_checkout_review($panes, $form) {
 
drupal_add_css(drupal_get_path('module', 'uc_cart') .'/uc_cart.css');
 
 
$output = check_markup(variable_get('uc_checkout_review_instructions', uc_get_message('review_instructions')), variable_get('uc_checkout_review_instructions_format', FILTER_FORMAT_DEFAULT), FALSE)
           .
'<table class="order-review-table">';

//MOD - do not output the billing information pane - need to search arrays because the position could change
 
foreach ($panes['Payment method'] as $arr) {
    foreach(
$arr as $v) {
      if(
$v == 'Check') {
        unset(
$panes['Billing information']);
      }
    }
  }
 
//end MOD

 

foreach ($panes as $title => $data) {
   
$output .= '<tr class="pane-title-row"><td colspan="2">'. $title
             
.'</td></tr>';
    if (
is_array($data)) {
      foreach (
$data as $row) {
        if (
is_array($row)) {
          if (isset(
$row['border'])) {
           
$border = ' class="row-border-'. $row['border'] .'"';
          }
          else {
           
$border = '';
          }
         
$output .= '<tr valign="top"'. $border .'><td class="title-col" '
                   
.'nowrap>'. $row['title'] .':</td><td class="data-col">'
                  
. $row['data'] .'</td></tr>';
        }
        else {
         
$output .= '<tr valign="top"><td colspan="2">'. $row .'</td></tr>';
        }
      }
    }
    else {
     
$output .= '<tr valign="top"><td colspan="2">'. $data .'</td></tr>';
    }
  }

 

$output .= '<tr class="review-button-row"><td colspan="2">'. $form
           
.'</td></tr></table>';

  return

$output;
}
?>

Finally, put in the code for the new submit handler. Since the billing fields are still necessary for credit card payments, I needed to validate them on my own since the FAPI is no longer treating them as required. I still can't figure out why form_set_error isn't highlighting the fields for error, but it wasn't a show stopper for me.

<?php
function my_module_checkout_form_validate($form, &$form_state) {

 

//validate the billing fields if payment was not by check
 
if($form_state['values']['panes']['payment']['payment_method'] != 'check'){
   
$billing_fields = $form_state['values']['panes']['billing'];
   
$readable = array(
                     
'billing_first_name' => 'First Name',
                     
'billing_last_name' => 'Last Name',
                     
'billing_street1' => 'Street Address',
                     
'billing_city' => 'City',
                     
'billing_zone' => 'State/Province',
                     
'billing_postal_code' => 'Postal Code',
                     
'billing_phone' => 'Phone number',
                      );
   
    foreach(
$billing_fields as $field => $value){
      if(
$field != 'billing_company' && $field != 'billing_street2'){
       
//check for empty field
       
if(!$value){
         
form_set_error($field, 'The ' . $readable[$field] . ' field is required.');
        }
      }
    }
  }
 
}
?>

It all seems kind of long winded, but I had been working at this for a bit. If anyone sees anything really wrong with this or has a better solution I would appreciate any feedback.

Hopefully if someone else is trying to do this it might help.

Again, huge thanks to longwave for the tips!!

Cheers - nash

do username = jimmynash

majnoona@drupal.org's picture
Offline
Joined: 07/02/2009
Juice: 90
Thanks

I'm working on a similar issue -- user can apply a 100% discount coupon at which point I need the billing fields to disappear. I've already done a customization for a "Free Registration" mode when users select products with a price of $0, but coupons are applied on the checkout page so it needs to be ajaxy.

My javascript skills are pretty non-existent -- how do I get the pane to refresh so the hide kicks in?

Is there no way to set $form['panes']['billing']['#access'] = FALSE ? That gets around the validation issues without having to hack it.

Thanks,
maj

jnasharu's picture
Offline
Joined: 03/09/2010
Juice: 18
mmmm coopuns...

I haven't spent much time with coupons in ubercart.... yet.

It looks like the coupon actions are ajaxy already. You enter the coupon code on the checkout screen and hit the Apply to Order button. If the coupon code is valid it applies it.

I'm not sure how you would check for that action and then if the cart total is zero, hide the billing fields. The hack I applied to the ubercart javascript listed above was because I wasn't sure how to ride along with that post action from another script.

Basically you would need to detect the ajax post that validates the coupon code and if the cart total is zero, run your own script to apply a $('#billing-pane').hide(); JQuery call to hide the fields. You could modify the ubercart script that does the coupon application to hide the fields but I don't recommend it.
The problem I had was that the billing fields needed to be required if they weren't paying by check, thus the custom validation. If you can leave them as optional all the time then you don't need that.

I'm not sure what you mean by the $form['panes']['billing']['#access'] item. The ones I needed to worry about were the #required attributes because the form api would always try to validate them if that was set.

Sorry if I'm not being much help, but the JQuery should be pretty easy if you take a look at whatever file ubercart is using to do the coupon validation.

Best of luck, post what you end up with!

Cheers!
-nash

do username = jimmynash

majnoona@drupal.org's picture
Offline
Joined: 07/02/2009
Juice: 90
PHP vs AJAX

Thanks for the reply

I guess I'm confused about what AJAX can do vs what PHP/hook_form_alter can do.

If you use hook_form_alter to set $form['panes']['billing']['#access'] = FALSE it hides the billing pane and doesn't try to validate it, which is what I want. BUT hook_form_alter only works when loading the form.

In the uc_free_order.js I see

$("input:radio[value=free_order]").attr('disabled', 'disabled').parent().hide(0);
$("input:radio[value=free_order]").removeAttr('disabled').attr('checked', 'checked').parent().show(0);

Which hides the free order option if the order has a total > 0.1 -- can I do the same thing for the billing fields? Or make them not required and then hide() them... sorry I'm grasping here, but I guess I need to do what you did and unrequire everything and then re-require as needed, it just seems like the wrong way around...

Thanks,
m

jnasharu's picture
Offline
Joined: 03/09/2010
Juice: 18
AHAH was my first thought

Initially I wanted to make changes to the form using AHAH within Drupal. I haven't used it very much but when I do need to I check out a few posts and look at the core Poll module because it uses AHAH for adding new poll choices.

The problem with the form lies in the fact that the billing fields are already set to be required. Hiding them with JQuery is pretty trivial, but getting the FAPI to not validate those fields is not.

The technique using AHAH uses JQuery ajax to make a call to a menu callback function. That function then needs to pull the form from the form_cache and alter it, and the reload it. That way you can make ajax calls that can alter the form array without having to reload the page. This is what needs to happen on the checkout form since we don't know what the total is going to be until after we're there.

What I ran into is that the checkout form with ubercart wasn't in the cache table so that I could pull and alter it. When I forced the form into cache, it started acting really weird. Someone more familiar with ubercart would probably have to speak to that. It seems that a lot of ubercart stuff happens in the session.

So even though it seems like the wrong way around, for me it was the only way around since the "right" way as I understand it was to alter the form with AHAH. I just couldn't get it done in the timeframe I had available.

Here's some AHAH stuff in case you want to spend a little more time on it.
http://drupal.org/node/331941
http://www.netaustin.net/2009/01/26/dynamic-form-altering-using-ahah-rig...
http://drupalsn.com/learn-drupal/drupal-tutorials/getting-going-ahah-and...

Best of luck!
-nash

do username = jimmynash

bkotj's picture
Offline
Joined: 10/21/2010
Juice: 4
Remove panes with js

Hey M,

There's probably a more appropriate way to do this with a module, but oh well, I used js. In my page.tpl.php (or anywhere the js code will get called) I added:

$(document).ready(function(){
var amount = $(".subtotal .uc-price").html();
if (amount=="$0.00"){
$("#cart-pane, #payment-pane, .messages").hide();
}
});

'?>

Once the document is loaded, I grab the subtotal (you could grab any price, just select the appropriate class). If it is equal to 0 I then call the hide() function to hide whatever I want, which in this case is the cart pane, payment pane, and the drupal messages. To be safer, you could use some js string manipulation to remove the "$", convert the string "0.00" to an int, then check if it is less than 0.1, or something.

Late, but oh well!

Cheers,
Kotj

jpdaut's picture
Offline
Joined: 02/25/2009
Juice: 70
Re: My solution

Could you share your code on uc_cart.pages.inc and uc_payment.js? While your explanations are clear, it would still help me to see working code, where you inserted, for ex., taking requirement off of billing fields in uc_cart.page.inc etc.

Thanks
JPD

jnasharu's picture
Offline
Joined: 03/09/2010
Juice: 18
Custom module

The first part of the code where I remove the required on the billing fields was placed in a custom module inside hook_form_alter. The only part of ubercart that I touched was the uc_payment.js. All other modifications were made in either a custom module using hooks to override or in the template.php file to change variables for display.

It's kind of hard to show where everything goes since it is spread between The theme layer and my own custom module. The only reason I messed with the .js file was because I haven't figured out how to hook onto js event to run my own js code.

If you are trying this and are stuck on something in particular I can try and help. And I recommend trying to find a way to not alter uc_payment.js if at all possible.

do username = jimmynash

jj
jj's picture
Offline
Joined: 06/21/2011
Juice: 7
Re: Custom module

Hey buddy, thanks for the code, it's been really useful to me.
Just to avoid hacking the uc_payment.js, would be better to have something like this in your own .js file:

$("input[@name='panes[payment][payment_method]']").change(function(){
     
      if ($("input[@name='panes[payment][payment_method]']:checked").val()=='check')
            $('#billing-pane').hide();
      else
            $('#billing-pane').show();
   
});
weespoon's picture
Offline
Joined: 01/23/2012
Juice: 3
Custom Module D7

In Drupal 7 the local JS in your module looks like this:

(function ($) {

Drupal.behaviors.uc_stored_auth = {
    attach: function(context, settings) {
      if (Drupal.ajax['edit-panes-payment-payment-method-check'].element.checked)
        jQuery('#billing-pane').hide();
      else
        jQuery('#billing-pane').show();
      }
    }
})(jQuery);

paired with form alter:

function uc_stored_auth_form_uc_cart_checkout_form_alter(&$form, &$form_state) {
    drupal_add_js(drupal_get_path('module','uc_stored_auth').'/uc_stored_auth.js');
}