Post by Dayo » Fri Apr 10, 2009 11:59 pm

It seems every time a user creates a cart, an order is created and stored. So, from just browsing on my site in trying to create the moneybookers module, I already have almost 100 orders stored in the db.

What should happen is that the order should be stored in the session only with a temp id number until the user returns to the success page after a transaction after which it can be stored as an order in the database when a real order number can be generated.

New member

Posts

Joined
Thu Nov 13, 2008 3:27 am

Post by Daniel » Sat Apr 11, 2009 2:47 am

A order is not made from just browsering your site. You have to reach the checkout confirm page.

Payment gateways need order numbers generated before the order information is sent to the paymenbt gateway. Otherwise you can not reference which orders have been paid.

Trust me! I have spent months trying to find the best solution.

You clients should not be referencing the db directly but the admin order page.

You are onyl making so many orders because you are testing.

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by Dayo » Sat Apr 11, 2009 5:22 am

Well I get what you are saying but it is still a bug.

If a customer decides to pay by Method A but changes their mind for whatever reason during the process and ultimately decides on Method B, the store owner will be left with a record of an order that is basically a spurious order. You can be sure that there will be several cases of this happening.

Couldn't you store the order details including a temp order number in the session until the client returns to the success page after which this can be written to the database. That way, the orders on record equal orders that have been paid for.

I know you feel you shouldn't copy anything from other cart programs but this is how Zencart does it (simplified) and they have it right.

Something to think about.

New member

Posts

Joined
Thu Nov 13, 2008 3:27 am

Post by Daniel » Sat Apr 11, 2009 6:09 am

Dayo wrote:Well I get what you are saying but it is still a bug.
Its not a bug.
Dayo wrote:If a customer decides to pay by Method A but changes their mind for whatever reason during the process and ultimately decides on Method B, the store owner will be left with a record of an order that is basically a spurious order. You can be sure that there will be several cases of this happening.
Only if the customer clicks on the confirm button before leaving the store will the order be shown to the store owner. I have actually changed this in the lastest version so store owners using PayPal can choose clicking the confirm button or waiting for the callback before their order appears.

I have been contacted many times from people who's orders have been lost because of there payment gateways callback not working. This method is the only sure fire way.
Dayo wrote:Couldn't you store the order details including a temp order number in the session until the client returns to the success page after which this can be written to the database. That way, the orders on record equal orders that have been paid for.
No because temp order numbers may be the same if to many people are making orders at the same time. If you start using transactions in the orders that still means you miss order numbers and my lose the order if the callback is not working.

As for the success page. Well not all payment gateways redirect clients back to the straght away and customers may decide to click the back button.
Dayo wrote:I know you feel you shouldn't copy anything from other cart programs but this is how Zencart does it (simplified) and they have it right.
Really? I once built 15 web sites for one company using zencart and osc and the biggest problem we had was that some orders where being lost because the callback did not always work. It took people ages to go though each transaction and call the customer up asking what they ordered.

zen cart and osc really on that the callback will always reach there web site.

Also I have told you clients will not be looking at the DB they look in the admin for new orders. The unconfirmed orders get deleted after a month.

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by Dayo » Sat Apr 11, 2009 3:25 pm

Daniel wrote:
Dayo wrote:Well I get what you are saying but it is still a bug.
Its not a bug.
Sorry but it is a bug. For instance, when I look at the sales report in admin, it does not show me what my sales are but the cumulation of temporary orders stored in the db.
Daniel wrote:
Dayo wrote:I know you feel you shouldn't copy anything from other cart programs but this is how Zencart does it (simplified) and they have it right.
Really? I once built 15 web sites for one company using zencart and osc and the biggest problem we had was that some orders where being lost because the callback did not always work. It took people ages to go though each transaction and call the customer up asking what they ordered.
zen cart and osc really on that the callback will always reach there web site.
Also I have told you clients will not be looking at the DB they look in the admin for new orders. The unconfirmed orders get deleted after a month.
Perhaps the critical thing is on the "once" as in, sometime in the past. You might want to look at it again as it certainly works very well now.

When you say the client (I think you mean store owner) would only look at order in the admin, That is correct. The problem is that when you look at orders in the admin, you see a load of spurious orders (in the admin itself). This is logical when you think about it. Afterall, the admin display is simply a call on the data in the db.

The storing the number in the session is a simplified explanation of what ZC does. A more accurate but still simplified description is that the unique order (including order number) is stored temporarily in a separate table (let us call it Temp_Orders). When the order is confirm by the customer getting to the success page, the last order recored against the customer is retrieved and this is what gets stored (let us call this Actual_Orders). The data in Actual_Orders is what is displayed in admin views etc.

Looking at the sales report in admin, it tells me I have over £25,000 of sales but I have actually not completed a single one.

At the moment, OPC's Orders table is equivalent to the Temp_Orders and has entries for each time the customer hits the confirmation page and is what gets displayed in the admin and is thus not very useful or accurate. I don't want to see the temporary stuff in the admin and while I know it is necessary to store them, this should be behind the scenes.

What you could do is rename it as a temp table and populate the actual orders table based on the criteria described.

Also, when the customer returns and you are making the actual orders table, you can clear every order by the customer older than the last one being used. In this way, you will keep the size of the temp orders down.

New member

Posts

Joined
Thu Nov 13, 2008 3:27 am

Post by Daniel » Sat Apr 11, 2009 7:25 pm

"Sorry but it is a bug. For instance, when I look at the sales report in admin, it does not show me what my sales are but the cumulation of temporary orders stored in the db."

This does nto happen on my version.

Have you altered the code?

Did you follow the upgrade instructions from 1.2.5 > 1.2.6 correctly? I have recently removed the field confirm and i have set it to only show an order if an order status is set.

In the latest version the reports are set doing this "WHERE order_status_id > 0"

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by Dayo » Sat Apr 11, 2009 7:33 pm

