Jump to content


Photo

E-Commerce with ProcessWire?


  • Please log in to reply
196 replies to this topic

#21 ryan

ryan

    Hero Member

  • Administrators
  • 5,771 posts
  • 3114

  • LocationAtlanta, GA

Posted 29 January 2012 - 09:40 AM

Count me in to help anywhere needed too. I don't consider myself an ecommerce expert, so not really sure on best approach with a lot of things, and don't think I'd be the right one to lead such an effort. When you get into supporting multiple gateways, shipping methods, currencies and such it becomes quite a complex thing. As a result, I've always delegated ecommerce to other software. Currently I run Drupal UberCart and Shopify. I've also run OSCommerce in years past (what a mess that is). The thought of having a PW-based solution is attractive.

#22 Oliver

Oliver

    Sr. Member

  • Members
  • PipPipPipPip
  • 133 posts
  • 25

  • LocationBasel, Switzerland

Posted 29 January 2012 - 10:04 AM

Yeah, I’ve been dealing with a xtc:commerce (a osCommerce fork) and it’s a incredible mess, too. Every “module” you want to implement needs you to change stuff in core files. But at least I got a good impression of the complexity of a shop system and how the different features influence - or corrupt ;) - each other.

I’m definitely no ecommerce expert either. Nor am I a really good coder, rather a hobbyist. But I’ll spend some thoughts on this as I have a project coming up that could be a opportunity to experiment with several ideas.

apeisa, I’d love to learn more about your shopping cart and order management. Maybe you’ll find the time to put your approach into a few words?

#23 Soma

Soma

    Hero Member

  • Moderators
  • 3,187 posts
  • 1744

  • LocationSH, Switzerland

Posted 29 January 2012 - 11:47 AM

I'm currently setting up a simple shop, using the module apeisa started. It had to go quickly, so I had no time to wait for further progression on this. But I wanted to share my experience these days with you guys.

It is really a simple shop with products that have color variations. The payment method will be credit cards using a external service (little like paypal). Since they have already an order management, it doesn't even need the orders management apeisa started implementing, but it will be good to additionally save orders in PW.

As for the variations I created sub pages on the product, so I changed the cart functions to look for if the variation has a price, else it takes the price from the parent.

The cart saving feature in the custom module table is great, but I doesn't need address, so only session id and the serialized cart will suffice. And the saving order using a template with the needed fields isn't touched but in my case it does need a lot more fields than apeisa started with. So the gender, address, city, country can be entered.

As for the module to keep it flexible, I think it is best to leave out all hard coded "html" and "forms" generation of the module code. So I have the sc-checkout template all the logic of the process. So I'm able to code the shopping cart the way I need it, and just request the items array using the public module function. I didn't even used the renderAddToCart like function to generate the "add to cart" form fo a product, as it is simple to code a form to my needs and just have the product id in there, and the action url to the cart page. So only really function to handle the cart are necessary.

I'll be glad to help with this module, and I'll share the project once it's done next week.

@somartist | modules created | support me, flattr my work flattr.com


#24 apeisa

apeisa

    Hero Member

  • Moderators
  • 2,521 posts
  • 848

  • LocationVihti, Finland

Posted 29 January 2012 - 11:56 AM

Great to hear that this was help for you Soma. I agree with you, all logic and html should be separated (not sure how well my start does that, since that is quickly coded). On top of that I think we should allow good defaults for render functions, which should be easy to customize (or just build own markup like you have done).

How you handled the variations is pretty much how I have always thought of doing that. Although I think that cleanest way in long run would be to build new fieldtypeMultiple which holds all the variations. Since there are things like additional options (think checkbox: "add extended quarantine 199€") then using child pages for things like that also would get messy.

Eager to hear how my module worked? Did you find any bugs or some code that didn't make any sense?

#25 Soma

Soma

    Hero Member

  • Moderators
  • 3,187 posts
  • 1744

  • LocationSH, Switzerland

Posted 29 January 2012 - 12:06 PM

I don't understand exactly how it could get messy, but maybe you're right.

