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) { |
|
|
|
|
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) { |
|
|
|
|
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
|
|
|
|
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.