I installed 1.2.6 direct. I tried 0.7.something a few months ago and deleted it after a couple of days. The 1.2.6 install was a fresh installation and no modifications have been made apart from creating a new module for moneybookers.

I will try deleting the 1.2.6 files, dropping the db and reinstalling from scratch again just to be sure I didn't mess anything up while I was having a look at files in trying to understand the application while building the module.

New member

Posts

Joined
Thu Nov 13, 2008 3:27 am

Post by Dayo » Sat Apr 11, 2009 8:56 pm

Confirmed as a bug.

I deleted the 1.2.6 files, dropped the db and reinstalled from scratch and can confirm that when I look at the reports in admin after going past the confirm page, the sales report has the order listed as a sale.

It is also listed in the products purchased report.


As I go in and out out the confirmation page, the reports get incremented.

While doing this, I picked up another bug which may be Safari Specific.

New member

Posts

Joined
Thu Nov 13, 2008 3:27 am

Post by Daniel » Sun Apr 12, 2009 1:55 am

which payment module does this problem happen with?

All of them?

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by Daniel » Sun Apr 12, 2009 2:35 am

do you have a link to your site?

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by Daniel » Sun Apr 12, 2009 2:55 am

when you say:

I deleted the 1.2.6 files, dropped the db and reinstalled from scratch and can confirm that when I look at the reports in admin after going past the confirm page, the sales report has the order listed as a sale.

So it is working as its supposed to! You go past the confirm page the order is shown.

What a store owner does is delete these orders or investigate why when leaving store to pay that the customer has not paid. it could be because the credit card did not go through or it could be the payment gateway was down. This has happened with worldpay. Some hackers wanted money from worldpay for stopping DOS attacks. WorldPay losts a lot of money over one xmas period.

Also storing order_id's in the session is not a good idea becuase the payment gateway can not access some one session.

Look its like some one paying by cheque. The order needs to be held with all the information once the person has clicked confim. Then once the payment has been confirmed the order status changes.

ZenCart only works as well as the payment gateway lets it.

Its easy to stop a zencart store from getting a callback if you do a DOS attack or if the server is temporarily down. If this is the case you lose the whole order. Also godaddy servers block paypal callbacks.

Some competitors do acutally do DOS attacks againest sites in busy periods like xmas. It happen when I worked for a company in china and when I works for a company printing xmas cards.

You are basiclly suggesting using the callback like zencart? Which I have told people many times it will not 100% of the time work. Even some hosting companies can only give a 99% uptime.

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by Dayo » Sun Apr 12, 2009 4:42 am

Daniel wrote:which payment module does this problem happen with?
All of them?
It will happen with every module where you go past the confirmation page.
Daniel wrote:do you have a link to your site?
I don't have a site up with this and only just evaluating it. I am ultimately thinking of developing a bridge between OPC and the coming Gallery3 program.

I did one for Gallery2 and Zencart but think this might have some potential because of the straightforward admin end.

I have some comments on this app in relation to that.

Daniel wrote:when you say:
"I deleted the 1.2.6 files, dropped the db and reinstalled from scratch and can confirm that when I look at the reports in admin after going past the confirm page, the sales report has the order listed as a sale."

So it is working as its supposed to! You go past the confirm page the order is shown.

What a store owner does is delete these orders or investigate why when leaving store to pay that the customer has not paid. it could be because the credit card did not go through or it could be the payment gateway was down. This has happened with worldpay. Some hackers wanted money from worldpay for stopping DOS attacks. WorldPay losts a lot of money over one xmas period.
Sorry but I disagree with that.

Here is a slightly extreme example...

01. A customer selects Product X worth £100.
02. You offer Paypal & Moneybookers
03. Customer selects Paypal
04. Customer goes past confirmation page and OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 01" and redirects to form action url which is Paypal site
05. Customer gets to paypal and decides not to use selected shipping method and returns to store
06. Customer changes shipping method and selects Paypal again
07. Customer goes past confirmation page and OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 02" and redirects to form action url which is Paypal site
08. Customer gets to paypal and decides not to use selected shipping method and returns to store
09. Customer changes shipping method and selects Paypal again
10. Customer goes past confirmation page and OPC redirects to form action url which is Paypal site
11. OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 02"
12. Customer gets to paypal and decides not to use paypal afterall and returns to store
13. Customer selects Moneybookers
14. Customer goes past confirmation page and OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 03" and redirects to form action url which is Moneybookers
15. Customer makes payment and completes transaction
16. Customer returns to success page

When you look in your reports in OPC Admin, it will read the orders table and tell you that you have sold £300 worth of Product X. This is clearly wrong and should not be the case. I know the scenario might not happen very often, but the thing is that the reports should be accurate all the time.
Daniel wrote: Also storing order_id's in the session is not a good idea becuase the payment gateway can not access some one session.
Fair enough but there is no reason why the order could not be stored in a temp orders table and when the user hits the success page, the final_orders table could then be updated with the most recent order id for the customer after which all records for that customer is cleared from the temp-orders table.

The final_orders table would then be what the reports in the admin read and store owners will always get accurate reports.

In the example I gave earlier, here is how it should go...
01. A customer selects Product X worth £100.
02. You offer Paypal & Moneybookers
03. Customer selects Paypal
04. Customer goes past confirmation page and OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 01" and redirects to form action url which is Paypal site
05. Customer gets to paypal and decides not to use selected shipping method and returns to store
06. Customer changes shipping method and selects Paypal again
07. Customer goes past confirmation page and OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 02" and redirects to form action url which is Paypal site
08. Customer gets to paypal and decides not to use selected shipping method and returns to store
09. Customer changes shipping method and selects Paypal again
10. Customer goes past confirmation page and OPC redirects to form action url which is Paypal site
11. OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 02"
12. Customer gets to paypal and decides not to use paypal afterall and returns to store
13. Customer selects Moneybookers
14. Customer goes past confirmation page and OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 03" and redirects to form action url which is Moneybookers
15. Customer makes payment and completes transaction
16. Customer returns to success page.
17. OPC validates request for success page and if passed, looks up last recorded entry for Customer01 in temp_orders table and writes this to orders table saying "Customer01 buys Product X worth £100 with Order ID 03". At the same time, OPC clears all records in temp_orders table more than three months old to keep the size down.

