Jump to content

E-Commerce with ProcessWire?


Crash-n-Burn

Recommended Posts

What I mean with messy is that you have different kind of variations and options. You can have variations like "yellow / red / blue / orange", then on top of that you might have another set with similar variations like "XL, L, M, S" (so we need to allow combinations). Then you might have checkbox style of options, like "Premium quality", "Signature by apeisa" etc...

This is of course possible to do with using child pages, but I think that it will introduce some challenges to build it in a way that is easy to use... Although what I do like in that approach that it is easy to build, it is "pw-style" and it something that most of site builders can then easily customize (not the case with custom fieldtypes). It is also out of the box "compatible" with the current module.

EDIT: Also interesting to see how you used your variations and looking forward what you came up with. You were right about using pages to hold orders too (I have discussed about this earlier with Soma), so I think that you might be right this time too Soma :)

Link to comment
Share on other sites

Current version you can see the dev site here : http://dev.bag-shop.ch. Here you can see a product with color variations http://dev.bag-shop.ch./de/kollektion/taschen/paula/ . I will provide a profil export once I got the time maybe next week.

I think the "problem" regarding product options and variation is not a simple one, once you need to do colors, different sizes with own prices, and even more options per product. But certainly not impossible, though it could get tricky. Maybe just not as nice to handle those in the admin, as it may could be with custom fields, repeatable fields, or even custom page types.

But since PW is still evolving in this area, I think it may best to wait a little to have more options available. For instance the repeatable fields could then be used for variations. But my best guess would be that the way it will be implemented is by using invisible subpages anyway, so it's hard to tell right now how this could be used.

I'm also very unsure about how far such a PW shop module should go and provide all the necessary functions and options a full blown shop system should have. There's certainly a lot of things that come together. For example things like stocks, multilanguage, reduced prices, shipping costs depending on total prices etc. customer management and so on...

I'm also not very experience in full blown shop, as I really just coded 2 very simple shops for customers before, that didn't needed any of those features. Also the shop module in our CMS was only able with a little trick to create variations with different prices per product, but provides an interface and API for them and its much like repeatable elements. I'm not sure how more complex variation could be handled but certainly there's a point where it might needs some additional coding in the core or shop module. I don't know any of the avaiable ecommerce software out there but I also haven't heard much good from some of them, so I guess it also hard to go with that route and chose among the ones out there anyway. I love to have a flexible CMS/CMF that allows easy coding and freedom, so many of the features can be done in some way or another.

Maybe we should look for what is the most needed features for simple shop setups, and allow simple extending by using PW style 100%. I mean only provide the essential core functions a shop needs. So an more experiences site builder can easily setup something. But if someone wants to go for a complete out of the box shop module, I certainly don't want to stop them. Certainly something for people that don't have the ability to do advanced coding.

Link to comment
Share on other sites

Words of wisdom Soma. Everytime I have used "full blown ecommerce software" (I have used OsCommerce and CS-Cart) I have had similar experience: it has all the bells and whistles, lots of settings and things to tweak from the admin... But then there is something simple, like "I need to change some markup" or "I need to customize those emails" and it all falls down. You need to start customizing core files or do some very strange hacks. It would be great to build this same way like PW works: enable regular developers to do amazing stuff easily. Let developers build stuff, not tweak and hack.

Also - big eCommerce products are huge, that understanding how things work really take time... So I generally agree with everything you wrote above. Also would be nice if our "shop" would be build on lot's of small modules that work well together. Something like this:

  • Shopping cart module (adding or removing products etc)
  • Checkout module (handles the checkout process)
    • Payment modules (paypal, google checkout etc)

    [*]Order management module (shows orders in admin)

    [*]etc...

You could cherry pick what you need on project basis.

What would make things very interesting would be to have one more module:

  • Shop module (would install all or some of the modules above, adds product template and just gives you a simple shop from one click)
Link to comment
Share on other sites

Current version you can see the dev site here : http://dev.bag-shop.ch. Here you can see a product with color variations http://dev.bag-shop..../taschen/paula/ .

Out of topic, how come your links direct to a "http://dev.bag-shop.ch." urls (notice the final dot) and still work ?

Edit: forget about it, it works everywhere, I thought this kind of urls were not allowed, seems I was wrong.

