In D7 Commerce there was an automatic rounding for CHF currency (in Switzerland) where prices are rounded to the next 0.05 CHF. I think that this feature has been introduced with the applied patch of https://www.drupal.org/project/commerce/issues/958944 for D7 Commerce 1.

Is there an equivalent functionality for D8 Commerce 2?

Comments

M_Z created an issue. See original summary.

bojanz’s picture

Category: Feature request » Support request

We evaluated this feature for 2.x back in 2015 with a number of our Swiss contributors, and the consensus was that the Commerce 1.x feature was not implemented correctly and shouldn't be ported over.

Rounding to the nearest 0.05 CHF is called cash rounding, or Swedish rounding.
By definition it should only be applied to the order total (19.93 -> 19.95), and it should only be done if the order will be paid in cash.

The Commerce 1.x approach is to do this via currency rounding, which means that each individual (per-order-item) VAT amount and discount is rounded to the nearest 0.05, and done regardless of the payment method used.

Assuming that our understanding is correct, the right approach would be to provide a custom adjustment type (called "Rounding" or something similar), and an order processor, which would calculate the required amount, and add it as an order-level adjustment (using the newly defined adjustment type). The order processor would also need to look at $order->payment_gateway to decide whether to kick-in. That would also allow sites to decide whether to always round, or to round for a specific gateway only (such as "cash on delivery").

I think that it would be best for this functionality to live in a contrib module (commerce_cash_rounding?), and not in Commerce core.

bojanz’s picture

Title: Currency specific rounding (e.g. for Swiss Francs CHF) for Commerce 2 » How to implement cash rounding (e.g. for Swiss Francs CHF) for Commerce 2

Improving the title.

M_Z’s picture

Thank you bojanz for pointing the direction for a solution.

Some more detailed Questions:

1. Could you tell me some Commerce core sub-modules or Commerce contrib modules that implement an order processor which is doing something similar as you told ("calculate the required amount")? I think that tax or discount calculation could be used as starting point for a rounding contrib module, but maybe you can suggest a less complex module?

2. Must the adjustment type be declared as plugin or something similar? (Maybe the sample module from question 1 shows such a declaration?)

The part of your answer with "$order->payment_gateway" seems to be clear and I know what you mean.

Many thanks in advance.

czigor’s picture

Hi M_Z,
1. I'd suggest to look at commerce_shipping's order processor, ShipmentOrderProcessor. The ones in tax and promotion are a bit more complicated.
2. No, you don't need to define the adjustment type. You can just use any string you want for the type. See Bojan's answer below.

bojanz’s picture

I would still define a custom adjustment type, to make it cleaner.

In yourmodule.commerce_adjustment_types.yml:

rounding:
  label: 'Rounding'
  singular_label: 'rounding adjustment'
  plural_label: 'rounding adjustments'
  has_ui: true
  weight: 20

And an example order processor in src/RoundingOrderProcessor.php:

<?php

namespace Drupal\yourmodule;

use Drupal\commerce_order\Adjustment;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_order\OrderProcessorInterface;

/**
 * Processes the order's shipments.
 */
class RoundingOrderProcessor implements OrderProcessorInterface {

  /**
   * {@inheritdoc}
   */
  public function process(OrderInterface $order) {
    $payment_gateway = $order->get('payment_gateway')->entity;
    if (!$payment_gateway) {
      // The payment gateway hasn't been selected yet.
    }

    if ($payment_gateway->id() == 'your_gateway') {
      $rounder = \Drupal::service('commerce_price.rounder');
      $order_total = $order->getTotalPrice();
      // Rounds down 10.21 and 10.22 to 10.20, rounds up 10.23 and 10.24 to 10.25. 
      $rounded_total = $rounder->round($order_total->divide(5))->multiply(5);
      $difference = $rounded_total->subtract($order_total);

      $order->addAdjustment(new Adjustment([
        'type' => 'rounding',
        'label' => 'Rounding',
        'amount' => $difference,
      ]));
    }
  }

}

Don't forget the entry in yourmodule.services.yml:

  yourmodule.rounding_order_processor:
    class: Drupal\yourmodule\RoundingOrderProcessor
    tags:
      - { name: commerce_order.order_processor, priority: -10 }
bojanz’s picture

Status: Active » Fixed

Closing since an answer was given in #6.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.