When you look in your reports in OPC Admin, it will read the orders table and tell you that you have sold £100 worth of Product X which would be correct.

Even if the customer bails out of moneybookers without returning to the site, you will still be able to look into the temp_orders table.
Daniel wrote: Look its like some one paying by cheque. The order needs to be held with all the information once the person has clicked confim. Then once the payment has been confirmed the order status changes.
With the example I gave. If paying by cheque...

01. A customer selects Product X worth £100.
02. You offer Paypal & Cheque
03. Customer selects Paypal
04. Customer goes past confirmation page and OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 01" and redirects to form action url which is Paypal site05. Customer gets to paypal and decides not to use selected shipping method and returns to store
06. Customer changes shipping method and selects Paypal again
07. Customer goes past confirmation page and OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 02" and redirects to form action url which is Paypal site
08. Customer gets to paypal and decides not to use selected shipping method and returns to store
09. Customer changes shipping method and selects Paypal again
10. Customer goes past confirmation page and OPC redirects to form action url which is Paypal site
11. OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 02"
12. Customer gets to paypal and decides not to use paypal afterall and returns to store
13. Customer selects cheque
14. Customer goes past confirmation page and OPC writes order to temp_orders table saying "Customer01 buys Product X worth £100 with Order ID 03" and redirects to form action url which is OPC Success Page
15. OPC validates request for success page and if passed, looks up last recorded entry for Customer01 in temp orders table and writes this to orders table saying "Customer01 buys Product X worth £100 with Order ID 03". At the same time, OPC clears all records in temp_orders table more than three months old to keep the size down.

Again, when you look in your reports in OPC Admin, it will read the orders table and tell you that you have sold £100 worth of Product X which would be correct.
Daniel wrote: ZenCart only works as well as the payment gateway lets it.

Its easy to stop a zencart store from getting a callback if you do a DOS attack or if the server is temporarily down. If this is the case you lose the whole order. Also godaddy servers block paypal callbacks.

Some competitors do acutally do DOS attacks againest sites in busy periods like xmas. It happen when I worked for a company in china and when I works for a company printing xmas cards.

You are basiclly suggesting using the callback like zencart? Which I have told people many times it will not 100% of the time work. Even some hosting companies can only give a 99% uptime.
Being hit by a DOS is something separate to how the site structure flows. I also suspect it has been a while since you last looked at ZC. The interfact is clunky but it does what it does very well.

I think you have the admin interface down pat and by adopting a few items from others and modifying accordingly, you will have a killer tool.

New member

Posts

Joined
Thu Nov 13, 2008 3:27 am

Post by Daniel » Sun Apr 12, 2009 6:31 am

"When you look in your reports in OPC Admin, it will read the orders table and tell you that you have sold £300 worth of Product X. This is clearly wrong and should not be the case. I know the scenario might not happen very often, but the thing is that the reports should be accurate all the time."

The £300 problem can occur when people claim they are going to pay by cheque or bank transfer. That is why there is a filter on the reports. You select which ever order status is the completed status.

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by rombeh » Sun Apr 12, 2009 2:14 pm