Link to comment
Share on other sites

@apeisa, yeah that sounds like a good approach. I like the idea. And the shop cart module is already there, just needs some tweaking and maybe some more options. I think a plan would be helpful to list what we got and how it could work out, in other words a project plan? :D Or really look closer into what is needed as a base to help build a shop (cart module?) and build up different modules around it that add functionality independent, so one could pick what he likes and add to the project with a module. So with a PW install everybody has access, we could build up some default shop to try it out and everyone can contribute, export a profil (fork, github?) and try it out on local server if wanted.

@jbroussia, you're right it's strange, something I didn't recognized until now. Seems it is something done on domain name server routing that ignores it. Or could be even on browser level. Not sure.

Link to comment
Share on other sites

Soma, apeisa, I absolutely agree. Single and specific modules with hookable methods for each functionality to be combined and used the way the dev wants to in combination with templates would be my choice. This would allow us to more easily extend the functionality individually for each project when necessary. And it could make the whole thing much more powerful.

So a project plan would be great. Soma’s idea with the PW install and shop repository sounds good.

Link to comment
Share on other sites

I agree. What I like most in Processwire is that you don't have to tweak absolutely nothing to get the results you want. It's better to have more work building something new on top of a simple system than hacking a very complete system that makes too many assumptions.

I can't help with the programming, but I can always help testing :)

Link to comment
Share on other sites

  • 2 weeks later...

One thing I’m thinking about again and again is, how the products could be dealt with after being put into the cart or being part of an order. The product within the catalog could be considered a kind of mask providing all possible variations. Whereas the product in the basket is the final result of selections made by the customer. So should a selected product be saved as a page with a certain template providing a page ref field to the products original page, a text field for saving the selected values maybe json-encoded and a field for quantity? How to keep the data, the archive of orders and ordered products consistent, when the product data could be changed after having already been sold before? Ideas?

Link to comment
Share on other sites

If variants are child pages, then there won't be this problem. And I have (once again) turned my head and I do think that is the direction we should take. At least variants which have things like SKU and stock count should have their own page in catalog side also.

I don't see issue about "product data could be changed after having already been sold". I think that is possible with every eCommerce software. After sale I think what we need to store per product are product title, id, qty sold and price. If that product is later removed or edited for something totally different (you shouldn't do that user!), we still have the record what customer bought and what was the price. I think you are probably meaning more about variants here, but I think that will be same case if we use pages for variants also.

I actually have few days for real work with shop module next week, since I will build the actual shop why I started this module. So I believe I have some nice updates here also. What I at least hope to do is one or more payment modules and more stuff on backend (like sales statistics etc). This shop won't need product variants and stuff like that, but I hope that I will have time to build more solid ground for future progress.

EDIT: Forget to say that it seems Ryan has had good progress with repeatable fields. That could be one nice possibility for variants also - we will see :) I also think that variants is not the first issue to solve: we need to focus building solid elements and add advanced stuff later on. I know I have spoken about those a lot too, since they are problem where there isn't easy or clear solution, that is the reason it is so interesting to solve I guess :)

Link to comment
Share on other sites

Oliver, that's pretty much how the current unfinished version apeisa coded works already.

Product cart is saved in a custom table , once order completed the order gets saved as page under /admin/shop/orders/ with childpages as the products in the order. The template has a page field reference that is linked with the product.

My current shop project bases on apeisa's start, and is pretty much still the same except I have variations. Only colors though, but I have solved it by having a "product-variation" template that presents the variable fields or any additional fields of the base product template. Then the product added to the cart will be one of these variations, the main product parent page then can serve for certain global settings. This allows for maximum flexibility in how to create products and variations. This needs some coding/pw skills to then adapt the template code for the different forms and some shop module code. But it's just me, I prefer this way of having control over code and setup while having some helper modules to help with certain common tasks.

As for more complex variations with prize,color,size t-shirt example. Then the more usual way of having options as serialized data along with the main data like prize, product id, name would be required. I think having repeatable/flexible fields could be helpful here a lot. What you think?

But still a simple setup with subpages and references could do this all and I like to be able to easily extend it and add thumbs for each variation and so on. It also is just a matter of having the cart helper support options serialized and building the right forms to add the products at the end.

