Avatar of Julian Hansen
Julian Hansen
Flag for South Africa asked on

Looking for a good WordPress marketplace solution

Looking for recommendations on a WordPress (preferably WooCommerce) solution for a MarketPlace.


The MarketPlace will be built around a specific category (Example Second hand Shoes). The idea is people who have products (Second Hand shoes in this case) can create a Vendor account and load their offering. The market place will then handle the selling for them. The requirement is not for a multisite setup i.e. vendors will not maintain their own identity - all products from all vendors will be visible to the client.


A Google search shows there are a number of offerings out there that might fit the bill. Looking for any insights anyone might have around what is good and what to avoid.


WooCommerce is preferred as one of the payment integration options we are using has a WC plugin (not a deal breaker though as we can build around the PG API).


Appreciate any and all input.

WordPress

Avatar of undefined
Last Comment
David Favor

8/22/2022 - Mon
David Favor

Woo is a bit heavy + for what you've described might be a good choice.

Also checkout the search - woocommerce alternatives - researching some of the alternatives.

You might find a more lightweight option, which meets all your requirements.
David Favor

Aside: I have a similar requirement for an upcoming project.

https://www.wpeasycart.com/wordpress-shopping-cart-comparison/ looks promising, as one of the problems with Woo is every single new feature you add seems to be $100+/year. WP Easy Cart seems to include many Woo features... right off the shelf...

So I've added this cart to my list to test.
Scott Fell

Just to offer an alternative to woo, not that woo is bad, but I have always like opencart  https://www.opencart.com/ and there are modules for multi seller that may work or at least do most of the work for you https://www.opencart.com/index.php?route=marketplace/extension&filter_search=multivendor as well as affiliates http://docs.opencart.com/en-gb/marketing/affiliate/

It is in php and set up as MVC

This is what is in the controller folder
 