I don't know if it's related to this talk, but what I found is everytime I enter confirmation page, opencart insert new record so it's duplicate order and it's related table record over and over.
I try to resolve this problem, but I don't know if it's good solution (but it's work for me):
I create new field called session_id on order table, store php session_id() as a value, and modified catalog/model/checkout/order.php to first check if new record with current session_id is already there
Here is my version of the file.

Code: Select all

<?php
class ModelCheckoutOrder extends Model {
	public function getOrder($order_id) {
		$query = $this->db->query("SELECT * FROM `order` WHERE order_id = '" . (int)$order_id . "'");

		return $query->row;
	}

    private function _removeAbandonedCart(){
        $query  = $this->db->query("SELECT order_id FROM `order` WHERE date_added < '" . date('Y-m-d', strtotime('-1 month')) . "' AND `order_status_id` = '0'");
        $results = $query->rows;
        if(!empty($results)){
            $tables = array('order','order_history','order_product','order_option','order_download','order_total');
            foreach ($results as $result) {
                $orderId = intval($result['order_id']);
                $query = null;
                $conditions = " `order_id`='" . $orderId . "'";
                foreach($tables as $key=>$table){
                    $query = "DELETE FROM `" . $table . "` WHERE " . $conditions;
                    $this->db->query($query);
                }
            }
        }
        return true;
    }

    // first add session_id column to `order` table
    // ALTER TABLE `order` ADD `order_id` VARCHAR( 40 ) NOT NULL AFTER `order_id`
	public function create($data) {
        $this->_removeAbandonedCart();
        // get active session id for current customer/cart, I think it's better to pass it's value from controller
        if(isset($data['session_id'])){
            $sessId = sha1($data['session_id']);
        }else{
            $sessId = sha1(session_id());
        }

        // is carts data already inserted?
        $order_id = null;
        $query  = $this->db->query("SELECT `order_id` FROM `order` WHERE `session_id`='" . $sessId . "' AND `order_status_id` = '0'");
        if($query->num_rows){
            $order_id = $query->row['order_id'];
        }

        // prepare sql statement, descide if we need to insert new record or update existing record
        $query_prefix = "INSERT INTO `order` SET ";
        $query_data = " `session_id`='" . $sessId . "',
                    customer_id = '" . (int)$data['customer_id'] . "',
                    firstname = '" . $this->db->escape($data['firstname']) . "',
                    lastname = '" . $this->db->escape($data['lastname']) . "',
                    email = '" . $this->db->escape($data['email']) . "',
                    telephone = '" . $this->db->escape($data['telephone']) . "',
                    fax = '" . $this->db->escape($data['fax']) . "',
                    total = '" . (float)$data['total'] . "',
                    language_id = '" . $this->db->escape($data['language_id']) . "',
                    currency = '" . $this->db->escape($data['currency']) . "',
                    currency_id = '" . $this->db->escape($data['currency_id']) . "',
                    value = '" . (float)$data['value'] . "', ip = '" . $this->db->escape($data['ip']) . "',
                    shipping_firstname = '" . $this->db->escape($data['shipping_firstname']) . "',
                    shipping_lastname = '" . $this->db->escape($data['shipping_lastname']) . "',
                    shipping_company = '" . $this->db->escape($data['shipping_company']) . "',
                    shipping_address_1 = '" . $this->db->escape($data['shipping_address_1']) . "',
                    shipping_address_2 = '" . $this->db->escape($data['shipping_address_2']) . "',
                    shipping_city = '" . $this->db->escape($data['shipping_city']) . "',
                    shipping_postcode = '" . $this->db->escape($data['shipping_postcode']) . "',
                    shipping_zone = '" . $this->db->escape($data['shipping_zone']) . "',
                    shipping_country = '" . $this->db->escape($data['shipping_country']) . "',
                    shipping_address_format = '" . $this->db->escape($data['shipping_address_format']) . "',
                    shipping_method = '" . $this->db->escape($data['shipping_method']) . "',
                    payment_firstname = '" . $this->db->escape($data['payment_firstname']) . "',
                    payment_lastname = '" . $this->db->escape($data['payment_lastname']) . "',
                    payment_company = '" . $this->db->escape($data['payment_company']) . "',
                    payment_address_1 = '" . $this->db->escape($data['payment_address_1']) . "',
                    payment_address_2 = '" . $this->db->escape($data['payment_address_2']) . "',
                    payment_city = '" . $this->db->escape($data['payment_city']) . "',
                    payment_postcode = '" . $this->db->escape($data['payment_postcode']) . "',
                    payment_zone = '" . $this->db->escape($data['payment_zone']) . "',
                    payment_country = '" . $this->db->escape($data['payment_country']) . "',
                    payment_address_format = '" . $this->db->escape($data['payment_address_format']) . "',
                    payment_method = '" . $this->db->escape($data['payment_method']) . "',
                    comment = '" . $this->db->escape($data['comment']) . "',
                    date_modified = NOW(),
                    date_added = NOW()";

        $conditions = null;
        if(!empty($order_id)){
            // delete previously recorded data, reinserted later
            $tables = array('order_product','order_option','order_download','order_total');
            foreach($tables as $tableId=>$table){
                $this->db->query("DELETE FROM `" . $table . "` WHERE `order_id`='" . $order_id . "'");
            }
            // change query mode to UPDATE, append query conditions
            $query_prefix = "UPDATE `order` SET ";
            $conditions = " WHERE `order_id`='" . $order_id . "'";
        }
        // construct complete query statement
        $query = $query_prefix . $query_data . $conditions;
        //print_r($order_id);
        //print_r($query);
        //die;
        // execute query
        $this->db->query($query);

        // get order_id from db->lastInsertId if it's new record
        if(empty($order_id)){
            $order_id = $this->db->getLastId();
        }

        foreach ($data['products'] as $product) {
            $this->db->query("INSERT INTO order_product SET order_id = '" . (int)$order_id . "', product_id = '" . (int)$product['product_id'] . "', name = '" . $this->db->escape($product['name']) . "', model = '" . $this->db->escape($product['model']) . "', price = '" . (float)$product['price'] . "', discount = '" . (float)$product['discount'] . "', total = '" . (float)$product['total'] . "', tax = '" . (float)$product['tax'] . "', quantity = '" . (int)$product['quantity'] . "'");

            $order_product_id = $this->db->getLastId();

            foreach ($product['option'] as $option) {
                $this->db->query("INSERT INTO order_option SET order_id = '" . (int)$order_id . "', order_product_id = '" . (int)$order_product_id . "', name = '" . $this->db->escape($option['name']) . "', `value` = '" . $this->db->escape($option['value']) . "', price = '" . (float)$product['price'] . "', prefix = '" . $this->db->escape($option['prefix']) . "'");
            }

            foreach ($product['download'] as $download) {
                $this->db->query("INSERT INTO order_download SET order_id = '" . (int)$order_id . "', order_product_id = '" . (int)$order_product_id . "', name = '" . $this->db->escape($download['name']) . "', filename = '" . $this->db->escape($download['filename']) . "', mask = '" . $this->db->escape($download['mask']) . "', remaining = '" . (int)($download['remaining'] * $product['quantity']) . "'");
            }
        }

        foreach ($data['totals'] as $total) {
            $this->db->query("INSERT INTO order_total SET order_id = '" . (int)$order_id . "', title = '" . $this->db->escape($total['title']) . "', text = '" . $this->db->escape($total['text']) . "', `value` = '" . (float)$total['value'] . "', sort_order = '" . (int)$total['sort_order'] . "'");
        }

        return $order_id;
	}

	public function confirm($order_id, $order_status_id, $comment = '') {
		$order_query = $this->db->query("SELECT *, l.code AS language FROM `order` o LEFT JOIN language l ON (o.language_id = l.language_id) WHERE o.order_id = '" . (int)$order_id . "' AND o.order_status_id = '0'");

		if ($order_query->num_rows) {
			$this->db->query("UPDATE `order` SET order_status_id = '" . (int)$order_status_id . "' WHERE order_id = '" . (int)$order_id . "'");

			$this->db->query("INSERT INTO order_history SET order_id = '" . (int)$order_id . "', order_status_id = '" . (int)$order_status_id . "', comment = '" . $this->db->escape($comment) . "', date_added = NOW()");

			$this->language->load($order_query->row['filename'], $order_query->row['language']);
			$this->language->load('checkout/confirm', $order_query->row['language']);

			$subject = sprintf($this->language->get('mail_new_order_subject'), $this->config->get('config_store'), $order_id);

			$message  = $this->language->get('mail_new_order_greeting') . "\n\n";
			$message .= $this->language->get('mail_new_order_order') . ' ' . $order_id . "\n";
			$message .= $this->language->get('mail_new_order_date_added') . ' ' . date($this->language->get('date_format_short'), strtotime($order_query->row['date_added'])) . "\n";

			$order_status_query = $this->db->query("SELECT * FROM order_status WHERE order_status_id = '" . (int)$order_status_id . "' AND language_id = '" . (int)$order_query->row['language_id'] . "'");

			$message .= $this->language->get('mail_new_order_order_status') . ' ' . @$order_status_query->row['name'] . "\n\n";
			$message .= $this->language->get('mail_new_order_product') . "\n";

			$order_product_query = $this->db->query("SELECT * FROM order_product WHERE order_id = '" . (int)$order_id . "'");

			foreach ($order_product_query->rows as $result) {
				$message .= $result['quantity'] . 'x ' . $result['name'] . ' (' . $result['model'] . ') ' . $this->currency->format($result['total'], $order_query->row['currency'], $order_query->row['value']) . "\n";
			}

			$message .= "\n";

			$message .= $this->language->get('mail_new_order_total') . "\n";

			$order_total_query = $this->db->query("SELECT * FROM order_total WHERE order_id = '" . (int)$order_id . "' ORDER BY sort_order ASC");

			foreach ($order_total_query->rows as $result) {
				$message .= $result['title'] . ' ' . $result['text'] . "\n";
			}

			$message .= "\n";

			$message .= $this->language->get('mail_new_order_invoice') . "\n";
			$message .= html_entity_decode($this->url->http('account/invoice&order_id=' . $order_id)) . "\n\n";

			$order_download_query = $this->db->query("SELECT * FROM order_download WHERE order_id = '" . (int)$order_id . "'");

			if ($order_download_query->num_rows) {
				$message .= $this->language->get('mail_new_order_download') . "\n";
				$message .= $this->url->http('account/download') . "\n\n";
			}

			if ($comment) {
				$message .= $this->language->get('mail_new_order_comment') . "\n\n";
				$message .= $comment . "\n\n";
			}

			$message .= $this->language->get('mail_new_order_footer');

			$mail = new Mail();
			$mail->setTo($order_query->row['email']);
			$mail->setFrom($this->config->get('config_email'));
			$mail->setSender($this->config->get('config_store'));
			$mail->setSubject($subject);
			$mail->setText($message);
			$mail->send();
		}
	}

	public function update($order_id, $order_status_id, $comment = '', $notifiy = FALSE) {
		$query = $this->db->query("SELECT * FROM `order` WHERE order_id = '" . (int)$order_id . "' AND order_status_id > '0'");

		if ($query->num_rows) {
			$this->db->query("UPDATE `order` SET order_status_id = '" . (int)$order_status_id . "', date_modified = NOW() WHERE order_id = '" . (int)$order_id . "'");

			$this->db->query("INSERT INTO order_history SET order_id = '" . (int)$order_id . "', order_status_id = '" . (int)$order_status_id . "', comment = '" . $this->db->escape($comment) . "', date_added = NOW()");

			if ($notifiy) {
				$query = $this->db->query("SELECT *, os.name AS status, l.code AS language FROM `order` o LEFT JOIN order_status os ON (o.order_status_id = os.order_status_id AND os.language_id = o.language_id) LEFT JOIN language l ON (o.language_id = l.language_id) WHERE o.order_id = '" . (int)$order_id . "'");

				if ($query->num_rows) {
					$this->language->load($query->row['filename'], $query->row['language']);
					$this->language->load('checkout/confirm', $order_query->row['language']);

					$subject = sprintf($this->language->get('mail_update_order_subject'), $this->config->get('config_store'), $order_id);

					$message  = $this->language->get('mail_update_order_order') . ' ' . $order_id . "\n";
					$message .= $this->language->get('mail_update_order_date_added') . ' ' . date($this->language->get('date_format_short'), strtotime($query->row['date_added'])) . "\n\n";
					$message .= $this->language->get('mail_update_order_order_status') . "\n\n";
					$message .= $query->row['status'] . "\n\n";

					$message .= $this->language->get('mail_update_order_invoice') . "\n";
					$message .= html_entity_decode($this->url->http('account/invoice&order_id=' . $order_id));

					if ($comment) {
						$message .= $this->language->get('mail_update_order_comment') . "\n\n";
						$message .= $comment . "\n\n";
					}

					$message .= $this->language->get('mail_update_order_footer');

					$mail = new Mail();
					$mail->setTo($query->row['email']);
					$mail->setFrom($this->config->get('config_email'));
					$mail->setSender($this->config->get('config_store'));
					$mail->setSubject($subject);
					$mail->setText($message);
					$mail->send();
				}
			}
		}
	}

	public function complete($order_id) {
		if ($this->config->get('config_stock_subtract')) {
			$order_query = $this->db->query("SELECT * FROM `order` WHERE order_id = '" . (int)$order_id . "' AND order_status_id > '1'");

			if ($order_query->num_rows) {
				$order_product_query = $this->db->query("SELECT * FROM order_product WHERE order_id = '" . (int)$order_id . "'");

				foreach ($order_product_query->rows as $result) {
					$this->db->query("UPDATE product SET quantity = (quantity - " . (int)$result['quantity'] . ") WHERE product_id = '" . (int)$result['product_id'] . "'");
				}
			}
		}
	}
}
?>

OC ext: JNE Shipping (automatic realtime/up-to-date quotes/rates)
OC works: ev-style | everlastingcatalog | jade&alice
WTF: goroworks | play&forget


Newbie

Posts

Joined
Sun Apr 12, 2009 1:59 pm

Post by Daniel » Sun Apr 12, 2009 4:09 pm

rombeh wrote:I don't know if it's related to this talk, but what I found is everytime I enter confirmation page, opencart insert new record so it's duplicate order and it's related table record over and over.
I try to resolve this problem, but I don't know if it's good solution (but it's work for me):
I create new field called session_id on order table, store php session_id() as a value, and modified catalog/model/checkout/order.php to first check if new record with current session_id is already there
Here is my version of the file.

Code: Select all

<?php
class ModelCheckoutOrder extends Model {
	public function getOrder($order_id) {
		$query = $this->db->query("SELECT * FROM `order` WHERE order_id = '" . (int)$order_id . "'");

		return $query->row;
	}

    private function _removeAbandonedCart(){
        $query  = $this->db->query("SELECT order_id FROM `order` WHERE date_added < '" . date('Y-m-d', strtotime('-1 month')) . "' AND `order_status_id` = '0'");
        $results = $query->rows;
        if(!empty($results)){
            $tables = array('order','order_history','order_product','order_option','order_download','order_total');
            foreach ($results as $result) {
                $orderId = intval($result['order_id']);
                $query = null;
                $conditions = " `order_id`='" . $orderId . "'";
                foreach($tables as $key=>$table){
                    $query = "DELETE FROM `" . $table . "` WHERE " . $conditions;
                    $this->db->query($query);
                }
            }
        }
        return true;
    }

    // first add session_id column to `order` table
    // ALTER TABLE `order` ADD `order_id` VARCHAR( 40 ) NOT NULL AFTER `order_id`
	public function create($data) {
        $this->_removeAbandonedCart();
        // get active session id for current customer/cart, I think it's better to pass it's value from controller
        if(isset($data['session_id'])){
            $sessId = sha1($data['session_id']);
        }else{
            $sessId = sha1(session_id());
        }

        // is carts data already inserted?
        $order_id = null;
        $query  = $this->db->query("SELECT `order_id` FROM `order` WHERE `session_id`='" . $sessId . "' AND `order_status_id` = '0'");
        if($query->num_rows){
            $order_id = $query->row['order_id'];
        }

        // prepare sql statement, descide if we need to insert new record or update existing record
        $query_prefix = "INSERT INTO `order` SET ";
        $query_data = " `session_id`='" . $sessId . "',
                    customer_id = '" . (int)$data['customer_id'] . "',
                    firstname = '" . $this->db->escape($data['firstname']) . "',
                    lastname = '" . $this->db->escape($data['lastname']) . "',
                    email = '" . $this->db->escape($data['email']) . "',
                    telephone = '" . $this->db->escape($data['telephone']) . "',
                    fax = '" . $this->db->escape($data['fax']) . "',
                    total = '" . (float)$data['total'] . "',
                    language_id = '" . $this->db->escape($data['language_id']) . "',
                    currency = '" . $this->db->escape($data['currency']) . "',
                    currency_id = '" . $this->db->escape($data['currency_id']) . "',
                    value = '" . (float)$data['value'] . "', ip = '" . $this->db->escape($data['ip']) . "',
                    shipping_firstname = '" . $this->db->escape($data['shipping_firstname']) . "',
                    shipping_lastname = '" . $this->db->escape($data['shipping_lastname']) . "',
                    shipping_company = '" . $this->db->escape($data['shipping_company']) . "',
                    shipping_address_1 = '" . $this->db->escape($data['shipping_address_1']) . "',
                    shipping_address_2 = '" . $this->db->escape($data['shipping_address_2']) . "',
                    shipping_city = '" . $this->db->escape($data['shipping_city']) . "',
                    shipping_postcode = '" . $this->db->escape($data['shipping_postcode']) . "',
                    shipping_zone = '" . $this->db->escape($data['shipping_zone']) . "',
                    shipping_country = '" . $this->db->escape($data['shipping_country']) . "',
                    shipping_address_format = '" . $this->db->escape($data['shipping_address_format']) . "',
                    shipping_method = '" . $this->db->escape($data['shipping_method']) . "',
                    payment_firstname = '" . $this->db->escape($data['payment_firstname']) . "',
                    payment_lastname = '" . $this->db->escape($data['payment_lastname']) . "',
                    payment_company = '" . $this->db->escape($data['payment_company']) . "',
                    payment_address_1 = '" . $this->db->escape($data['payment_address_1']) . "',
                    payment_address_2 = '" . $this->db->escape($data['payment_address_2']) . "',
                    payment_city = '" . $this->db->escape($data['payment_city']) . "',
                    payment_postcode = '" . $this->db->escape($data['payment_postcode']) . "',
                    payment_zone = '" . $this->db->escape($data['payment_zone']) . "',
                    payment_country = '" . $this->db->escape($data['payment_country']) . "',
                    payment_address_format = '" . $this->db->escape($data['payment_address_format']) . "',
                    payment_method = '" . $this->db->escape($data['payment_method']) . "',
                    comment = '" . $this->db->escape($data['comment']) . "',
                    date_modified = NOW(),
                    date_added = NOW()";

        $conditions = null;
        if(!empty($order_id)){
            // delete previously recorded data, reinserted later
            $tables = array('order_product','order_option','order_download','order_total');
            foreach($tables as $tableId=>$table){
                $this->db->query("DELETE FROM `" . $table . "` WHERE `order_id`='" . $order_id . "'");
            }
            // change query mode to UPDATE, append query conditions
            $query_prefix = "UPDATE `order` SET ";
            $conditions = " WHERE `order_id`='" . $order_id . "'";
        }
        // construct complete query statement
        $query = $query_prefix . $query_data . $conditions;
        //print_r($order_id);
        //print_r($query);
        //die;
        // execute query
        $this->db->query($query);

        // get order_id from db->lastInsertId if it's new record
        if(empty($order_id)){
            $order_id = $this->db->getLastId();
        }

        foreach ($data['products'] as $product) {
            $this->db->query("INSERT INTO order_product SET order_id = '" . (int)$order_id . "', product_id = '" . (int)$product['product_id'] . "', name = '" . $this->db->escape($product['name']) . "', model = '" . $this->db->escape($product['model']) . "', price = '" . (float)$product['price'] . "', discount = '" . (float)$product['discount'] . "', total = '" . (float)$product['total'] . "', tax = '" . (float)$product['tax'] . "', quantity = '" . (int)$product['quantity'] . "'");

            $order_product_id = $this->db->getLastId();

            foreach ($product['option'] as $option) {
                $this->db->query("INSERT INTO order_option SET order_id = '" . (int)$order_id . "', order_product_id = '" . (int)$order_product_id . "', name = '" . $this->db->escape($option['name']) . "', `value` = '" . $this->db->escape($option['value']) . "', price = '" . (float)$product['price'] . "', prefix = '" . $this->db->escape($option['prefix']) . "'");
            }

            foreach ($product['download'] as $download) {
                $this->db->query("INSERT INTO order_download SET order_id = '" . (int)$order_id . "', order_product_id = '" . (int)$order_product_id . "', name = '" . $this->db->escape($download['name']) . "', filename = '" . $this->db->escape($download['filename']) . "', mask = '" . $this->db->escape($download['mask']) . "', remaining = '" . (int)($download['remaining'] * $product['quantity']) . "'");
            }
        }

        foreach ($data['totals'] as $total) {
            $this->db->query("INSERT INTO order_total SET order_id = '" . (int)$order_id . "', title = '" . $this->db->escape($total['title']) . "', text = '" . $this->db->escape($total['text']) . "', `value` = '" . (float)$total['value'] . "', sort_order = '" . (int)$total['sort_order'] . "'");
        }

        return $order_id;
	}

	public function confirm($order_id, $order_status_id, $comment = '') {
		$order_query = $this->db->query("SELECT *, l.code AS language FROM `order` o LEFT JOIN language l ON (o.language_id = l.language_id) WHERE o.order_id = '" . (int)$order_id . "' AND o.order_status_id = '0'");

		if ($order_query->num_rows) {
			$this->db->query("UPDATE `order` SET order_status_id = '" . (int)$order_status_id . "' WHERE order_id = '" . (int)$order_id . "'");

			$this->db->query("INSERT INTO order_history SET order_id = '" . (int)$order_id . "', order_status_id = '" . (int)$order_status_id . "', comment = '" . $this->db->escape($comment) . "', date_added = NOW()");

			$this->language->load($order_query->row['filename'], $order_query->row['language']);
			$this->language->load('checkout/confirm', $order_query->row['language']);

			$subject = sprintf($this->language->get('mail_new_order_subject'), $this->config->get('config_store'), $order_id);

			$message  = $this->language->get('mail_new_order_greeting') . "\n\n";
			$message .= $this->language->get('mail_new_order_order') . ' ' . $order_id . "\n";
			$message .= $this->language->get('mail_new_order_date_added') . ' ' . date($this->language->get('date_format_short'), strtotime($order_query->row['date_added'])) . "\n";

			$order_status_query = $this->db->query("SELECT * FROM order_status WHERE order_status_id = '" . (int)$order_status_id . "' AND language_id = '" . (int)$order_query->row['language_id'] . "'");

			$message .= $this->language->get('mail_new_order_order_status') . ' ' . @$order_status_query->row['name'] . "\n\n";
			$message .= $this->language->get('mail_new_order_product') . "\n";

			$order_product_query = $this->db->query("SELECT * FROM order_product WHERE order_id = '" . (int)$order_id . "'");

			foreach ($order_product_query->rows as $result) {
				$message .= $result['quantity'] . 'x ' . $result['name'] . ' (' . $result['model'] . ') ' . $this->currency->format($result['total'], $order_query->row['currency'], $order_query->row['value']) . "\n";
			}

			$message .= "\n";

			$message .= $this->language->get('mail_new_order_total') . "\n";

			$order_total_query = $this->db->query("SELECT * FROM order_total WHERE order_id = '" . (int)$order_id . "' ORDER BY sort_order ASC");

			foreach ($order_total_query->rows as $result) {
				$message .= $result['title'] . ' ' . $result['text'] . "\n";
			}

			$message .= "\n";

			$message .= $this->language->get('mail_new_order_invoice') . "\n";
			$message .= html_entity_decode($this->url->http('account/invoice&order_id=' . $order_id)) . "\n\n";

			$order_download_query = $this->db->query("SELECT * FROM order_download WHERE order_id = '" . (int)$order_id . "'");

			if ($order_download_query->num_rows) {
				$message .= $this->language->get('mail_new_order_download') . "\n";
				$message .= $this->url->http('account/download') . "\n\n";
			}

			if ($comment) {
				$message .= $this->language->get('mail_new_order_comment') . "\n\n";
				$message .= $comment . "\n\n";
			}

			$message .= $this->language->get('mail_new_order_footer');

			$mail = new Mail();
			$mail->setTo($order_query->row['email']);
			$mail->setFrom($this->config->get('config_email'));
			$mail->setSender($this->config->get('config_store'));
			$mail->setSubject($subject);
			$mail->setText($message);
			$mail->send();
		}
	}

	public function update($order_id, $order_status_id, $comment = '', $notifiy = FALSE) {
		$query = $this->db->query("SELECT * FROM `order` WHERE order_id = '" . (int)$order_id . "' AND order_status_id > '0'");

		if ($query->num_rows) {
			$this->db->query("UPDATE `order` SET order_status_id = '" . (int)$order_status_id . "', date_modified = NOW() WHERE order_id = '" . (int)$order_id . "'");

			$this->db->query("INSERT INTO order_history SET order_id = '" . (int)$order_id . "', order_status_id = '" . (int)$order_status_id . "', comment = '" . $this->db->escape($comment) . "', date_added = NOW()");

			if ($notifiy) {
				$query = $this->db->query("SELECT *, os.name AS status, l.code AS language FROM `order` o LEFT JOIN order_status os ON (o.order_status_id = os.order_status_id AND os.language_id = o.language_id) LEFT JOIN language l ON (o.language_id = l.language_id) WHERE o.order_id = '" . (int)$order_id . "'");

				if ($query->num_rows) {
					$this->language->load($query->row['filename'], $query->row['language']);
					$this->language->load('checkout/confirm', $order_query->row['language']);

					$subject = sprintf($this->language->get('mail_update_order_subject'), $this->config->get('config_store'), $order_id);

					$message  = $this->language->get('mail_update_order_order') . ' ' . $order_id . "\n";
					$message .= $this->language->get('mail_update_order_date_added') . ' ' . date($this->language->get('date_format_short'), strtotime($query->row['date_added'])) . "\n\n";
					$message .= $this->language->get('mail_update_order_order_status') . "\n\n";
					$message .= $query->row['status'] . "\n\n";

					$message .= $this->language->get('mail_update_order_invoice') . "\n";
					$message .= html_entity_decode($this->url->http('account/invoice&order_id=' . $order_id));

					if ($comment) {
						$message .= $this->language->get('mail_update_order_comment') . "\n\n";
						$message .= $comment . "\n\n";
					}

					$message .= $this->language->get('mail_update_order_footer');

					$mail = new Mail();
					$mail->setTo($query->row['email']);
					$mail->setFrom($this->config->get('config_email'));
					$mail->setSender($this->config->get('config_store'));
					$mail->setSubject($subject);
					$mail->setText($message);
					$mail->send();
				}
			}
		}
	}

	public function complete($order_id) {
		if ($this->config->get('config_stock_subtract')) {
			$order_query = $this->db->query("SELECT * FROM `order` WHERE order_id = '" . (int)$order_id . "' AND order_status_id > '1'");

			if ($order_query->num_rows) {
				$order_product_query = $this->db->query("SELECT * FROM order_product WHERE order_id = '" . (int)$order_id . "'");

				foreach ($order_product_query->rows as $result) {
					$this->db->query("UPDATE product SET quantity = (quantity - " . (int)$result['quantity'] . ") WHERE product_id = '" . (int)$result['product_id'] . "'");
				}
			}
		}
	}
}
?>

Problem is though if the custoemr does not reach the success page and clicks backyou overwrite the last order.

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by Dayo » Mon Apr 13, 2009 12:38 am

Daniel wrote: That is why there is a filter on the reports. You select which ever order status is the completed status.
Not this report showing me that I have sold an IPod for £100 which didn't actually happen.
Spurious_Ipod.jpg

Ipod - Spurious_Ipod.jpg (29.61 KiB) Viewed 5200 times


New member

Posts

Joined
Thu Nov 13, 2008 3:27 am

Post by Daniel » Mon Apr 13, 2009 7:43 pm

ok fine. I will add a order status filter there aswell.

also what about things like refunded orders?

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by Dayo » Thu Apr 16, 2009 2:01 am

Daniel wrote:ok fine. I will add a order status filter there aswell.

also what about things like refunded orders?
Hi Daniel

There is no point putting a filter there because the way you have set up recording orders is flawed.

This item already has a status of "pending" although as said, it was was never bought and just generated because I hit the confirmation page.

The "orders" generated when a user hits the confirmation page should be written to a temp orders table and only after the user gets to the success page should they be written to the orders table. Using what should be the temp table as the orders table is a fundamental flaw that means the admin reports are inaccurate as well as the stuff the customer sees when they look at "My Order History".

New member

Posts

Joined
Thu Nov 13, 2008 3:27 am

Post by Daniel » Thu Apr 16, 2009 5:04 am

1. Not all customers reach the success page!
2. Not all the clients web sityes receive a callback!

Even so opencart is very flexible. You can redo one of the payment gateways to process the orders on the success page.

For instance, if you want the the order processed on the success page in paypal you do this:

1. Remove the checkout confirm method from the paypal class and the AJAX in the template.
2. Set the return url to index.php?route=payment/paypal/success
3. Create a new method in paypal class called success
4. Run the model_checkout_order->confirm on the paypal success page and then set a redirect to the success page

You can even set in the admin a setting that lets the customer choose where they want the order to be confirmed.

Forget temporary order tables unless you are going to make the system so clients can click a button and transfer missing paid orders from the temp table. There is no difference from having a temp table and using one table. The client can delete the non confirmed orders just like he can click a button to transfer from temp to real.

Also if you are worried about report data being wrong. Then the same problem is there for refunded orders or non paid cheques.

ZenCart is bloated crap.

User avatar
Administrator

Posts

Joined
Fri Nov 03, 2006 6:57 pm

Post by Dayo » Thu Apr 16, 2009 5:24 am

Daniel wrote: 1. Not all customers reach the success page!
2. Not all the clients web sityes receive a callback!
...
ZenCart is bloated crap.
Hi

Well, whether zencart is bloated or cr@p or not is not important to me. All I am trying to get to is for you to see that there is an issue and that this issue should be fixed.

Try this...

1. Do a fresh install
2. Add Product X to your cart
3. Start checkout and go up to the confirmation page
4. Go back and add Product Y to your cart
5. Go again up to the confirmation page.
6. Log Out of OPC
7. Click on products ordered under "my account" as a customer and it will tell you that you have ordered two pieces of Product X and one of Product Y when actually you have not ordered anything.

Similarly on the admin side, OPC will tell you two pieces of Product X and one of Product Y have been ordered when nothing has been. Putting a filter on the admin will not cure the problem because firstly, the items have a pending status and secondly, it is just treating the symptom rather than the problem.

Can't you see that this should not be the case and that what zencart or any other cart is or is not is another separate issue?

Wouldn't you consider that the orders table should only hold real orders?

I hope you are not getting upset as it will mean digging in of heels and refusing to take a fresh look at an issue.

I like how OPC is set out but some things need fixing and the orders flow is one of them. A cart is not much good if you can't get a reliable list of items ordered.

New member

Posts

Joined
Thu Nov 13, 2008 3:27 am
Who is online

Users browsing this forum: No registered users and 12 guests