Jump to content
cinolas

Checkout when order total is $0

Recommended Posts

I'm using osC 2.34 BS GOLD and I'm customizing a Gift Voucher contrib along with a Free payment module. Everything is in place and working well but osC refuses to let me checkout when the order total = $0. I get to checkout_confirmation.php, click Confirm Order and I'm sent back to checkout_payment.php with the message:

Quote

 

Please select a payment method for your order.

I'm told this is normal behaviour for osCommerce, and is part of core code that needs to be changed. But I also suspect that the Free payment module I'm using may be a bit outdated, and so may be missing something.

Debugging this led me to line 90 of checkout_process.php where this IF sends me back to checkout_payment.php with the error:

if ( ($payment_modules->selected_module != $payment) || ( is_array($payment_modules->modules) && (sizeof($payment_modules->modules) > 1) && !is_object($$payment) ) || (is_object($$payment) && ($$payment->enabled == false)) ) {
    tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode(ERROR_NO_PAYMENT_MODULE_SELECTED), 'SSL'));
  }

Specifically this part returns TRUE:

!is_object($$payment)

The Free payment module I'm using has a code of "free". $payment = 'free' so $$payment = $free, and if I put a var_dump on checkout_confirmation.php I see:

["free"]=>
    object(free)#19 (5) {
      ["code"]=>
      string(4) "free"
      ["title"]=>
      string(36) "Pay with my Gift Certificate balance"
      ["description"]=>
      string(12) "Free Product"
      ["enabled"]=>
      bool(true)
      ["sort_order"]=>
      string(1) "1"
    }

It looks like an object to me. But I admittedly don't know much about php objects...

So why is !is_object($$payment) returning TRUE? Could the $$ nomenclature not be working? Is there a way to test that?

Or maybe I'm going at this the wrong way? How can allow checkout when the total = $0?

Any clues are greatly appreciated. Thanks!

Share this post


Link to post
Share on other sites

If you change that section of code to

  if ( ($payment_modules->selected_module != $payment) || ( is_array($payment_modules->modules) && (sizeof($payment_modules->modules) > 1) && !is_object($$payment) ) || (is_object($$payment) && ($$payment->enabled == false)) ) {
var_dump($payment_modules);
var_dump(!is_object($$payment));
var_dump($$payment);
exit();
    tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode(ERROR_NO_PAYMENT_MODULE_SELECTED), 'SSL'));
  }

what do you get? 


Always back up before making changes.

Share this post


Link to post
Share on other sites

I get:

object(payment)#18 (2) { ["modules"]=> array(4) { [0]=> string(8) "free.php" [1]=> string(7) "cod.php" [2]=> string(16) "moneriscampg.php" [3]=> string(18) "paypal_express.php" } ["selected_module"]=> NULL } bool(true) NULL

 

Share this post


Link to post
Share on other sites

@cinolasI don't have a Gold version setup but I do have an Edge version that is one or two versions prior to the final version. In that shop, I set free shipping for all, enabled the Check/Money Order option and added a product with $0 for the price. I was able to complete a checkout for it. I don't know what the difference in the code is but you may want to install the Frozen version, verify you can complete a checkout with a $0 product and then compare its code to what's in your version.

Share this post


Link to post
Share on other sites

That says that no payment module is selected.  So I don't think that the problem is on checkout_confirmation but checkout_payment.  Something is going wrong with the selection code.  Perhaps post that.  How are you setting $_SESSION['payment'] to be 'free'? 


Always back up before making changes.

Share this post


Link to post
Share on other sites

Thanks @ecartz, 'free' is the module code for the Free payment module I'm using as part of my install. It gets selected like a regular payment module. I've only made one change to it. I changed the update_status() so that it would only be available if there's enough Gift Voucher balance on that customer's account to pay for the entire order:

        if (tep_session_is_registered('customer_id')) {
                $gv_query = tep_db_query("select amount from coupon_gv_customer where customer_id = '" .         tep_db_input($_SESSION['customer_id']) . "'");
                $gv_result = tep_db_fetch_array($gv_query);
        }													      
  
        if ($order->info['total'] > $gv_result['amount']) {
                $this->enabled = false;
        }

Re-reading this just now I got suspicious that the function may not work the second time, once the Gift Voucher has been applied and the total is $0. And it looked to me like it was being run from checkout_process.php, line 88, just before the problematic, but I commented out the disabling in the payment module and it gave me the same results, so I clearly don't understand what's happening.

I'm not sure exactly where the $_SESSION['payment'] is normally set.

