Completed
Push — master ( 753652...d0e0e5 )
by Dmitry
04:54
created

CartCurrencyNegotiator::renderBalance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
namespace hipanel\modules\finance\widgets;
4
5
use hipanel\helpers\ArrayHelper;
6
use hipanel\modules\client\models\Client;
7
use hipanel\modules\finance\cart\CartCalculator;
8
use hipanel\modules\finance\models\ExchangeRate;
9
use hipanel\modules\finance\models\Purse;
10
use hipanel\modules\finance\Module;
11
use hiqdev\yii2\cart\ShoppingCart;
12
use LogicException;
13
use OutOfRangeException;
14
use Yii;
15
use yii\base\Widget;
16
17
/**
18
 * Class CartCurrencyNegotiator is a widget that suggests
19
 * possible cart payment options, that may include currency converting
20
 *
21
 * @author Dmytro Naumenko <[email protected]>
22
 */
23
final class CartCurrencyNegotiator extends Widget
24
{
25
    /**
26
     * @var ShoppingCart
27
     */
28
    public $cart;
29
30
    /**
31
     * @var Client
32
     */
33
    public $client;
34
35
    /**
36
     * @var Module
37
     */
38
    public $merchantModule;
39
    /**
40
     * @var CartCalculator
41
     */
42
    private $calculator;
43
44
    public function __construct($config = [])
45
    {
46
        parent::__construct($config);
47
48
        $this->calculator = new CartCalculator($this->cart);
49
    }
50
51
    public function run()
52
    {
53
        $cartCurrency = $this->cart->getCurrency();
54
        $this->renderCurrencyOptions($this->cart->getTotal(), $cartCurrency);
55
56
        $convertibleCurrencies = $this->convertibleCurrencies(
57
            ArrayHelper::getColumn($this->client->purses, 'currency'),
58
            $cartCurrency
59
        );
60
        foreach ($convertibleCurrencies as $rate) {
61
            $this->renderCurrencyOptions($this->getCartAmountInCurrency($rate->from), $rate->from);
62
        }
63
    }
64
65
    /**
66
     * @return string formatted cart totals
67
     * @throws \yii\base\InvalidConfigException
68
     */
69
    public function renderCartTotals(): string
70
    {
71
        return Yii::$app->formatter->asCurrency($this->cart->getTotal(), $this->cart->getCurrency());
72
    }
73
74
    /**
75
     * @param string $currency
76
     * @return float
77
     * @throws \yii\web\UnprocessableEntityHttpException
78
     */
79
    public function getCartAmountInCurrency(string $currency): float
80
    {
81
        $cartCurrency = $this->cart->getCurrency();
82
        if ($cartCurrency === $currency) {
83
            return $this->cart->getTotal();
84
        }
85
86
        $convertibleCurrencies = $this->convertibleCurrencies(
87
            ArrayHelper::getColumn($this->client->purses, 'currency'),
88
            $cartCurrency
89
        );
90
91
        foreach ($convertibleCurrencies as $rate) {
92
            if ($rate->from === strtoupper($currency)) {
93
                return $this->cart->getTotal() / $rate->rate;
94
            }
95
        }
96
97
        throw new OutOfRangeException("No exchange rate from \"$cartCurrency\" to \"$currency\"");
98
    }
99
100
    public function getFullAmount(string $currency): float
101
    {
102
        $purse = $this->getClientPurseByCurrency($currency);
103
104
        if ($purse->getBudget() < 0) {
105
            return -$purse->getBudget() + $this->getCartAmountInCurrency($currency);
106
        }
107
108
        return $this->getCartAmountInCurrency($currency);
109
    }
110
111
    public function getPartialAmount(string $currency): float
112
    {
113
        $purse = $this->getClientPurseByCurrency($currency);
114
115
        return $this->getCartAmountInCurrency($currency) - $purse->getBudget();
116
    }
117
118
    public function renderBalance(): string
119
    {
120
        return $this->render('balance');
121
    }
122
123
    /**
124
     * @param string[] $clientPursesCurrencies
125
     * @param string $cartCurrency
126
     * @return ExchangeRate[]
127
     * @throws \yii\web\UnprocessableEntityHttpException
128
     */
129
    private function convertibleCurrencies(array $clientPursesCurrencies, string $cartCurrency): array
130
    {
131
        /** @var ExchangeRate[] $rates */
132
        $rates = Yii::$app->cache->getOrSet(['exchange-rates', Yii::$app->user->id], static function () {
133
            return ExchangeRate::find()->select(['from', 'to', 'rate'])->all();
134
        }, 3600);
135
136
        $result = [];
137
138
        foreach ($clientPursesCurrencies as $currency) {
139
            foreach ($rates as $rate) {
140
                if ($rate->from === strtoupper($currency) && $rate->to === strtoupper($cartCurrency)) {
141
                    $result[] = $rate;
142
                }
143
            }
144
        }
145
146
        return $result;
147
    }
148
149
    public function getViewPath()
150
    {
151
        return parent::getViewPath() . DIRECTORY_SEPARATOR . 'cartPaymentOptions';
152
    }
153
154
    public function render($view, $params = [])
155
    {
156
        return parent::render($view, array_merge($params, [
157
            'cart' => $this->cart,
158
            'client' => $this->client,
159
        ]));
160
    }
161
162
    private function renderCurrencyOptions(float $amount, string $currency): void
163
    {
164
        $currency = strtolower($currency);
165
166
        $purse = $this->getClientPurseByCurrency($currency);
167
        $options = [
168
            'purse' => $purse,
169
            'amount' => $amount,
170
            'currency' => $currency,
171
        ];
172
173 View Code Duplication
        if ($purse->getBudget() >= $amount) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174
            echo $this->render('enough', $options);
175
        } elseif ($purse->getBudget() > 0) {
176
            echo $this->render('partial', array_merge($options, [
177
                'amount' => $this->getPartialAmount($currency)
178
            ]));
179
        }
180
181 View Code Duplication
        if ($purse->getBudget() < 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
182
            echo $this->render('full_payment', array_merge($options, [
183
                'amount' => $this->getFullAmount($currency),
184
                'debt' => -$purse->getBudget(),
185
            ]));
186
        } else {
187
            echo $this->render('full_payment', $options);
188
        }
189
    }
190
191
    private function getClientPurseByCurrency(string $currency): Purse
192
    {
193
        $purse = $this->client->getPurseByCurrency($currency);
194
        if ($purse !== null) {
195
            return $purse;
196
        }
197
198
        $firstPurse = $this->client->getPrimaryPurse();
199
        if ($firstPurse === null) {
200
            throw new LogicException('Primary purse was not found');
201
        }
202
203
        $fakePurse = clone $firstPurse;
204
        $fakePurse->id = null;
205
        $fakePurse->currency = $currency;
206
        $fakePurse->balance = 0;
207
        $fakePurse->credit = 0;
208
209
        return $fakePurse;
210
    }
211
}
212