In the checkout folder there is a cart.php that looks like below.  For me, this is all a lot easier to follow than WP.
<?php
class ControllerCheckoutCart extends Controller {
    public function index() {
        $this->load->language('checkout/cart');


        $this->document->setTitle($this->language->get('heading_title'));


        $data['breadcrumbs'] = array();


        $data['breadcrumbs'][] = array(
            'href' => $this->url->link('common/home'),
            'text' => $this->language->get('text_home')
        );


        $data['breadcrumbs'][] = array(
            'href' => $this->url->link('checkout/cart'),
            'text' => $this->language->get('heading_title')
        );


        if ($this->cart->hasProducts() || !empty($this->session->data['vouchers'])) {
            if (!$this->cart->hasStock() && (!$this->config->get('config_stock_checkout') || $this->config->get('config_stock_warning'))) {
                $data['error_warning'] = $this->language->get('error_stock');
            } elseif (isset($this->session->data['error'])) {
                $data['error_warning'] = $this->session->data['error'];


                unset($this->session->data['error']);
            } else {
                $data['error_warning'] = '';
            }


            if ($this->config->get('config_customer_price') && !$this->customer->isLogged()) {
                $data['attention'] = sprintf($this->language->get('text_login'), $this->url->link('account/login'), $this->url->link('account/register'));
            } else {
                $data['attention'] = '';
            }


            if (isset($this->session->data['success'])) {
                $data['success'] = $this->session->data['success'];


                unset($this->session->data['success']);
            } else {
                $data['success'] = '';
            }


            $data['action'] = $this->url->link('checkout/cart/edit', '', true);


            if ($this->config->get('config_cart_weight')) {
                $data['weight'] = $this->weight->format($this->cart->getWeight(), $this->config->get('config_weight_class_id'), $this->language->get('decimal_point'), $this->language->get('thousand_point'));
            } else {
                $data['weight'] = '';
            }


            $this->load->model('tool/image');
            $this->load->model('tool/upload');


            $data['products'] = array();


            $products = $this->cart->getProducts();


            foreach ($products as $product) {
                $product_total = 0;


                foreach ($products as $product_2) {
                    if ($product_2['product_id'] == $product['product_id']) {
                        $product_total += $product_2['quantity'];
                    }
                }


                if ($product['minimum'] > $product_total) {
                    $data['error_warning'] = sprintf($this->language->get('error_minimum'), $product['name'], $product['minimum']);
                }


                if ($product['image']) {
                    $image = $this->model_tool_image->resize($product['image'], $this->config->get('theme_' . $this->config->get('config_theme') . '_image_cart_width'), $this->config->get('theme_' . $this->config->get('config_theme') . '_image_cart_height'));
                } else {
                    $image = '';
                }


                $option_data = array();


                foreach ($product['option'] as $option) {
                    if ($option['type'] != 'file') {
                        $value = $option['value'];
                    } else {
                        $upload_info = $this->model_tool_upload->getUploadByCode($option['value']);


                        if ($upload_info) {
                            $value = $upload_info['name'];
                        } else {
                            $value = '';
                        }
                    }


                    $option_data[] = array(
                        'name'  => $option['name'],
                        'value' => (utf8_strlen($value) > 20 ? utf8_substr($value, 0, 20) . '..' : $value)
                    );
                }


                // Display prices
                if ($this->customer->isLogged() || !$this->config->get('config_customer_price')) {
                    $unit_price = $this->tax->calculate($product['price'], $product['tax_class_id'], $this->config->get('config_tax'));
                    
                    $price = $this->currency->format($unit_price, $this->session->data['currency']);
                    $total = $this->currency->format($unit_price * $product['quantity'], $this->session->data['currency']);
                } else {
                    $price = false;
                    $total = false;
                }


                $recurring = '';


                if ($product['recurring']) {
                    $frequencies = array(
                        'day'        => $this->language->get('text_day'),
                        'week'       => $this->language->get('text_week'),
                        'semi_month' => $this->language->get('text_semi_month'),
                        'month'      => $this->language->get('text_month'),
                        'year'       => $this->language->get('text_year')
                    );


                    if ($product['recurring']['trial']) {
                        $recurring = sprintf($this->language->get('text_trial_description'), $this->currency->format($this->tax->calculate($product['recurring']['trial_price'] * $product['quantity'], $product['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']), $product['recurring']['trial_cycle'], $frequencies[$product['recurring']['trial_frequency']], $product['recurring']['trial_duration']) . ' ';
                    }


                    if ($product['recurring']['duration']) {
                        $recurring .= sprintf($this->language->get('text_payment_description'), $this->currency->format($this->tax->calculate($product['recurring']['price'] * $product['quantity'], $product['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']), $product['recurring']['cycle'], $frequencies[$product['recurring']['frequency']], $product['recurring']['duration']);
                    } else {
                        $recurring .= sprintf($this->language->get('text_payment_cancel'), $this->currency->format($this->tax->calculate($product['recurring']['price'] * $product['quantity'], $product['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']), $product['recurring']['cycle'], $frequencies[$product['recurring']['frequency']], $product['recurring']['duration']);
                    }
                }


                $data['products'][] = array(
                    'cart_id'   => $product['cart_id'],
                    'thumb'     => $image,
                    'name'      => $product['name'],
                    'model'     => $product['model'],
                    'option'    => $option_data,
                    'recurring' => $recurring,
                    'quantity'  => $product['quantity'],
                    'stock'     => $product['stock'] ? true : !(!$this->config->get('config_stock_checkout') || $this->config->get('config_stock_warning')),
                    'reward'    => ($product['reward'] ? sprintf($this->language->get('text_points'), $product['reward']) : ''),
                    'price'     => $price,
                    'total'     => $total,
                    'href'      => $this->url->link('product/product', 'product_id=' . $product['product_id'])
                );
            }


            // Gift Voucher
            $data['vouchers'] = array();


            if (!empty($this->session->data['vouchers'])) {
                foreach ($this->session->data['vouchers'] as $key => $voucher) {
                    $data['vouchers'][] = array(
                        'key'         => $key,
                        'description' => $voucher['description'],
                        'amount'      => $this->currency->format($voucher['amount'], $this->session->data['currency']),
                        'remove'      => $this->url->link('checkout/cart', 'remove=' . $key)
                    );
                }
            }


            // Totals
            $this->load->model('setting/extension');


            $totals = array();
            $taxes = $this->cart->getTaxes();
            $total = 0;
            
            // Because __call can not keep var references so we put them into an array.             
            $total_data = array(
                'totals' => &$totals,
                'taxes'  => &$taxes,
                'total'  => &$total
            );
            
            // Display prices
            if ($this->customer->isLogged() || !$this->config->get('config_customer_price')) {
                $sort_order = array();


                $results = $this->model_setting_extension->getExtensions('total');


                foreach ($results as $key => $value) {
                    $sort_order[$key] = $this->config->get('total_' . $value['code'] . '_sort_order');
                }


                array_multisort($sort_order, SORT_ASC, $results);


                foreach ($results as $result) {
                    if ($this->config->get('total_' . $result['code'] . '_status')) {
                        $this->load->model('extension/total/' . $result['code']);
                        
                        // We have to put the totals in an array so that they pass by reference.
                        $this->{'model_extension_total_' . $result['code']}->getTotal($total_data);
                    }
                }


                $sort_order = array();


                foreach ($totals as $key => $value) {
                    $sort_order[$key] = $value['sort_order'];
                }


                array_multisort($sort_order, SORT_ASC, $totals);
            }


            $data['totals'] = array();


            foreach ($totals as $total) {
                $data['totals'][] = array(
                    'title' => $total['title'],
                    'text'  => $this->currency->format($total['value'], $this->session->data['currency'])
                );
            }


            $data['continue'] = $this->url->link('common/home');


            $data['checkout'] = $this->url->link('checkout/checkout', '', true);


            $this->load->model('setting/extension');


            $data['modules'] = array();
            
            $files = glob(DIR_APPLICATION . '/controller/extension/total/*.php');


            if ($files) {
                foreach ($files as $file) {
                    $result = $this->load->controller('extension/total/' . basename($file, '.php'));
                    
                    if ($result) {
                        $data['modules'][] = $result;
                    }
                }
            }


            $data['column_left'] = $this->load->controller('common/column_left');
            $data['column_right'] = $this->load->controller('common/column_right');
            $data['content_top'] = $this->load->controller('common/content_top');
            $data['content_bottom'] = $this->load->controller('common/content_bottom');
            $data['footer'] = $this->load->controller('common/footer');
            $data['header'] = $this->load->controller('common/header');


            $this->response->setOutput($this->load->view('checkout/cart', $data));
        } else {
            $data['text_error'] = $this->language->get('text_empty');
            
            $data['continue'] = $this->url->link('common/home');


            unset($this->session->data['success']);


            $data['column_left'] = $this->load->controller('common/column_left');
            $data['column_right'] = $this->load->controller('common/column_right');
            $data['content_top'] = $this->load->controller('common/content_top');
            $data['content_bottom'] = $this->load->controller('common/content_bottom');
            $data['footer'] = $this->load->controller('common/footer');
            $data['header'] = $this->load->controller('common/header');


            $this->response->setOutput($this->load->view('error/not_found', $data));
        }
    }


    public function add() {
        $this->load->language('checkout/cart');


        $json = array();


        if (isset($this->request->post['product_id'])) {
            $product_id = (int)$this->request->post['product_id'];
        } else {
            $product_id = 0;
        }


        $this->load->model('catalog/product');


        $product_info = $this->model_catalog_product->getProduct($product_id);


        if ($product_info) {
            if (isset($this->request->post['quantity'])) {
                $quantity = (int)$this->request->post['quantity'];
            } else {
                $quantity = 1;
            }


            if (isset($this->request->post['option'])) {
                $option = array_filter($this->request->post['option']);
            } else {
                $option = array();
            }


            $product_options = $this->model_catalog_product->getProductOptions($this->request->post['product_id']);


            foreach ($product_options as $product_option) {
                if ($product_option['required'] && empty($option[$product_option['product_option_id']])) {
                    $json['error']['option'][$product_option['product_option_id']] = sprintf($this->language->get('error_required'), $product_option['name']);
                }
            }


            if (isset($this->request->post['recurring_id'])) {
                $recurring_id = $this->request->post['recurring_id'];
            } else {
                $recurring_id = 0;
            }


            $recurrings = $this->model_catalog_product->getProfiles($product_info['product_id']);


            if ($recurrings) {
                $recurring_ids = array();


                foreach ($recurrings as $recurring) {
                    $recurring_ids[] = $recurring['recurring_id'];
                }


                if (!in_array($recurring_id, $recurring_ids)) {
                    $json['error']['recurring'] = $this->language->get('error_recurring_required');
                }
            }


            if (!$json) {
                $this->cart->add($this->request->post['product_id'], $quantity, $option, $recurring_id);


                $json['success'] = sprintf($this->language->get('text_success'), $this->url->link('product/product', 'product_id=' . $this->request->post['product_id']), $product_info['name'], $this->url->link('checkout/cart'));


                // Unset all shipping and payment methods
                unset($this->session->data['shipping_method']);
                unset($this->session->data['shipping_methods']);
                unset($this->session->data['payment_method']);
                unset($this->session->data['payment_methods']);


                // Totals
                $this->load->model('setting/extension');


                $totals = array();
                $taxes = $this->cart->getTaxes();
                $total = 0;
        
                // Because __call can not keep var references so we put them into an array.             
                $total_data = array(
                    'totals' => &$totals,
                    'taxes'  => &$taxes,
                    'total'  => &$total
                );


                // Display prices
                if ($this->customer->isLogged() || !$this->config->get('config_customer_price')) {
                    $sort_order = array();


                    $results = $this->model_setting_extension->getExtensions('total');


                    foreach ($results as $key => $value) {
                        $sort_order[$key] = $this->config->get('total_' . $value['code'] . '_sort_order');
                    }


                    array_multisort($sort_order, SORT_ASC, $results);


                    foreach ($results as $result) {
                        if ($this->config->get('total_' . $result['code'] . '_status')) {
                            $this->load->model('extension/total/' . $result['code']);


                            // We have to put the totals in an array so that they pass by reference.
                            $this->{'model_extension_total_' . $result['code']}->getTotal($total_data);
                        }
                    }


                    $sort_order = array();


                    foreach ($totals as $key => $value) {
                        $sort_order[$key] = $value['sort_order'];
                    }


                    array_multisort($sort_order, SORT_ASC, $totals);
                }


                $json['total'] = sprintf($this->language->get('text_items'), $this->cart->countProducts() + (isset($this->session->data['vouchers']) ? count($this->session->data['vouchers']) : 0), $this->currency->format($total, $this->session->data['currency']));
            } else {
                $json['redirect'] = str_replace('&amp;', '&', $this->url->link('product/product', 'product_id=' . $this->request->post['product_id']));
            }
        }


        $this->response->addHeader('Content-Type: application/json');
        $this->response->setOutput(json_encode($json));
    }


    public function edit() {
        $this->load->language('checkout/cart');


        $json = array();


        // Update
        if (!empty($this->request->post['quantity'])) {
            foreach ($this->request->post['quantity'] as $key => $value) {
                $this->cart->update($key, $value);
            }


            $this->session->data['success'] = $this->language->get('text_remove');


            unset($this->session->data['shipping_method']);
            unset($this->session->data['shipping_methods']);
            unset($this->session->data['payment_method']);
            unset($this->session->data['payment_methods']);
            unset($this->session->data['reward']);


            $this->response->redirect($this->url->link('checkout/cart'));
        }


        $this->response->addHeader('Content-Type: application/json');
        $this->response->setOutput(json_encode($json));
    }


    public function remove() {
        $this->load->language('checkout/cart');


        $json = array();


        // Remove
        if (isset($this->request->post['key'])) {
            $this->cart->remove($this->request->post['key']);


            unset($this->session->data['vouchers'][$this->request->post['key']]);


            $json['success'] = $this->language->get('text_remove');


            unset($this->session->data['shipping_method']);
            unset($this->session->data['shipping_methods']);
            unset($this->session->data['payment_method']);
            unset($this->session->data['payment_methods']);
            unset($this->session->data['reward']);


            // Totals
            $this->load->model('setting/extension');


            $totals = array();
            $taxes = $this->cart->getTaxes();
            $total = 0;


            // Because __call can not keep var references so we put them into an array.             
            $total_data = array(
                'totals' => &$totals,
                'taxes'  => &$taxes,
                'total'  => &$total
            );


            // Display prices
            if ($this->customer->isLogged() || !$this->config->get('config_customer_price')) {
                $sort_order = array();


                $results = $this->model_setting_extension->getExtensions('total');


                foreach ($results as $key => $value) {
                    $sort_order[$key] = $this->config->get('total_' . $value['code'] . '_sort_order');
                }


                array_multisort($sort_order, SORT_ASC, $results);


                foreach ($results as $result) {
                    if ($this->config->get('total_' . $result['code'] . '_status')) {
                        $this->load->model('extension/total/' . $result['code']);


                        // We have to put the totals in an array so that they pass by reference.
                        $this->{'model_extension_total_' . $result['code']}->getTotal($total_data);
                    }
                }


                $sort_order = array();


                foreach ($totals as $key => $value) {
                    $sort_order[$key] = $value['sort_order'];
                }


                array_multisort($sort_order, SORT_ASC, $totals);
            }


            $json['total'] = sprintf($this->language->get('text_items'), $this->cart->countProducts() + (isset($this->session->data['vouchers']) ? count($this->session->data['vouchers']) : 0), $this->currency->format($total, $this->session->data['currency']));
        }


        $this->response->addHeader('Content-Type: application/json');
        $this->response->setOutput(json_encode($json));
    }
}

Open in new window

I started with Experts Exchange in 2004 and it's been a mainstay of my professional computing life since. It helped me launch a career as a programmer / Oracle data analyst
William Peck
Julian Hansen

ASKER
@David,

Thanks for the response - I am looking for actual recommendations for products / plugins. I have a list of them here already and I would like to shortcut having to evaluate each one - as time on this project is under pressure.

As I mentioned in my OP Woo is preferred because of the integration to the Payment system but it is not cast in stone.

Appreciate the input but I am looking for recommendations that I can use to shorten my list.
Scott Fell

I have never had to do a multi-seller option, so I can't comment on a specific module. I just have always like opencart for selling.
Julian Hansen

ASKER
@Scott,

Thanks
If I understand it correctly you would need a plugin to Integrate WP to OC (something like this https://www.opencart.com/index.php?route=marketplace/extension/info&extension_id=22078 which was last updated in 2015), but I am guessing you were suggesting OC as an alternative to WP? Is that correct?

WP (unfortunately) is the one given in this equation. The agency doing the work won't budge on that - I am just there to help them with the technical aspects of integration. I will suggest it though - as I agree that WP is not a great platform for this.



Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
Scott Fell

Yea, my suggestion is OpenCart as an alternative to WP, not as a plug in. If you are bound to WP/Woo, I couldn't offer anything you don't already know or could find out for managing multiple sellers like that.
lenamtl

Julian Hansen

ASKER
@Scott - in your opinion how does OpenCart compare to Magento?
(Had a chat with the guys and the door might be open to allow a non WP solution)

The problem is we will be using an Escrow payment gateway that has plugins for Stripe / WooCom and Magento - so ideally we want to go that route or else we have to build our own integration using their API = more hair loss.

I have no experience with OC - but have some with Magento - which did not endear me much to that product either - but it is possible it gets better with more knowledge. In terms of setup / configuration / maintenance - what is the experience there?
This is the best money I have ever spent. I cannot not tell you how many times these folks have saved my bacon. I learn so much from the contributors.
rwheeler23
Julian Hansen

ASKER
@Lena - thanks I do have most of those on my list - looking for a way to narrow the list down quickly to the best 2 if possible.

lenamtl

I would use WCFM Multi Vendor marketplace
It has +30K install and 4.4 star rates which is very good I would be confident.
And it was recently updated for it is maintained

In any case you will need to install and test to see if it fit your needs.
ASKER CERTIFIED SOLUTION
Scott Fell

Log in or sign up to see answer
Become an EE member today7-DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform
Sign up - Free for 7 days
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.
Not exactly the question you had in mind?
Sign up for an EE membership and get your own personalized solution. With an EE membership, you can ask unlimited troubleshooting, research, or opinion questions.
ask a question
Julian Hansen

ASKER
Thanks all, I believe I have enough to go to the client with. We are going to present them with three options weighted by price.

WP + WC with Multi Vendor Market place
OpenCart with custom plugin for payment gateway
Magento with multi vendor plugin

I don't think any of these is the ideal solution but part of what we do is build, learn and rebuild and given the specification reads more like a wish list I believe this is the best we can come up with in the time frames.
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
lenamtl

Hi,
I would stay away from Opencart as it is now very buggy , it use to be good +6 years ago and now it is a nightmare ...
Magento is now Adobe so it may be interresting to see what have changed this is a lot more robust solution...

The only issue that I have with Woo is that there is no good plugin for multilanguage.
I got so many issues with WPML, so for now I prefer not to use any solution for language and keep only 1 language.
Translatepress is an alternative but still need some tweek.
Also Woo DB is no good as everything is in the same BD table...

For a project like your I would create my own ecom.

David Favor

For completeness...

As @Scott mentioned, the multi-seller option is a bit complex.

https://www.x-cart.com/multivendor was code I used on a multi-vendor project I worked on years ago.

This isn't a WordPress plugin, so I didn't mention it + as I recall, I chose X-Cart because the licensing for all features required was far cheaper than any WordPress plugin option at the time.

If you can tolerate running a separate host, like store.site.com where you run X-Cart on this 1x host, X-Cart might be worth researching.