Each Product is associated with a Merchant (a standard Craft User), who has 2-3 flat-rate Shipping Prices. There could be any number of Merchants and Products.

What I would like to do is on the Shipping page of Checkout, allow the Customer to select a Shipping method from a drop-down per LineItem. Then when they progress to the Payment Page, these Shipping Costs are Totalled and applied to the overall Order.

I've got everything up to the drop-downs on the Shipping page and I'm able to save the value the Customer has chosen to the LineItem via the Options collection, but that's where I've hit a brick wall.

I've had a look at Adjusters and I don't think that's the solution. It was suggested on the Craft Slack that Custom Shipping Methods might be a way forward, but they are very heavily geared towards applying the Shipping Method to the whole Order rather than having separate ones for each lineItem.

Does anyone have any ideas on a suitable approach? Any help gratefully received.


You're right that the shipping calculator API doesn't lend itself to different rates per lineItem as such, but I'm pretty sure you can still make it do what you want. (But longer term I agree that getLineItemRate(lineItem) or similar should be an option!).

Basically, for now, you can do anything you like in the getBaseRate() and return the total from that to the cart. (Personally I implement all my shipping stuff as services so I can call them from controllers etc too (e.g. to do shipping quotes via a controller separate to the cart as such - but in this I'll do ti right here). But you can also do stuff like save data to the order in here, which should be enough to make this work for you.

Basically, you'd loop through the lineItems, looking at the value in the dropdown for each and using that to sum up your shipping costs. You probably want to list out the chosen options and costs for each lineitem separately on that payment page, so you'll also need to save that data somewhere too...so create a holder variable lineItemShippingOptions in your order for that data and set it as part of the process so you can refer to it in the later templates.

(I'd use a non-editable incognito field for that so your order processors can't accidentally change the data later - https://github.com/mmikkel/IncognitoField-Craft (I have PR-ed an improvement to that for just this sort of thing).

(Obviously - this is untested sketch code!)

public function getBaseRate()
   $shippingCost = 0;
   $dataToSave = [];

   $this->cart = craft()->commerce_cart->getCart();
   foreach ($cart->lineItems as $item){
        $shippingOptionChosen = item.options.whatever;
        if($shippingOptionChosen == "service1" ){
            $shippingCost = $20;
            $dataToSave[item.id] = ['chosenOption'=>$shippingOptionChosen, 'chosenPrice'=>20];   
        elseif ($shippingOptionChosen == "service2"){
   //Save this data in to the order

Then in your templates you can refer to lineItemShippingOptions to print the info out to the customer. (That's probably not the ideal way to build the template data you're saving but should serve the purpose to demonstrate the idea...basically you can build any format array you like really).

I use this same approach in a very complicated custom shipping calculator (crazily this is some 2000 lines of php!) - that deals with multiple services (including some table data ones and some live quote ones), plus grouping & boxing services & caching to make it all work as quickly as possible (live rates always being much slower of course...). Because at times the checkout flow gets awkward relying on Commerce's template tags, I also have an actionShippingOptions controller that calls the same services and returns the quotes & data that way (also very handy for debugging purposes!) - but you may well not need that. But even with the existing API you can do damn near anything you like between setting the rate and saving data to the order and showing it later, basically.

  • Thanks Jeremy, that was just the ticket. Rather than store the shipping in options then move it over to an order field in getBaseRate(), I've decided to cut out the middleman. I need a controller to validate that they've chosen shipping for each item anyway, so I though there would be a good place to save the values straight to the Incognito'd order field. That way when the getBaseRate() is run, I'll just spin through what they chosen in the order field directly, saves having the values in two places. May 23 '16 at 9:32
  • Hey Steve... not quite following...getBaseRate saves it straight in to the order field, just one spot...but sounds like you're on to it anyway! May 23 '16 at 10:16
  • Hi Jeremy, it's does yes, but prior to that I've gone through saving it to the options collection first, which seems a redundant step? Is it, or am I missing something? May 23 '16 at 10:48
  • No that makes sense ... in my case I calculate options and present to user for choice, but given your fixed options you've already got the choices so yeah, you can indeed cut out the middle bit :) May 23 '16 at 10:52
  • Thanks Jeremy, this is my first time working with Commerce, so I'm still finding my feet :) May 23 '16 at 10:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.