Completed
Push — master ( 3380b3...070995 )
by Action
02:30
created

UnitPay::responseError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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 if ip address is in 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 result.
33
     * @param $message
34
     * @return mixed
35
     */
36
    public function responseError($message)
37
    {
38
        $result['error']['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 $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar 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.

Loading history...
39
40
        return $result;
41
    }
42
43
    /**
44
     * Return json success result.
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 $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar 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.

Loading history...
51
52
        return $result;
53
    }
54
55
    /**
56
     * Fill event details to pass 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 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 $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar 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.

Loading history...
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.unitpayId' => 'required',
212
        ]);
213
214
        if ($validator->fails()) {
215
            return false;
216
        }
217
218
        return true;
219
    }
220
221
    /**
222
     * Validate request signature from UnitPay gate.
223
     * @param Request $request
224
     * @return bool
225
     */
226
    public function validateSignature(Request $request)
227
    {
228
        $sign = $this->getSignature($request->get('method'), $request->get('params'), config('unitpay.UNITPAY_SECRET_KEY'));
229
230
        if ($request->input('params.signature') != $sign) {
231
            return false;
232
        }
233
234
        return true;
235
    }
236
237
    /**
238
     * Validate ip, request params and signature from UnitPay gate.
239
     * @param Request $request
240
     * @return bool
241
     */
242
    public function validateOrderRequestFromGate(Request $request)
243
    {
244
        if (! $this->AllowIP($request->ip()) || ! $this->validate($request) || ! $this->validateSignature($request)) {
245
            $this->eventFillAndSend('unitpay.error', 'validateOrderRequestFromGate', $request);
246
247
            return false;
248
        }
249
250
        return true;
251
    }
252
253
    /**
254
     * Call SearchOrderFilter and check return order params.
255
     * @param Request $request
256
     * @return bool
257
     * @throws InvalidConfiguration
258
     */
259
    public function callFilterSearchOrder(Request $request)
260
    {
261
        $callable = config('unitpay.SearchOrderFilter');
262
263
        if (! is_callable($callable)) {
264
            throw InvalidConfiguration::searchOrderFilterInvalid();
265
        }
266
267
        /*
268
         *  SearchOrderFilter
269
         *  Search order in the database and return order details
270
         *  Must return array with:
271
         *
272
         *  orderStatus
273
         *  orderCurrency
274
         *  orderSum
275
         */
276
277
        $order = $callable($request, $request->input('params.account'));
278
279
        if (! $order) {
280
            $this->eventFillAndSend('unitpay.error', 'orderNotFound', $request);
281
282
            return false;
283
        }
284
285
        if (! array_key_exists('orderStatus', $order)) {
286
            $this->eventFillAndSend('unitpay.error', 'orderStatusInvalid', $request);
287
288
            return false;
289
        }
290
291
        if (! array_key_exists('orderSum', $order) && $request->input('params.orderSum') != $order['orderSum']) {
292
            $this->eventFillAndSend('unitpay.error', 'orderSumInvalid', $request);
293
294
            return false;
295
        }
296
297
        if (! array_key_exists('orderCurrency', $order) && $request->input('params.orderCurrency') != $order['orderCurrency']) {
298
            $this->eventFillAndSend('unitpay.error', 'orderCurrencyInvalid', $request);
299
300
            return false;
301
        }
302
303
        return $order;
304
    }
305
306
    /**
307
     * Call PaidOrderFilter if order not paid.
308
     * @param Request $request
309
     * @param $order
310
     * @return mixed
311
     * @throws InvalidConfiguration
312
     */
313
    public function callFilterPaidOrder(Request $request, $order)
314
    {
315
        $callable = config('unitpay.PaidOrderFilter');
316
317
        if (! is_callable($callable)) {
318
            throw InvalidConfiguration::orderPaidFilterInvalid();
319
        }
320
321
        // Run PaidOrderFilter callback
322
        return $callable($request, $order);
323
    }
324
325
    /**
326
     * Run UnitPay::payOrderFromGate($request) when receive request from UnitPay gate.
327
     * @param Request $request
328
     * @return bool
329
     */
330
    public function payOrderFromGate(Request $request)
331
    {
332
        // Validate request params from UnitPay server.
333
        if (! $this->validateOrderRequestFromGate($request)) {
334
            return $this->responseError('validateOrderRequestFromGate');
335
        }
336
337
        // Search and return order
338
        $order = $this->callFilterSearchOrder($request);
339
340
        if (! $order) {
341
            return $this->responseError('searchOrderFilter');
342
        }
343
344
        // Return success response for check and error methods
345
        if (in_array($request->get('method'), ['check', 'error'])) {
346
            $this->eventFillAndSend('unitpay.info', 'payOrderFromGate method = '.$request->get('method'), $request);
347
348
            return $this->responseOK('OK');
349
        }
350
351
        // If method unknown then return error
352
        if ($request->get('method') != 'pay') {
353
            return $this->responseError('Invalid request');
354
        }
355
356
        // If method pay and current order status is paid
357
        // return success response and notify info
358
        if (mb_strtolower($order['orderStatus']) === 'paid') {
359
            $this->eventFillAndSend('unitpay.info', 'order already paid', $request);
360
361
            return $this->responseOK('OK');
362
        }
363
364
        // Current order is paid in UnitPay and not paid in database
365
366
        $this->eventFillAndSend('unitpay.success', 'paid order', $request);
367
368
        // PaidOrderFilter - update order into DB as paid & other actions
369
        // if return false then error
370
        if (! $this->callFilterPaidOrder($request, $order)) {
371
            $this->eventFillAndSend('unitpay.error', 'callFilterPaidOrder', $request);
372
373
            return $this->responseError('callFilterPaidOrder');
374
        }
375
376
        // Order is paid in UnitPay and updated in database
377
        return $this->responseOK('OK');
378
    }
379
}
380