This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace ActionM\UnitPay; |
||
4 | |||
5 | use Illuminate\Http\Request; |
||
6 | use ActionM\UnitPay\Events\UnitPayEvent; |
||
7 | use Illuminate\Support\Facades\Validator; |
||
8 | use ActionM\UnitPay\Exceptions\InvalidConfiguration; |
||
9 | |||
10 | class UnitPay |
||
11 | { |
||
12 | public function __construct() |
||
13 | { |
||
14 | } |
||
15 | |||
16 | /** |
||
17 | * Allow access, if the ip address is in the whitelist. |
||
18 | * @param $ip |
||
19 | * @return bool |
||
20 | */ |
||
21 | public function allowIP($ip) |
||
22 | { |
||
23 | // Allow local ip |
||
24 | if ($ip == '127.0.0.1') { |
||
25 | return true; |
||
26 | } |
||
27 | |||
28 | return in_array($ip, config('unitpay.allowed_ips')); |
||
29 | } |
||
30 | |||
31 | /** |
||
32 | * Return JSON error message. |
||
33 | * @param $message |
||
34 | * @return mixed |
||
35 | */ |
||
36 | public function responseError($message) |
||
37 | { |
||
38 | $result['error']['message'] = $message; |
||
0 ignored issues
–
show
|
|||
39 | |||
40 | return $result; |
||
41 | } |
||
42 | |||
43 | /** |
||
44 | * Return JSON success message. |
||
45 | * @param $message |
||
46 | * @return mixed |
||
47 | */ |
||
48 | public function responseOK($message) |
||
49 | { |
||
50 | $result['result']['message'] = $message; |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code. Let’s take a look at an example: foreach ($collection as $item) {
$myArray['foo'] = $item->getFoo();
if ($item->hasBar()) {
$myArray['bar'] = $item->getBar();
}
// do something with $myArray
}
As you can see in this example, the array This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop. ![]() |
|||
51 | |||
52 | return $result; |
||
53 | } |
||
54 | |||
55 | /** |
||
56 | * Fill event details to pass the title and request params as array. |
||
57 | * @param $event_type |
||
58 | * @param $event_title |
||
59 | * @param Request $request |
||
60 | */ |
||
61 | public function eventFillAndSend($event_type, $event_title, Request $request) |
||
62 | { |
||
63 | $event_details = [ |
||
64 | 'title' => 'UnitPay: '.$event_title, |
||
65 | 'ip' => $request->ip(), |
||
66 | 'request' => $request->all(), |
||
67 | ]; |
||
68 | |||
69 | event( |
||
70 | new UnitPayEvent($event_type, $event_details) |
||
71 | ); |
||
72 | } |
||
73 | |||
74 | /** |
||
75 | * Return hash for the order form params. |
||
76 | * @param $account |
||
77 | * @param $currency |
||
78 | * @param $desc |
||
79 | * @param $sum |
||
80 | * @param $secretKey |
||
81 | * @return string |
||
82 | */ |
||
83 | public function getFormSignature($account, $currency, $desc, $sum, $secretKey) |
||
84 | { |
||
85 | $hashStr = $account.'{up}'.$currency.'{up}'.$desc.'{up}'.$sum.'{up}'.$secretKey; |
||
86 | |||
87 | return hash('sha256', $hashStr); |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * Return hash for params from UnitPay gate. |
||
92 | * @param $method |
||
93 | * @param array $params |
||
94 | * @param $secretKey |
||
95 | * @return string |
||
96 | */ |
||
97 | public function getSignature($method, array $params, $secretKey) |
||
98 | { |
||
99 | ksort($params); |
||
100 | unset($params['sign'], $params['signature']); |
||
101 | array_push($params, $secretKey); |
||
102 | array_unshift($params, $method); |
||
103 | |||
104 | return hash('sha256', implode('{up}', $params)); |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * Generate UnitPay order array with required array for order form. |
||
109 | * @param $payment_amount |
||
110 | * @param $payment_no |
||
111 | * @param $user_email |
||
112 | * @param $item_name |
||
113 | * @param $currency |
||
114 | * @return array |
||
115 | */ |
||
116 | public function generateUnitPayOrderWithRequiredFields($payment_amount, $payment_no, $user_email, $item_name, $currency) |
||
117 | { |
||
118 | $order = [ |
||
119 | 'PAYMENT_AMOUNT' => $payment_amount, |
||
120 | 'PAYMENT_NO' => $payment_no, |
||
121 | 'USER_EMAIL' => $user_email, |
||
122 | 'ITEM_NAME' => $item_name, |
||
123 | 'CURRENCY' => $currency, |
||
124 | ]; |
||
125 | |||
126 | $this->requiredOrderParamsCheck($order); |
||
127 | |||
128 | return $order; |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * Check required order params for order form and raise an exception if fails. |
||
133 | * @param $order |
||
134 | * @throws InvalidConfiguration |
||
135 | */ |
||
136 | public function requiredOrderParamsCheck($order) |
||
137 | { |
||
138 | $required_fields = [ |
||
139 | 'PAYMENT_AMOUNT', |
||
140 | 'PAYMENT_NO', |
||
141 | 'USER_EMAIL', |
||
142 | 'ITEM_NAME', |
||
143 | 'CURRENCY', |
||
144 | ]; |
||
145 | |||
146 | foreach ($required_fields as $key => $value) { |
||
147 | if (! array_key_exists($value, $order) || empty($order[$value])) { |
||
148 | throw InvalidConfiguration::generatePaymentFormOrderParamsNotSet($value); |
||
149 | } |
||
150 | } |
||
151 | |||
152 | $currency_arr = [ |
||
153 | 'RUB', |
||
154 | 'UAH', |
||
155 | 'BYR', |
||
156 | 'EUR', |
||
157 | 'USD', |
||
158 | ]; |
||
159 | |||
160 | if (! in_array($order['CURRENCY'], $currency_arr)) { |
||
161 | throw InvalidConfiguration::generatePaymentFormOrderInvalidCurrency($order['CURRENCY']); |
||
162 | } |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Generate html forms from view with payment buttons |
||
167 | * Note: you can customise the view via artisan:publish. |
||
168 | * @param $order |
||
169 | * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View |
||
170 | */ |
||
171 | public function generatePaymentForm($payment_amount, $payment_no, $user_email, $item_name, $currency) |
||
172 | { |
||
173 | $order = $this->generateUnitPayOrderWithRequiredFields($payment_amount, $payment_no, $user_email, $item_name, $currency); |
||
174 | |||
175 | $this->requiredOrderParamsCheck($order); |
||
176 | |||
177 | $payment_fields['LOCALE'] = config('unitpay.locale', 'ru'); |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
$payment_fields was never initialized. Although not strictly required by PHP, it is generally a good practice to add $payment_fields = array(); before regardless.
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code. Let’s take a look at an example: foreach ($collection as $item) {
$myArray['foo'] = $item->getFoo();
if ($item->hasBar()) {
$myArray['bar'] = $item->getBar();
}
// do something with $myArray
}
As you can see in this example, the array This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop. ![]() |
|||
178 | $payment_fields['PUB_KEY'] = config('unitpay.UNITPAY_PUBLIC_KEY'); |
||
179 | $payment_fields['PAYMENT_AMOUNT'] = $order['PAYMENT_AMOUNT']; |
||
180 | $payment_fields['PAYMENT_NO'] = $order['PAYMENT_NO']; |
||
181 | $payment_fields['USER_EMAIL'] = $order['USER_EMAIL']; |
||
182 | $payment_fields['ITEM_NAME'] = $order['ITEM_NAME']; |
||
183 | $payment_fields['CURRENCY'] = $order['CURRENCY']; |
||
184 | |||
185 | $payment_fields['SIGN'] = $this->getFormSignature( |
||
186 | $payment_fields['PAYMENT_NO'], |
||
187 | $payment_fields['CURRENCY'], |
||
188 | $payment_fields['ITEM_NAME'], |
||
189 | $payment_fields['PAYMENT_AMOUNT'], |
||
190 | config('unitpay.UNITPAY_SECRET_KEY') |
||
191 | ); |
||
192 | |||
193 | return view('unitpay::payment_form', compact('payment_fields')); |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * Validate request params from UnitPay gate. |
||
198 | * @param Request $request |
||
199 | * @return bool |
||
200 | */ |
||
201 | public function validate(Request $request) |
||
202 | { |
||
203 | $validator = Validator::make($request->all(), [ |
||
204 | 'method' => 'required|in:check,pay,error', |
||
205 | 'params.account' => 'required', |
||
206 | 'params.date' => 'required', |
||
207 | 'params.payerSum' => 'required', |
||
208 | 'params.payerCurrency' => 'required', |
||
209 | 'params.signature' => 'required', |
||
210 | 'params.orderSum' => 'required', |
||
211 | 'params.orderCurrency' => 'required', |
||
212 | 'params.unitpayId' => 'required', |
||
213 | ]); |
||
214 | |||
215 | if ($validator->fails()) { |
||
216 | return false; |
||
217 | } |
||
218 | |||
219 | return true; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Validate request signature from UnitPay gate. |
||
224 | * @param Request $request |
||
225 | * @return bool |
||
226 | */ |
||
227 | public function validateSignature(Request $request) |
||
228 | { |
||
229 | $sign = $this->getSignature($request->get('method'), $request->get('params'), config('unitpay.UNITPAY_SECRET_KEY')); |
||
230 | |||
231 | if ($request->input('params.signature') != $sign) { |
||
232 | return false; |
||
233 | } |
||
234 | |||
235 | return true; |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * Validate ip, request params and signature from UnitPay gate. |
||
240 | * @param Request $request |
||
241 | * @return bool |
||
242 | */ |
||
243 | public function validateOrderRequestFromGate(Request $request) |
||
244 | { |
||
245 | if (! $this->AllowIP($request->ip()) || ! $this->validate($request) || ! $this->validateSignature($request)) { |
||
246 | $this->eventFillAndSend('unitpay.error', 'validateOrderRequestFromGate', $request); |
||
247 | |||
248 | return false; |
||
249 | } |
||
250 | |||
251 | return true; |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Validate the required attributes of the found order. |
||
256 | * @param Request $request |
||
257 | * @param $order |
||
258 | * @return bool |
||
259 | */ |
||
260 | public function validateSearchOrderRequiredAttributes(Request $request, $order) |
||
261 | { |
||
262 | if (! $order) { |
||
263 | $this->eventFillAndSend('unitpay.error', 'orderNotFound', $request); |
||
264 | |||
265 | return false; |
||
266 | } |
||
267 | |||
268 | // check required found order attributes |
||
269 | $attr = ['UNITPAY_orderStatus', 'UNITPAY_orderSum', 'UNITPAY_orderCurrency']; |
||
270 | |||
271 | foreach ($attr as $k => $value) { |
||
272 | if (! $order->getAttribute($value)) { |
||
273 | $this->eventFillAndSend('unitpay.error', $value.'Invalid', $request); |
||
274 | |||
275 | return false; |
||
276 | } |
||
277 | } |
||
278 | |||
279 | // compare order attributes vs request params |
||
280 | $attr = ['UNITPAY_orderSum', 'UNITPAY_orderCurrency']; |
||
281 | foreach ($attr as $k => $value) { |
||
282 | if ($order->getAttribute($value) != $request->input('params.'.str_replace('UNITPAY_', '', $value))) { |
||
283 | $this->eventFillAndSend('unitpay.error', $value.'Invalid', $request); |
||
284 | |||
285 | return false; |
||
286 | } |
||
287 | } |
||
288 | |||
289 | return true; |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * Call SearchOrderFilter and check return order params. |
||
294 | * @param Request $request |
||
295 | * @return bool |
||
296 | * @throws InvalidConfiguration |
||
297 | */ |
||
298 | public function callFilterSearchOrder(Request $request) |
||
299 | { |
||
300 | $callable = config('unitpay.searchOrderFilter'); |
||
301 | |||
302 | if (! is_callable($callable)) { |
||
303 | throw InvalidConfiguration::searchOrderFilterInvalid(); |
||
304 | } |
||
305 | |||
306 | /* |
||
307 | * SearchOrderFilter |
||
308 | * Search order in the database and return order details |
||
309 | * Must return array with: |
||
310 | * |
||
311 | * orderStatus |
||
312 | * orderCurrency |
||
313 | * orderSum |
||
314 | */ |
||
315 | |||
316 | $order = $callable($request, $request->input('params.account')); |
||
317 | |||
318 | if (! $this->validateSearchOrderRequiredAttributes($request, $order)) { |
||
319 | return false; |
||
320 | } |
||
321 | |||
322 | return $order; |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Call PaidOrderFilter if order not paid. |
||
327 | * @param Request $request |
||
328 | * @param $order |
||
329 | * @return mixed |
||
330 | * @throws InvalidConfiguration |
||
331 | */ |
||
332 | public function callFilterPaidOrder(Request $request, $order) |
||
333 | { |
||
334 | $callable = config('unitpay.paidOrderFilter'); |
||
335 | |||
336 | if (! is_callable($callable)) { |
||
337 | throw InvalidConfiguration::orderPaidFilterInvalid(); |
||
338 | } |
||
339 | |||
340 | // unset the custom order attributes for Eloquent support |
||
341 | unset($order['UNITPAY_orderSum'], $order['UNITPAY_orderCurrency'], $order['UNITPAY_orderStatus']); |
||
342 | |||
343 | // Run PaidOrderFilter callback |
||
344 | return $callable($request, $order); |
||
345 | } |
||
346 | |||
347 | /** |
||
348 | * Run UnitPay::payOrderFromGate($request) when receive request from UnitPay gate. |
||
349 | * @param Request $request |
||
350 | * @return bool |
||
351 | */ |
||
352 | public function payOrderFromGate(Request $request) |
||
353 | { |
||
354 | // Validate request params from UnitPay server. |
||
355 | if (! $this->validateOrderRequestFromGate($request)) { |
||
356 | return $this->responseError('validateOrderRequestFromGate'); |
||
357 | } |
||
358 | |||
359 | // Search and return order |
||
360 | $order = $this->callFilterSearchOrder($request); |
||
361 | |||
362 | if (! $order) { |
||
363 | return $this->responseError('searchOrderFilter'); |
||
364 | } |
||
365 | |||
366 | // Return success response for check and error methods |
||
367 | if (in_array($request->get('method'), ['check', 'error'])) { |
||
368 | $this->eventFillAndSend('unitpay.info', 'payOrderFromGate method = '.$request->get('method'), $request); |
||
369 | |||
370 | return $this->responseOK('OK'); |
||
371 | } |
||
372 | |||
373 | // If method unknown then return error |
||
374 | if ($request->get('method') != 'pay') { |
||
375 | return $this->responseError('Invalid request'); |
||
376 | } |
||
377 | |||
378 | // If method pay and current order status is paid |
||
379 | // return success response and notify info |
||
380 | if (mb_strtolower($order->UNITPAY_orderStatus) === 'paid') { |
||
381 | $this->eventFillAndSend('unitpay.info', 'order already paid', $request); |
||
382 | |||
383 | return $this->responseOK('OK'); |
||
384 | } |
||
385 | |||
386 | // Current order is paid in UnitPay and not paid in database |
||
387 | |||
388 | $this->eventFillAndSend('unitpay.success', 'paid order', $request); |
||
389 | |||
390 | // PaidOrderFilter - update order into DB as paid & other actions |
||
391 | // if return false then error |
||
392 | if (! $this->callFilterPaidOrder($request, $order)) { |
||
393 | $this->eventFillAndSend('unitpay.error', 'callFilterPaidOrder', $request); |
||
394 | |||
395 | return $this->responseError('callFilterPaidOrder'); |
||
396 | } |
||
397 | |||
398 | // Order is paid in UnitPay and updated in database |
||
399 | return $this->responseOK('OK'); |
||
400 | } |
||
401 | } |
||
402 |
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.
Let’s take a look at an example:
As you can see in this example, the array
$myArray
is initialized the first time when the foreach loop is entered. You can also see that the value of thebar
key is only written conditionally; thus, its value might result from a previous iteration.This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.