While I'm prepping for the Kentico Conference in Denver this week, I thought I'd write up a quick post on a topic I got asked about in Kentico's Slack chat community.
Splitting up a shopping cart so that you can process parts of it separately is useful any time you want to allow the user to finalize their purchase of one product or group of products, without having to also process the whole cart. This may be if you have an 'ecommerce cart' along with event bookings along with donations, and maybe you want the donations to be a recurring order but the rest of your cart certainly isn't recurring.
Or maybe you have sub-sections of your site that you don't want to share transactions with because the payments go to different places.
You can, of course, just ignore the Cart entirely, and do donations / bookings or these side sections as a form that connects to your payment processor directly, but you lose out on a lot of Kentico handling by doing that - tax classes, shipping options, product options and variants, order items, and payment statuses are all things you might need to 'reinvent' if you aren't using Kentico e-commerce.
So, when I ran into this issue on my end, where a client wanted two separate checkout processes that appeared to the user to be separate, I went back to the basics of what a shopping cart was - just a storage carrier for items and order information, until they become an order. Everything else - the shopping cart content web part, order subtotals/totals/etc, coupons, even the cart manager that eventually turns the whole thing into an order... are just code.
If you just need to be able to purchase a single webinar, or do a donation, outside of the workflow, I wouldn't go this route. Just go with a bizform and use Accept.js (or whatever equivalent client-side submission your payment provider supplies) and a global event handler to submit the payment. The form acts as your data record, and if you can get away with not doing more, don't do more.
If you're doing your project in MVC, you're actually 99% of the way there already: Kentico isn't really customizing your experience in any case - you're turning products into orders yourself, and can show or hide whatever products you want. Go ahead and do step 1, then the rest is in your hands - show the products you want, show the totals you want, then create your order containing the products you want, and remove them from the cart as they're ready. It's not that different from one cart.
For those of you on Kentico 11, or 12 Portal Engine, you're probably working with web parts and the standard workflow though... so, here's the process I used.
Part 1: Split your data and Design your process
Maybe you're going to split products based on page type. Maybe you're splitting by Department, or Product Type (donation/e-product/product), or you've added a custom field to each product that determines which 'bucket' it goes in. Either way, assigning that data to your products is step 1. I'll refer to this as the 'split factor' later on - the thing on which you're splitting up your content into buckets.
At the same time, I would consider your shopping cart experience. If you can set it up so that the user can't get into multiple 'checkout steps' at the same time, that's ideal - it lets you use functionality like coupons and location-based shipping costs. By this I mean, if they enter and start doing one order but then mid-checkout go start doing another sub-cart, you're going to have to reconcile that, probably by storing the data for each cart separately - and that becomes a lot of extra data to store.
Single-page checkout processes work well for this - either they've completed a sub-cart, or they haven't started processing it at all. There's no data being saved that they expect to come back to, if they abandon the sub-cart.
Part 2: Custom Shopping Cart Content controls
Go ahead and make a copy of the standard Shopping Cart Control web part, and add a filter on your split factor, that you can set in the web part properties. You'll use this when you want to show your individual 'carts'.
Part 3: Custom other controls
With having done that, the next step is very similar - anything related to your eventual 'order': totals, subtotals, discount totals, all need to be separately calculated based on your sub-cart. This is just a matter of doing the math based on the items in your sub-cart, rather than relying on the Kentico totals fields.
If you need to actually store different *coupons* per cart, I would update the Shopping Cart module class to add a custom 'coupon code' field for each sub-cart - and you should just remove the default coupon code from any of your processes, as you don't want it inadvertently being mixed into your other products' data. We'll add in the 'correct' coupon code at the last minute, in step 4.
Same goes for if you have to store separate addresses for location-based shipping costs.
Step 4: Hidden processing page
The normal shopping cart process, as the last step, closes out your cart, and converts the whole thing into an order. Instead of customizing the step manager code, what I like to do is insert a hidden 'extra' page as a final step in the checkout, and call it 'processing'. This page will use a completely blank template, not even using a master page - all it will have on it is a 'SubCartProcessing' web part. This web part has nothing in the ascx, but is what will do the gruntwork of this solution: it will look at the cart, know which subcart we're processing,
So, the user hits 'pay now' or whatever you've set the 'next' button to look like in this case, and get ShoppingCart.CartContentItems, filtering based on your split factor.
At this point, create a new order, and fill it in using the Orders API: https://docs.kentico.com/api10/e-commerce/orders#Orders-Creatinganeworder - You should be adding in the addresses, the coupon code if any, the order items, and setting the order status, at this time. Call Update and Evaluate on the cart, and raise the SHOPPING_CART_CHANGED event, just to make sure the updates are saved.
Assuming we succeed, we know we've now 'completed' this part of the cart, so we should manually (shoppingCartInfoProvider) Remove the individual shopping cart items and delete them (you have to do both to each cart item!) and, if there are no items left in the cart at all, delete the entire shopping cart... If there are, we should at least clear any shopping cart parameters related to this sub-cart - coupon codes or other custom fields.
This web part, as the last thing it should do, is redirect you, always. We don't ever want the user to know this page exists, so if we've created an order, we want to go to a payment page (don't forget the o= querystring parameter). If we've not created an order due to an error, we want to redirect to an 'error, order not processed' page (any user-fixable errors should have been resolved back during validation on the prior pages), and if we're just suddenly on this page without an order, just redirect back to the cart. The point is, this page is just a processor, not a page with visual elements. With these server-side redirects, this page will never hit the client's machine.
Step 5: Payment
Since we're now dealing with an order containing only the items we care about, the Payment page, and anything after that, can go by the standard Kentico process, and should be individually visible . Congrats!