This is especially useful for discounts or a shipping calculator. Based on what items are in the users cart, you could charge an additional amount to the order for shipping costs. There are three types of OrderModifiers (denoted by the "Type" enum field below): - 'None': This is for a state when you need to indicate to the user an amount, but it doesn't modify the total. (very useful for indicating tax on an order where tax is inclusive on the products being purchased) - 'Chargable': Charges an amount to the order. A good example of this would be shipping costs. - 'Deductable': Discounts an amount from the order. A good example is a discount being offered to the customer; a rewards scheme, or offering a 10% discount if the customer is a VIP or frequent customer. OrderModifiers can be as simple or as complex as needed. {@link OrderModifier::Amount()} returns the amount to be charged or discounted. The amount is calculated in {@link OrderModifier::calculateAmount()} (this should be implemented on your modifier classes). If the OrderModifier is saved to the DB (user has checked out), then {@link OrderModifier::Amount()} will return the amount from the DB instead of performing a calculation.
Inheritance: extends DataObject
 /**
  * When SimpleShippingModifier is written for the
  * first time, set the field to the return values
  * of the overloaded functions.
  */
 public function onBeforeWrite()
 {
     parent::onBeforeWrite();
     if (!$this->ID) {
         $this->setField('Country', $this->Country());
     }
 }
 public function TableTitle()
 {
     $title = parent::TableTitle();
     if ($this->Rate) {
         $title .= " " . sprintf(_t("TaxModifier.ATRATE", "@ %s"), number_format($this->Rate * 100, 1) . "%");
     }
     return $title;
 }
 public function TableTitle()
 {
     $title = parent::TableTitle();
     if ($this->Rate) {
         $title .= ' ' . _t('TaxModifier.AtRate', '@ {Rate}%', '', array('Rate' => number_format($this->Rate * 100, 1)));
     }
     return $title;
 }
 /**
  * When TaxModifer is written for the first time,
  * set the field to the return values of the
  * overloaded functions.
  */
 public function onBeforeWrite()
 {
     parent::onBeforeWrite();
     if (!$this->ID) {
         $this->setField('Country', $this->Country());
         $this->setField('Rate', $this->Rate());
         $this->setField('Name', $this->Name());
         $this->setField('TaxType', $this->TaxType());
     }
 }
 function getCMSFields()
 {
     $fields = parent::getCMSFields();
     $keys = array_keys($this->regionoptions);
     $newArray = array();
     foreach ($keys as $key) {
         $newArray[$key] = $key;
     }
     $fields->replaceField("Region", new DropdownField("Region", "Region", $newArray));
     return $fields;
 }
 public function onBeforeWrite()
 {
     parent::onBeforeWrite();
 }
 /**
  * some modifiers can be hidden after an ajax update (e.g. if someone enters a discount coupon and it does not exist).
  * There might be instances where ShowInTable (the starting point) is TRUE and HideInAjaxUpdate return false.
  * @return Boolean
  **/
 public function HideInAjaxUpdate()
 {
     //we check if the parent wants to hide it...
     //we need to do this first in case it is being removed.
     if (parent::HideInAjaxUpdate()) {
         return true;
     }
     // we do NOT hide it if values have been entered
     if ($this->CouponCodeEntered) {
         return false;
     }
     return true;
 }
 /**
  * some modifiers can be hidden after an ajax update (e.g. if someone enters a discount coupon and it does not exist).
  * There might be instances where ShowInTable (the starting point) is TRUE and HideInAjaxUpdate return false.
  *@return Boolean
  **/
 public function HideInAjaxUpdate()
 {
     if (parent::HideInAjaxUpdate()) {
         return true;
     }
     if ($this->OrderFor) {
         return false;
     }
     return true;
 }
 /**
  * PRECONDITION: The order item is not saved in the database yet.
  */
 public function onBeforeWrite()
 {
     parent::onBeforeWrite();
     $this->Rate = $this->LiveRate();
     $this->Name = $this->LiveName();
     $this->TaxType = $this->LiveIsExclusive() ? 'Exclusive' : 'Inclusive';
 }
 protected function onAfterWrite()
 {
     parent::onAfterWrite();
     if ($this->willFail) {
         user_error('Modifier failure!');
     }
 }
 /**
  * standard modifier method
  * @param Boolean $force - should the update run no matter what
  */
 public function runUpdate($force = false)
 {
     $this->addProductsPerCombo();
     parent::runUpdate($force);
 }
 /**
  *
  * @param Array $js javascript array
  * @return Array for AJAX JSON
  **/
 function updateForAjax(array $js)
 {
     $js = parent::updateForAjax($js);
     $jsonOptions = array();
     $liveOptions = $this->LiveOptions();
     if ($liveOptions && $liveOptions->count()) {
         $optionsArray = $liveOptions->map('ID', 'Name');
         if ($optionsArray && !is_array($optionsArray)) {
             $optionsArray = $optionsArray->toArray();
         }
         if ($optionsArray && count($optionsArray)) {
             foreach ($optionsArray as $id => $name) {
                 $jsonOptions[] = array('id' => $id, 'name' => $name);
             }
         }
     }
     $js[] = array('t' => 'dropdown', 's' => 'PickupOrDeliveryType', 'p' => $this->LiveOptionID(), 'v' => $jsonOptions);
     return $js;
 }
 function onBeforeWrite()
 {
     parent::onBeforeWrite();
     $this->Country = $this->LiveCountry();
     $this->ShippingChargeType = $this->LiveIsDefaultCharge() ? 'Default' : 'ForCountry';
 }
 /**
  * some modifiers can be hidden after an ajax update (e.g. if someone enters a discount coupon and it does not exist).
  * There might be instances where ShowInTable (the starting point) is TRUE and HideInAjaxUpdate return false.
  *@return Boolean
  **/
 public function HideInAjaxUpdate()
 {
     //we check if the parent wants to hide it...
     //we need to do this first in case it is being removed.
     if (parent::HideInAjaxUpdate()) {
         return true;
     }
     // we do NOT hide it if values have been entered
     if ($this->hasDonation() || $this->ShowFormInEditableOrderTable()) {
         return false;
     }
     return true;
 }
 /**
  * updates database fields
  * @param Bool $force - run it, even if it has run already
  * @return void
  */
 public function runUpdate($force = true)
 {
     //order is important!
     $this->checkField("DefaultCountry");
     $this->checkField("Country");
     $this->checkField("DefaultRate");
     $this->checkField("CurrentRate");
     $this->checkField("TaxType");
     $this->checkField("RawTableValue");
     $this->checkField("DebugString");
     parent::runUpdate($force);
 }
 function updateForAjax(array $js)
 {
     parent::updateForAjax($js);
     self::apply_min_max();
     if (is_array(self::$ids_of_items_adjusted) && count(self::$ids_of_items_adjusted)) {
         $items = OrderItem::get()->filter(array('ID' => self::$ids_of_items_adjusted));
         if ($items->count()) {
             foreach ($items as $item) {
                 $item->updateForAjax($js);
             }
         }
     }
     return $js;
 }
 public function testDelete()
 {
     Config::inst()->update('FlatTaxModifier', 'rate', 0.25);
     Config::inst()->update('Order', 'modifiers', array('FlatTaxModifier'));
     $order = Order::create();
     $shirt = $this->objFromFixture("Product", "tshirt");
     $mp3player = $this->objFromFixture("Product", "mp3player");
     $order->Items()->add($shirt->createItem(3));
     $order->Items()->add($mp3player->createItem(1));
     $order->write();
     $order->calculate();
     $statusLogId = OrderStatusLog::create(array('Title' => 'Test status log', 'OrderID' => $order->ID))->write();
     $paymentId = Payment::create(array('OrderID' => $order->ID))->init('Manual', 343.75, 'NZD')->write();
     $this->assertEquals(4, $order->Items()->Quantity());
     $this->assertEquals(1, $order->Modifiers()->count());
     $this->assertEquals(1, $order->OrderStatusLogs()->count());
     $this->assertEquals(1, $order->Payments()->count());
     $itemIds = Product_OrderItem::get()->filter('OrderID', $order->ID)->column('ID');
     $modifierIds = OrderModifier::get()->filter('OrderID', $order->ID)->column('ID');
     $order->delete();
     // Items should no longer be linked to order
     $this->assertEquals(0, $order->Items()->count());
     $this->assertEquals(0, $order->Modifiers()->count());
     $this->assertEquals(0, $order->OrderStatusLogs()->count());
     $this->assertEquals(0, $order->Payments()->count());
     // Ensure the order items have been deleted!
     $this->assertEquals(0, Product_OrderItem::get()->filter('ID', $itemIds)->count());
     $this->assertEquals(0, OrderModifier::get()->filter('ID', $modifierIds)->count());
     $this->assertEquals(0, OrderStatusLog::get()->filter('ID', $statusLogId)->count());
     // Keep the payment… it might be relevant for book keeping
     $this->assertEquals(1, Payment::get()->filter('ID', $paymentId)->count());
 }
 /**
  * Removes a modifier from the cart
  * @param Int/ OrderModifier
  * @return Boolean
  */
 public function addModifier($modifier)
 {
     if (is_numeric($modifier)) {
         $modifier = OrderModifier::get()->byID($modifier);
     } elseif (!is_a($modifier, Object::getCustomClass("OrderModifier"))) {
         user_error("Bad parameter provided to ShoppingCart::addModifier", E_USER_WARNING);
     }
     if (!$modifier) {
         $this->addMessage(_t("Order.MODIFIERNOTFOUND", "Modifier could not be found."), 'bad');
         return false;
     }
     $modifier->HasBeenRemoved = 0;
     $modifier->write();
     $this->addMessage(_t("Order.MODIFIERREMOVED", "Added."), 'good');
     return true;
 }
 /**
  * updates database fields
  * @param Bool $force - run it, even if it has run already
  * @return void
  */
 public function runUpdate($force = true)
 {
     $this->checkField("Country");
     $this->checkField("ShippingChargeType");
     parent::runUpdate($force);
 }