Working with sale.order.ajax in Bitrix
The last notes
All English-language materials have been translated fully automatically using the Google service
Section navigation:
- Customizing sale.order.ajax
- Open all blocks and remove unnecessary
- Determining the user's location in automatic mode
- Hide some property
- Exclude zero shipping rate from display
- Migrating Shipping Address, Comments, Locations fields in sale.order.ajax
- Move the Index field to the custom properties block in sale.order.ajax
- Banning Bitrix from choosing default delivery
- order_ajax_ext.js
- Brief description of the sale.order.ajax error functions
- Code execution after page reload
- Program change of city in sale.order.ajax to javascript
- Calculating shipping costs for all shipping services
- Retrieving shipping costs for a product after discounts and cart rules have been applied
- We do free delivery in Russia on condition
- Making our own field validator using the example of custom validation of the "Phone" field
- Useful Variables
order_ajax.js
- Array of matching international two-letter country codes
- Alternative (custom) sale.order.ajax
- Error in Bitrix when placing an order
window .__ jsonp_ymaps_map is not a function
& nbsp;
This material is aggregate and written more for yourself. As I work and gain experience, I update it.
You need to clearly understand that not all the methods and methods described in the material will work for you. There are many Bitrix versions and the code changes quite significantly. Once worked, the solution will not necessarily work in future updates. For example, some of the methods that worked for me in version 18 stopped working in version 20.
During the work, I noticed that the use of the above methods can cause execution errors in cases when the order of blocks is changed, the quick edit mode is enabled, or the options "Autocomplete payment and delivery for the previous order" are set in the component parameters, or "Compatibility mode for previous template"
Customization & nbsp; sale.order.ajax
Almost none of my clients are satisfied with the appearance of sale.order.ajax. Here I will describe most of the techniques that I use myself when customizing this complex component.
In the template.php
file, order blocks are marked with comments:
BASKET ITEMS BLOCK - order basket - product table
REGION BLOCK - select the payer type and enter the location - the buyer's city
DELIVERY BLOCK - the choice of a delivery service
PAY SYSTEMS BLOCK - choosing a payment method
BUYER PROPS BLOCK - a form with fields for entering customer data
ORDER SAVE BLOCK - total and order confirmation button
To make all blocks active, and not just the first one, comment on the second line in this block
initFirstSection: function()
{
var firstSection = this.orderBlockNode.querySelector('.bx-soa-section.bx-active');
//BX.addClass(firstSection, 'bx-selected');
this.activeSectionId = firstSection.id;
},
If deliveries give a computation error, then the delivery blocks are hidden. You can disable this by commenting out the following code:
/*if (this.result.DELIVERY.length > 0)
{
BX.addClass(this.deliveryBlockNode, 'bx-active');
this.deliveryBlockNode.removeAttribute('style');
}
else
{
BX.removeClass(this.deliveryBlockNode, 'bx-active');
this.deliveryBlockNode.style.display = 'none';
}*/
We remove the hiding of blocks during authorization (when the option `` register with the checkout '' is disabled in the settings).
if (this.result.SHOW_AUTH && section.id != this.authBlockNode.id && section.id != this.basketBlockNode.id)
section.style.display = 'none';
else if (section.id != this.pickUpBlockNode.id)
section.style.display = '';
Open all blocks and remove unnecessary ones
To reveal all hidden blocks, you can use the following methods (personally used on versions up to 20):
Look for the line var active = section.id == this.activeSectionId
and change it to
var active = true
Next, turn off the response to a click on the block header
BX.unbindAll(titleNode);
if (this.result.SHOW_AUTH)
{
BX.bind(titleNode, 'click', BX.delegate(function(){
this.animateScrollTo(this.authBlockNode);
this.addAnimationEffect(this.authBlockNode, 'bx-step-good');
}, this));
}
else
{
BX.bind(titleNode, 'click', BX.proxy(this.showByClick, this));
editButton = titleNode.querySelector('.bx-soa-editstep');
editButton && BX.bind(editButton, 'click', BX.proxy(this.showByClick, this));
}
So that Region and User are always open
if (this.activeSectionId !== this.regionBlockNode.id)
this.editFadeRegionContent(this.regionBlockNode.querySelector('.bx-soa-section-content'));
if (this.activeSectionId != this.propsBlockNode.id)
this.editFadePropsContent(this.propsBlockNode.querySelector('.bx-soa-section-content'));
Чтобы убрать кнопки Далее/Назад
node.appendChild(
BX.create('DIV', {
props: {className: 'row bx-soa-more'},
children: [
BX.create('DIV', {
props: {className: 'bx-soa-more-btn col-xs-12'},
children: buttons
})
]
})
);
To remove "edit" links from all blocks in editOrder
(~ 2222 pages)
var editSteps = this.orderBlockNode.querySelectorAll('.bx-soa-editstep'), i;
for (i in editSteps) {
if (editSteps.hasOwnProperty(i)) {
BX.remove(editSteps[i]);
}
}
To hide the notification that this is filled in automatically add to the end of the file style.css
.alert.alert-warning{display:none;}
Determining the user's location in automatic mode
use Bitrix\Main\EventManager;
$eventManager = EventManager::getInstance();
$eventManager->addEventHandler("sale", "OnSaleComponentOrderProperties", Array("Example", "OnSaleComponentOrderProperties"));
class Example
{
/**
* I know the ID and NAME of the location by the condition of the task
*/
static $curCityId = XX; //the numeric value of the location id
static $curCityName = 'City name';
/**
* Order properties ID
*/
const PROP_LOCATION = 6;
const PROP_ZIP = 4;
const PROP_LOCATION_NAME = 5;
static function OnSaleComponentOrderProperties(&$arFields)
{
$rsLocaction = CSaleLocation::GetLocationZIP(self::$curCityId);
$arLocation = $rsLocaction->Fetch();
$arFields['ORDER_PROP'][self::PROP_ZIP] = $arLocation['ZIP'];
$arFields['ORDER_PROP'][self::PROP_LOCATION_NAME] = self::$curCityName;
$arFields['ORDER_PROP'][self::PROP_LOCATION] = CSaleLocation::getLocationCODEbyID(self::$curCityId);
}
}
But this modification allows you to determine the location only by the name of the city:
class Example {
static function OnSaleComponentOrderProperties(&$arFields)
{
static $curCityName = 'City name';
const PROP_LOCATION = 6; // - Property ID with location
static function OnSaleComponentOrderProperties(&$arFields)
{
$res = Bitrix\Sale\Location\LocationTable::getList(array(
'filter' => array('=NAME.NAME' => self::$curCityName, '=NAME.LANGUAGE_ID' => LANGUAGE_ID),
'select' => array('CODE' => 'CODE', 'NAME_RU' => 'NAME.NAME', 'TYPE_CODE' => 'TYPE.CODE') //'*',
));
while($item = $res->fetch())
{
$code = $item["CODE"];
}
$arFields['ORDER_PROP'][self::PROP_LOCATION] = $code;
}
}
}
Hide some property
If you need to hide some property, for example, the index property - set the default value and not show this field to users, then you can make an adjustment in JS. In the getPropertyRowNode function, after switch (propertyType), add the hiding of this property:
if(property.getId()==6){// identifier of the property to hide
var addressInput=propsItemNode.querySelector('textarea');
propsItemNode.style='display:none;';
addressInput.value='нужное значение';
}
We hide the message "You ordered in our online store, so we filled in all the data automatically"
Go to the checkNotifications
function on ~ 750 pages
and find the code
informer.appendChild(
BX.create('DIV', {
props: {className: 'row'},
children: [
BX.create('DIV', {
props: {className: 'col-xs-12'},
style: {position: 'relative', paddingLeft: '48px'},
children: [
BX.create('DIV', {props: {className: 'icon-' + className}}),
BX.create('DIV', {html: text})
]
})
]
})
);
BX.addClass(informer, 'alert alert-' + className);
And framing this additional code. check
if(this.params.MESS_SUCCESS_PRELOAD_TEXT.indexOf('You ordered on our website') === false) {
//We put the code above here
}
Hide the message "Select your city in the list. If you did not find your city, select another location" and enter the city in the "City" field
Go to the getDeliveryLocationInput
function and comment out the code:
/*
if (location && location[0])
{
node.appendChild(
BX.create('DIV', {
props: {className: 'bx-soa-reference'},
html: this.params.MESS_REGION_REFERENCE
})
);
}
*/
Or, using styles to hide the class bx-soa-reference
#bx-soa-region .bx_soa_location .bx-soa-reference{display: none}
Exclude from displaying zero shipping charges
In function & nbsp; getDeliveryPriceNodes: function (delivery)
& nbsp; in block & quot; else & quot; replace. Instead of:
priceNodesArray = [delivery.PRICE_FORMATED];
write:
if(delivery.PRICE>0) priceNodesArray = [delivery.PRICE_FORMATED];
This will hide the zero price from the collapsed block with the selected delivery.
Next, you need to hide the zeros in the list of delivery services. To do this, in the createDeliveryItem: function (item) function, we do a strict zero check. Instead of:
if (item.PRICE >= 0 || typeof item.DELIVERY_DISCOUNT_PRICE !== 'undefined')
write:
if (item.PRICE > 0 || typeof item.DELIVERY_DISCOUNT_PRICE !== 'undefined')
And also instead of:
else if (deliveryCached && (deliveryCached.PRICE >= 0 || typeof deliveryCached.DELIVERY_DISCOUNT_PRICE !== 'undefined'))
write:
else if (deliveryCached && (deliveryCached.PRICE > 0 || typeof deliveryCached.DELIVERY_DISCOUNT_PRICE !== 'undefined'))
And the last one is to hide the zero delivery from the totals. To do this, in the editTotalBlock: function () function, we also set the strict check to zero. Instead of
if (parseFloat(total.DELIVERY_PRICE) >= 0 && this.result.DELIVERY.length)
write:
if (parseFloat(total.DELIVERY_PRICE) > 0 && this.result.DELIVERY.length)
As a result, zero delivery will not be shown to the user.
Migration of Delivery Address, Comments, Locations fields in sale.order.ajax
Remove the field Delivery address from the output of the User block
The methods described by my colleagues did not work in my case. I decided to go in an ugly but effective way
Go to the editPropsItems
function and find the code:
if (
this.deliveryLocationInfo.loc == property.getId()
|| this.deliveryLocationInfo.zip == property.getId()
|| this.deliveryLocationInfo.city == property.getId()
)
Change it to:
if (
this.deliveryLocationInfo.loc == property.getId()
|| this.deliveryLocationInfo.zip == property.getId()
|| this.deliveryLocationInfo.city == property.getId()
|| property.getName()=='Адрес доставки (улица, дом, квартира)' //где property.getName() equate to the name of the address field in your system
)
Moving the "Index" field to the custom properties block in sale.order.ajax
In a basic standard template, the index field is easy to move. Let's move the Index field from the Locations
block to the Custom Properties
block
Find the getDeliveryLocationInput
function (~ 4451 pages) and comment out the code this.getZipLocationInput(node);
Go to the editPropsItems
function (~ 6728 pages) and before propsNode.appendChild (propsItemsContainer)
add the code this.getZipLocationInput (propsItemsContainer);
.
In theory, this should be enough. But if it still does not work, then go to the address /bitrix/admin/sale_order_props.php?lang=en
and in the Index
field translate it to the Personal property group data
Output of the "Delivery address" field in the "Delivery" block
Go to the editDeliveryInfo
function and add the code at the very end:
var deliveryItemsContainer = BX.create('DIV', {props: {className: 'col-sm-12 bx-soa-delivery'}}),
group, property, groupIterator = this.propertyCollection.getGroupIterator(), propsIterator;
if (!deliveryItemsContainer)
deliveryItemsContainer = this.propsBlockNode.querySelector('.col-sm-12.bx-soa-delivery');
while (group = groupIterator())
{
propsIterator = group.getIterator();
while (property = propsIterator())
{
if (property.getName()=='Адрес доставки (улица, дом, квартира)') { //If the property is the same as the name of the address field on your system
this.getPropertyRowNode(property, deliveryItemsContainer, false); //insert the property into the prepared container
deliveryNode.appendChild(deliveryItemsContainer); //add the container together with the property in it at the end of the block with the description (deliveryInfoContainer)
}
}
}
Move the "Delivery address" field into a separate new (!) block
This need arises, for example, in Aspro templates, where the "Delivery address" field in the block with the choice of deliveries looks alien and ugly
In the template.php
component of the sale.order.ajax
component, add a new block in the most convenient place for us
<div id="bx-soa-adress-dostavki" data-visited="false" class="bx-soa-section bx-active">
<div class="bx-soa-section-title-container">
<h2 class="bx-soa-section-title col-sm-9">
<span class="bx-soa-section-title-count"></span>Данные для доставки
</h2>
</div>
<div class="bx-soa-section-content container-fluid"></div>
</div>
We go to the file order_ajax.js
and in the init
function on ~ 77 pages
add a call to the newly added block
this.orderAdresBlockNode = BX('bx-soa-adress-dostavki');
Hide the output of the Delivery Address field as shown above
if (
this.deliveryLocationInfo.loc == property.getId()
|| this.deliveryLocationInfo.zip == property.getId()
|| this.deliveryLocationInfo.city == property.getId()
|| property.getName()=='Адрес доставки' //where property.getName() equates to the name of the address field on your system
)
We implement the output of the new field in an additional block. In the standard case, you will have to take into account that the "Delivery address" field is generated separately for each of the payer types, has a different property id, and our block itself must be hidden if payments and delivery are selected in which the "Delivery address" field is not displayed. Therefore, the code will be like this.
Go to the editDeliveryInfo
function (any similar or your own) and after the line deliveryNode.appendChild (deliveryInfoContainer)
or at the end of the function add:
var deliveryItemsContainer = BX.create('DIV', {props: {className: 'col-sm-12 bx-soa-delivery'}}),
group, property, groupIterator = this.propertyCollection.getGroupIterator(), propsIterator;
if (!deliveryItemsContainer)
deliveryItemsContainer = this.propsBlockNode.querySelector('.col-sm-12.bx-soa-delivery');
while (group = groupIterator())
{
propsIterator = group.getIterator();
while (property = propsIterator())
{
var personType = this.getSelectedPersonType();
this.orderAdresBlockNode.querySelector('.bx-soa-section-content').innerHTML = '';
if (property.getName()=='Адрес доставки' && personType.ID === '1' && property.getId() == '7') {
this.getPropertyRowNode(property, deliveryItemsContainer, false);
this.orderAdresBlockNode.querySelector('.bx-soa-section-content').appendChild(deliveryItemsContainer);
this.orderAdresBlockNode.classList.remove('hidden');
}else if (property.getName()=='Адрес доставки' && personType.ID === '2' && property.getId() == '19') {
this.getPropertyRowNode(property, deliveryItemsContainer, false);
this.orderAdresBlockNode.querySelector('.bx-soa-section-content').appendChild(deliveryItemsContainer);
this.orderAdresBlockNode.classList.remove('hidden');
}else{
this.orderAdresBlockNode.classList.add('hidden');
}
}
}
The code is not the most beautiful and correct, but it works
Move the "Order Comments" field to the end of the form
Go to the editActivePropsBlock
function and comment on the line:
this.editPropsComment(propsNode);
To display the field, look for the function of the last block in the template. In my case, this is the output of the order. We are looking for the function editBasketItems
and add to the very end:
this.editPropsComment(basketItemsNode);
Moving the "Locations" field to the custom properties block
By default, this field is artificially excluded from the custom properties block. To put it back in place, go to the editPropsItems
function and delete the code:
if (
this.deliveryLocationInfo.loc == property.getId()
|| this.deliveryLocationInfo.zip == property.getId()
|| this.deliveryLocationInfo.city == property.getId()
)
continue;
Banning Bitrix from choosing delivery by default
In case the user has a saved profile, the last delivery chosen by him will be automatically selected, but Bitrix does not know anything about the fact that we also have required fields there. Therefore, we remove the default delivery selection in the OnSaleComponentOrderJsDataHandler handler. We already have it, we add to it:
if (isset($arResult['JS_DATA']['LAST_ORDER_DATA']['DELIVERY'])
&& $arResult['JS_DATA']['LAST_ORDER_DATA']['DELIVERY']!='') {
$arResult['JS_DATA']['LAST_ORDER_DATA']['DELIVERY'] = '';}
In this case, the block with deliveries will always be open, and the user will immediately pay attention to the need to fill in the fields. But! If a user deletes a profile in the account, the field with the location will be blank, and after filling it out, the delivery block will automatically close without the possibility of editing it (the `` Change '' button will disappear). This is very difficult to fix so that everything else does not fall apart, so we decided to remove the ability to edit profiles in the user's account (done by unchecking the box in the settings of the personal account component)
At the moment, I have everything. Of course, this code was written for a specific project and with certain assumptions. But I hope that this note turned out to be useful to you and will lead you on the right path in solving your problem. Because there is no documentation on the methods of the OrderAjaxComponent class and will not be. If you have anything to add or correct, I will be glad to comment.
order_ajax_ext.js
Create a file order_ajax_ext.js
in the folder with the sale.order.ajax component template (in the same place as the order_ajax.js file) with the content:
(function () {
'use strict';
var initParent = BX.Sale.OrderAjaxComponent.init,
getBlockFooterParent = BX.Sale.OrderAjaxComponent.getBlockFooter,
editOrderParent = BX.Sale.OrderAjaxComponent.editOrder
;
BX.namespace('BX.Sale.OrderAjaxComponentExt');
BX.Sale.OrderAjaxComponentExt = BX.Sale.OrderAjaxComponent;
//Пример перехвата стандартной функции
BX.Sale.OrderAjaxComponentExt.init = function (parameters) {
initParent.apply(this, arguments);
var editSteps = this.orderBlockNode.querySelectorAll('.bx-soa-editstep'), i;
for (i in editSteps) {
if (editSteps.hasOwnProperty(i)) {
BX.remove(editSteps[i]);
}
}
};
})();
In separate variables we define the function-methods of the parent BX.Sale.OrderAjaxComponent
so that they can be called in child functions and do not get the Maximum call stack size exceeded error.
Copy link from BX.Sale.OrderAjaxComponent
to BX.Sale.OrderAjaxComponentExt
.
In the BX.Sale.OrderAjaxComponentExt.init
method, we call the parent init, then nail down the `` edit '' links all blocks. We don't need them.
In the BX.Sale.OrderAjaxComponentExt.getBlockFooter
method we nail the Back buttons and `` Forward '' at the blocks. We don't need them either & mdash; all blocks are expanded.
In the BX.Sale.OrderAjaxComponentExt.editOrder
method, add the bx-soa-section-hide css class to unnecessary blocks-sections. We will use it to hide unnecessary blocks. And also in this method we reveal only the blocks we need: `` Buyer '' and 'Products in order'.
Leave the BX.Sale.OrderAjaxComponentExt.initFirstSection
method just empty. If this is not done, then anonymous users will get an error when they try to register because of the lack of required required fields.
Let's move on.
In the template.php file of our new design template, add the connection of our order_ajax_ext.js script
After the line:
$this->addExternalJs($templateFolder.'/order_ajax.js');
add:
$this->addExternalJs($templateFolder.'/order_ajax_ext.js');
And also in the template.php file, change all calls to BX.Sale.OrderAjaxComponent to BX.Sale.OrderAjaxComponentExt
Well, do not forget to add to the stylesheet so that unnecessary blocks are hidden
.bx-soa-section-hide {
display: none;
}
Summary of sale.order.ajax functions
showValidationResult: function (inputs, errors)
- a function in which the hasError class is added to fields with an error, which is flagged with an error (in the standard version, it adds a red outline).
showErrorTooltip: function (tooltipId, targetNode, text)
- a function that adds tooltips for fields with errors.
showError: function (node, msg, border)
- a function that displays errors in the "group container"
refreshOrder: function (result)
- a function that parses errors that come from the server. There is a branch there result.error
The first 3 functions are responsible for validation on the form without reloading, and the fourth for processing the results from the server.
Code execution after page reload
It happens that you need to regularly execute the code after reloading the page (changing order options). For example, you need to redraw the select. It's simple. Open the file order_ajax.js
and add at the very end:
(function ($) {
$(function() {
BX.addCustomEvent('onAjaxSuccess', function(){
$(function() {
$('select').ikSelect({
autoWidth:false,
ddFullWidth:false
});
});
});
$('select').ikSelect({
autoWidth:false,
ddFullWidth:false
});
window.onresize = function() {
$('select').ikSelect('redraw');
}
});
})(jQuery);
Standard ajax returns data to a frame container. Accordingly, all js already loaded on the page is one level higher. Correspondingly, to access it from html loaded via ajax, you must specify this (top.function_name or top.variable_name). Thanks Evgeny Zhukov
top.$
For styling selects that look awful by default, it's best to use the awesome library ikSelect
Programmatic change of city in sale.order.ajax to javascript
Often, according to the terms of reference, a programmatic change of the city to javascript with ajax page reload and form recalculation is required. I'll show you how to do it.
Let's assume that you already have code
locations from Bitrix locations. Then in the part of the code where it is necessary to change the location, write:
//This code will work not only inside sale.order.ajax, but also in external scripts, since we access its functions through the BX.Sale.OrderAjaxComponent namespace
var code = '0000000001';//Your location code. In this case, it is Belarus
$('.dropdown-field').val(code);//Updating the code in the hidden input
//$('.bx-ui-sls-fake').val(code);
BX.Sale.OrderAjaxComponent.params.newCity=code; //We write the city we need in the parameters of the component
BX.Sale.OrderAjaxComponent.sendRequest();//Updating the form
Here we have filled in the hidden input with the code we need and written it into a variable that we will use when rebuilding the form. Go to the prepareLocations
function (~ 1557 pages). Find the code:
temporaryLocations.push({
And above it we write:
//We do a double check.
if(typeof this.params!='undefined'){//In the first case, the fact that the parameters are generally set, since the code is executed for the first time before their initialization
if(typeof this.params.newCity!='undefined'){//The second check is whether our variable is set
locations[i].lastValue = this.params.newCity;//If the variable is set, then we substitute it into the location
delete this.params.newCity;//We set our variable to zero
}
}
In the end, I got it like this:
for (k in output)
{
if (output.hasOwnProperty(k))
{
if(typeof this.params!='undefined'){
if(typeof this.params.newCity!='undefined'){
locations[i].lastValue = this.params.newCity;
delete this.params.newCity;//
}
}
temporaryLocations.push({
output: BX.processHTML(output[k], false),
showAlt: locations[i].showAlt,
lastValue: locations[i].lastValue,
coordinates: locations[i].coordinates || false
});
}
}
Calculation of shipping costs for all delivery services
Let's assume that the sale.order.ajax
component is located in a separate folder
Then go to the file /local/components/YOUR_NAMESPACE/sale.order.ajax/class.php
and find the function protected functionDeliveries (Order $ calculate order)
(~ 4289 pages)
Find the condition if ((int) $ shipment-> getDeliveryId () === $ deliveryId)
and in the else
area just after the code:
$mustBeCalculated = $this->arParams['DELIVERY_NO_AJAX'] === 'Y'
|| ($this->arParams['DELIVERY_NO_AJAX'] === 'H' && $deliveryObj->isCalculatePriceImmediately());
write:
$mustBeCalculated = true;
$calcResult = $deliveryObj->calculate($shipment);
$calcOrder = $order;
Now, after contacting the server, delivery services with calculated costs come to our order_ajax.js
. All that remains is to process and display them.
In the script we find the function createDeliveryItem: function (item)
and work with the parameter item.PRICE
or item.PRICE_FORMATED
and display it where needed .
Retrieving shipping costs for a product after discounts and cart rules have been applied
Thanks for the script Denis Myagkov
/**
*Retrieving shipping costs for a product after applying discounts, cart rules */
function getDeliveryPriceForProduct($bitrixProductId, $siteId, $userId, $personTypeId, $deliveryId, $paySystemId, $userCityId)
{
$result = null;
\Bitrix\Main\Loader::includeModule('catalog');
\Bitrix\Main\Loader::includeModule('sale');
$products = array(
array(
'PRODUCT_ID' => $bitrixProductId,
'QUANTITY' => 1,
// 'NAME' => 'Item 1',
// 'PRICE' => 500,
// 'CURRENCY' => 'RUB',
),
);
/** @var \Bitrix\Sale\Basket $basket */
$basket = \Bitrix\Sale\Basket::create($siteId);
foreach ($products as $product) {
$item = $basket->createItem("catalog", $product["PRODUCT_ID"]);
unset($product["PRODUCT_ID"]);
$item->setFields($product);
}
/** @var \Bitrix\Sale\Order $order */
$order = \Bitrix\Sale\Order::create($siteId, $userId);
$order->setPersonTypeId($personTypeId);
$order->setBasket($basket);
/** @var \Bitrix\Sale\PropertyValueCollection $orderProperties */
$orderProperties = $order->getPropertyCollection();
/** @var \Bitrix\Sale\PropertyValue $orderDeliveryLocation */
$orderDeliveryLocation = $orderProperties->getDeliveryLocation();
$orderDeliveryLocation->setValue($userCityId); // In which city we are "delivering" (where to deliver).
/** @var \Bitrix\Sale\ShipmentCollection $shipmentCollection */
$shipmentCollection = $order->getShipmentCollection();
$delivery = \Bitrix\Sale\Delivery\Services\Manager::getObjectById($deliveryId);
/** @var \Bitrix\Sale\Shipment $shipment */
$shipment = $shipmentCollection->createItem($delivery);
/** @var \Bitrix\Sale\ShipmentItemCollection $shipmentItemCollection */
$shipmentItemCollection = $shipment->getShipmentItemCollection();
/** @var \Bitrix\Sale\BasketItem $basketItem */
foreach ($basket as $basketItem) {
$item = $shipmentItemCollection->createItem($basketItem);
$item->setQuantity($basketItem->getQuantity());
}
/** @var \Bitrix\Sale\PaymentCollection $paymentCollection */
$paymentCollection = $order->getPaymentCollection();
/** @var \Bitrix\Sale\Payment $payment */
$payment = $paymentCollection->createItem(
\Bitrix\Sale\PaySystem\Manager::getObjectById($paySystemId)
);
$payment->setField("SUM", $order->getPrice());
$payment->setField("CURRENCY", $order->getCurrency());
// $result = $order->save(); // We DO NOT save the order in bitrix - we only need the applied "discounts" and "cart rules" for the order.
// if (!$result->isSuccess()) {
// //$result->getErrors();
// }
$deliveryPrice = $order->getDeliveryPrice();
if ($deliveryPrice === '') {
$deliveryPrice = null;
}
$result = $deliveryPrice;
return $result;
}
// Usage
$deliveryPriceForProductCourier = getDeliveryPriceForProduct(
$bitrixProductId,
SITE_ID,
$USER->GetID(),
'1', // Entity /bitrix/admin/sale_person_type.php?lang=ru
'1386', // Courier delivery to your home (if you have a "profile" - indicate its id) /bitrix/admin/sale_delivery_service_edit.php?lang=ru
'37', // Cash or card upon receipt /bitrix/admin/sale_pay_system.php?lang=ru
$userCity['ID'] // User city
);
We make free delivery in Russia on condition
Suppose we have a task to make delivery in Russia free if the cost of goods in the basket exceeds a certain amount.
In my case, there is a separate "Country" field in the order properties, so the task is simplified and all the code can be reduced to a couple of checks
<?php
use Bitrix\Main\EventManager;
use Bitrix\Main\Event;
use \Bitrix\Main\EventResult;
// Get the price of goods when creating a cart
EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleComponentOrderCreated',
'OnSaleComponentOrderCreated'
);
function OnSaleComponentOrderCreated($order, &$arUserResult, $request, &$arParams, &$arResult, &$arDeliveryServiceAll, &$arPaySystemServiceAll)
{
// Write down the cost of goods
$_SESSION['ORDER_BASKET_PRICE'] = $order->getBasket()->getPrice();
// Write the code of the current country from the order property with id = 24
$_SESSION['ORDER_LOCATION'] = $arUserResult['ORDER_PROP']['24'];
}
// Change the shipping cost during each shipping cost calculation
EventManager::getInstance()->addEventHandler(
'sale',
'onSaleDeliveryServiceCalculate',
'onSaleDeliveryServiceCalculate'
);
function onSaleDeliveryServiceCalculate(\Bitrix\Main\Event $event)
{
$baseResult = $event->getParameter('RESULT');
$shipment = $event->getParameter('SHIPMENT');
//If the price is set, the country is Russia and the delivery service is not EMS
if(isset($_SESSION['ORDER_BASKET_PRICE'])
&& $_SESSION['ORDER_LOCATION']=='0000073738'
&& $event->getParameter('DELIVERY_ID') != 45
&& $event->getParameter('DELIVERY_ID') != 31
&& $event->getParameter('DELIVERY_ID') != 33){
//We look, if we have already received the delivery settings in the current session, then we skip the block
if(!$_SESSION['DELIVERY_PRICE_WHILE_RUSSIA']){
//We get the settings for the cost of the goods at which the delivery should be free
$arSelect = Array('ID','PROPERTY_VAL');
$arFilter = Array(
'=IBLOCK_ID' => 27, //Settings
'=IBLOCK_SECTION_ID' => 86, //Free shipping settings in Russia
'=ACTIVE' => 'Y',
'=ID' => array(1776), //Delivery within Russia is free if the price is higher
);
$res = CIBlockElement::GetList(Array(), $arFilter, false, Array(), $arSelect);
//Looking for free shipping settings
while($ob = $res->GetNextElement())
{
$arFields = $ob->GetFields();
$_SESSION['DELIVERY_PRICE_WHILE_RUSSIA'] = (int)$arFields['PROPERTY_VAL_VALUE'];
}
}
$basketPrice = $_SESSION['ORDER_BASKET_PRICE'];
// If the price of goods is higher than the value from the settings
if($basketPrice > $_SESSION['DELIVERY_PRICE_WHILE_RUSSIA']) {
//we make delivery free
$baseResult->setDeliveryPrice(0);
}
}
//Re-save the result
$event->addResult(
new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS, array('RESULT' => $baseResult)
)
);
}
/*
// The handler can also be called in the old way.
AddEventHandler("sale", "onSaleDeliveryServiceCalculate", "onSaleDeliveryServiceCalculate");
function onSaleDeliveryServiceCalculate($result, $shipment, $deliveryID){
// Check the id of the delivery service
// 17 - Russian Post
// 20 - Delivery by courier
// 21 - CDEK pick-up point
// 24 - Boxberry pick-up point
// 33 - EMS Russian Post
if($deliveryID == 20 ){
if(isset($_SESSION['ORDER_BASKET_PRICE']) )
{
$basketPrice = $_SESSION['ORDER_BASKET_PRICE'];
if($basketPrice > 1000){
// We write down the new value of the shipping price
$shipment->setBasePriceDelivery(0, true);
}
}
}
if($deliveryID == 31 || $deliveryID == 33){
$shipment->setBasePriceDelivery(2500, true);
}
}*/
Don't forget that the code should be placed in init.php
If you know more elegant ways, I will be glad if you share them.
Making our own field validator using the example of custom validation of the "Phone" field
There are two ways to validate any field in sale.order.ajax
.
- Use regular expressions
- Subscribe to OnSaleOrderBeforeSaved
and implement your own validation logic
I will describe both methods
1. Go to Store-> Settings-> Order Properties-> Property List
and open the desired property. In our case, this is the phone.
We are looking for the field Regular expression to check:
and enter it:
/^(\s*)?(\+)?([- _():=+]?\d[- _():=+]?){10,14}(\s*)?$/
This regular pattern is suitable for checking such variants of writing a phone:
+7(903)888-88-88
8(999)99-999-99
+380(67)777-7-777
001-541-754-3010
+1-541-754-3010
19-49-89-636-48018
+233 205599853
2. In most cases, this is sufficient, but we can implement our own validation rule.
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler('sale', 'OnSaleOrderBeforeSaved', ['Class\MyEvent', 'OnSaleOrderBeforeSavedHandler']);
...
class MyEvent {
public static function OnSaleOrderBeforeSavedHandler(\Bitrix\Main\Event $event)
{
/** @var \Bitrix\Sale\Order $order */
$order = $event->getParameter("ENTITY");
if ($order->isNew()) {//Если проверка требуется только для нового заказа, а не при его редактировании
$propertyCollection = $order->getPropertyCollection();
$phoneProperty = $propertyCollection->getPhone();
$isPhoneValid = MyEvent::checkPhoneNumber($phoneProperty->getValue());
if ($isPhoneValid != 1) {
$event->addResult(new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::ERROR,
new \Bitrix\Sale\ResultError('Введите корректный номер телефона', 'PHONE_PROPERTY_INVALID')
));
}
}
}
public static function checkPhoneNumber($phone) {
if(preg_match("#^([0-9\+]{0,3})\s?\(([0-9]{1,6})\)\s?([0-9\-]{1,9})$#", $phone))
return 1;
else
return 0;
}
}
Useful Variables order_ajax.js
this.result.TOTAL //Current price and cart value data
Array of matching international two-letter country codes
const C_COUNTRIES = [
"RU" => 1, //Россия
"AZ" => 2, //Азербайджан
"AM" => 3, //Армения
"BY" => 4, //Беларусь
"GE" => 5, //Грузия
"KZ" => 6, //Казахстан
"KG" => 7, //Киргизия
"LV" => 8, //Латвия
"LT" => 9, //Литва
"MD" => 10, //Молдавия
"TJ" => 11, //Таджикистан
"TM" => 12, //Туркменистан
"UZ" => 13, //Узбекистан
"UA" => 14, //Украина
"EE" => 15, //Эстония
"AU" => 16, //Австралия
"AT" => 17, //Австрия
"AL" => 18, //Албания
"DZ" => 19, //Алжир
"AO" => 20, //Ангола
"AE" => 21, //Арабские Эмираты
"AR" => 22, //Аргентина
"AW" => 23, //Аруба
"AF" => 24, //Афганистан
"BS" => 25, //Багамские острова
"BD" => 26, //Бангладеш
"BB" => 27, //Барбадос
"BE" => 28, //Бельгия
"BJ" => 29, //Бенин
"BM" => 30, //Бермудские острова
"BG" => 31, //Болгария
"BO" => 32, //Боливия
"BA" => 33, //Босния и Герцеговина
"BR" => 34, //Бразилия
"BN" => 35, //Бруней
"GB" => 36, //Великобритания
"HU" => 37, //Венгрия
"VE" => 38, //Венесуэлла
"VN" => 39, //Вьетнам
"HT" => 40, //Гаити
"GM" => 41, //Гамбия
"HN" => 42, //Гондурас
"GP" => 43, //Гваделупа
"GT" => 44, //Гватемала
"GN" => 45, //Гвинея
"DE" => 46, //Германия
"GI" => 47, //Гибралтар
"NL" => 48, //Нидерланды
"HK" => 49, //Гонконг
"GD" => 50, //Гренада
"GL" => 51, //Гренландия
"GR" => 52, //Греция
"GU" => 53, //Гуана
"DK" => 54, //Дания
"DO" => 55, //Доминиканская Республика
"EG" => 56, //Египет
"CD" => 57, //Демократическая республика Конго
"ZM" => 58, //Замбия
"ZW" => 59, //Зимбабве
"IL" => 60, //Израиль
"IN" => 61, //Индия
"ID" => 62, //Индонезия
"JO" => 63, //Иордания
"IQ" => 64, //Ирак
"IR" => 65, //Иран
"IE" => 66, //Ирландия
"IS" => 67, //Исландия
"ES" => 68, //Испания
"IT" => 69, //Италия
"YE" => 70, //Йемен
"KY" => 71, //Каймановы острова
"CM" => 72, //Камерун
"CA" => 73, //Канада
"KE" => 74, //Кения
"CY" => 75, //Кипр
"CN" => 76, //Китай
"CO" => 77, //Колумбия
"KH" => 78, //Камбоджа
"CG" => 79, //Конго
"KR" => 80, //Корея (Южная)
"CR" => 81, //Коста Рика
"CU" => 82, //Куба
"KW" => 83, //Кувейт
"LR" => 84, //Либерия
"LI" => 85, //Лихтенштейн
"LU" => 86, //Люксембург
"MR" => 87, //Мавритания
"MG" => 88, //Мадагаскар
"MK" => 89, //Македония
"MY" => 90, //Малайзия
"ML" => 91, //Мали
"MT" => 92, //Мальта
"MX" => 93, //Мексика
"MZ" => 94, //Мозамбик
"MC" => 95, //Монако
"MN" => 96, //Монголия
"MA" => 97, //Морокко
"NA" => 98, //Намибия
"NP" => 99, //Непал
"NG" => 100, //Нигерия
"NI" => 102, //Никарагуа
"NZ" => 103, //Новая Зеландия
"NO" => 104, //Норвегия
"PK" => 105, //Пакистан
"PA" => 106, //Панама
"PG" => 107, //Папуа Новая Гвинея
"PY" => 108, //Парагвай
"PE" => 109, //Перу
"PL" => 110, //Польша
"PT" => 111, //Португалия
"PR" => 112, //Пуэрто Рико
"RO" => 113, //Румыния
"SA" => 114, //Саудовская Аравия
"SN" => 115, //Сенегал
"SG" => 116, //Сингапур
"SY" => 117, //Сирия
"SK" => 118, //Словакия
"SI" => 119, //Словения
"SO" => 120, //Сомали
"SD" => 121, //Судан
"US" => 122, //США
"TW" => 123, //Тайвань
"TH" => 124, //Таиланд
"TT" => 125, //Тринидад и Тобаго
"TN" => 126, //Тунис
"TR" => 127, //Турция
"UG" => 128, //Уганда
"UY" => 129, //Уругвай
"PH" => 130, //Филиппины
"FI" => 131, //Финляндия
"FR" => 132, //Франция
"TD" => 133, //Чад
"CZ" => 134, //Чехия
"CL" => 135, //Чили
"CH" => 136, //Швейцария
"SE" => 137, //Швеция
"LK" => 138, //Шри-Ланка
"EC" => 139, //Эквадор
"ET" => 140, //Эфиопия
"ZA" => 141, //ЮАР
"RS" => 142, //Сербия
"JM" => 143, //Ямайка
"JP" => 144, //Япония
"BH" => 145, //Бахрейн
"AD" => 146, //Андорра
"BZ" => 147, //Белиз
"BT" => 148, //Бутан
"BW" => 149, //Ботсвана
"BF" => 150, //Буркина Фасо
"BI" => 151, //Бурунди
"CF" => 152, //Центральноафриканская Республика
"KM" => 153, //Коморос
"CI" => 154, //Кот-Д`ивуар
"DJ" => 155, //Джибути
"TL" => 156, //Восточный Тимор
"SV" => 157, //Эль Сальвадор
"GQ" => 158, //Экваториальная Гвинея
"ER" => 159, //Эритрея
"FJ" => 160, //Фижи
"GA" => 161, //Габон
"GH" => 162, //Гана
"GW" => 163, //Гвинея-Биссау
"KP" => 164, //Корея (Северная)
"LB" => 165, //Ливан
"LS" => 166, //Лесото
"LY" => 167, //Ливия
"MV" => 168, //Мальдивы
"MH" => 169, //Маршалские острова
"NE" => 170, //Нигер
"OM" => 171, //Оман
"QA" => 172, //Катар
"RW" => 173, //Руанда
"WS" => 174, //Самоа
"SC" => 175, //Сейшеллы
"SL" => 176, //Сьерра-Леоне
"SR" => 177, //Суринам
"SZ" => 178, //Свазиленд
"TZ" => 179, //Танзания
"EH" => 180, //Западная Сахара
"HR" => 181, //Хорватия
"AI" => 182, //Ангилья
"AQ" => 183, //Антарктида
"AG" => 184, //Антигуа и Барбуда
"BV" => 185, //Остров Буве
"IO" => 186, //Британские территории в Индийском Океане
"VG" => 187, //Британские Виргинские острова
"MM" => 188, //Мьянма
"CV" => 189, //Кабо-Верде
"CX" => 190, //Остров Рождества
"CC" => 191, //Кокосовые острова
"CK" => 192, //Острова Кука
"DM" => 193, //Доминика
"FK" => 194, //Фолклендские острова
"FO" => 195, //Фарерские острова
"GF" => 196, //Гвиана
"PF" => 197, //Французская Полинезия
"TF" => 198, //Южные Французские территории
"HM" => 199, //Острова Херд и Макдоналд
"KI" => 200, //Кирибати
"LA" => 201, //Лаос
"MO" => 202, //Макао
"MW" => 203, //Малави
"MQ" => 204, //Мартиника
"MU" => 205, //Маврикий
"YT" => 206, //Майотта
"FM" => 207, //Микронезия
"MS" => 208, //Монтсеррат
"NR" => 209, //Науру
// 210 Антильские острова - они же Карибы, относятся к нескольким государствам, каое имеется ввиду в Битриксе - хз
"NC" => 211, //Новая Каледония
"NU" => 212, //Ниуэ
"NF" => 213, //Остров Норфолк
"PW" => 214, //Палау
"PS" => 215, //Палестина
"PN" => 216, //Остров Питкэрн
"RE" => 217, //Реюньон
"SH" => 218, //Остров Св.Елены
"KN" => 219, //Острова Сент-Киттс и Невис
"LC" => 220, //Санта-Лючия
"PM" => 221, //Острова Сен-Пьер и Микелон
"VC" => 222, //Сент-Винсент и Гренадины
"SM" => 223, //Сан-Марино
"SB" => 224, //Соломоновы острова
"LK" => 225, //Южная Георгия и Южные Сандвичевы острова
"SJ" => 226, //Острова Шпицберген и Ян-Майен
"TG" => 227, //Того
"TK" => 228, //Токелау
"TO" => 229, //Тонга
"TC" => 230, //Острова Тёркс и Кайкос
"TV" => 231, //Тувалу
"VI" => 232, //Американские Виргинские острова
"VU" => 233, //Вануату
"VA" => 234, //Ватикан
"WF" => 235, //Острова Уоллис и Футуна
"ME" => 236, //Черногория
];
Alternative (custom) sale.order.ajax
Interesting custom sale.order.ajax suggested by alorian in Opensource Bitrix Order
Error in Bitrix when placing an order window .__ jsonp_ymaps_map is not a function
Most likely you use several delivery services, such as CDEK or Boxberry. Go to the settings of these services and disable the use of Yandex maps in one of them. One Yandex API connection will be enough for work
Bug with phone autocomplete. After any action, the initial digit (7) is duplicated, erasing the current phone number
I ran into a problem on one of the Aspro Optimus templates. I solved it in a workaround.
Go to the alterProperty
function and disable the standard masking mechanisms by commenting out these lines:
//if (settings.IS_PHONE == 'Y')
//textNode.setAttribute('autocomplete', 'tel');
I connected the library Inputmask and added the following lines to the end of the file order_ajax.js
, where < code> # soa-property-3 this is the identifier of our property with phone
$(function() {
$('#soa-property-3').inputmask({"mask": "7 (999) 999-99-99"});
BX.addCustomEvent('onAjaxSuccess', function(){
$('#soa-property-3').inputmask({"mask": "7 (999) 999-99-99"});
});
});
Based on materials:
- New sale.order.ajax
- Usefulness for sale.order.ajax
- New sale.order.ajax template: customization
- 1C-Bitrix Site Management: Integration of layout into the checkout component
- 1C-Bitrix. Customization of the new template of the sale.order.ajax component
- Country codes
- bitrix Custom checkout - strange methodology
- Делаем собственный sale.order.ajax на D7
Comments