Link to comment
Share on other sites

Made some progress today with eCommerce stuff. I simplified a lot of code, re-factored it to have fully independent Shopping Cart module and then two heavily tied modules ShopOrders and ShopCheckout. Not sure if that is best way to go, but it is much cleaner than the old stuff.

I have kept the post logic there, so that you don't have to code your own if you don't want to (of course it is pretty easy to do if you want). I plan to provide Ajax returns also so it will be nice and easy to create ajax stuff on your cart.

I had good idea to make the user fields custom, so you could choose from admin what fields you want to ask on checkout process... but that is little bit harder than I first thought, since then I don't know what fields to save -> it would make next to impossible to build good backend for orders management. So my next idea is to have few defaults (name, address, city, zip, country, phone etc) and have few options on those (not in use / optional / required etc).

Next I will implement first payment module. My plan is to have payment modules with naming convention like this: PaymentPaypal, PaymentInvoice etc.. Ryan, what is the best way to look for installed modules that start with word "Payment"? Or should I use some other way to find payment modules? Like creating abstract module and implement payment modules from there?

I will soon push this to GitHub. https://github.com/a...for-ProcessWire

Link to comment
Share on other sites

Nice work on this apeisa.

I've looked at the code. Since I worked with the first incarnation of you shop module.

It's good you simplified a lot and made it more structured. It's a shame I won't be able to use your new version in my current shop project as is, as my version now has some special features/functions I implemented, some of which are very specific to the project including product variation and multilanguage. But anyway good to see you progress on this, as I'm sure it will be also helpful to other people and projects.

One thing that struck me is, why did you move some cart "functionality" to the checkout module? Like the cart form, update, remove stuff. Just wondering, as I got it tied with the cart and not the checkout process. To me the cart page, is still something that belongs to the cart and not the checkout process, but maybe I'm wrong here.

I haven't got the time to go through all, but here's some I got implemented in the cart.

Ajax:

I got it progressively implemented in my shop, using jquery ajax and have the cart return json in case of an ajax product add. I ended up having the ready function like this:

public function ready() {

	if (!($this->page->template->name == "sc-cart")) return false;

	// insert product
	if($this->input->post->product_id) {
		$product_id = (int) $this->input->post->product_id;
		$qty = (int) $this->input->post->qty;
		$this->addProductToCart($this->pages->get($product_id), $qty);

		// if requested through ajax, requested through script.js ajax call
		if($this->config->ajax){
			$response = array();
			$response['cart_count'] = $this->getCurrentCartCount();
			$response['qty'] = $qty;
			$response['product_title'] = $this->pages->get($product_id)->parent()->title .", ". $this->pages->get($product_id)->title;

			echo json_encode($response);
			exit();
		}
		else {
			// normal http request
			$this->session->redirect("./");
		}

	}

	// update cart
	if($this->input->post->update) {
		$this->updateCart($this->input->post->items);
		$this->session->redirect("./");
	}

	// remove item from cart
	if($this->input->get->remove) {
		$product_id = (int) $this->input->get->remove;
		$this->addProductToCart($this->pages->get($product_id), 0);
		$this->session->redirect("./");
	}

	// go to checkout page
	if($this->input->post->checkout) {
		$this->updateCart($this->input->post->items);
		$this->session->redirect("../checkout");
	}

}

"$this->pages->get($product_id)->parent()->title"

As you can see here is something that comes from the variation implementation I got, it gets the title from the product from the parent page, as the variations are done through child pages.

So I also got a function getCurrentCartCount(); to return total number of items in cart:


public function getCurrentCartCount() {
	$sid = session_id();
	$result = $this->db->query("SELECT items FROM {$this->className} WHERE status = 'in_progress' AND session_id = '$sid' ORDER BY last_modified LIMIT 1");
	if($result->num_rows == 0) {
		return 0;
	}
	else {
		list($items) = $result->fetch_array();
		$items_array = json_decode($items);
		$count = 0;
		if(!empty($items_array)){
			foreach($items_array as $item) {
				$count += (int) $item->qty;
			}
		}
		return $count;
	}
}

Here's a sample of my ajax script that enables add to cart through progressive enhancement.

