CartCurrencyNegotiator   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 200
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 9
dl 0
loc 200
ccs 0
cts 122
cp 0
rs 10
c 0
b 0
f 0

13 Methods

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