Wordpress: WooCommerce счет на оплату в формате PDF

Занялся Интернет-магазином на базе Woocommerce. Заказчик попросил добавить метод оплаты по счету для клиентов без привязки к какой-либо платежной системе. Поискав в сети обнаружил, что готового плагина для этих целей нет. Cоздавать свой плагин для этой цели нет ни возможности, ни бюджета. В данной статье мы воспользуемся 2 плагинами: WooCommerce PDF Invoices & Packing Slips для создания счёта на оплату и WooCommerce Invoice Gateway для добавления метода оплаты по счёту.

Итак, установим первый плагин и создадим форму счета. В нашем случае сумма будет без НДС, доставка не нужна, а реквизиты получателя мы впишем напрямую в форму. Для этого создаем каталог /woocommerce/pdf/MyInvoice в вашей (дочерней) теме. В эту папку копируем все файлы из папки /wp-content/plugins/woocommerce-pdf-invoices-packing-slips/templates/Simple. После этого в настройках плагина появится ваш шаблон.
Теперь переходим к файлу /wp-content/themes/ваша-тема/woocommerce/pdf/MyInvoice/invoice.php
<?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
<?php do_action( 'wpo_wcpdf_before_document', $this->type, $this->order ); ?>
    <style>
        body { margin-left: auto; margin-right: auto; border: 1px #efefef solid; font-size: 9pt;}
        table.invoice_bank_rekv { border-collapse: collapse; border: 1px solid; }
        table.invoice_bank_rekv > tbody > tr > td, table.invoice_bank_rekv > tr > td { border: 1px solid; }
        table.invoice_items { border: 2px solid; border-collapse: collapse; margin-bottom: 8px; }
        table.invoice_items td, table.invoice_items th { border: 1px solid; text-align:  center;}
        .woocommerce-Price-currencySymbol { display: none; }
        .price { width: 10%;} .sum { width: 12%;}
        .number { width: 5%;} .quantity, .value { width: 8%;}
    </style>
<table>
<tr>
    <td class="header">
</td>
<td class="shop-info">
<div class="shop-name"><h3><?php /* $this->shop_name(); */?></h3></div>
<div class="shop-address"><?php /* $this->shop_address(); */?></div>
</td>
</tr>
</table>
<h1 class="document-type-label">
<?php if( $this->has_header_logo() ) echo $this->get_title(); ?>
</h1>
<?php do_action( 'wpo_wcpdf_after_document_label', $this->type, $this->order ); ?>
<table width="100%" cellpadding="2" cellspacing="2" class="invoice_bank_rekv">
    <tr>
        <td colspan="2" rowspan="2" style="min-height:13mm;">
            <table width="100%" border="0" cellpadding="0" cellspacing="0" style="height: 13mm;">
                <tr>
                    <td valign="top">
                        <div>Тут название банка получателя</div>
                    </td>
                </tr>
                <tr>
                    <td valign="bottom" style="height: 3mm;">
                        <div>Банк получателя        </div>
                    </td>
                </tr>
            </table>
        </td>
        <td style="min-height:7mm;height:auto; ">
            <div>БИK</div>
        </td>
        <td rowspan="2" style="vertical-align: top; ">
            <div style=" height: 7mm; line-height: 7mm; vertical-align: top;">045004774</div>
            <div>тут номер счёта</div>
        </td>
    </tr>
    <tr>
        <td style="width: 25mm;">
            <div>Сч. №</div>
        </td>
    </tr>
    <tr>
        <td style="min-height:6mm; height:auto; ">
            <div>ИНН тут инн</div>
        </td>БзкуЮ
        <td style="min-height:6mm; height:auto;">
            <div>КПП</div>
        </td>
        <td rowspan="2" style="min-height:19mm; height:auto; vertical-align: top;">
            <div>Сч. №</div>
        </td>
        <td rowspan="2" style="min-height:19mm; height:auto; vertical-align: top;;">
            <div>тут счет</div>
        </td>
    </tr>
    <tr>
        <td colspan="2" style="min-height:13mm; height:auto;">
            <table border="0" cellpadding="0" cellspacing="0" style="height: 13mm;">
                <tr>
                    <td valign="top">
                        <div>Тут наименование получателя</div>
                    </td>
                </tr>
                <tr>
                    <td valign="bottom" style="height: 3mm;">
                        <div style="font-size: 10pt;">Получатель</div>
                    </td>
                </tr>
            </table>
        </td>
    </tr>
</table>
<br/>
<div style="font-weight: bold; font-size: 16pt; padding: 5px 0 0 5px;">
    Счет №<?php $this->invoice_number(); ?> от <?php $this->invoice_date(); ?></div>
<br/>
<div style="background-color:#000000; width:100%; font-size:1px; height:2px;">&nbsp;</div>
<table width="100%">
    <tr>
        <td style="width: 30mm; padding-top: 5px;">
            <div style=" padding-left:2px; padding-bottom: 5px; padding-top: 5px;">Поставщик (Исполнитель):    </div>
        </td>
        <td>
            <div style="font-weight:bold;  padding-left:2px; padding-top: 5px;">
                Тут наименование поставщика, ИНН 000000000000, ОГРНИП 111111111111111</div>
        </td>
    </tr>
    <tr>
        <td style="width: 30mm;">
            <div style=" padding-left:2px; padding-top: 5px;">Покупатель:    </div>
        </td>
        <td>
            <div style="font-weight:bold;  padding-left:2px; padding-bottom: 5px; padding-top: 5px;">
                <?php $this->custom_field('last_name'); echo " "; $this->custom_field('first_name'); echo " "; $this->custom_field('billing_patronymic_name'); ?></div>
        </td>
    </tr>
        <tr>
        <td style="width: 30mm;">
            <div style=" padding-left:2px; padding-top: 5px;">Основание:    </div>
        </td>
        <td>
            <div style="font-weight:bold;  padding-left:2px; padding-top: 5px; padding-bottom: 10px; ">
                Заказ №<?php $this->order_number(); ?> от <?php $this->order_date(); ?></div>
        </td>
    </tr>
</table>
<?php do_action( 'wpo_wcpdf_before_order_details', $this->type, $this->order ); ?>
<table width="100%" class="invoice_items" cellpadding="2" cellspacing="2">
<thead>
<tr>
<th class="number">№</th>
<th class="product">Товары (работы, услуги)</th>
<th class="quantity">Кол-во</th>
<th class="value">Ед.</th>
<th class="price">Цена</th>
<th class="sum">Сумма</th>
</tr>
</thead>
<tbody>
<?php $i=0 /* для нумерации строк и подсчета количества позиций */ ?> 
<!-- В цикле выводим список позиций -->
<?php $items = $this->get_order_items(); if( sizeof( $items ) > 0 ) : foreach( $items as $item_id => $item ) : ?>
<tr class="<?php echo apply_filters( 'wpo_wcpdf_item_row_class', $item_id, $this->type, $this->order, $item_id ); ?>">
<td><?php $i++; echo $i; ?></td><!-- номер строки -->
<td style="text-align: left;" class="product">
<?php $description_label = __( 'Description', 'woocommerce-pdf-invoices-packing-slips' ); // registering alternate label translation ?>
<span class="item-name"><?php echo $item['name']; ?></span>
<?php do_action( 'wpo_wcpdf_before_item_meta', $this->type, $item, $this->order  ); ?>
    <span class="item-meta"><?php echo $item['meta']; ?></span>
<?php do_action( 'wpo_wcpdf_after_item_meta', $this->type, $item, $this->order  ); ?>
</td><!-- наименование продукта -->
<td style="text-align:  right;" class="quantity"><?php echo $item['quantity']; ?></td><!-- количество -->
<td style="text-align:  right;" class="value">шт.
</td><!-- единица измерения -->
<td style="text-align:  right;" class="price"><?php echo $item['single_line_total']; ?></td><!-- цена за штуку -->
<td style="text-align:  right;" class="sum"><?php echo $item['order_price']; ?></td><!-- сумма -->
</tr>
<?php endforeach; endif; ?>
    </tbody>
</table>
<!-- Выводим таблицу Итого -->
<table border="0" width="100%" cellpadding="1" cellspacing="1">
    <tr>
        <td></td>
        <td style="width:27mm; font-weight:bold;  text-align:right;">Итого:</td>
        <td style="width:27mm; font-weight:bold;  text-align:right;"><?php echo $order->get_total();?></td>
    </tr>
    <tr>
       <td></td>
        <td style="width:40mm; font-weight:bold;  text-align:right;">Без налога (НДС):</td>
        <td style="width:27mm; font-weight:bold;  text-align:right;">-</td>
    </tr>
<tr>
        <td></td>
        <td style="width:27mm; font-weight:bold;  text-align:right;">Всего к оплате:</td>
        <td style="width:27mm; font-weight:bold;  text-align:right;"><?php echo $order->get_total(); ?></td>
    </tr>
</table>
Всего наименований: <?php echo $i; ?> на сумму <?php echo $order->get_total(); ?> рублей.<br>
<?php 
/* Указываем переменной orderp чистое значение суммы заказа */
$orderp = $order->get_total();
/* Конвертируем его из строки в тип double */
$orderp = (double)$orderp;
/* Оборачиваем в функцию для возвращения суммы прописью (функция описана в functions.php моей темы) */
$slova = num2str($orderp);
/* Выводим сумму словами, первую букву делаем заглавную и добавляем точку в конце*/
echo "<b>".mb_strtoupper(mb_substr($slova, 0, 1)) . mb_substr($slova, 1, mb_strlen($slova)).".</b>"; ?>
<div style ="margin-top: 50px;">
Внимание!<br>
Оплата данного счета означает согласие с условиями поставки товара.<br>
Уведомление об оплате обязательно, в противном случае не гарантируется наличие товара на складе.<br>
Товар отпускается по факту прихода денег на р/с Поставщика, самовывозом, при наличии доверенности и паспорта.</div>
<?php if ( $this->get_footer() ): ?>
<div id="footer">
 <?php $this->footer(); ?>
</div><!-- #letter-footer -->
<?php endif; ?>
<?php do_action( 'wpo_wcpdf_after_document', $this->type, $this->order ); ?> 
Вам нужно будет указать свои реквизиты в нужных местах. Единицы измерения у меня всегда шт., поэтому эти данные указал напрямую. Я также добавил отдельное поля для отчества пользователя, которое он вводит при регистрации: billing_patronymic_name. В коде мы использовали функцию для вывода суммы прописью из данной статьи на хабре. Для того, чтобы она заработала, я добавил саму функцию в файл functions.php своей темы между тегами <?php ?> :
/* Эти функции (num2str и morph) нужна для переводы суммы счета в буквенный формат
Мы используем её в файле /wp-content/themes/mytheme/woocommerce/pdf/MyInvoice/invoice.php */

/**
 * Возвращает сумму прописью
 * @author runcore
 * @uses morph(...)
 */
if (!function_exists('num2str')) {
function num2str($num) {
    $nul='ноль';
    $ten=array(
        array('','один','два','три','четыре','пять','шесть','семь', 'восемь','девять'),
        array('','одна','две','три','четыре','пять','шесть','семь', 'восемь','девять'),
    );
    $a20=array('десять','одиннадцать','двенадцать','тринадцать','четырнадцать' ,'пятнадцать','шестнадцать','семнадцать','восемнадцать','девятнадцать');
    $tens=array(2=>'двадцать','тридцать','сорок','пятьдесят','шестьдесят','семьдесят' ,'восемьдесят','девяносто');
    $hundred=array('','сто','двести','триста','четыреста','пятьсот','шестьсот', 'семьсот','восемьсот','девятьсот');
    $unit=array( // Units
        array('копейка' ,'копейки' ,'копеек',    1),
        array('рубль'   ,'рубля'   ,'рублей'    ,0),
        array('тысяча'  ,'тысячи'  ,'тысяч'     ,1),
        array('миллион' ,'миллиона','миллионов' ,0),
        array('миллиард','милиарда','миллиардов',0),
    );
    //
    list($rub,$kop) = explode('.',sprintf("%015.2f", floatval($num)));
    $out = array();
    if (intval($rub)>0) {
        foreach(str_split($rub,3) as $uk=>$v) { // by 3 symbols
            if (!intval($v)) continue;
            $uk = sizeof($unit)-$uk-1; // unit key
            $gender = $unit[$uk][3];
            list($i1,$i2,$i3) = array_map('intval',str_split($v,1));
            // mega-logic
            $out[] = $hundred[$i1]; # 1xx-9xx
            if ($i2>1) $out[]= $tens[$i2].' '.$ten[$gender][$i3]; # 20-99
            else $out[]= $i2>0 ? $a20[$i3] : $ten[$gender][$i3]; # 10-19 | 1-9
            // units without rub & kop
            if ($uk>1) $out[]= morph($v,$unit[$uk][0],$unit[$uk][1],$unit[$uk][2]);
        } //foreach
    }
    else $out[] = $nul;
    $out[] = morph(intval($rub), $unit[1][0],$unit[1][1],$unit[1][2]); // rub
    $out[] = $kop.' '.morph($kop,$unit[0][0],$unit[0][1],$unit[0][2]); // kop
    return trim(preg_replace('/ {2,}/', ' ', join(' ',$out)));
}
}

/**
 * Склоняем словоформу
 * @ author runcore
 */
if (!function_exists('morph')) {
function morph($n, $f1, $f2, $f5) {
    $n = abs(intval($n)) % 100;
    if ($n>10 && $n<20) return $f5;
    $n = $n % 10;
    if ($n>1 && $n<5) return $f2;
    if ($n==1) return $f1;
    return $f5;
}
}

Теперь счет формируется и формате PDF. Осталось добавить метод оплаты и при использовании метода выдавать ссылку на счет. Для этого устанавливаем второй плагин WooCommerce Invoice Gateway и настраиваем по моему подобию:

Название метода "Оплата по счету" мы в дальнейшем будем использовать как условие для отображение ссылки на счет.
Далее идем в functions.php вашей темы и добавляем такой код:
/* Добавляем ссылку на счет после выбора оплаты по счету */
add_filter('woocommerce_thankyou_order_received_text', 'wpo_wcpdf_thank_you_link', 10, 2);
function wpo_wcpdf_thank_you_link( $text, $order ) {
    if ( is_user_logged_in() ) {
        /* Выводим ссылку на счет, только если в качестве оплаты выбран способ Оплата по счету */
        if(wp_kses_post( $order->get_payment_method_title() ) == 'Оплата по счету') {
            $order_id = method_exists($order, 'get_id') ? $order->get_id() : $order->id;
            $pdf_url = wp_nonce_url( admin_url( 'admin-ajax.php?action=generate_wpo_wcpdf&template_type=invoice&order_ids=' . $order_id . '&my-account'), 'generate_wpo_wcpdf' );
            $text .= '<p><a style="color: red;" href="'.esc_attr($pdf_url).'" target="_blank">Скачать счет на оплату (формат PDF)</a></p>';
        }

    }
    return $text;
}
Теперь заказы, для которых был выбран метод оплаты по счету меняют статус на "На удержании", а покупатель получает ссылку на счет и может оплатить по реквизитам в любом банке.

6 комментариев:

  1. А как сделать что бы после того как человек нажмет на оплату его сразу переносило на PDF страницу

    ОтветитьУдалить
    Ответы
    1. Думаю, можно попробовать используя JS с таймаутом, например, в 1 секунду вместо ссылки на файл
      setTimeout(function() {window.location.href = "esc_attr($pdf_url)";}, 1000);
      не забыв взять это в тег script.

      Удалить
  2. Спасибо, отличный метод! Было бы здорово добавить ссылку на ПДФ в имейл уведомление, или прикрепить счет к письму в ПДФ формате.
    Я попытался прописать строку со ссылкой в шаблон письам но ничего не вышло. Ссылка ведет на неверную страницу

    ОтветитьУдалить
  3. Подскажите как не очень понятно для чего плагин WooCommerce Invoice Gateway если счет выставляется и по средствам WooCoommerce такой же.
    И знаете ли как можно реализовать выставление от двух разных компаний? В зависимости от выбора покупателей

    ОтветитьУдалить
  4. Как можно реализовать выставление с двух разных компаний? Зависит от выбора с НДС или без.
    И для чего плагин WooCommerce Invoice Gateway? Со штатным средством WooCommerce это тоже работает

    ОтветитьУдалить
    Ответы
    1. Добрый день, к сожалению статью писал давно и уже не вспомню подробностей. Плагин WooCommerce Invoice Gateway использовал т.к. на том момент штатными средствами у меня не получилось. По поводы выбора компании в зависимости от НДС, я бы попробовал подправить код из functions.php в конце статьи добавив туда условие при выборе НДС и без и создав шаблон или метод под каждый счет (нужно смотреть как будет удобнее).

      Удалить