Version: current

      1 <?php
      2
      3 /**
      4  * Страница управления заказом
      5  *
      6  * @package cart
      7  * @author inxo, dvp
      8  */
      9 class CheckoutPage extends Page {
     10
     11     static $icon = "cart/img/cart";
     12     static $allowed_children = 'none';
     13
     14     static $db = array(
     15         'PurchaseComplete' => 'HTMLText',
     16         'ChequeMessage' => 'HTMLText'
     17     );
     18
     19     static $has_one = array(
     20         'TermsPage' => 'Page'
     21     );
     22
     23     static $defaults = array(
     24         'AutoChild' => 0,
     25         "DevEditType" => "Fixed",
     26         'ShowInSiteMap' => 0,
     27         'ShowInSearch' => 0,
     28         'ShowInMenu' => 0,
     29         'URLSegment' => 'checkout',
     30     );
     31
     32     static $use_steps = false;
     33
     34     /**
     35      * Возвращает ссылку на страницу заказа
     36      *
     37      * @param string $action - действие со страницей
     38      * @return string ссылка на страницу
     39      */
     40     static function find_link($action = '') {
     41         if (!$page = DataObject::get_one('CheckoutPage')) {
     42             user_error('Создайте страницу заказа', E_USER_ERROR);
     43         }
     44
     45         return $page->Link($action);
     46     }
     47
     48     /**
     49      * Автоматическая генерация страницы заказа
     50      * TODO локализация!!!
     51      */
     52     function requireDefaultRecords() {
     53         parent::requireDefaultRecords();
     54
     55         if (!$page = DataObject::get_one('CheckoutPage')) {
     56             $page = new CheckoutPage();
     57             $page->writeToStage('Stage');
     58             $page->publish('Stage', 'Live');
     59
     60             if (method_exists('DB', 'alteration_message'))
     61                 DB::alteration_message('Checkout page created', 'created');
     62         }
     63
     64         if ($page->TermsPageID == 0 && $termsPage = DataObject::get_one('Page', "\"URLSegment\" = 'terms'")) {
     65             $page->TermsPageID = $termsPage->ID;
     66             $page->writeToStage('Stage');
     67             $page->publish('Stage', 'Live');
     68
     69             if (method_exists('DB', 'alteration_message'))
     70                 DB::alteration_message("Page '{$termsPage->Title}' linked to the Checkout page '{$page->Title}'", 'changed');
     71         }
     72     }
     73
     74     function canCreate() {
     75         return (DataObject::get_one('CheckoutPage')) ? false : true;
     76     }
     77
     78     function populateDefaults() {
     79         parent::populateDefaults();
     80
     81         $this->Title = $this->i18n_singular_name();
     82         $this->PurchaseComplete = _t('CheckoutPage.default_PurchaseComplete', 'Thank You for purchase');
     83         $this->ChequeMessage = _t('CheckoutPage.default_ChequeMessage', 'Payment accepted via Cheque. Please note : products will not be shipped until payment has been received');
     84         $this->URLSegment = 'checkout';
     85     }
     86
     87     function getCMSFields() {
     88         SiteTree::disableCMSFieldsExtensions();
     89         $fields = parent::getCMSFields();
     90         SiteTree::enableCMSFieldsExtensions();
     91
     92         $fields->addFieldToTab('Root.Content.Main', new TreeDropdownField('TermsPageID', $this->fieldLabel('TermsPage'), 'SiteTree'));
     93
     94         $fields->addFieldToTab('Root.Content', new Tab("Messages", _t('CheckoutPage.tab_Messages', 'Message Config')), 'Metadata');
     95         $fields->addFieldsToTab('Root.Content.Messages', array(
     96             new HtmlEditorField('PurchaseComplete', $this->fieldLabel('PurchaseComplete'), 20),
     97             new HtmlEditorField('ChequeMessage', $this->fieldLabel('ChequeMessage'), 20)
     98         ));
     99
    100         $this->extend('updateCMSFields', $fields);
    101
    102         return $fields;
    103     }
    104
    105     /**
    106      * Проверяет достаточна ли сумма заказа для оформления покупки
    107      *
    108      * @param int $price - сумма заказа [сумма в корзине]
    109      *
    110      * @return boolean
    111      */
    112     function CanCheckout($price=null) {
    113         return Cart::current_order()->CanCheckout($price);
    114     }
    115
    116     /**
    117      * Возвращает минимальную сумму для заказа
    118      *
    119      * @param bool $real - возвращать число, а не объект CatalogPrice
    120      *
    121      * @return int
    122      */
    123     function MinTotalPrice($real=false) {
    124         return Cart::current_order()->MinTotalPrice($real);
    125     }
    126
    127     /**
    128      * Проверяет, есть ли в корзине товары
    129      *
    130      * @return int
    131      */
    132     function HasItems() {
    133         return Cart::has_items();
    134     }
    135
    136     /**
    137      * Возвращает текущий заказ
    138      *
    139      * @return Order
    140      */
    141     function Cart() {
    142         return Cart::current_order();
    143     }
    144 }
    145
    146 class CheckoutPage_Controller extends Page_Controller {
    147
    148     static $allowed_actions = array('order', 'printorder', 'OrderForm');
    149
    150     /**
    151      * Хранит сообщение о ошибках валидации заказа для шаблона
    152      * @var string
    153      */
    154     private $orderError = '';
    155
    156     function isEmptyContent() {
    157         return false;
    158     }
    159
    160     /**
    161      * Обертка для OrderForm для шаблона
    162      *
    163      * @return Form|NULL
    164      */
    165     function Form() {
    166         if (!$form = $this->OrderForm()) return false;
    167
    168         if($member = Member::currentUser()){
    169             $form->loadDataFrom($member);
    170         }
    171         $form->loadDataFrom(Cart::get_info());
    172
    173         return $form;
    174     }
    175
    176     /**
    177      * Форма оформления заказа без шагов
    178      *
    179      * Форма годится только для простого случая (без авторизации, доставки или оплаты)
    180      * и не используется если настроены шаги
    181      *
    182      * Форму можно изменить с помощью расширения подключенного к CheckoutPage_Controller
    183      * В расширении можно определить метод:
    184      * updateOrderForm(Form &$form) - для изменения формы в целом.
    185      *
    186      * Либо с помощью расширений подключенных к Order
    187      * @see Order::getFrontendFields()
    188      *
    189      * @return <type>
    190      */
    191     function OrderForm() {
    192         if (CheckoutPage::$use_steps) return false;
    193         $siteConfig = SiteConfig::current_site_config();
    194         if ($siteConfig->CartRegisterRequire && !Member::currentUser()) {
    195             return $this->redirect('/Security/login?BackURL=' . $this->Link());
    196         }
    197
    198         $fields = singleton('Order')->getFrontendFields();
    199         if ($this->TermsPageID && $terms = $this->TermsPage()) {
    200             $fields->push(new CheckboxField('Agree', sprintf(_t('OrderForm.Agree', 'I agree with <a href="%s">terms and conditions</a>'), $terms->Link())));
    201         }
    202
    203         $required = $this->getOrderRequired();
    204         if ($siteConfig->hasMethod('SiteAgreementField') && ($rulesField = $siteConfig->SiteAgreementField())) {
    205             $required[] = $rulesField->Name();
    206         }
    207         $form = new Form(
    208             $this,
    209             'OrderForm',
    210             $fields,
    211             new FieldSet(new FormAction("saveorder", _t('OrderForm.SaveOrderButton', "Confirm and Pay"))),
    212             new CustomRequiredFields($required)
    213         );
    214         $form->addExtraClass('OrderForm');
    215
    216         $this->extend('updateOrderForm', $form);
    217         return $form;
    218     }
    219
    220     /**
    221      * Метод для размещения заказа.
    222      *
    223      * Вызывается из формы при ее субмите.
    224      * Первоначально запоминает данные формы в корзине, после - вызывает метод CheckoutPage->placeOrder() для размещения заказа на сайте.
    225      * В случае успешного размещения заказа - показывает страницу с уведомлением.
    226      * Использует шаблоны: CheckoutPage_done, CheckoutPage, Page
    227      *
    228      * @param array $data - данные формы
    229      * @param Form $form - форма шага
    230      *
    231      * @return string
    232      */
    233     function saveorder($data, $form) {
    234         foreach ($form->fields()->saveableFields() as $field) {
    235             if ($field->name == 'SecurityID') continue;
    236             if ($field->name == 'Agree') continue;
    237             $val = 0;
    238             if (isset($data[$field->name])) {
    239                 $val = $data[$field->name];
    240             }
    241             Cart::set_info($field->name, $val);
    242         }
    243
    244         if (!$order = $this->placeOrder()) {
    245             $form->sessionMessage($this->getOrderError(), 'bad');
    246             Director::redirectBack();
    247             return false;
    248         }
    249
    250         //by menedem для платежей
    251         if (class_exists('PaymentMethod') && Order::$use_payments) {
    252             $paymentResult = false;
    253             if ($paymentMethodID = Cart::get_info('PaymentMethod')) {
    254                 $method = DataObject::get_by_id('PaymentMethod', $paymentMethodID);
    255                 if ($method) {
    256                     $paymentResult = $method->makePayment($order);
    257                 } else {
    258                     user_error("Ошибочный тип платежа!", E_USER_ERROR);
    259                 }
    260             }
    261
    262             // Возможно нужны разные действия при Payment_Success и Payment_Processing
    263             if (!Director::redirected_to()) {
    264                 $value = false;
    265                 if ($paymentResult)
    266                     $value = $paymentResult->getValue();
    267                 //какое-то кеширование проклятое
    268                 $newOrder = DataObject::get_by_id('Order', $order->ID);
    269                 $html = $this->renderWith(
    270                     array('CheckoutPage_done', 'CheckoutPage', 'Page'),
    271                     array('Form' => false, 'Order' => $newOrder, 'PaymentResult' => $value)
    272                 );
    273             }
    274         } else {
    275             if (!Director::redirected_to()) {
    276                 $html = $this->renderWith(
    277                     array('CheckoutPage_done', 'CheckoutPage', 'Page'),
    278                     array('Form' => false, 'Order' => $order)
    279                 );
    280             }
    281         }
    282
    283         // отправляем письмо с составом заказа после всех оформлений, чтоб в него можно было вложить информацию о способе платежа
    284         $order->extend('OnAfterCreateOrder', Member::CurrentUser());
    285         return $html;
    286     }
    287
    288     /**
    289      * Размещает заказ в магазине.
    290      * Вызывается после прохождения всех шагов оформления заказа.
    291      * В случае ошибки ее текст можно получить с помощью getOrderError().
    292      * Позволяет использовать расширения:
    293      * - OnBeforeOrder(Order &$order) - вызывается до размещения заказа и проверок на валидность
    294      * - OnAfterOrder(Order &$order) - вызывается после размещения заказа до отсылки письма
    295      *
    296      * @param Order $order - предварительный заказ для размещения, по умолчанию - текущая корзина.
    297      * @return mixed $order или false если заказ не прошел валидацию
    298      */
    299     function placeOrder($order = null) {
    300         if (!$order) $order = Cart::current_order();
    301
    302         $this->extend('OnBeforeOrder', $order);
    303
    304         $result = $order->validate();
    305         if (!$result->valid()) {
    306             $this->orderError = $result->message();
    307             return false;
    308         }
    309
    310         $order->write();
    311         $order->createItems(Cart::get_items(), true);
    312         $this->extend('OnAfterOrder', $order);
    313
    314         Cart::set_info('ClientNotes', '');
    315         Cart::clear();
    316
    317         return $order;
    318     }
    319
    320     /**
    321      * Возвращает ошибку при размещении заказа
    322      *
    323      * @return string
    324      */
    325     function getOrderError() {
    326         return $this->orderError;
    327     }
    328
    329     /* Page Actions */
    330
    331     /**
    332      * Показывает информацию о заказе после его оформления
    333      *
    334      * Заказ определяется через url вида /order/order/204b22a36e328000dbb841cb3ac4d45e по HashLink заказа
    335      * Использует шаблон CheckoutPage_order
    336      * @TODO дополнительная проверка пользователя по email ? номеру заказа
    337      */
    338     function order() {
    339         if (!$order = Order::get_by_hash($this->getRequest()->param('ID'))) $this->httpError(404, 'Page not found');
    340
    341         return $this->render(array(
    342             'Form' => false,
    343             'Order' => $order,
    344         ));
    345     }
    346
    347     /**
    348      * Показывает страницу печати заказа
    349      * Использует шаблон CheckoutPage_printorder
    350      */
    351     function printorder() {
    352         return $this->order();
    353     }
    354
    355     /* Field generators */
    356
    357     /**
    358      * Список полей для формы контактной информации
    359      *
    360      * В расширении можно определить метод:
    361      * updateContactDetailsFields(FieldSet &$fields) - для изменения списка полей формы
    362      *
    363      * @return FieldSet
    364      */
    365     function getContactDetailsFields() {
    366         $fields = singleton('Order')->getContactFields();
    367         $this->extend('updateContactDetailsFields', $fields);
    368         return $fields;
    369     }
    370
    371     /**
    372      * Список полей для формы подтверждения заказа
    373      *
    374      * В расширении можно определить метод:
    375      * updateSummaryFields(FieldSet &$fields) - для изменения списка полей формы
    376      *
    377      * @return FieldSet
    378      */
    379     function getSummaryFields() {
    380         $fields = singleton('Order')->getSummaryFields();
    381
    382         if ($this->TermsPageID && $terms = $this->TermsPage()) {
    383             $fields->push(new CheckboxField('Agree', sprintf(_t('OrderForm.Agree', 'I agree with <a href="%s">terms and conditions</a>'), $terms->Link())));
    384         }
    385
    386         $this->extend('updateSummaryFields', $fields);
    387         return $fields;
    388     }
    389
    390     /**
    391      * Список обязательный полей для формы заказа
    392      *
    393      * В расширении можно определить метод:
    394      * updateOrderRequired(array &$fields) - для изменения списка обязательных полей формы
    395      *
    396      * @return array
    397      */
    398     function getOrderRequired() {
    399         $required = singleton('Order')->getRequredFields();
    400
    401         if ($this->TermsPageID && $terms = $this->TermsPage()) {
    402             $required[] = 'Agree';
    403         }
    404
    405         $this->extend('updateOrderRequired', $required);
    406         return $required;
    407     }
    408
    409 }
    410

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -