<?php
/**
 * @author Semenov Alexander <semenov@skeeks.com>
 * @link http://skeeks.com/
 * @copyright 2010 SkeekS (СкикС)
 * @date 21.09.2015
 */

namespace skeeks\cms\shop\controllers;

use skeeks\cms\base\Controller;
use skeeks\cms\shop\models\ShopOrder;
use skeeks\cms\shop\paySystems\robokassa\Merchant;
use skeeks\cms\shop\paySystems\RobokassaPaySystem;
use yii\helpers\Url;
use yii\web\BadRequestHttpException;

/**
 * Class RobocassaController
 * @package skeeks\cms\shop\controllers
 */
class RobokassaController extends Controller
{
    /**
     * @inheritdoc
     */
    public $enableCsrfValidation = false;

    /*public function actionInvoice()
    {
        $model = new Invoice();
        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            /** @var \robokassa\Merchant $merchant
            $merchant = Yii::$app->get('robokassa');
            return $merchant->payment($model->sum, $model->id,  \Yii::t('skeeks/shop/app', 'Refill'), null, Yii::$app->user->identity->email);
        } else {
            return $this->render('invoice', [
                'model' => $model,
            ]);
        }
    }*/

    public function actionSuccess()
    {
        RobokassaPaySystem::logInfo('success request');

        if (!isset($_REQUEST['OutSum'], $_REQUEST['InvId'], $_REQUEST['SignatureValue'])) {
            RobokassaPaySystem::logError('Not found params');
            throw new BadRequestHttpException('Not found params');
        }

        $order = $this->loadModel($_REQUEST['InvId']);
        $merchant = $this->getMerchant($order);
        $shp = $this->getShp();

        if ($merchant->checkSignature($_REQUEST['SignatureValue'], $_REQUEST['OutSum'], $_REQUEST['InvId'],
            $merchant->sMerchantPass1, $shp)
        ) {

            $order->ps_status = "STATUS_ACCEPTED";
            $order->save();
            return $this->redirect(Url::to(['/shop/order/view', 'id' => $order->id]));
        }

        RobokassaPaySystem::logError('bad signature');
        throw new BadRequestHttpException('bad signature');
    }
    /**
     * Загрузка заказа
     *
     * @param integer $id
     * @return ShopOrder
     * @throws \yii\web\BadRequestHttpException
     */
    protected function loadModel($id)
    {
        $model = ShopOrder::findOne($id);
        if ($model === null) {
            throw new BadRequestHttpException("Order: {$id} not found");
        }
        return $model;
    }
    /**
     * @param ShopOrder $order
     * @return \skeeks\cms\shop\paySystems\robokassa\Merchant
     * @throws BadRequestHttpException
     */
    protected function getMerchant(ShopOrder $order)
    {
        /** @var \skeeks\cms\shop\paySystems\robokassa\Merchant $merchant */
        $paySystemHandler = $order->paySystem->paySystemHandler;
        if (!$paySystemHandler || !$paySystemHandler instanceof RobokassaPaySystem) {
            RobokassaPaySystem::logError('Not found pay system');
            throw new BadRequestHttpException('Not found pay system');
        }

        $merchant = $paySystemHandler->getMerchant();

        if (!$merchant instanceof Merchant) {
            RobokassaPaySystem::logError('Not found merchant');
            throw new BadRequestHttpException('Not found merchant');
        }

        return $merchant;
    }

    /**
     * @inheritdoc
     */
    /*public function actions()
    {
        return [
            'result' => [
                'class' => '\robokassa\ResultAction',
                'callback' => [$this, 'resultCallback'],
            ],
            'success' => [
                'class' => '\robokassa\SuccessAction',
                'callback' => [$this, 'successCallback'],
            ],
            'fail' => [
                'class' => '\robokassa\FailAction',
                'callback' => [$this, 'failCallback'],
            ],
        ];
    }*/

    /**
     * Callback.
     * @param \robokassa\Merchant $merchant merchant.
     * @param integer             $nInvId invoice ID.
     * @param float               $nOutSum sum.
     * @param array               $shp user attributes.
     */
    /*public function successCallback($merchant, $nInvId, $nOutSum, $shp)
    {
        $this->loadModel($nInvId)->updateAttributes(['status' => Invoice::STATUS_ACCEPTED]);
        $order = $this->loadModel($nInvId);
        $order->ps_status = "STATUS_ACCEPTED";
        $order->save();
        return $this->goBack();
    }*/
    /**
     * @return array
     */
    public function getShp()
    {
        $shp = [];
        foreach ($_REQUEST as $key => $param) {
            if (strpos(strtolower($key), 'shp') === 0) {
                $shp[$key] = $param;
            }
        }

        return $shp;
    }
    public function actionResult()
    {
        RobokassaPaySystem::logInfo('result request');

        if (!isset($_REQUEST['OutSum'], $_REQUEST['InvId'], $_REQUEST['SignatureValue'])) {
            RobokassaPaySystem::logError('Not found params');
            throw new BadRequestHttpException('Not found params');
        }

        $order = $this->loadModel($_REQUEST['InvId']);
        $merchant = $this->getMerchant($order);
        $shp = $this->getShp();

        if ($merchant->checkSignature($_REQUEST['SignatureValue'], $_REQUEST['OutSum'], $_REQUEST['InvId'],
            $merchant->sMerchantPass2, $shp)
        ) {

            RobokassaPaySystem::logInfo('result signature OK');
            if ($order->payed != "Y") {
                $order->processNotePayment();
            }

            $order->ps_status = "STATUS_SUCCESS";
            $order->payed = "Y";
            $order->save();

            return 'Ok';
        }

        RobokassaPaySystem::logError('bad signature');

        throw new BadRequestHttpException;
    }
    public function actionFail()
    {
        RobokassaPaySystem::logInfo('fail request');

        if (!isset($_REQUEST['OutSum'], $_REQUEST['InvId'])) {
            RobokassaPaySystem::logError('Not found params');
            throw new BadRequestHttpException;
        }

        $order = $this->loadModel($_REQUEST['InvId']);
        $merchant = $this->getMerchant($order);
        $shp = $this->getShp();

        $order->ps_status = "STATUS_FAIL";
        $order->save();
        return $this->redirect(Url::to(['/shop/order/view', 'id' => $order->id]));
        //$this->loadModel($nInvId)->updateAttributes(['status' => Invoice::STATUS_SUCCESS]);
        return 'Ok';
    }

}


<?php

namespace skeeks\cms\shop\models;

use skeeks\cms\components\Cms;
use skeeks\cms\models\CmsContentElement;
use skeeks\cms\models\CmsSite;
use skeeks\cms\models\CmsUser;
use skeeks\cms\money\models\MoneyCurrency;
use skeeks\cms\money\Money;
use skeeks\cms\shop\helpers\ProductPriceHelper;
use skeeks\cms\shop\Module;
use yii\base\ModelEvent;
use yii\db\AfterSaveEvent;
use yii\helpers\ArrayHelper;
use yii\helpers\Url;

/**
 * This is the model class for table "{{%shop_order}}".
 *
 * @property integer                    $id
 * @property integer                    $created_by
 * @property integer                    $updated_by
 * @property integer                    $created_at
 * @property integer                    $updated_at
 * @property integer                    $cms_site_id
 * @property integer                    $shop_person_type_id
 * @property integer                    $paid_at
 * @property integer                    $shop_buyer_id
 * @property integer                    $canceled_at
 * @property string                     $reason_canceled
 * @property string                     $status_code
 * @property integer                    $status_at
 * @property string                     $delivery_amount
 * @property string                     $allow_delivery
 * @property string                     $is_allowed_payment
 * @property integer                    $allow_delivery_at
 * @property string                     $amount
 * @property string                     $currency_code
 * @property string                     $discount_amount
 * @property integer                    $shop_pay_system_id
 * @property integer                    $shop_delivery_id
 * @property string                     $user_description
 * @property string                     $additional_info
 * @property string                     $comments
 * @property string                     $tax_amount
 * @property string                     $paid_amount
 * @property integer                    $locked_by
 * @property integer                    $locked_at
 * @property integer                    $shop_affiliate_id
 * @property string                     $delivery_doc_num
 * @property integer                    $delivery_doc_at
 * @property string                     $tracking_number
 * @property integer                    $shop_order_status_id
 * @property string                     $code
 * @property boolean                    $is_created Заказ создан? Если заказ не создан он связан с корзиной пользователя.
 *
 * ***
 *
 * @property ShopPaySystem              $shopPaySystem
 * @property ShopPersonType             $shopPersonType
 * @property ShopBuyer                  $shopBuyer
 * @property ShopDelivery               $shopDelivery
 * @property CmsSite                    $cmsSite
 * @property ShopAffiliate              $shopAffiliate
 * @property ShopOrderItem[]            $shopOrderItems
 * @property ShopOrderStatus            $shopOrderStatus
 *
 * @property ShopCart                   $shopCart
 * @property CmsUser|null               $cmsUser
 *
 * @property CmsContentElement          $store
 * @property Currency                   $currency
 * @property CmsUser                    $lockedBy
 *
 * @property ShopOrderChange[]          $shopOrderChanges
 * @property ShopOrder2discountCoupon[] $shopOrder2discountCoupons
 * @property ShopDiscountCoupon[]       $shopDiscountCoupons
 *
 * @property ShopTypePrice[]            $buyTypePrices
 *
 *
 * @property Money                      $money Итоговая цена к оплате
 * @property Money                      $moneyDelivery Цена доставки
 * @property Money                      $moneyVat Цена налога
 * @property Money                      $moneyDiscount Цена скидки
 * @property Money                      $moneyItems Цена всех товаров без скидки
 * @property Money                      $moneyPaid
 *
 * @property Money                      $calcMoney          Итоговая цена к оплате
 * @property Money                      $calcMoneyDelivery  Цена доставки
 * @property Money                      $calcMoneyVat  Цена налога
 * @property Money                      $calcMoneyDiscount  Цена скидки
 * @property Money                      $calcMoneyItems     Сумма всех позиций корзины
 *
 * @property int                        $weight
 *
 *
 * @property string                     $email read-only
 * @property string                     $payUrl read-only ссылка на оплату
 * @property string                     $url read-only ссылка на заказ
 *
 * @property int                        $countShopOrderItems
 */
class ShopOrder extends \skeeks\cms\models\Core
{
    protected $_email = null;
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%shop_order}}';
    }

    public function notifyNew()
    {
        if ($this->email) {
            \Yii::$app->mailer->view->theme->pathMap['@app/mail'][] = '@skeeks/cms/shop/mail';

            \Yii::$app->mailer->compose('create-order', [
                'order' => $this,
            ])
                ->setFrom([\Yii::$app->cms->adminEmail => \Yii::$app->cms->appName.''])
                ->setTo($this->email)
                ->setSubject(\Yii::$app->cms->appName.': '.\Yii::t('skeeks/shop/app',
                        'New order').' #'.$this->id)
                ->send();
        }
    }
    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();

        //$this->on(self::EVENT_AFTER_UPDATE,    [$this, "afterUpdateCallback"]);
        $this->on(self::EVENT_AFTER_UPDATE,    [$this, "_afterUpdateCallback"]);
        $this->on(self::EVENT_BEFORE_UPDATE, [$this, "_beforeUpdateCallback"]);

    }
    public function _afterUpdateCallback(AfterSaveEvent $event)
    {
        //Заказ создан
        if (in_array("is_created", array_keys($event->changedAttributes)) && $this->is_created) {

            \Yii::info($this->id . " is_created!", self::class);

            (new ShopOrderChange([
                'type'          => ShopOrderChange::ORDER_ADDED,
                'shop_order_id' => $this->id,
            ]))->save();

            try {
                //Notify admins
                if (\Yii::$app->shop->notifyEmails) {
                    foreach (\Yii::$app->shop->notifyEmails as $email) {

                        \Yii::$app->mailer->view->theme->pathMap['@app/mail'][] = '@skeeks/cms/shop/mail';

                        \Yii::$app->mailer->compose('create-order', [
                            'order' => $this,
                        ])
                            ->setFrom([\Yii::$app->cms->adminEmail => \Yii::$app->cms->appName.''])
                            ->setTo($email)
                            ->setSubject(\Yii::t('skeeks/shop/app',
                                    'New order').' #'.$this->id)
                            ->send();
                    }
                }
            } catch (\Exception $e) {
                \Yii::error("Email seinding error: " . $e->getMessage(), self::class);
            }

            try {
                //Письмо тому кто заказывает
                if ($this->email) {
                    $this->email = trim($this->email);
                    $this->notifyNew();
                }
            } catch (\Exception $e) {
                \Yii::error("Email client seinding error '{$this->email}': " . $e->getMessage(), self::class);
            }

        }
    }

    public function _beforeUpdateCallback(ModelEvent $e)
    {
        /*var_dump($this->is_created);
        var_dump($this->isAttributeChanged('is_created'));
        die;*/
        //После создания заказа делать его пересчет
        if ($this->isAttributeChanged('is_created') && $this->is_created) {
            $this->recalculate();
        }
        if ($this->isAttributeChanged('shop_delivery_id')) {
            $this->recalculate();
        }
        if ($this->isAttributeChanged('shop_pay_system_id')) {
            $this->recalculate();
        }

        if ($this->isAttributeChanged('shop_order_status_id')) {
            $this->status_at = \Yii::$app->formatter->asTimestamp(time());

            (new ShopOrderChange([
                'type'          => ShopOrderChange::ORDER_STATUS_CHANGED,
                'shop_order_id' => $this->id,
                'data'          =>
                [
                    'status' => $this->shopOrderStatus->name,
                ],
            ]))->save();

            //Письмо тому кто заказывает
            if ($this->email) {
                try {
                    \Yii::$app->mailer->view->theme->pathMap['@app/mail'][] = '@skeeks/cms/shop/mail';

                    \Yii::$app->mailer->compose('order-status-change', [
                        'order' => $this,
                    ])
                        ->setFrom([\Yii::$app->cms->adminEmail => \Yii::$app->cms->appName.''])
                        ->setTo($this->email)
                        ->setSubject(\Yii::t('skeeks/shop/app',
                                'Change order status').' #'.$this->id)
                        ->send();

                } catch (\Exception $e) {
                    \Yii::error('Ошибка отправки email: '.$e->getMessage(), Module::class);
                }

            }
        }

        if ($this->isAttributeChanged('paid_at') && $this->paid_at) {

            \Yii::info(print_r($this->toArray(), true), self::class);

            (new ShopOrderChange([
                'type'          => ShopOrderChange::ORDER_PAYED,
                'shop_order_id' => $this->id,
            ]))->save();

            $emails = \Yii::$app->shop->notifyEmails;
            if ($this->email) {
                $emails[] = $this->email;
            }

            if ($emails) {
                foreach ($emails as $email) {
                    try {
                        \Yii::$app->mailer->view->theme->pathMap['@app/mail'][] = '@skeeks/cms/shop/mail/order';

                        \Yii::$app->mailer->compose('payed', [
                            'order' => $this,
                        ])
                            ->setFrom([\Yii::$app->cms->adminEmail => \Yii::$app->cms->appName.''])
                            ->setTo($email)
                            ->setSubject(\Yii::t('skeeks/shop/app',
                                    'Order successfully paid').' №'.$this->id)
                            ->send();

                    } catch (\Exception $e) {
                        \Yii::error('Ошибка отправки email: '.$e->getMessage(), Module::class);
                    }
                }

            }
        }

        if ($this->isAttributeChanged('is_allowed_payment') && $this->is_allowed_payment) {
            (new ShopOrderChange([
                'type'          => ShopOrderChange::ORDER_ALLOW_PAYMENT,
                'shop_order_id' => $this->id,
            ]))->save();

            //Письмо тому кто заказывает
            if ($this->email) {
                try {

                    \Yii::$app->mailer->view->theme->pathMap['@app/mail'][] = '@skeeks/cms/shop/mail/order';

                    \Yii::$app->mailer->compose('allow-payment', [
                        'order' => $this,
                    ])
                        ->setFrom([\Yii::$app->cms->adminEmail => \Yii::$app->cms->appName.''])
                        ->setTo($this->email)
                        ->setSubject(\Yii::t('skeeks/shop/app',
                                'Resolution of payment on request').' №'.$this->id)
                        ->send();

                } catch (\Exception $e) {
                    \Yii::error('Ошибка отправки email: '.$e->getMessage(), Module::class);
                }
            }
        }

        if ($this->isAttributeChanged('allow_delivery') && $this->allow_delivery == Cms::BOOL_Y) {
            (new ShopOrderChange([
                'type'          => ShopOrderChange::ORDER_ALLOW_DELIVERY,
                'shop_order_id' => $this->id,
            ]))->save();

            //Письмо тому кто заказывает
            if ($this->email) {
                try {

                    \Yii::$app->mailer->view->theme->pathMap['@app/mail'][] = '@skeeks/cms/shop/mail';

                    \Yii::$app->mailer->compose('order-allow-delivery', [
                        'order' => $this,
                    ])
                        ->setFrom([\Yii::$app->cms->adminEmail => \Yii::$app->cms->appName.''])
                        ->setTo($this->email)
                        ->setSubject(\Yii::t('skeeks/shop/app',
                                'Resolution of payment on request').' №'.$this->id)
                        ->send();

                } catch (\Exception $e) {
                    \Yii::error('Ошибка отправки email: '.$e->getMessage(), Module::class);
                }
            }
        }

        if ($this->isAttributeChanged('canceled_at') && $this->canceled_at) {
            (new ShopOrderChange([
                'type'          => ShopOrderChange::ORDER_CANCELED,
                'shop_order_id' => $this->id,
                'data'          => [
                    'reason_canceled' => $this->reason_canceled,
                ],
            ]))->save();

            //Письмо тому кто заказывает
            if ($this->email) {
                try {

                    \Yii::$app->mailer->view->theme->pathMap['@app/mail'][] = '@skeeks/cms/shop/mail/order';

                    \Yii::$app->mailer->compose('canceled', [
                        'order' => $this,
                    ])
                        ->setFrom([\Yii::$app->cms->adminEmail => \Yii::$app->cms->appName.''])
                        ->setTo($this->email)
                        ->setSubject(\Yii::t('skeeks/shop/app',
                                'Cancellations').' №'.$this->id)
                        ->send();
                } catch (\Exception $e) {
                    \Yii::error('Ошибка отправки email: '.$e->getMessage(), Module::class);
                }
            }
        }
    }
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [
                [
                    'created_by',
                    'updated_by',
                    'created_at',
                    'updated_at',
                    'shop_person_type_id',
                    'paid_at',
                    'canceled_at',
                    'status_at',
                    'allow_delivery_at',
                    'shop_pay_system_id',
                    'locked_by',
                    'locked_at',
                    'shop_affiliate_id',
                    'delivery_doc_at',
                    'shop_order_status_id',
                ],
                'integer',
            ],
            [
                [
                    'is_created',
                ],
                'boolean',
            ],
            [['delivery_amount', 'amount', 'discount_amount', 'tax_amount', 'paid_amount'], 'number'],
            [['comments'], 'string'],
            [['shop_buyer_id'], 'integer'],
            [['is_allowed_payment'], 'integer'],
            [['cms_site_id'], 'integer'],
            [
                [
                    'allow_delivery',
                ],
                'string',
                'max' => 1,
            ],
            [
                [
                    'reason_canceled',
                    'user_description',
                    'additional_info',
                ],
                'string',
                'max' => 255,
            ],
            [['currency_code'], 'string', 'max' => 3],
            [['shop_delivery_id'], 'integer'],
            [['delivery_doc_num'], 'string', 'max' => 20],
            [['tracking_number'], 'string', 'max' => 100],

            [
                [
                    'allow_delivery',
                ],
                'default',
                'value' => Cms::BOOL_N,
            ],
            [['status_at'], 'default', 'value' => \Yii::$app->formatter->asTimestamp(time())],
            [['currency_code'], 'default', 'value' => \Yii::$app->money->currencyCode],
            [['cms_site_id'], 'default', 'value' => \Yii::$app->cms->site->id],

            [['reason_canceled'], 'validateCanceled'],

            [
                ['is_allowed_payment'],
                'default',
                'value' => function () {
                    return (\Yii::$app->shop->payAfterConfirmation == Cms::BOOL_Y) ? 0 : 1;
                },
            ],

            [['code'], 'string'],
            [['code'], 'default', 'value' => \Yii::$app->security->generateRandomString()],

            [['is_created'], 'default', 'value' => 0],
            [['shop_order_status_id'], 'default', 'value' => \Yii::$app->shop->start_order_status_id],

            [
                ['shop_person_type_id'],
                'default',
                'value' => function () {
                    $shopPersonType = \Yii::$app->shop->shopPersonTypes[0];
                    return $shopPersonType->id;
                },
            ],

            /*['reason_canceled', 'required', 'when' => function($model) {
                return $model->canceled == Cms::BOOL_Y;
            }, 'whenClient' => "function (attribute, value) {
                return $('#country').val() == 'Y';
            }"],*/

        ];
    }
    /**
     * Процесс оплаты заказа
     *
     * @return $this
     */
    public function processNotePayment()
    {
        /*$transaction = new ShopUserTransact();
        $transaction->cms_user_id = $this->user_id;
        $transaction->shop_order_id = $this->id;
        $transaction->amount = $this->money->amount;
        $transaction->currency_code = $this->money->currency->code;
        $transaction->debit = "Y";
        $transaction->description = ShopUserTransact::OUT_CHARGE_OFF;
        $transaction->save();

        $transaction = new ShopUserTransact();
        $transaction->cms_user_id = $this->user_id;
        $transaction->shop_order_id = $this->id;
        $transaction->amount = $this->money->amount;
        $transaction->currency_code = $this->money->currency->code;
        $transaction->debit = "N";
        $transaction->description = ShopUserTransact::ORDER_PAY;
        $transaction->save();*/

        /*$this->payed = "Y";
        $this->save();*/

        return $this;
    }
    /**
     * Отмена оплаты заказа
     * @return $this
     */
    public function processCloseNotePayment()
    {
        $this->payed = "N";
        $this->save();

        return $this;
    }
    /**
     * Валидация причины отмены
     *
     * @param $attribute
     */
    public function validateCanceled($attribute)
    {
        if ($this->canceled_at && !$this->reason_canceled) {
            $this->addError($attribute, \Yii::t('skeeks/shop/app', 'Enter the reason for cancellation'));
        }
    }
    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id'                  => \Yii::t('skeeks/shop/app', 'ID'),
            'created_by'          => \Yii::t('skeeks/shop/app', 'Created By'),
            'updated_by'          => \Yii::t('skeeks/shop/app', 'Updated By'),
            'created_at'          => \Yii::t('skeeks/shop/app', 'Created At'),
            'updated_at'          => \Yii::t('skeeks/shop/app', 'Updated At'),
            'cms_site_id'         => \Yii::t('skeeks/shop/app', 'Site'),
            'shop_person_type_id' => \Yii::t('skeeks/shop/app', 'Person Type ID'),
            'payed'               => \Yii::t('skeeks/shop/app', 'Оплачен'),
            'paid_at'            => \Yii::t('skeeks/shop/app', 'Оплачен'),
            'canceled'            => \Yii::t('skeeks/shop/app', 'Canceled'),
            'canceled_at'         => \Yii::t('skeeks/shop/app', 'Canceled'),
            'reason_canceled'     => \Yii::t('skeeks/shop/app', 'Reason of cancellation'),
            'shop_order_status_id'         => \Yii::t('skeeks/shop/app', 'Status'),
            'status_at'           => \Yii::t('skeeks/shop/app', 'Status At'),
            'delivery_amount'     => \Yii::t('skeeks/shop/app', 'Price Delivery'),
            'allow_delivery'      => \Yii::t('skeeks/shop/app', 'Allow Delivery'),
            'allow_delivery_at'   => \Yii::t('skeeks/shop/app', 'Allow Delivery At'),
            'amount'              => \Yii::t('skeeks/shop/app', 'Price'),
            'currency_code'       => \Yii::t('skeeks/shop/app', 'Currency Code'),
            'discount_amount'     => \Yii::t('skeeks/shop/app', 'Discount Value'),
            'shop_pay_system_id'  => \Yii::t('skeeks/shop/app', 'Pay System ID'),
            'shop_delivery_id'    => \Yii::t('skeeks/shop/app', 'Delivery'),
            'user_description'    => \Yii::t('skeeks/shop/app', 'User Description'),
            'additional_info'     => \Yii::t('skeeks/shop/app', 'Additional Info'),
            'comments'            => \Yii::t('skeeks/shop/app', 'Comments'),
            'tax_amount'          => \Yii::t('skeeks/shop/app', 'Tax Value'),
            'paid_amount'         => \Yii::t('skeeks/shop/app', 'Sum Paid'),
            'locked_by'           => \Yii::t('skeeks/shop/app', 'Locked By'),
            'locked_at'           => \Yii::t('skeeks/shop/app', 'Locked At'),
            'shop_affiliate_id'   => \Yii::t('skeeks/shop/app', 'Affiliate ID'),
            'delivery_doc_num'    => \Yii::t('skeeks/shop/app', 'Delivery Doc Num'),
            'delivery_doc_at'     => \Yii::t('skeeks/shop/app', 'Delivery Doc At'),
            'tracking_number'     => \Yii::t('skeeks/shop/app', 'Tracking Number'),
            'shop_buyer_id'       => \Yii::t('skeeks/shop/app', 'Profile of buyer'),
            'is_allowed_payment'       => \Yii::t('skeeks/shop/app', 'Allow Payment'),

            'shop_delivery_id'   => \Yii::t('skeeks/shop/app', 'Delivery service'),
            'shop_buyer_id'      => \Yii::t('skeeks/shop/app', 'Profile of buyer'),
            'shop_pay_system_id' => \Yii::t('skeeks/shop/app', 'Payment system'),

            'is_created' => \Yii::t('skeeks/shop/app', 'Заказ создан?'),
        ];
    }
    /**
     * @return null|string
     */
    public function getEmail()
    {
        if ($this->_email !== null) {
            return $this->_email;
        }

        if ($this->shopBuyer) {
            if ($this->shopBuyer->email) {
                return $this->shopBuyer->email;
            }
        }

        if ($this->cmsUser && $this->cmsUser->email) {
            return $this->cmsUser->email;
        }

        return null;
    }
    /**
     * @return null|string
     */
    public function setEmail($email)
    {
        $this->_email = $email;
        return $this;
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopAffiliate()
    {
        return $this->hasOne(ShopAffiliate::class, ['id' => 'shop_affiliate_id']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getCurrency()
    {
        return $this->hasOne(MoneyCurrency::class, ['code' => 'currency_code']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getLockedBy()
    {
        return $this->hasOne(CmsUser::class, ['id' => 'locked_by']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopDelivery()
    {
        return $this->hasOne(ShopDelivery::class, ['id' => 'shop_delivery_id']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopPaySystem()
    {
        return $this->hasOne(ShopPaySystem::class, ['id' => 'shop_pay_system_id']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopPersonType()
    {
        return $this->hasOne(ShopPersonType::class, ['id' => 'shop_person_type_id']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getCmsSite()
    {
        return $this->hasOne(CmsSite::class, ['id' => 'cms_site_id']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getStatus()
    {
        return $this->getShopOrderStatus();
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopOrderStatus()
    {
        return $this->hasOne(ShopOrderStatus::class, ['id' => 'shop_order_status_id']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopBuyer()
    {
        return $this->hasOne(ShopBuyer::class, ['id' => 'shop_buyer_id']);
    }
    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopOrderChanges()
    {
        return $this->hasMany(ShopOrderChange::class,
            ['shop_order_id' => 'id'])->orderBy(['created_at' => SORT_DESC]);
    }
    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopOrder2discountCoupons()
    {
        return $this->hasMany(ShopOrder2discountCoupon::class, ['order_id' => 'id']);
    }
    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopDiscountCoupons()
    {
        return $this->hasMany(ShopDiscountCoupon::class, ['id' => 'discount_coupon_id'])
            ->via('shopOrder2discountCoupons');
    }
    /**
     * Итоговая стоимость заказа с учетом скидок, доставок, наценок, то что будет платить человек
     * @return Money
     */
    public function getMoney()
    {
        return new Money($this->amount, $this->currency_code);
    }
    /**
     * Итоговая стоимость заказа с учетом скидок, доставок, наценок, то что будет платить человек
     * Рассчитанная динамически
     *
     * @return Money
     */
    public function getCalcMoney()
    {
        $money = $this->calcMoneyItems;
        $money->add($this->calcMoneyDelivery);
        $money->sub($this->calcMoneyDiscount);
        return $money;
    }

    /**
     * Цена всех позиций в заказе без скидок
     * Динамически рассчитанная
     *
     * @return Money
     */
    public function getCalcMoneyItems()
    {
        $money = new Money("", $this->currency_code);

        foreach ($this->shopOrderItems as $shopOrderItem) {
            $money = $money->add($shopOrderItem->moneyOriginal->multiply($shopOrderItem->quantity));
        }

        return $money;
    }

    /**
     * @return Money
     */
    public function getMoneyItems()
    {
        $money = new Money("", $this->currency_code);

        $money->add($this->money);
        $money->sub($this->moneyDelivery);
        $money->add($this->moneyDiscount);

        return $money;
    }

    /**
     * Налог
     *
     * @return Money
     */
    public function getMoneyVat()
    {
        return new Money($this->tax_amount, $this->currency_code);
    }
    /**
     * Налог
     *
     * @return Money
     */
    public function getCalcMoneyVat()
    {
        $money = new Money("", $this->currency_code);

        foreach ($this->shopOrderItems as $shopOrderItem) {
            $money = $money->add($shopOrderItem->moneyVat->multiply($shopOrderItem->quantity));
        }

        return $money;
    }

    /**
     * Скидка наценка
     *
     * @return Money
     */
    public function getMoneyDiscount()
    {
        return new Money($this->discount_amount, $this->currency_code);
    }

    /**
     * Налог
     *
     * @return Money
     */
    public function getCalcMoneyDiscount()
    {
        $money = new Money("", $this->currency_code);

        //Товарные скидки
        foreach ($this->shopOrderItems as $shopOrderItem) {
            $money = $money->add($shopOrderItem->moneyDiscount->multiply($shopOrderItem->quantity));
        }

        //Скидка на корзину
        if ($this->shopDiscountCoupons) {
            foreach ($this->shopDiscountCoupons as $shopDiscountCoupon)
            {
                $shopDiscount = $shopDiscountCoupon->shopDiscount;
                if ($shopDiscountCoupon->shopDiscount->assignment_type == ShopDiscount::ASSIGNMENT_TYPE_CART) {

                    if ($shopDiscount->value_type == ShopDiscount::VALUE_TYPE_F) {
                        $discountMoney = new Money($shopDiscount->value, $shopDiscount->currency_code);
                        $money->add($discountMoney);
                    }

                    if ($shopDiscount->value_type == ShopDiscount::VALUE_TYPE_P) {
                        $moneyOrderItems = $this->calcMoneyItems;
                        $moneyValue = $moneyOrderItems->amount * $shopDiscount->value / 100;
                        $discountMoney = new Money($moneyValue, $moneyOrderItems->currency);
                        $money->add($discountMoney);
                    }

                    if ($shopDiscount->isLast) {
                        break;
                    }

                }
            }
        }

        return $money;
    }

    /**
     * Итоговая стоимость позиции без скидок и наценок
     * Цена товара в момент укладки товара в корзину
     *
     * @return Money
     * @deprecated
     */
    public function getMoneyOriginal()
    {
        return $this->moneyItems;
    }

    /**
     * Уже оплачено по заказу
     *
     * @return Money
     */
    public function getMoneyPaid()
    {
        return new Money($this->paid_amount, $this->currency_code);
    }
    /**
     * @return Money
     * @deprecated
     */
    public function getMoneySummPaid()
    {
        return $this->moneyPaid;
    }

    /**
     * Стоимость доставки
     * @return Money
     */
    public function getMoneyDelivery()
    {
        return new Money($this->delivery_amount, $this->currency_code);
    }

    /**
     * Стоимость доставки
     * @return Money
     */
    public function getCalcMoneyDelivery()
    {
        if ($this->shopDelivery) {
            return $this->shopDelivery->money;
        }

        return new Money("", $this->currency_code);
    }

    /**
     * @return int
     */
    public function getWeight()
    {
        $result = 0;

        foreach ($this->shopOrderItems as $shopOrderItem) {
            $result = $result + ($shopOrderItem->weight * $shopOrderItem->quantity);
        }

        return $result;
    }
    /**
     * Доступные платежные системы
     *
     * @return ShopPaySystem[]
     */
    public function getPaySystems()
    {
        return $this->shopPersonType->getPaySystems()->andWhere([ShopPaySystem::tableName().".active" => Cms::BOOL_Y]);
    }
    /**
     * @return $this
     */
    public function recalculate()
    {
        $this->tax_amount = $this->calcMoneyVat->amount;
        $this->amount = $this->calcMoney->amount;
        $this->discount_amount = $this->calcMoneyDiscount->amount;
        $this->delivery_amount = $this->calcMoneyDelivery->amount;

        return $this;
    }
    /**
     * @param array $options
     * @param bool  $scheme
     * @return string
     */
    public function getPayUrl($options = [], $scheme = true)
    {
        return Url::to(ArrayHelper::merge(
            ['/shop/order/pay', 'code' => $this->code],
            $options
        ), $scheme);
    }
    /**
     * @deprecated
     * @return string
     */
    public function getPublicUrl($options = [], $scheme = true)
    {
        return $this->getUrl($options = [], $scheme = true);
    }
    /**
     * @param array $options
     * @param bool  $scheme
     * @return string
     */
    public function getUrl($options = [], $scheme = true)
    {
        return Url::to(ArrayHelper::merge(
            ['/shop/order/finish', 'code' => $this->code],
            $options
        ), $scheme);
    }
    /**
     *
     * Массив для json ответа, используется при обновлении корзины, добавлении позиций и т.д.
     *
     * @return array
     */
    public function jsonSerialize()
    {
        return ArrayHelper::merge($this->toArray([], $this->extraFields()), [
            'money'         => ArrayHelper::merge($this->money->jsonSerialize(),
                ['convertAndFormat' => \Yii::$app->money->convertAndFormat($this->money)]),
            'moneyDelivery' => ArrayHelper::merge($this->moneyDelivery->jsonSerialize(),
                ['convertAndFormat' => \Yii::$app->money->convertAndFormat($this->moneyDelivery)]),
            'moneyDiscount' => ArrayHelper::merge($this->moneyDiscount->jsonSerialize(),
                ['convertAndFormat' => \Yii::$app->money->convertAndFormat($this->moneyDiscount)]),
            'moneyOriginal' => ArrayHelper::merge($this->moneyOriginal->jsonSerialize(),
                ['convertAndFormat' => \Yii::$app->money->convertAndFormat($this->moneyOriginal)]),
            'moneyVat'      => ArrayHelper::merge($this->moneyVat->jsonSerialize(), [
                'convertAndFormat' => \Yii::$app->money->convertAndFormat($this->moneyVat),
            ]),
        ]);
    }
    /**
     * @return array
     */
    public function extraFields()
    {
        return [
            'shopOrderItems',
            'quantity',
            'countShopOrderItems',

            'countShopBaskets',
        ];
    }
    /**
     * Количество позиций в корзине
     *
     * @return int
     */
    public function getCountShopOrderItems()
    {
        return count($this->shopOrderItems);
    }
    /**
     * @return float
     */
    public function getQuantity()
    {
        $result = 0;

        if ($this->shopOrderItems) {
            foreach ($this->shopOrderItems as $item) {
                $result = $item->quantity + $result;
            }
        }
        return (float)$result;
    }
    /**
     * @param ShopCmsContentElement $shopCmsContentElement
     * @return ProductPriceHelper
     */
    public function getProductPriceHelper(ShopCmsContentElement $shopCmsContentElement)
    {
        $ids = ArrayHelper::map($this->buyTypePrices, 'id', 'id');
        $minPh = null;

        if ($shopCmsContentElement->shopProduct && $shopCmsContentElement->shopProduct->shopProductPrices) {
            foreach ($shopCmsContentElement->shopProduct->shopProductPrices as $price) {

                if (in_array($price->type_price_id, $ids)) {

                    $ph = new ProductPriceHelper([
                        'shopCmsContentElement' => $shopCmsContentElement,
                        'shopOrder'             => $this,
                        'price'                 => $price,
                    ]);

                    if ($minPh === null) {
                        $minPh = $ph;
                        continue;
                    }

                    if ((float)$minPh->minMoney->amount == 0) {
                        $minPh = $ph;
                    } elseif ((float)$minPh->minMoney->amount > (float)$ph->minMoney->amount && (float)$ph->minMoney->amount > 0) {
                        $minPh = $ph;
                    }
                }
            }
        }

        return $minPh;
    }
    /**
     *
     * Доступные цены для покупки на сайте
     *
     * @return ShopTypePrice[]
     */
    public function getBuyTypePrices()
    {
        return \Yii::$app->shop->getCanBuyTypePrices($this->cmsUser);

        /*foreach (\Yii::$app->shop->shopTypePrices as $typePrice) {
            if (\Yii::$app->authManager->checkAccess($this->cmsUser ? $this->cmsUser->id : null, $typePrice->buyPermissionName)
                || $typePrice->isDefault
            ) {
                $result[$typePrice->id] = $typePrice;
            }
        }

        return $result;*/
    }
    /**
     * Добавить в заказ еще позиции
     *
     * @param array $items
     * @return $this
     */
    public function addShopOrderItems($items = [])
    {
        /**
         * @var ShopOrderItem[] $items
         * @var ShopOrderItem   $currentBasket
         */
        foreach ($items as $item) {
            //Если в корзине которую необходимо добавить продукт такой же который уже есть у текущего пользователя, то нужно обновить количество.
            if ($currentBasket = $this->getShopOrderItems()->andWhere(['shop_product_id' => $item->shop_product_id])->one()) {
                $currentBasket->quantity = $currentBasket->quantity + $item->quantity;
                $currentBasket->save();

                $item->delete();
            } else {
                $item->shop_order_id = $this->id;
                $item->save();
            }
        }

        return $this;
    }
    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopOrderItems()
    {
        return $this->hasMany(ShopOrderItem::class, ['shop_order_id' => 'id']);
    }
    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopCart()
    {
        return $this->hasOne(ShopCart::class, ['shop_order_id' => 'id']);
    }

    /**
     * @return null|CmsUser
     */
    public function getCmsUser()
    {
        if ($this->shopBuyer) {
            return $this->shopBuyer->cmsUser;
        }

        if ($this->shopCart) {
            return $this->shopCart->cmsUser;
        }

        return null;
    }

    /**
     * @return int
     * @deprecated
     */
    public function getCountShopBaskets()
    {
        return $this->countShopOrderItems;
    }

    /**
     * @return Money
     * @deprecated
     */
    public function getBasketsMoney()
    {
        return $this->calcMoneyItems;
    }

    /**
     * @return \yii\db\ActiveQuery
     * @deprecated
     */
    public function getPaySystem()
    {
        return $this->getShopPaySystem();
    }

    /**
     * @return string
     * @deprecated
     */
    public function getPayed()
    {
        return $this->paid_at ? "Y" : "N";
    }

    /**
     * @return string
     * @deprecated
     */
    public function getCanceled()
    {
        return $this->canceled_at ? "Y" : "N";
    }

    /**
     * @return \yii\db\ActiveQuery
     * @deprecated
     */
    public function getBuyer()
    {
        return $this->getShopBuyer();
    }
    /**
     * @return \yii\db\ActiveQuery
     * @deprecated
     */
    public function getShopBaskets()
    {
        return $this->getShopOrderItems();
    }

    public function getAllow_payment()
    {
        return $this->is_allowed_payment ? "Y" : "N";
    }
}


<?php
/**
 * @author Semenov Alexander <semenov@skeeks.com>
 * @link http://skeeks.com/
 * @copyright 2010 SkeekS (СкикС)
 * @date 09.10.2015
 */

namespace skeeks\cms\shop\models;

use skeeks\cms\base\ActiveRecord;
use skeeks\cms\models\StorageFile;
use skeeks\cms\money\models\MoneyCurrency;
use skeeks\cms\money\Money;
use skeeks\modules\cms\catalog\models\Product;
use yii\helpers\ArrayHelper;
use yii\helpers\Json;
use yii\helpers\Url;

/**
 * This is the model class for table "{{%shop_basket}}".
 *
 * @property integer           $id
 * @property integer           $created_by
 * @property integer           $updated_by
 * @property integer           $created_at
 * @property integer           $updated_at
 * @property integer           $shop_order_id
 * @property integer           $shop_product_id
 * @property integer           $shop_product_price_id
 * @property string            $amount
 * @property string            $currency_code
 * @property string            $weight
 * @property string            $quantity
 * @property string            $name
 * @property string            $notes
 * @property string            $discount_amount
 * @property string            $discount_name
 * @property string            $discount_value
 * @property string            $vat_rate
 * @property double            $reserve_quantity
 * @property string            $dimensions
 * @property string            $measure_name
 * @property integer           $measure_code
 *
 * ***
 *
 * @property StorageFile       $image
 * @property string            $url
 * @property string            $absoluteUrl
 *
 * @property ShopOrder         $shopOrder
 * @property shopProduct       $shopProduct
 * @property shopProductPrice  $shopProductPrice
 *
 * @property MoneyCurrency     $currency
 * @property ShopOrderItemProperty[] $shopOrderItemProperties
 *
 * @property Money             $money
 * @property Money             $moneyOriginal
 * @property Money             $moneyDiscount
 * @property Money             $moneyVat
 */
class ShopOrderItem extends ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%shop_order_item}}';
    }

    /*public function behaviors()
    {
        return array_merge(parent::behaviors(), [
            HasJsonFieldsBehavior::class => [
                'class'     => HasJsonFieldsBehavior::class,
                'fields'    => ['dimensions']
            ]
        ]);
    }*/
    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();

        //$this->on(self::EVENT_BEFORE_INSERT,    [$this, "beforeSaveEvent"]);

        $this->on(self::EVENT_AFTER_INSERT, [$this, "afterSaveCallback"]);
        $this->on(self::EVENT_AFTER_UPDATE, [$this, "afterSaveCallback"]);
        $this->on(self::EVENT_AFTER_DELETE, [$this, "afterSaveCallback"]);
    }

    public function afterSaveCallback($event)
    {
        //Эта позиция привязана к заказу, после ее обновления нужно обновить заказ целиком
        if ($this->shopOrder) {
            $this->shopOrder->recalculate()->save();
        }
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [
                [
                    'created_by',
                    'updated_by',
                    'created_at',
                    'updated_at',
                    'shop_order_id',
                    'shop_product_id',
                    'shop_product_price_id',
                    'measure_code',
                ],
                'integer',
            ],
            [['name'], 'required'],
            [['amount', 'weight', 'quantity', 'discount_amount', 'vat_rate', 'reserve_quantity'], 'number'],
            [['quantity'], 'number', 'max' => \Yii::$app->shop->maxQuantity, 'min' => \Yii::$app->shop->minQuantity],
            [['currency_code'], 'string', 'max' => 3],

            [
                [
                    'name',
                    'notes',
                    'discount_name',
                    'dimensions',
                ],
                'string',
                'max' => 255,
            ],
            [['discount_value'], 'string', 'max' => 32],
            [['measure_name'], 'string', 'max' => 50],

            [['quantity'], 'default', 'value' => 1],
            [['currency_code'], 'default', 'value' => \Yii::$app->money->currencyCode],
            [['amount'], 'default', 'value' => 0],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id'                    => \Yii::t('skeeks/shop/app', 'ID'),
            'created_by'            => \Yii::t('skeeks/shop/app', 'Created By'),
            'updated_by'            => \Yii::t('skeeks/shop/app', 'Updated By'),
            'created_at'            => \Yii::t('skeeks/shop/app', 'Created At'),
            'updated_at'            => \Yii::t('skeeks/shop/app', 'Updated At'),
            'shop_order_id'         => \Yii::t('skeeks/shop/app', 'Order ID'),
            'shop_product_id'       => \Yii::t('skeeks/shop/app', 'Product'),
            'shop_product_price_id' => \Yii::t('skeeks/shop/app', 'Product Price ID'),
            'amount'                => \Yii::t('skeeks/shop/app', 'Price'),
            'currency_code'         => \Yii::t('skeeks/shop/app', 'Currency Code'),
            'weight'                => \Yii::t('skeeks/shop/app', 'Weight'),
            'quantity'              => \Yii::t('skeeks/shop/app', 'Amount'),
            'name'                  => \Yii::t('skeeks/shop/app', 'Name'),
            'notes'                 => \Yii::t('skeeks/shop/app', 'Notes'),
            'discount_amount'       => \Yii::t('skeeks/shop/app', 'Discount Price'),
            'discount_name'         => \Yii::t('skeeks/shop/app', 'Discount Name'),
            'discount_value'        => \Yii::t('skeeks/shop/app', 'Discount Value'),
            'vat_rate'              => \Yii::t('skeeks/shop/app', 'Vat Rate'),
            'reserve_quantity'      => \Yii::t('skeeks/shop/app', 'Reserve Quantity'),
            'dimensions'            => \Yii::t('skeeks/shop/app', 'Dimensions'),
            'measure_name'          => \Yii::t('skeeks/shop/app', 'Measure Name'),
            'measure_code'          => \Yii::t('skeeks/shop/app', 'Measure Code'),
        ];
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getCurrency()
    {
        return $this->hasOne(MoneyCurrency::class, ['code' => 'currency_code']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopOrder()
    {
        return $this->hasOne(ShopOrder::class, ['id' => 'shop_order_id']);
    }
    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopProduct()
    {
        return $this->hasOne(ShopProduct::class, ['id' => 'shop_product_id']);
    }
    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopProductPrice()
    {
        return $this->hasOne(ShopProductPrice::class, ['id' => 'shop_product_price_id']);
    }

    /**
     * Итоговая стоимость одной позиции  включая скидки и наценки
     *
     * @return Money
     */
    public function getMoney()
    {
        return new Money((string)$this->amount, $this->currency_code);
    }
    /**
     * Итоговая стоимость позиции без скидок и наценок
     * Цена товара в момент укладки товара в корзину
     *
     * @return Money
     */
    public function getMoneyOriginal()
    {
        return new Money((string)($this->amount + $this->discount_amount), $this->currency_code);
    }
    /**
     * Итоговая стоимость скидки
     * @return Money
     */
    public function getMoneyDiscount()
    {
        return new Money((string)$this->discount_amount, $this->currency_code);
    }

    /**
     *
     * Пересчет состояния позиции согласно текущемим данным
     *
     *
     * @return $this
     */
    public function recalculate()
    {
        if (!$this->shopProduct) {
            return $this;
        }

        $priceHelper = $this->shopOrder->getProductPriceHelper($this->shopProduct->cmsContentElement);
        /*$priceHelper = new ProductPriceHelper([
            'shopCmsContentElement' => $this->shopProduct->cmsContentElement,
            'shopCart' => $this->cart,
        ]);*/

        $product = $this->shopProduct;
        $parentElement = $product->cmsContentElement->parentContentElement;

        $productPrice = $product->minProductPrice;
        $productPriceMoney = $productPrice->money->convertToCurrency(\Yii::$app->money->currencyCode);

        $this->measure_name = $product->measure->symbol_rus;
        $this->measure_code = $product->measure->code;
        $this->shop_product_price_id = $productPrice->id;
        $this->notes = $productPrice->typePrice->name;

        $this->name = $parentElement ? $parentElement->name : $product->cmsContentElement->name;
        $this->weight = $product->weight;

        $this->dimensions = Json::encode([
            'height' => $product->height,
            'width'  => $product->width,
            'length' => $product->length,
        ]);

        //Рассчет налогов TODO: переделать
        /*if ($product->vat && (float)$product->vat->rate > 0) {
            $this->vat_rate = $product->vat->rate;

            if ($product->vat_included == Cms::BOOL_Y) {
                $this->price = $productPriceMoney->getValue();
            } else {
                $this->price = $productPriceMoney->getValue() * $this->vat_rate;
            }

        } else {
            $this->price = $productPriceMoney->getValue();
        }*/

        $this->currency_code = $productPriceMoney->currency->code;

        //Проверка скидок
        $this->discount_amount = 0;
        $this->discount_value = "";
        $this->discount_name = "";
        $this->amount = $priceHelper->minMoney->amount;

        if ($priceHelper->hasDiscount) {
            $this->discount_amount = $priceHelper->discountMoney->amount;
            $this->discount_name = implode(" + ", ArrayHelper::map($priceHelper->applyedDiscounts, 'id', 'name'));
            $this->discount_value = \Yii::$app->formatter->asPercent($priceHelper->percent);
        }

        //Если это предложение, нужно добавить свойства
        if ($parentElement && !$this->isNewRecord) {

            if ($product->cmsContentElement->name != $parentElement->name) {
                $basketProperty = new ShopOrderItemProperty();
                $basketProperty->shop_order_item_id = $this->id;
                $basketProperty->code = 'name';
                $basketProperty->value = $product->cmsContentElement->name;
                $basketProperty->name = \Yii::t('skeeks/cms', 'name');

                $basketProperty->save();
            }

            if ($properties = $product->cmsContentElement->relatedPropertiesModel->toArray()) {
                foreach ($properties as $code => $value) {
                    if (!$this->getShopOrderItemProperties()->andWhere(['code' => $code])->count() && $value) {
                        $property = $product->cmsContentElement->relatedPropertiesModel->getRelatedProperty($code);

                        $basketProperty = new ShopOrderItemProperty();
                        $basketProperty->shop_order_item_id = $this->id;
                        $basketProperty->code = $code;
                        $basketProperty->value = $product->cmsContentElement->relatedPropertiesModel->getSmartAttribute($code);
                        $basketProperty->name = $property->name;

                        $basketProperty->save();
                    }
                }
            }

        }

        return $this;
    }
    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopOrderItemProperties()
    {
        return $this->hasMany(ShopOrderItemProperty::class, ['shop_order_item_id' => 'id']);
    }
    /**
     * Значение налога за одну единицу товара
     *
     * @return Money
     */
    public function getMoneyVat()
    {
        if ((float)$this->vat_rate == 0) {
            return new Money("0", $this->currency_code);
        }

        $value = $this->money->getValue();
        $calculateValue = $value - ($value * 100 / 118);

        return new Money((string)$calculateValue, $this->currency_code);
    }

    /**
     * @return string
     */
    public function getUrl()
    {
        if ($this->shopProduct) {
            //Это предложение у него есть родительский элемент
            if ($parent = $this->shopProduct->cmsContentElement->parentContentElement) {
                return $parent->url;
            } else {
                return $this->shopProduct->cmsContentElement->url;
            }
        }

        return "";
    }

    /**
     * @return string
     */
    public function getAbsoluteUrl()
    {
        if ($this->shopProduct) {
            //Это предложение у него есть родительский элемент
            if ($parent = $this->shopProduct->cmsContentElement->parentContentElement) {
                return $parent->absoluteUrl;
            } else {
                return $this->shopProduct->cmsContentElement->absoluteUrl;
            }
        }

        return Url::home()."";
    }

    /**
     * @return null|\skeeks\cms\models\CmsStorageFile
     */
    public function getImage()
    {
        if ($this->shopProduct) {
            //Это предложение у него есть родительский элемент
            if ($parent = $this->shopProduct->cmsContentElement->parentContentElement) {
                return $parent->image;
            } else {
                return $this->shopProduct->cmsContentElement->image;
            }
        }

        return null;
    }

    /**
     * @return shopProduct
     * @deprecated
     */
    public function getProduct()
    {
        return $this->shopProduct;
    }

    /**
     * @return ShopOrderItemProperty[]
     * @deprecated
     */
    public function getShopBasketProps()
    {
        return $this->getShopOrderItemProperties();
    }
}


<?php

namespace pietercolpaert\hardf;

/** a clone of the N3Util class from the N3js code by Ruben Verborgh **/
class Util
{
    const XSD = 'http://www.w3.org/2001/XMLSchema#';
    const XSDSTRING  = self::XSD . 'string';
    const XSDINTEGER = self::XSD . 'integer';
    const XSDDECIMAL = self::XSD . 'decimal';
    const XSDFLOAT = self::XSD . 'float';
    const XSDDOUBLE = self::XSD . 'double';
    const XSDBOOLEAN = self::XSD . 'boolean';
    const RDFLANGSTRING = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString';

    // Tests whether the given entity (triple object) represents an IRI in the N3 library
    public static function isIRI($term) {
        if (!$term) {
            return $term;
        }
        $firstChar = substr($term,0,1);
        return $firstChar !== '"' && $firstChar !== '_';
    }

    public static function isLiteral ($term)
    {
        return $term && substr($term,0,1) === '"';
    }

    public static function isBlank ($term)
    {
        return $term && substr($term,0,2) === '_:';
    }

    public static function isDefaultGraph ($term)
    {
        return empty($term);
    }

    // Tests whether the given $triple is in the default graph
    public static function inDefaultGraph ($triple)
    {
        return !(isset($triple) && $triple["graph"]);
    }

    // Gets the string value of a literal in the N3 library
    public static function getLiteralValue ($literal)
    {
        preg_match("/^\"(.*)\"/", $literal, $match); //TODO: somehow the copied regex did not work. To be checked. Contained [^]
        if (empty($match)) {
            throw new \Exception($literal . ' is not a literal');
        }
        return $match[1];
    }
    // Gets the type of a literal in the N3 library
    public static function getLiteralType ($literal)
    {
        preg_match('/^".*"(?:\^\^([^"]+)|(@)[^@"]+)?$/s',$literal,$match);//TODO: somehow the copied regex did not work. To be checked. Contained [^] instead of the .
        if (empty($match))
            throw new \Exception($literal . ' is not a literal');
        if (!empty($match[1])) {
            return $match[1];
        } else {
            return !empty($match[2]) ? self::RDFLANGSTRING : self::XSDSTRING;
        }
    }

    // Gets the language of a literal in the N3 library
    public static function getLiteralLanguage ($literal)
    {
        preg_match('/^".*"(?:@([^@"]+)|\^\^[^"]+)?$/s', $literal, $match);
        if (empty($match))
            throw new \Exception($literal . ' is not a literal');
        return isset($match[1]) ? strtolower($match[1]) : '';
    }

    // Tests whether the given entity ($triple object) represents a prefixed name
    public static function isPrefixedName ($term)
    {
        return !empty($term) && preg_match("/^[^:\/\"']*:[^:\/\"']+$/", $term);
    }

    // Expands the prefixed name to a full IRI (also when it occurs as a literal's type)
    public static function expandPrefixedName ($prefixedName, $prefixes)
    {
        preg_match("/(?:^|\"\^\^)([^:\/#\"'\^_]*):[^\/]*$/", $prefixedName, $match, PREG_OFFSET_CAPTURE);
        $prefix = "";
        $base = "";
        $index = "";

        if (!empty($match)) {
            $prefix = $match[1][0];
            $base = "";
            if (isset($prefixes[$prefix]))
            {
                $base = $prefixes[$prefix];
            } else {
                $base = null;
            }
            $index = $match[1][1];
        }
        if (!$base) {
            return $prefixedName;
        }

        // The match index is non-zero when expanding a literal's type
        if ($index === 0) {
            // base + prefixedName.substr(prefix.length + 1)
            return $base . substr($prefixedName, strlen($prefix) + 1);
        } else {
            // prefixedName.substr(0, index + 3) + base + prefixedName.substr(index + prefix.length + 4);
            return substr($prefixedName, 0, $index) . $base . substr($prefixedName, $index + strlen($prefix) + 1);
        }
    }

    // Creates an IRI in N3.js representation
    public static function createIRI ($iri)
    {
        return !empty($iri) && substr($iri,0,1) === '"' ? self::getLiteralValue($iri) : $iri;
    }

    // Creates a literal in N3.js representation
    public static function createLiteral ($value, $modifier = null)
    {
        if (!$modifier) {
            switch (gettype($value)) {
                case 'boolean':
                    $value = $value ? "true":"false";
                    $modifier = self::XSDBOOLEAN;
                    break;
                case 'integer':
                    $modifier = self::XSDINTEGER;
                    break;
                case 'double':
                    $modifier = self::XSDDOUBLE;
                    break;
                case 'float':
                    $modifier = self::XSDFLOAT;
                    break;
                default:
                    return '"' . $value . '"';
            }
        }
        return '"' . $value . (preg_match("/^[a-z]+(-[a-z0-9]+)*$/i", $modifier) ? '"@'  . strtolower($modifier) : '"^^' . $modifier);
    }

/* TODO -- CONVERT TO proper PHP
    // Creates a function that prepends the given IRI to a local name
    public static function prefix ($iri)
    {
        return self::prefixes({ '': $iri })('');
    };

    // Creates a function that allows registering and expanding prefixes
    public static function prefixes ($defaultPrefixes)
    {
        // Add all of the default prefixes
        var $prefixes = {};

        for ($defaultPrefixes as $index => $prefix) {
            processPrefix($prefix, defaultPrefixes[$prefix]);
        }

        // Registers a new prefix (if an IRI was specified)
        // or retrieves a function that expands an existing prefix (if no IRI was specified)
        function processPrefix($prefix, $iri) {
            // Create a new prefix if an IRI is specified or the prefix doesn't exist
            if ($iri || !($prefix in $prefixes)) {
                var $cache = {};
                $iri = $iri || '';
                // Create a function that expands the prefix
                $prefixes[$prefix] = function ($localName) {
                    return $cache[$localName] || ($cache[$localName] = $iri . $localName);
                };
            }
            return $prefixes[$prefix];
        }
        return $processPrefix;
    }
*/
};


<?php
/**
 * @author Semenov Alexander <semenov@skeeks.com>
 * @link http://skeeks.com/
 * @copyright 2010 SkeekS (СкикС)
 * @date 21.12.2016
 */

namespace skeeks\cms\shop\widgets\notice;

use skeeks\cms\shop\models\ShopQuantityNoticeEmail;
use yii\base\InvalidConfigException;
use yii\bootstrap\Modal;
use yii\helpers\ArrayHelper;

/**
 * Class NotifyProductEmailModalWidget
 *
 * @package skeeks\cms\shop\widgets\notice
 */
class NotifyProductEmailModalWidget extends Modal
{
    public $product_id = null;
    public $form_options = [];
    public $view_file = '';
    public $success_modal_id = '';

    /**
     * @var ActiveFormAjaxSubmit
     */
    public $form = null;

    /**
     * @var ShopQuantityNoticeEmail
     */
    public $model = null;

    public function init()
    {
        if (!$this->header) {
            $this->header = \Yii::t('skeeks/shop/app', 'Notify admission');
        }

        $this->toggleButton = ArrayHelper::merge([
            'label' => \Yii::t('skeeks/shop/app', 'Notify admission'),
        ], $this->toggleButton);

        parent::init();

        $success_modal_id = $this->success_modal_id;
        $this->form = \skeeks\cms\base\widgets\ActiveFormAjaxSubmit::begin(ArrayHelper::merge([
            'action'                 => \yii\helpers\Url::to('/shop/notify/add'),
            'validationUrl'          => \yii\helpers\Url::to('/shop/notify/add-validate'),
            'id'                     => $this->id."-form",
            'enableClientValidation' => false,
            'enableAjaxValidation'   => true,
            'afterValidateCallback'  => new \yii\web\JsExpression(<<<JS
            function(jForm, ajax)
            {
                var success_modal = "{$success_modal_id}";

                var handler = new sx.classes.AjaxHandlerStandartRespose(ajax, {
                    'blockerSelector' : $('#' + jForm.attr('id')).closest('.modal-body'),
                    'enableBlocker' : true,
                });

                handler.bind('success', function(e, response)
                {
                    _.delay(function()
                    {
                        $('div').modal('hide');

                        _.delay(function()
                        {
                            if (success_modal)
                            {
                                $('#' + success_modal).modal('show');
                            }
                        }, 300);

                    }, 300);
                });

                handler.bind('error', function(e, response)
                {
                    console.log(response);
                });
            }
JS
            ),
        ], $this->form_options));
    }

    /**
     * Renders the widget.
     */
    public function run()
    {
        $this->model = new ShopQuantityNoticeEmail();
        $this->model->loadDefaultValues();
        $this->model->shop_product_id = $this->product_id;

        if (!$this->model->shop_product_id) {
            throw new InvalidConfigException("Porduct id not found");
        }

        if (!$this->view_file) {
            if ($this->footer !== false) {
                $formId = $this->id."-form";
                $this->footer = '
                    <button class="btn btn-primary" onclick="$(\'#'.$formId.'\').submit(); return false;">'.\Yii::t('skeeks/shop/app',
                        'Submit').'</button>

                ';

                /*<button type="button" class="btn btn-default" data-dismiss="modal">' . \Yii::t('skeeks/shop/app', 'Close') . '</button>*/
            }

            echo $this->render('notify-modal-email');
        } else {
            echo $this->render($this->view_file);
        }

        \skeeks\cms\base\widgets\ActiveFormAjaxSubmit::end();
        parent::run();
    }
}


<?php
/**
 * @author Semenov Alexander <semenov@skeeks.com>
 * @link http://skeeks.com/
 * @copyright 2010 SkeekS (СкикС)
 * @date 08.10.2015
 */

namespace skeeks\cms\shop\widgets;

use skeeks\cms\mail\helpers\Html;
use skeeks\cms\models\CmsUser;
use skeeks\cms\modules\admin\widgets\AdminImagePreviewWidget;
use yii\base\Widget;

/**
 * Class AdminBuyerUserWidget
 * @package skeeks\cms\shop\widgets
 */
class AdminBuyerUserWidget extends Widget
{
    /**
     * @var CmsUser
     */
    public $user = null;

    /**
     * Подготовка данных для шаблона
     * @return $this
     */
    public function run()
    {
        if (!$this->user) {
            return '-';
        }

        return (new AdminImagePreviewWidget([
                'image'    => $this->user ? $this->user->image : null,
                'maxWidth' => "25px",
            ]))->run()." ".Html::a($this->user->displayName, \skeeks\cms\helpers\UrlHelper::construct([
                '/shop/admin-buyer-user/update',
                'pk' => $this->user->id,
            ])->enableAdmin(), [
                'data-pjax' => 0,
            ]);
    }

}


<?php
/**
 * @author Semenov Alexander <semenov@skeeks.com>
 * @link http://skeeks.com/
 * @copyright 2010 SkeekS (СкикС)
 * @date 12.10.2015
 */

namespace skeeks\cms\shop\paySystems;

use skeeks\cms\shop\components\PaySystemHandlerComponent;
use skeeks\cms\shop\models\ShopBill;
use skeeks\cms\shop\models\ShopOrder;
use yii\bootstrap\Alert;
use yii\helpers\ArrayHelper;
use yii\widgets\ActiveForm;

/**
 * @property string $baseUrl
 *
 * Class YandexKassaPaySystem
 * @package skeeks\cms\shop\paySystems
 */
class YandexKassaPaySystem extends PaySystemHandlerComponent
{
    const SECURITY_MD5 = 'MD5';
    const SECURITY_PKCS7 = 'PKCS7';

    public $isLive = false;
    public $shop_password;
    public $security_type = self::SECURITY_MD5;
    public $shop_id;
    public $scid;
    public $payment_type = "";
    /**
     * Можно задать название и описание компонента
     * @return array
     */
    static public function descriptorConfig()
    {
        return array_merge(parent::descriptorConfig(), [
            'name' => \Yii::t('skeeks/shop/app', 'YandexKassa'),
        ]);
    }
    static public function getRequest()
    {
        if (\Yii::$app->request->contentType == "application/pkcs7-mime") {
            return static::_verifySign();
        }

        return $_REQUEST;
    }
    /**
     * Checking for sign when XML/PKCS#7 scheme is used.
     * @return array if request is successful, returns key-value array of request params, null otherwise.
     */
    static private function _verifySign()
    {
        $descriptorspec = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => ["pipe", "w"]];
        $certificate = \Yii::getAlias('@skeeks/cms/shop/paySystems/yandexkassa/pksc7-key-for-encode.cer');

        \Yii::info("_verifySign(): {$certificate}", YandexKassaPaySystem::class);

        $process = proc_open('openssl smime -verify -inform PEM -nointern -certfile '.$certificate.' -CAfile '.$certificate,
            $descriptorspec, $pipes);

        if (is_resource($process)) {

            \Yii::info("_verifySign() is_resource: true", YandexKassaPaySystem::class);

            // Getting data from request body.
            $data = file_get_contents("php://input"); // "php://input"
            fwrite($pipes[0], $data);
            fclose($pipes[0]);
            $content = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            $resCode = proc_close($process);

            if ($resCode != 0) {
                \Yii::info("resCode(): {$resCode}", YandexKassaPaySystem::class);
                return null;
            } else {
                \Yii::info("_verifySign() content: {$content}", YandexKassaPaySystem::class);
                $xml = simplexml_load_string($content);
                $array = json_decode(json_encode($xml), true);
                return $array["@attributes"];
            }
        }

        return null;
    }
    public static function formatDateForMWS(\DateTime $date)
    {
        $performedDatetime = $date->format("Y-m-d")."T".$date->format("H:i:s").".000Z";
        return $performedDatetime;
    }
    public function getBaseUrl()
    {
        return $this->isLive ? 'https://money.yandex.ru/eshop.xml' : 'https://demomoney.yandex.ru/eshop.xml';
    }
    public function rules()
    {
        return ArrayHelper::merge(parent::rules(), [
            [['shop_password'], 'string'],
            [['security_type'], 'string'],
            [['shop_id'], 'string'],
            [['scid'], 'string'],
            [['isLive'], 'boolean'],
            [['payment_type'], 'string'],
        ]);
    }
    public function attributeLabels()
    {
        return ArrayHelper::merge(parent::attributeLabels(), [
            'isLive'         => 'Рабочий режим',
            'sMerchantPass2' => 'sMerchantPass2',
            'shop_password' => 'Секретный ключ',
        ]);
    }
    public function attributeHints()
    {
        return ArrayHelper::merge(parent::attributeHints(), [
            'isLive'       => '',
            'payment_type' => 'Смотреть https://tech.yandex.ru/money/doc/payment-solution/reference/payment-type-codes-docpage/',
        ]);
    }
    /**
     * @param ShopOrder $shopOrder
     * @return $this
     */
    public function paymentResponse(ShopOrder $shopOrder)
    {
        return \Yii::$app->response->redirect(['shop/yandex-kassa/order-form', 'key' => $shopOrder->key]);;
    }

    /**
     * @param ShopOrder $shopOrder
     * @return $this
     */
    public function actionPaymentResponse(ShopBill $shopBill)
    {
        return \Yii::$app->response->redirect(['shop/yandex-kassa/bill-form', 'code' => $shopBill->code]);;
    }

    public function renderConfigForm(ActiveForm $activeForm)
    {

        //echo $activeForm->field($this, 'isLive')->checkbox();
        echo $activeForm->field($this, 'shop_password');
        //echo $activeForm->field($this, 'security_type');
        echo $activeForm->field($this, 'shop_id');
        //echo $activeForm->field($this, 'scid');
        //echo $activeForm->field($this, 'payment_type');

        echo Alert::widget([
            'options' => [
                'class' => 'alert-info',
            ],
            'body'    => <<<HTML
Подключение магазина

В настройках вашего магазина на yandex укажите удрес для уведомлений:

Укажите https://printberi.ru/shop/yandex-kassa/payment-listener

HTML
            ,
        ]);

    }
    /**
     * @param $request
     *
     * @return bool
     */
    public function checkRequest($request)
    {
        if ($this->security_type == static::SECURITY_MD5) {
            if ($this->checkRequestMD5($request)) {
                \Yii::info("checkRequest true", YandexKassaPaySystem::class);
                return true;
            }
        } else {
            if ($yandexKassa->security_type == YandexKassaPaySystem::SECURITY_PKCS7) {
                // Checking for a certificate sign. If the checking fails, respond with "200" error code.
                if (($request = $this->verifySign()) == null) {
                    $response = $this->buildResponse($this->action, null, 200);
                    $this->sendResponse($response);
                }
                $this->log("Request: ".print_r($request, true));

                //TODO:; make it's
                \Yii::error('SECURITY_PKCS7 — todo:: not realized', YandexKassaPaySystem::class);
            }
        }

        \Yii::info("checkRequest true", YandexKassaPaySystem::class);
        return true;
    }
    /**
     * Checking the MD5 sign.
     * @param  array $request payment parameters
     * @return bool true if MD5 hash is correct
     */
    public function checkRequestMD5($request)
    {

        //return true;

        $str = $request['action'].";".
            $request['orderSumAmount'].";".$request['orderSumCurrencyPaycash'].";".
            $request['orderSumBankPaycash'].";".$request['shopId'].";".
            $request['invoiceId'].";".$request['customerNumber'].";".$this->shop_password;

        \Yii::info("String to md5: ".$str, static::class);

        $md5 = strtoupper(md5($str));
        if ($md5 != strtoupper($request['md5'])) {
            \Yii::error("Wait for md5:".$md5.", recieved md5: ".$request['md5'], self::class);
            return false;
        }
        return true;
    }
    /**
     * Checking for sign when XML/PKCS#7 scheme is used.
     * @return array if request is successful, returns key-value array of request params, null otherwise.
     */
    private function verifySign()
    {
        $descriptorspec = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => ["pipe", "w"]];
        $certificate = 'yamoney.pem';
        $process = proc_open('openssl smime -verify -inform PEM -nointern -certfile '.$certificate.' -CAfile '.$certificate,
            $descriptorspec, $pipes);
        if (is_resource($process)) {
            // Getting data from request body.
            $data = file_get_contents($this->settings->request_source); // "php://input"
            fwrite($pipes[0], $data);
            fclose($pipes[0]);
            $content = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            $resCode = proc_close($process);
            if ($resCode != 0) {
                return null;
            } else {
                $this->log("Row xml: ".$content);
                $xml = simplexml_load_string($content);
                $array = json_decode(json_encode($xml), true);
                return $array["@attributes"];
            }
        }
        return null;
    }
    /**
     * Building XML response.
     * @param  string $functionName "checkOrder" or "paymentAviso" string
     * @param  string $invoiceId transaction number
     * @param  string $result_code result code
     * @param  string $message error message. May be null.
     * @return string                prepared XML response
     */
    public function buildResponse($functionName, $invoiceId, $result_code, $message = null)
    {
        try {
            $performedDatetime = self::formatDate(new \DateTime());
            $response = '<?xml version="1.0" encoding="UTF-8"?><'.$functionName.'Response performedDatetime="'.$performedDatetime.
                '" code="'.$result_code.'" '.($message != null ? 'message="'.$message.'"' : "").' invoiceId="'.$invoiceId.'" shopId="'.$this->shop_id.'"/>';
            return $response;
        } catch (\Exception $e) {
            \Yii::error($e->getMessage(), static::class);
        }
        return null;
    }
    public static function formatDate(\DateTime $date)
    {
        $performedDatetime = $date->format("Y-m-d")."T".$date->format("H:i:s").".000".$date->format("P");
        return $performedDatetime;
    }
}


<?php
/**
 * @link https://cms.skeeks.com/
 * @copyright Copyright (c) 2010 SkeekS
 * @license https://cms.skeeks.com/license/
 * @author Semenov Alexander <semenov@skeeks.com>
 */

namespace skeeks\cms\shop\models;

use skeeks\cms\base\ActiveRecord;
use skeeks\cms\components\Cms;
use skeeks\cms\models\CmsContentElement;
use skeeks\cms\models\CmsSite;
use skeeks\cms\models\CmsUser;
use skeeks\cms\models\User;
use skeeks\cms\money\Money;
use skeeks\cms\shop\helpers\ProductPriceHelper;
use yii\base\Exception;
use yii\base\UserException;
use yii\db\ActiveQuery;

/**
 * @author Semenov Alexander <semenov@skeeks.com>
 *
 * Это объект корзины
 *
 * @property integer           $id
 * @property integer           $created_by
 * @property integer           $updated_by
 * @property integer           $created_at
 * @property integer           $updated_at
 * @property integer           $cms_user_id Пользователь сайта
 * @property integer           $shop_order_id
 *
 * ***
 *
 * @property CmsUser           $cmsUser
 *
 * @property ShopBasket[]      $shopBaskets
 * @property ShopDelivery      $delivery
 * @property ShopBuyer         $buyer
 * @property ShopPaySystem     $paySystem
 *
 * @property ShopPersonType    $personType
 * @property CmsSite           $site
 *
 * @property int               $countShopBaskets
 * @property float             $quantity
 *
 * @property ShopBuyer[]       $shopBuyers
 * @property ShopPaySystem[]   $paySystems
 *
 *
 *
 *
 * @property ShopOrder         $shopOrder
 * @property Money             $money
 * @property Money             $moneyOriginal
 * @property Money             $moneyVat
 * @property Money             $moneyDiscount
 * @property Money             $moneyDelivery
 *
 * @property int               $weight
 * @property bool              $isEmpty
 *
 * @property ShopTypePrice     $buyTypePrices
 * @property ShopTypePrice     $viewTypePrices
 * @property CmsContentElement $store
 */
class ShopCart extends ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%shop_cart}}';
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return array_merge(parent::attributeLabels(), [
            'cms_user_id'   => \Yii::t('skeeks/shop/app', 'User site'),
            'shop_order_id' => \Yii::t('skeeks/shop/app', 'Заказ'),
        ]);
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return array_merge(parent::rules(), [
            [
                [
                    'created_by',
                    'updated_by',
                    'created_at',
                    'updated_at',
                    'cms_user_id',
                    //'shop_order_id',
                ],
                'integer',
            ],
            [['cms_user_id'], 'unique'],

            [['shop_order_id'], 'default', 'value' => function(self $model) {
                $shopOrder = $this->shopOrder;

                if ($shopOrder->isNewRecord) {
                    if (!$shopOrder->save()) {
                        throw new UserException("Заказ-черновик не создан: ".print_r($shopOrder->errors, true));
                    }
                }

                return $shopOrder->id;
            }],
        ]);
    }
    /**
     * @return array
     */
    public function extraFields()
    {
        return [
            'countShopBaskets',
            'shopBaskets',
            'quantity',
        ];
    }
    /**
     * @return \yii\db\ActiveQuery
     */
    public function getCmsUser()
    {
        return $this->hasOne(User::class, ['id' => 'cms_user_id']);
    }

    /**
     * @var null|ShopOrder
     */
    protected $_newShopOrder = null;

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getShopOrder()
    {
        if ($this->isNewRecord) {

            if (!$this->_newShopOrder) {
                $this->_newShopOrder = new ShopOrder();

                if (\Yii::$app->cms->site) {
                    $this->_newShopOrder->cms_site_id = \Yii::$app->cms->site->id;
                }

                //Для того чтобы применились default rules
                $this->_newShopOrder->validate();
            }

            return $this->_newShopOrder;
        } elseif (!$this->shop_order_id) {
            //todo: добавить транзакцию
            $order = new ShopOrder();

            if (\Yii::$app->cms->site) {
                $order->cms_site_id = \Yii::$app->cms->site->id;
            }

                //Для того чтобы применились default rules
            if (!$order->save()) {
                throw new Exception("Заказ черновик не создан");
            }

            $this->shop_order_id = $order->id;
            if (!$this->save()) {
                throw new Exception("Заказ черновик не создан");
            }

            return $order;
        }

        return $this->hasOne(ShopOrder::class, ['id' => 'shop_order_id']);
    }

    /**
     * @return \yii\db\ActiveQuery
     * @deprecated
     */
    public function getShopBaskets()
    {
        return $this->shopOrder->getShopOrderItems();
    }
    /**
     * Количество позиций в корзине
     *
     * @return int
     */
    public function getCountShopBaskets()
    {
        return $this->shopOrder->countShopOrderItems;
    }
    /**
     * @return float
     * @deprecated
     */
    public function getQuantity()
    {
        return $this->shopOrder->quantity;
    }

    /**
     *
     * Итоговая стоимость корзины с учетом скидок, доставок, наценок, то что будет платить человек
     *
     * @return Money
     */
    public function getMoney()
    {
        return $this->shopOrder->calcMoney;

        /*$money = \Yii::$app->money->newMoney();

        foreach ($this->shopBaskets as $shopBasket) {
            $money = $money->add($shopBasket->money->multiply($shopBasket->quantity));
        }

        if ($this->moneyDelivery) {
            $money = $money->add($this->moneyDelivery);
        }

        return $money;*/
    }

    /**
     * Итоговая стоимость корзины, без учета скидок
     * @return Money
     */
    public function getMoneyOriginal()
    {
        return $this->shopOrder->calcMoneyItems;
    }
    /**
     * @return int
     */
    public function getWeight()
    {
        $result = 0;

        foreach ($this->shopBaskets as $shopBasket) {
            $result = $result + ($shopBasket->weight * $shopBasket->quantity);
        }

        return $result;
    }
    /**
     *
     * Итоговая стоимость налога
     * @return Money
     */
    public function getMoneyVat()
    {
        return $this->shopOrder->calcMoneyVat;
        /*$money = \Yii::$app->money->newMoney();

        foreach ($this->shopBaskets as $shopBasket) {
            $money = $money->add($shopBasket->moneyVat->multiply($shopBasket->quantity));
        }

        return $money;*/
    }
    /**
     *
     * Итоговая скидка по всей корзине
     *
     * @return Money
     * @deprecated
     */
    public function getMoneyDiscount()
    {
        return $this->shopOrder->calcMoneyDiscount;
        /*
        $money = \Yii::$app->money->newMoney();
        foreach ($this->shopBaskets as $shopBasket) {
            $money = $money->add($shopBasket->moneyDiscount->multiply($shopBasket->quantity));
        }
        return $money;*/
    }

    /**
     * Итоговая сумма доставки
     * @return Money
     * @deprecated
     */
    public function getMoneyDelivery()
    {
        return $this->shopOrder->calcMoneyDelivery;
    }

    /**
     * @return bool
     */
    public function getIsEmpty()
    {
        return $this->isEmpty();
    }
    /**
     * @return bool
     */
    public function isEmpty()
    {
        return (bool)($this->shopOrder->countShopOrderItems == 0);
    }

    /**
     *
     * @return ActiveQuery
     */
    public function getShopBuyers()
    {
        return $this->hasMany(ShopBuyer::class, ['cms_user_id' => 'id'])->via('user');
    }
    /**
     * Доступные платежные системы
     *
     * @return ShopPaySystem[]
     */
    public function getPaySystems()
    {
        if (!$this->personType) {
            $query = ShopPaySystem::find()->andWhere([ShopPaySystem::tableName().".active" => Cms::BOOL_Y]);
            $query->multiple = true;

            return $query;
        }

        return $this->personType->getPaySystems()->andWhere([ShopPaySystem::tableName().".active" => Cms::BOOL_Y]);
    }

    /**
     *
     * Доступные типы цен для просмотра
     *
     * @return ShopTypePrice[]
     * @deprecated
     */
    public function getViewTypePrices()
    {
        $result = [];

        foreach (\Yii::$app->shop->shopTypePrices as $typePrice) {
            if (\Yii::$app->authManager->checkAccess($this->user->id, $typePrice->viewPermissionName)) {
                $result[$typePrice->id] = $typePrice;
            }
        }

        return $result;
    }

    /**
     * @return $this
     * @deprecated
     */
    public function recalculate()
    {
        return $this->shopOrder->recalculate();
        /*if ($this->shopBaskets) {
            foreach ($this->shopBaskets as $shopBasket) {
                $shopBasket->recalculate()->save();
            }
        }

        return $this;*/
    }

    /**
     * @param ShopCmsContentElement $shopCmsContentElement
     * @return ProductPriceHelper
     * @deprecated
     */
    public function getProductPriceHelper(ShopCmsContentElement $shopCmsContentElement)
    {
        return $this->shopOrder->getProductPriceHelper($shopCmsContentElement);
    }

    /**
     *
     * Доступные цены для покупки на сайте
     *
     * @return ShopTypePrice[]
     * @deprecated
     */
    public function getBuyTypePrices()
    {
        return $this->shopOrder ? $this->shopOrder->buyTypePrices : \Yii::$app->shop->canBuyTypePrices;
    }

    /**
     * @return ShopDiscountCoupon[]
     * @deprecated
     */
    public function getDiscountCoupons()
    {
        return ($this->shopOrder && $this->shopOrder->shopDiscountCoupons ? $this->shopOrder->shopDiscountCoupons : []);
    }

}