// cart ajax progressive enhancement
$('form.add-to-cart').submit(function(){
	var cart_url = $(this).attr('action');
	var prod_data = {
		"submit": 1,
		"product_id": $(this).find("input[name='product_id']:checked").val(),
		"qty": $(this).find("input[name='qty']").val()
	}
	$.ajax({
		url: cart_url,
		type: 'POST',
		data: prod_data,
		datatype: "json",
		success: function(data){
			// parse returned data string
			var resp = $.parseJSON(data);
			if(resp.qty < 1) {
				showMessage("success", "Der Artikel <b>'" + resp.product_title + "'</b> wurde entfernt.");
			} else {
				showMessage("success", "Der Artikel <b>'" + resp.product_title + "'</b> wurde Ihrem Warenkorb hinzugefügt.");
			}

			updateCartCount(resp.cart_count);
		},
		error: function(data,status,error){
			showMessage("error", "Fehler: Etwas ist falsch gelaufen. Artikel konnte nicht hinzugefügt werden. Error: " + error);			
		}

	});

	return false;

});

To have the price being variable, I just had to adapt everywhere where it looks for the price or outputs cart stuff.

I've done it so: The product ID will be always be one of the child pages. So either 1 or more variation. So even if it's a product with no variations, it is created via the main product page and at least 1 (the first) variation. This can simplify a lot, as it's always the same and only needs little adaption.

		// see if variant has price field
		// if not choose from parent page
		if($product->sc_prize) $total_price = $item->qty * $product->sc_price;
			else $total_price = $item->qty * $product->parent()->sc_price;

		$total_sum = $total_sum + $total_price;
Link to comment
Share on other sites

One thing that struck me is, why did you move some cart "functionality" to the checkout module? Like the cart form, update, remove stuff. Just wondering, as I got it tied with the cart and not the checkout process. To me the cart page, is still something that belongs to the cart and not the checkout process, but maybe I'm wrong here.

You are right about this. Cart view and updating the cart definitely belongs to Cart class. I think I have transferred since I have badly named it in the first place ("renderCheckout"). It should be "renderCart" or "renderFullCart". I will do this change soon.

Thanks for the code snippets. Your shop looks very good! The Ajax thing has been implemented pretty much the same than what I have been thinking. Of course we would leave that front end implementation for end user or maybe offer that as configurable option at some point.

Hopefully I find time tonight to take this further. I haven't got any work done today, since everyone else are in fever at our house.

Link to comment
Share on other sites

Ryan, what is the best way to look for installed modules that start with word "Payment"? Or should I use some other way to find payment modules? Like creating abstract module and implement payment modules from there?

I think that your naming convention sounds good. You could find all modules like this:

$paymentModules = $modules->find('className^=Payment'); 

However, your idea of using an abstract class or an interface is something I think you'd also want to do. That's because you are going to want all of your Payment modules to adhere to a specific interface so that all will have a processPayment() method, for example. An abstract class and an interface are basically the same thing except that an abstract class can provide some code implementation (functions) and declare other functions as abstract, where the descending class has to provide the implementation. Whereas an interface provides no implementation, it just defines what the required functions will be. Any functions outlined in an interface would be the same thing as an abstract (no-implementation) function in an abstract class. Neither an abstract class or an interface can be instantiated to an object, so they mainly define requirements for other classes that either extend the abstract class or implement the interface. Here's an example:

interface PaymentInterface {
   /**
   * Process a payment and return true on success or false on failure
   *
   */
   public function processPayment($amount);
}

class PaymentModule extends WireData implements Module, PaymentInterface {
   public static function getModuleInfo() { return array(...); }
   public function init() { }
   public function processPayment($amount) {
       // a bunch of code here to process a payment
       return true;
   }
}

Since the module above adheres to both the Module and PaymentInterface interfaces, we know that it can be used in the situations where we need it. You know it will always have a processPayment() method. So once you've found your Payment modules, you'd want to make sure that they also implement the interface before using them:

if($someModule instanceof PaymentInterface) {
   // $someModule can be used as a payment module
}
Edited by ryan
Corrected last code sample to say instanceof rather than implements.
  • Like 1
Link to comment
Share on other sites

Thanks Ryan! I didn't realize we can use same selectors there on modules too! PW surprises all the time :)

That makes a lot of sense to me. I have never build interfaces or abstract classes before, only read and used sometimes. I agree that this is pretty ideal place to use one. This will probably end like a horrible mess, but I will try 8)

Link to comment
Share on other sites

Actually abstract classes / interfaces are there to help avoid messes. You could get along just fine without them, but using them keeps things in order and lets you make assumptions about objects rather than having to check that they meet some criteria on your own. If you say that something has to meet an interface and it doesn't, then PHP throws a fatal error. That helps to avoid messes before they start. :) There's not much more to it than that. The terms "abstract" and "interface" sound technical, but luckily the concept is simple.

Link to comment
Share on other sites

I just pushed a big update. I did some more refactoring and have renamed the old modules:

  • ShoppingCart
  • ShoppingOrdersManagement
  • ShoppingCheckout (You can now choose from all the installed payment methods which one to use.)

And now there is one new module included:

  • PaymentExample (which let's you choose: I have already paid / Send me an invoice)

There is still lot to do, but this is starting to take shape. Now this creates unpublished order right after you fill your shipping details. After successful payment it will be marked as published and maybe paid (depending on payment method).

I haven't given any thought about which methods should be hookable, what should be default shipping details, how default checkout process goes etc... Also code needs still a lot cleaning and security stuff etc. But if you guys see something wrong in big picture, now is pretty good time to say it :)

Link to comment
Share on other sites

And if someone is thinking why I save current carts in database (and not just use session) is that I would like to offer possibility for logged in users to save cart and get back to it after session is long gone. Also offer nice possibilities in future to see where people stop their shopping process (is it at checkout or even before that).

Link to comment
Share on other sites

I haven't given any thought about which methods should be hookable, what should be default shipping details, how default checkout process goes etc... Also code needs still a lot cleaning and security stuff etc. But if you guys see something wrong in big picture, now is pretty good time to say it :)

Just realized that checkout steps array is just a mess. Will implement that flow much better now that I got this together.

Link to comment
Share on other sites

You’re a machine, apeisa! Thanks for sharing your insights here. I hope I’ll find the time soon to have a closer look at your code. Especially how you deal with modular payment modules. Have been thinking about this a lot as I’m currently trying to work out a similar solution to handle login modules for signing in users with social networks etc.

Link to comment
Share on other sites

Thanks Oliver. It is actually very simple (payment methods), and I am just streamlining that code. It will be super simple to build payment methods. It was clever thing to save order (as unpublished) before payment, no need to save state etc... Also offers possibility to try paying again like week later, if you fail at first time.

Link to comment
Share on other sites

@Apeisa this looks great! The more I am getting into PW at the moment the more I am seeing that an eCommerce solution seems to be one of the last large pieces of functionality that PW lacks at the moment. Practically everything else is possible (okay - Forums might be another one but there are many more us cases for eCommerce).

For what it's worth I really like the way you are going about it, by building a simple set of functionality and then expanding over time. So many eCommerce systems out there try to do everything and end up being buggy and bloated - therefore doing nothing well.

Are you planning a simple PayPal gateway to serve common needs or are you leaving this up to the individual to build these gateways?

I should get some time to test this module over the next week or two and can help with anything up to the point of programming (coding is something I'm not particularly good at).

Link to comment
Share on other sites

Thanks raydale!

Yes, I am trying to keep it as simple and streamlined as possible. I think that PW makes it easy since I don't have to touch the front end / product catalog at all.

I think few features that I am thinking of that should be in "core" implementation, but I haven't touched at all yet:

  • Currencies (I think first just that you can choose the currency, later maybe multiple currencies)
  • Taxes
  • Stock levels (should support variations)

Are you planning a simple PayPal gateway to serve common needs or are you leaving this up to the individual to build these gateways?

There should definitely be a PayPal gateway as a module. I am not sure if I will build that, since I cannot add any hours from my day job into that (we don't need paypal with our customers), but I really don't think that it can take many hours, so I could do that at some point. I will build Finnish bank gateways first and one local integrator (www.verkkomaksut.fi). We mostly pay directly from our bank account here in Finland and many doesn't even have a credit card.

Link to comment
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
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...