I searched my code for bits that changed the selected_module, and of note is this part of /includes/classes/payment.php that deals with that and was changed by the Gift Voucher contrib. Everything else appears to be stock.

	// Begin Gift Vouchers Secure
 // check credit covers was setup to test whether credit covers is set in other parts of the code
  function check_credit_covers() {
     global $credit_covers;
     return $credit_covers;
  } 

  function pre_confirmation_check() {
      global $credit_covers, $payment_modules;  

      if (is_array($this->modules)) {
        if (is_object($GLOBALS[$this->selected_module]) && ($GLOBALS[$this->selected_module]->enabled) ) {

	    if ($credit_covers) {
            $GLOBALS[$this->selected_module]->enabled = false;
            $GLOBALS[$this->selected_module] = NULL;
            $payment_modules = '';
          } else { 
          $GLOBALS[$this->selected_module]->pre_confirmation_check();
        }
      }
    }
} 

// End Gift Vouchers Secure 

 

Share this post


Link to post
Share on other sites

Thanks @Jack_mcs, that sounds like a lot of work and I'm not sure it would be conclusive since I can't move everything to Frozen, and I don't understand quite enough to mix match both versions.

Could the bit above, from the Gift Voucher contrib, be deselecting the payment module? Which I figure could be an unforeseen side-effect of mixing those two contribs.

I'm not sure what check_credit_covers() does, so I'm not sure when $credit_covers is TRUE or not.

But I'm thinking it checks to see if the credit balance "covers" the total of the invoice, and if so disables the payment module? Which would make sense if you're operating without the Free payment module that I'm trying to integrate, but in my scenario I think I want to keep the module selected?

Searching blindly here, it may have nothing to do with my problem :)

Edited by cinolas

Share this post


Link to post
Share on other sites

Perhaps

	    if ($credit_covers && ('free' !== $this->selected_module)) {
            $GLOBALS[$this->selected_module]->enabled = false;
            $GLOBALS[$this->selected_module] = NULL;
            $payment_modules = '';
          } else { 
          $GLOBALS[$this->selected_module]->pre_confirmation_check();
        }

 


Always back up before making changes.

Share this post


Link to post
Share on other sites

That didn't do it, I still get:

object(payment)#18 (2) { ["modules"]=> array(4) { [0]=> string(8) "free.php" [1]=> string(7) "cod.php" [2]=> string(16) "moneriscampg.php" [3]=> string(18) "paypal_express.php" } ["selected_module"]=> NULL } bool(true) NULL

In fact I commented out the lines inside the first part of the IF and it didn't change anything, so I'm guessing that's not the problematic area after all.

Thanks for trying though! I'm a bit weary of just editing code that's part of the checkout process without knowing exactly what it does, for fear that it may have deeper repercussions than just fixing my problem. But you guys keep me going.

@Jack_mcs does the Git Voucher contrib empty the selected payment method for any reason? maybe it's doing it somewhere else during confirmation?

Cheers to you two!

Share this post


Link to post
Share on other sites

Out of curiosity, so I can learn as I go, why would !is_object($$payment) == TRUE just because ["selected_module"]=> NULL?

If I break down the IF in my original post and add custom debug messages, to see where it fails, like:

if ( ($payment_modules->selected_module != $payment) ) {
	tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode('Not same as selected payment'), 'SSL'));
  }

if ( ( is_array($payment_modules->modules) && (sizeof($payment_modules->modules) > 1) ) ) {
	if ( !is_object($$payment)) {
		tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode('Is an array bigger than 1 AND $$payment is not an object'), 'SSL'));
	} else {
	tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode('Is an array bigger than 1 but $$payment is an object'), 'SSL'));
	}
  }

I get "Is an array bigger than 1 AND $$payment is not an object"

Share this post


Link to post
Share on other sites
3 hours ago, cinolas said:

 does the Git Voucher contrib empty the selected payment method for any reason? maybe it's doing it somewhere else during confirmation?

No, it doesn't. But I think you may be looking at this wrong. If you can't complete a checkout with a price of $0, that has nothing to do with the Gift Vouchers code. You can uninstall the Gift Voucher and Coupon modules and that would mean their code never gets called. If you still can't complete a checkout with a price of $0 you should tackle that problem first.

Share this post


Link to post
Share on other sites

@Jack_mcs You're, once again, completely correct. I disabled the Gift Voucher payment module and installed COD, I created a free product and free shipping, and tried to check out and it still wouldn't let me do it. The code catches at exactly the same place:

!is_object($$payment)

during checkout_process.php

What methods are available for me to debug this further?

It looks like it IS an object, but obviously it isn't. How can I break this down to see why it's not an object?

Or taking this from the other end: Where in this version of osC does it test for the total = $0?

OR, combined, why would order total = $0 lead to $$payment ($free) not being an object?

Are there characteristic bits of code I should be searching for that might yield some clues?

