doc = new DOMDocument('1.0', 'UTF-8'); $this->doc->formatOutput = true; $root = $this->doc->createElement('КоммерческаяИнформация'); $root->setAttribute('ВерсияСхемы', '2.05'); $root->setAttribute('ДатаФормирования', current_time('Y-m-d\TH:i:s')); $this->doc->appendChild($root); } public static function empty(): string { return (new self())->xml(); } public function add(WC_Order $order): void { $this->doc->documentElement->appendChild($this->order($order)); } public function xml(): string { return (string) $this->doc->saveXML(); } private function order(WC_Order $order): DOMElement { $created = $order->get_date_created(); $node = $this->el('Документ'); $this->child($node, 'Ид', (string) $order->get_id()); $this->child($node, 'Номер', $order->get_order_number()); $this->child($node, 'Дата', $created ? $created->date_i18n('Y-m-d') : ''); $this->child($node, 'Время', $created ? $created->date_i18n('H:i:s') : ''); $this->child($node, 'ХозОперация', 'Заказ товара'); $this->child($node, 'Роль', 'Продавец'); $this->child($node, 'Валюта', Settings::str('order_currency') ?: $order->get_currency()); $this->child($node, 'Курс', '1'); $this->child($node, 'Сумма', self::money($order->get_total())); $this->child($node, 'Комментарий', $order->get_customer_note()); $node->appendChild($this->customer($order)); $items = $this->el('Товары'); foreach ($order->get_items() as $item) { if ($item instanceof \WC_Order_Item_Product) { $items->appendChild($this->product($item)); } } foreach ($order->get_fees() as $fee) { $sum = (float) $fee->get_total() + (float) $fee->get_total_tax(); $items->appendChild($this->service('ORDER_FEE_' . $fee->get_id(), $fee->get_name() ?: 'Сбор', $sum)); } if ((float) $order->get_shipping_total() > 0) { $sum = (float) $order->get_shipping_total() + (float) $order->get_shipping_tax(); $items->appendChild($this->service('ORDER_DELIVERY', 'Доставка', $sum)); } $node->appendChild($items); $node->appendChild($this->requisites([ 'Статус заказа' => wc_get_order_status_name($order->get_status()), 'Заказ оплачен' => $order->is_paid() ? 'true' : 'false', 'Метод оплаты' => $order->get_payment_method_title(), 'Способ доставки' => $order->get_shipping_method(), 'Отменен' => $order->has_status('cancelled') ? 'true' : 'false', 'Сайт' => (string) wp_parse_url(home_url(), PHP_URL_HOST), ])); return $node; } private function customer(WC_Order $order): DOMElement { $first = $order->get_billing_first_name(); $last = $order->get_billing_last_name(); $company = $order->get_billing_company(); $name = $company ?: (trim($last . ' ' . $first) ?: ($order->get_billing_email() ?: 'Покупатель')); $id = $order->get_customer_id() > 0 ? 'wc-customer-' . $order->get_customer_id() : 'wc-guest-' . md5(strtolower($order->get_billing_email() . '|' . $order->get_billing_phone())); $customer = $this->el('Контрагент'); $this->child($customer, 'Ид', $id); $this->child($customer, 'Наименование', $name); $this->child($customer, 'ПолноеНаименование', $name); $this->child($customer, 'Роль', 'Покупатель'); $this->child($customer, 'Фамилия', $last); $this->child($customer, 'Имя', $first); $countries = WC()->countries ? WC()->countries->get_countries() : []; $address = $this->el('АдресРегистрации'); $this->child($address, 'Представление', implode(', ', array_filter([ $order->get_billing_postcode(), $countries[$order->get_billing_country()] ?? $order->get_billing_country(), $order->get_billing_state(), $order->get_billing_city(), $order->get_billing_address_1(), $order->get_billing_address_2(), ]))); $customer->appendChild($address); $contacts = $this->el('Контакты'); foreach (['Почта' => $order->get_billing_email(), 'Телефон рабочий' => $order->get_billing_phone()] as $type => $value) { if ($value !== '') { $contact = $this->el('Контакт'); $this->child($contact, 'Тип', $type); $this->child($contact, 'Значение', $value); $contacts->appendChild($contact); } } $customer->appendChild($contacts); $wrap = $this->el('Контрагенты'); $wrap->appendChild($customer); return $wrap; } private function product(\WC_Order_Item_Product $item): DOMElement { $product = $item->get_product(); $quantity = max(1.0, (float) $item->get_quantity()); $total = (float) $item->get_total() + (float) $item->get_total_tax(); // уже со скидками купонов $node = $this->el('Товар'); $this->child($node, 'Ид', self::guid($item, $product)); $this->child($node, 'Артикул', $product ? $product->get_sku() : ''); $this->child($node, 'Наименование', $item->get_name()); $node->appendChild($this->unit()); $this->child($node, 'ЦенаЗаЕдиницу', self::money($total / $quantity)); $this->child($node, 'Количество', (string) wc_format_decimal($quantity)); $this->child($node, 'Сумма', self::money($total)); $node->appendChild($this->requisites(['ВидНоменклатуры' => 'Товар', 'ТипНоменклатуры' => 'Товар'])); return $node; } private function service(string $id, string $name, float $sum): DOMElement { $node = $this->el('Товар'); $this->child($node, 'Ид', $id); $this->child($node, 'Наименование', $name); $node->appendChild($this->unit()); $this->child($node, 'ЦенаЗаЕдиницу', self::money($sum)); $this->child($node, 'Количество', '1'); $this->child($node, 'Сумма', self::money($sum)); $node->appendChild($this->requisites(['ВидНоменклатуры' => 'Услуга', 'ТипНоменклатуры' => 'Услуга'])); return $node; } /** * GUID товара из 1С; у вариации без своего GUID — GUID родителя; товар не из 1С — «wc-ID». */ private static function guid(\WC_Order_Item_Product $item, ?\WC_Product $product): string { $guid = $product ? (string) $product->get_meta(Repo::META) : ''; if ($guid === '' && $product && $product->get_parent_id() > 0) { $parent = wc_get_product($product->get_parent_id()); $guid = $parent ? (string) $parent->get_meta(Repo::META) : ''; } return $guid !== '' ? $guid : 'wc-' . $item->get_product_id(); } private function unit(): DOMElement { $unit = $this->el('БазоваяЕдиница', 'шт'); $unit->setAttribute('Код', '796'); $unit->setAttribute('НаименованиеПолное', 'Штука'); $unit->setAttribute('МеждународноеСокращение', 'PCE'); return $unit; } /** * @param array $values */ private function requisites(array $values): DOMElement { $wrap = $this->el('ЗначенияРеквизитов'); foreach ($values as $name => $value) { $req = $this->el('ЗначениеРеквизита'); $this->child($req, 'Наименование', $name); $this->child($req, 'Значение', $value); $wrap->appendChild($req); } return $wrap; } private function child(DOMElement $parent, string $name, string $value): void { $parent->appendChild($this->el($name, $value)); } private function el(string $name, string $value = ''): DOMElement { $el = $this->doc->createElement($name); if ($value !== '') { // Недопустимые в XML управляющие символы вырезаем. $clean = (string) preg_replace('/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]/u', '', $value); $el->appendChild($this->doc->createTextNode($clean)); } return $el; } private static function money(float|string $value): string { return (string) wc_format_decimal($value, 2); } }