Completed
Push — master ( ef05f2...31c038 )
by Dmitry
12:13
created

CartCurrencyNegotiator::numberFormat()   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 2
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(
55
            $this->numberFormat($this->cart->getTotal(), $cartCurrency),
56
            $cartCurrency
57
        );
58
59
        $convertibleCurrencies = $this->convertibleCurrencies(
60
            ArrayHelper::getColumn($this->client->purses, 'currency'),
61
            $cartCurrency
62
        );
63
        foreach ($convertibleCurrencies as $rate) {
64
            $this->renderCurrencyOptions($this->getCartAmountInCurrency($rate->from), $rate->from);
65
        }
66
    }
67
68
    /**
69
     * @return string formatted cart totals
70
     * @throws \yii\base\InvalidConfigException
71
     */
72
    public function renderCartTotals(): string
73
    {
74
        return Yii::$app->formatter->asCurrency($this->cart->getTotal(), $this->cart->getCurrency());
75
    }
76
77
    /**
78
     * @param string $currency
79
     * @return float
80
     * @throws \yii\web\UnprocessableEntityHttpException
81
     */
82
    public function getCartAmountInCurrency(string $currency): float
83
    {
84
        $cartCurrency = $this->cart->getCurrency();
85
        if ($cartCurrency === $currency) {
86
            return $this->numberFormat($this->cart->getTotal(), $currency);
87
        }
88
89
        $convertibleCurrencies = $this->convertibleCurrencies(
90
            ArrayHelper::getColumn($this->client->purses, 'currency'),
91
            $cartCurrency
92
        );
93
94
        foreach ($convertibleCurrencies as $rate) {
95
            if ($rate->from === strtoupper($currency)) {
96
                return $this->numberFormat($this->cart->getTotal() / $rate->rate, $cartCurrency);
97
            }
98
        }
99
100
        throw new OutOfRangeException("No exchange rate from \"$cartCurrency\" to \"$currency\"");
101
    }
102
103
    public function getFullAmount(string $currency): float
104
    {
105
        $purse = $this->getClientPurseByCurrency($currency);
106
107
        if ($purse->getBudget() < 0) {
108
            return -$purse->getBudget() + $this->getCartAmountInCurrency($currency);
109
        }
110
111
        return $this->getCartAmountInCurrency($currency);
112
    }
113
114
    public function getPartialAmount(string $currency): float
115
    {
116
        $purse = $this->getClientPurseByCurrency($currency);
117
118
        return $this->getCartAmountInCurrency($currency) - $purse->getBudget();
119
    }
120
121
    public function renderBalance(): string
122
    {
123
        return $this->render('balance');
124
    }
125
126
    /**
127
     * @param string[] $clientPursesCurrencies
128
     * @param string $cartCurrency
129
     * @return ExchangeRate[]
130
     * @throws \yii\web\UnprocessableEntityHttpException
131
     */
132
    private function convertibleCurrencies(array $clientPursesCurrencies, string $cartCurrency): array
133
    {
134
        /** @var ExchangeRate[] $rates */
135
        $rates = Yii::$app->cache->getOrSet(['exchange-rates', Yii::$app->user->id], static function () {
136
            return ExchangeRate::find()->select(['from', 'to', 'rate'])->all();
137
        }, 3600);
138
139
        $result = [];
140
141
        foreach ($clientPursesCurrencies as $currency) {
142
            foreach ($rates as $rate) {
143
                if ($rate->from === strtoupper($currency) && $rate->to === strtoupper($cartCurrency)) {
144
                    $result[] = $rate;
145
                }
146
            }
147
        }
148
149
        return $result;
150
    }
151
152
    public function getViewPath()
153
    {
154
        return parent::getViewPath() . DIRECTORY_SEPARATOR . 'cartPaymentOptions';
155
    }
156
157
    public function render($view, $params = [])
158
    {
159
        return parent::render($view, array_merge($params, [
160
            'cart' => $this->cart,
161
            'client' => $this->client,
162
        ]));
163
    }
164
165
    private function renderCurrencyOptions(float $amount, string $currency): void
166
    {
167
        $currency = strtolower($currency);
168
169
        $purse = $this->getClientPurseByCurrency($currency);
170
        $options = [
171
            'purse' => $purse,
172
            'amount' => $amount,
173
            'currency' => $currency,
174
        ];
175
176
        if ($purse->getBudget() >= $amount || Yii::$app->user->can('manage')) {
177
            echo $this->render('enough', $options);
178
        } elseif ($purse->getBudget() > 0) {
179
            echo $this->render('partial', array_merge($options, [
180
                'amount' => $this->getPartialAmount($currency)
181
            ]));
182
        }
183
184
        if ($purse->getBudget() < 0) {
185
            echo $this->render('full_payment', array_merge($options, [
186
                'amount' => $this->getFullAmount($currency),
187
                'debt' => -$purse->getBudget(),
188
            ]));
189
        } else {
190
            echo $this->render('full_payment', $options);
191
        }
192
    }
193
194
    private function getClientPurseByCurrency(string $currency): Purse
195
    {
196
        $purse = $this->client->getPurseByCurrency($currency);
197
        if ($purse !== null) {
198
            return $purse;
199
        }
200
201
        $firstPurse = $this->client->getPrimaryPurse();
202
        if ($firstPurse === null) {
203
            throw new LogicException('Primary purse was not found');
204
        }
205
206
        $fakePurse = clone $firstPurse;
207
        $fakePurse->id = null;
208
        $fakePurse->currency = $currency;
209
        $fakePurse->balance = 0;
210
        $fakePurse->credit = 0;
211
212
        return $fakePurse;
213
    }
214
215
    private function numberFormat(string $amount, string $currencyCode): string
0 ignored issues
show
Unused Code introduced by
The parameter $currencyCode is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
216
    {
217
        return number_format($amount, 2, '.', '');
218
    }
219
}
220