Could my problem be in:

["selected_module"]=> NULL

Considering this IF returns FALSE:

$payment_modules->selected_module != $payment

Thanks all! I'm just really digging for any leads at this point. osC obviously doesn't handle $0 very well, I just have to find where the unhandled exception is.

Any help is GOOOOOLD!

Share this post


Link to post
Share on other sites

@ecartz You're onto something. With a var_dump on both the checkout_confirmation.php and checkout_process.php, when I try to checkout with an order total = $0 I get this on checkout_confirmation.php:

["selected_module"]=>
      string(4) "cod"

but on checkout_process.php the selected_module is now NULL:

["selected_module"]=>
      NULL

If I do the same with a regular order where the total > $0 selected_module remains "cod" even on the checkout_process.php page.
But why? Where does it empty the selected_module? How can I find that?

I searched my osC code for "selected_module" and it seems it should be happening in /includes/classes/payment.php or in the first part of checkout_process.php

I also have the Pay Without Account (PWA) contrib installed, and the file /includes/modules/content/login/cm_pwa_login.php also seems to meddle with the selected payment module through some JS. I doubt this has anything to do with it, but it might look like a red flag to you guys:

$output .= '<script>
                function htrn_cm_pwa_payment_update_cfg_value() {
                  var htrn_cm_pwa_payment_selected_modules = \'\';

                  if ($(\'input[name="cm_pwa_payment_module[]"]\').length > 0) {
                    $(\'input[name="cm_pwa_payment_module[]"]:checked\').each(function() {
                      htrn_cm_pwa_payment_selected_modules += $(this).attr(\'value\') + \';\';
                    });

                    if (htrn_cm_pwa_payment_selected_modules.length > 0) {
                      htrn_cm_pwa_payment_selected_modules = htrn_cm_pwa_payment_selected_modules.substring(0, htrn_cm_pwa_payment_selected_modules.length - 1);
                    }
                  }

                  $(\'#htrn_cm_pwa_payment_modules\').val(htrn_cm_pwa_payment_selected_modules);
                }

                $(function() {
                  htrn_cm_pwa_payment_update_cfg_value();

                  if ($(\'input[name="cm_pwa_payment_module[]"]\').length > 0) {
                    $(\'input[name="cm_pwa_payment_module[]"]\').change(function() {
                      htrn_cm_pwa_payment_update_cfg_value();
                    });
                  }
                });
                </script>';

 

Again and again, thank you so much for the help!

Share this post


Link to post
Share on other sites

@cinolasIn the includes/classes/payment.php file, try changing this line

$js .= "\n" . '  if (payment_value == null) {' . "\n" .

to

$js .= "\n" . '  if (false && payment_value == null) {' . "\n" .

That's where the message to select a payment method is created and it may be stopping the process. Of course, if it works, it will break the checkout if a payment method is not chosen but there is a way around that.

Share this post


Link to post
Share on other sites

Thanks for the clue @Jack_mcs I added the FALSE to the script but I still get

["selected_module"]=>
    NULL

on checkout_process.php

That spot is where I added the script that changes the value of the hidden input, so my code looks like this:

$js .= "\n" . 'if (payment_value == "free") {' . "\n" .
               '      	gc_box.value = 1;' . "\n" .
               '      	submitter = 1;' . "\n" .
               '  }' . "\n\n" .
               '  if (FALSE && payment_value == null && submitter != 1) {' . "\n" . 
               '    error_message = error_message + "' . JS_ERROR_NO_PAYMENT_MODULE_SELECTED . '";' . "\n" .
               '    error = 1;' . "\n" .
               '  }' . "\n\n" .
               '  if (error == 1 && submitter != 1) {' . "\n" . 
               '    alert(error_message);' . "\n" .
               '    return false;' . "\n" .
               '  } else {' . "\n" .
               '    return true;' . "\n" .
               '  }' . "\n" .
               '}' . "\n" .
               '//--></script>' . "\n";

And that seems to work (setting the gc_box.value). Again, I'm just mentioning this in case I made a newb mistake and that's what the problem is.

Share this post


Link to post
Share on other sites

I believe I've found what clears the selected payment method! Line 62 of my checkout_process.php has a line from the Gift Voucher contrib that clears $payment:

//Begin Gift Vouchers Secure
  if ($credit_covers) $payment=''; 
//End Gift Vouchers Secure

Even when I use the COD payment method $credit_covers is TRUE and $payment is set to ' ', which causes the following line:

$payment_modules = new payment($payment);

to deselect the payment method.

In /includes/classes/order_total.php, (line 267 after the Gift Voucher contrib modifications) is:

        if ($order->info['total'] - $total_deductions <= 0 ) {

					     if(!tep_session_is_registered('credit_covers')) tep_session_register('credit_covers');

          $credit_covers = true;

        }

Which explicitly sets $credit_covers to TRUE if the total = $0

@Jack_mcs FOUND IT! :D

NOW what is the best way to modify this for my purpose? Since I'm using the Free payment method contrib in conjunction with the Gift Voucher contrib to achieve a somewhat custom setup, I THINK I can just comment out this part of checkout process, like:

//Begin Gift Vouchers Secure
  //if ($credit_covers) $payment=''; 
//End Gift Vouchers Secure

So that even if the order total = $0, the $payment doesn't get cleared.

Is there a downside or risk to doing this?

THANKS!

Edited by cinolas

Share this post


Link to post
Share on other sites
3 hours ago, cinolas said:

if ($order->info['total'] - $total_deductions <= 0 ) {

You can try changing the <= in the above to <

If you comment it out and the coupon total is greater than the order then the customer would be able to complete the order with a price less than 0.

Share this post


Link to post
Share on other sites
17 hours ago, Jack_mcs said:

You can try changing the <= in the above to <

If you comment it out and the coupon total is greater than the order then the customer would be able to complete the order with a price less than 0.

That is a good idea.

Which actually leads me to the last modification I want to make: preventing checkout when the total after payment > 0 (not fully paid). In all my repetitive testing of the checkout process with Gift Vouchers I've found that sometimes (rarely, randomly it seems) the Gift Voucher balance doesn't actually get applied. I'm not sure why, and it only happens when going back and forth between checkout_confirm.php and checout_shipping.php repetitively. To prevent any chances of someone being able to checkout when the total after payment and discounts > $0 I'm thinking this would be a good place to add the check.

That IF looks like this now, after your recommendations:

        if ($order->info['total'] - $total_deductions < 0 ) {
                if(!tep_session_is_registered('credit_covers')) tep_session_register('credit_covers');
                $credit_covers = true;
        }else{   // belts and suspenders to get rid of credit_covers variable if it gets set once and they put something else in the cart
                if(tep_session_is_registered('credit_covers')) tep_session_unregister('credit_covers');	
        }

I'm changing it to:

        if (($order->info['total'] - $total_deductions < 0) || ($payment == 'free' && ($order->info['total'] - $total_deductions > 0))) {
                if(!tep_session_is_registered('credit_covers')) tep_session_register('credit_covers');
                $credit_covers = true;
        }else{   // belts and suspenders to get rid of credit_covers variable if it gets set once and they put something else in the cart
                if(tep_session_is_registered('credit_covers')) tep_session_unregister('credit_covers');	
        }

Does that make sense? Just making sure I'm not doing anything dumb.

Cheers!

Share this post


Link to post
Share on other sites
6 hours ago, cinolas said:

if (($order->info['total'] - $total_deductions < 0) || ($payment == 'free' && ($order->info['total'] - $total_deductions > 0))) {

 

This says that if the payment method is free, that the credit covers when the order total is less than the total deductions.  That wouldn't seem to be what you would want.  Perhaps replace that line with

if (($payment == 'free') && ($order->info['total'] > $total_deductions)) {
  tep_session_unregister('payment');
  $payment = null;
}

if ($order->info['total'] < $total_deductions) {

Which instead says, if the the payment is invalid, unset it.  Then continues normally. 

Note that I would actually expect that check to occur in the module.  If it's not working, that might be a sign of a bigger problem somewhere.  I.e. you might be better off figuring out why it isn't working rather than trying to make this particular circumstance work.  Because there may be another circumstance that you are not checking. 


Always back up before making changes.

Share this post


Link to post
Share on other sites
On 12/3/2020 at 6:57 PM, ecartz said:

This says that if the payment method is free, that the credit covers when the order total is less than the total deductions.  That wouldn't seem to be what you would want.  Perhaps replace that line with


if (($payment == 'free') && ($order->info['total'] > $total_deductions)) {
  tep_session_unregister('payment');
  $payment = null;
}

if ($order->info['total'] < $total_deductions) {

Which instead says, if the the payment is invalid, unset it.  Then continues normally.  

I'm not entirely following you here @ecartz This line:

if (($order->info['total'] - $total_deductions < 0) || ($payment == 'free' && ($order->info['total'] - $total_deductions > 0)))

In my setup, the payment fails if credit_covers = true. So it looks to me like this says: payment fails IF (too much deductions were applied (total < total_deductions)) OR (if $payment == 'free' AND not enough deductions were applied to cover the total (total > deductions))

The second part is to catch the bug where $payment is 'free' as it should, but the deductions don't cover the total because they were not applied (because of the bug).

Am I missing something? Thanks for working with me on this. Your help is GREATLY appreciated :D

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×