No I didn't encounter any errors, just added as function to get total items in cart. And I removed all of the cart, checkout form, add to cart markup genrating functions, as I don't really need them. And I added some more fields to the installer for the order template. Also as mentioned I added simple variation price check in the add to cart functions, was pretty easy and I think you've done a great job. I agree there could be some sort of markuup generation tools in there, but then it should be configurable or still one still being able to completely code their own in the templates. It is the case for another CMS we use at work that has a great sho module already built in. And it has no markup generation functions at all only helpers and method for adding, updating, removing from cart and saving the orders for use in the orders management.

Edit:
Oh and I thought the logic you got together with the markup genrating function, to add and render the checkout process was very hard to track and understand with all the post/get vars starting from the entry point in the renderCheckout function. That's what I simplyfied a lot by having some logic of it in the template. That way I'm not bound to it and can make the process of the ckeckout the way I need it.

@somartist | modules created | support me, flattr my work flattr.com


#26 apeisa

apeisa

    Hero Member

  • Moderators
  • 2,521 posts
  • 848

  • LocationVihti, Finland

Posted 29 January 2012 - 12:25 PM

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 :)

#27 Soma

Soma

    Hero Member

  • Moderators
  • 3,187 posts
  • 1744

  • LocationSH, Switzerland

Posted 29 January 2012 - 01:43 PM

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/ . 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.

@somartist | modules created | support me, flattr my work flattr.com


#28 apeisa

apeisa

    Hero Member

  • Moderators
  • 2,521 posts
  • 848

  • LocationVihti, Finland

Posted 29 January 2012 - 02:09 PM

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)


#29 jbroussia

jbroussia

    Full Member

  • Members
  • PipPipPip
  • 70 posts
  • 11

  • LocationFrance

Posted 29 January 2012 - 02:36 PM

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.

#30 Soma

Soma

    Hero Member

  • Moderators
  • 3,187 posts
  • 1744

  • LocationSH, Switzerland

Posted 29 January 2012 - 03:54 PM

@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.

@somartist | modules created | support me, flattr my work flattr.com


#31 Oliver

Oliver

    Sr. Member

  • Members
  • PipPipPipPip
  • 133 posts
  • 25

  • LocationBasel, Switzerland

Posted 29 January 2012 - 04:09 PM

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.

#32 diogo

diogo

    Hero Member

  • Moderators
  • 1,995 posts
  • 1072

  • LocationPorto, Portugal

Posted 29 January 2012 - 05:41 PM

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 :)

#33 Oliver

Oliver

    Sr. Member

  • Members
  • PipPipPipPip
  • 133 posts
  • 25

  • LocationBasel, Switzerland

Posted 07 February 2012 - 04:26 PM

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?

#34 apeisa

apeisa

    Hero Member

  • Moderators
  • 2,521 posts
  • 848

  • LocationVihti, Finland

Posted 07 February 2012 - 04:37 PM

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 :)

#35 Soma

Soma

    Hero Member

  • Moderators
  • 3,187 posts
  • 1744

  • LocationSH, Switzerland

Posted 07 February 2012 - 04:59 PM

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.

@somartist | modules created | support me, flattr my work flattr.com


#36 apeisa

apeisa

    Hero Member

  • Moderators
  • 2,521 posts
  • 848

  • LocationVihti, Finland

Posted 12 February 2012 - 05:18 PM

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

#37 Soma

Soma

    Hero Member

  • Moderators
  • 3,187 posts
  • 1744

  • LocationSH, Switzerland

Posted 13 February 2012 - 04:25 AM

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;

@somartist | modules created | support me, flattr my work flattr.com


#38 apeisa

apeisa

    Hero Member

  • Moderators
  • 2,521 posts
  • 848

  • LocationVihti, Finland

Posted 13 February 2012 - 09:01 AM

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.

#39 ryan

ryan

    Hero Member

  • Administrators
  • 5,771 posts
  • 3114

  • LocationAtlanta, GA

Posted 13 February 2012 - 03:37 PM

This is really coming along nicely. You have me seriously thinking that I need to switch from UbertCart to ProcessWire (rather than Shopify) for the store that I manage.

#40 ryan

ryan

    Hero Member

  • Administrators
  • 5,771 posts
  • 3114

  • LocationAtlanta, GA

Posted 13 February 2012 - 03:57 PM

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, 13 February 2012 - 04:00 PM.
Corrected last code sample to say instanceof rather than implements.





0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users