Completed
Push — master ( c17c7e...2fd82c )
by Andrii
02:11
created

Module   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 330
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 7

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 35
c 0
b 0
f 0
lcom 2
cbo 7
dl 0
loc 330
ccs 0
cts 3
cp 0
rs 9

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getCollection() 0 8 1
A getMerchant() 0 4 1
A hasMerchant() 0 4 1
A createMerchant() 0 8 1
A prepareRequestData() 0 17 1
A setUsername() 0 4 1
A getUsername() 0 15 4
A buildUrl() 0 16 2
A localizePage() 0 4 3
A getPage() 0 9 3
A rememberUrl() 0 4 1
A previousUrl() 0 4 1
A getPayController() 0 8 2
A renderDeposit() 0 4 1
B completeHistory() 0 13 5
A updateHistory() 0 4 1
A writeHistory() 0 9 2
A readHistory() 0 6 2
A getHistoryPath() 0 6 2
1
<?php
2
/**
3
 * Yii2 extension for payment processing with Omnipay, Payum and more later.
4
 *
5
 * @link      https://github.com/hiqdev/yii2-merchant
6
 * @package   yii2-merchant
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2015-2017, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\yii2\merchant;
12
13
use Closure;
14
use hiqdev\php\merchant\AbstractMerchant;
15
use hiqdev\php\merchant\Helper;
16
use hiqdev\yii2\merchant\Collection;
17
use hiqdev\yii2\merchant\models\Deposit;
18
use hiqdev\yii2\merchant\controllers\PayController;
19
use Yii;
20
use yii\base\InvalidConfigException;
21
use yii\helpers\FileHelper;
22
use yii\helpers\Json;
23
use yii\helpers\Url;
24
25
/**
26
 * Merchant Module.
27
 *
28
 * Example application configuration:
29
 *
30
 * ```php
31
 * 'modules' => [
32
 *     'merchant' => [
33
 *         'class'         => 'hiqdev\yii2\merchant\Module',
34
 *         'notifyPage'    => '/my/notify/page',
35
 *         'collection'    => [
36
 *             'PayPal' => [
37
 *                 'purse'     => $params['paypal_purse'],
38
 *                 'secret'    => $params['paypal_secret'],   /// NEVER keep secret in source control
39
 *             ],
40
 *             'webmoney_usd' => [
41
 *                 'gateway'   => 'WebMoney',
42
 *                 'purse'     => $params['webmoney_purse'],
43
 *                 'secret'    => $params['webmoney_secret'], /// NEVER keep secret in source control
44
 *             ],
45
 *         ],
46
 *     ],
47
 * ],
48
 * ```
49
 *
50
 * @var string returns username for usage in merchant
51
 */
52
class Module extends \yii\base\Module
53
{
54
    /**
55
     * The URL prefix that will be used as a key to save current URL in the session.
56
     *
57
     * @see rememberUrl()
58
     * @see previousUrl()
59
     * @see \yii\helpers\BaseUrl::remember()
60
     * @see \yii\helpers\BaseUrl::previous()
61
     */
62
    const URL_PREFIX = 'merchant_url_';
63
64
    /**
65
     * @var string merchant library name. Defaults to `Omnipay`
66
     */
67
    public $merchantLibrary = 'Omnipay';
68
69
    /**
70
     * @var string merchant collection class name. Defaults to [[Collection]]
71
     */
72
    public $collectionClass = Collection::class;
73
74
    /**
75
     * @var string Deposit model class name. Defaults to [[Deposit]]
76
     */
77
    public $depositClass = Deposit::class;
78
79
    /**
80
     * @var array|Closure list of merchants
81
     */
82
    protected $_collection = [];
83
84
    /**
85
     * @param array $params parameters for collection
86
     * @return AbstractMerchant[] list of merchants
87
     */
88
    public function getCollection(array $params = [])
89
    {
90
        return Yii::createObject(array_merge([
91
            'class'  => $this->collectionClass,
92
            'module' => $this,
93
            'params' => $params,
94
        ], (array) $this->_collection));
95
    }
96
97
    /**
98
     * @param string $id merchant id
99
     * @param array $params parameters for collection
100
     * @return AbstractMerchant merchant instance
101
     */
102
    public function getMerchant($id, array $params = [])
103
    {
104
        return $this->getCollection($params)->get($id);
105
    }
106
107
    /**
108
     * Checks if merchant exists in the hub.
109
     *
110
     * @param string $id merchant id
111
     * @return bool whether merchant exist
112
     */
113
    public function hasMerchant($id)
114
    {
115
        return $this->getCollection()->has($id);
116
    }
117
118
    /**
119
     * Creates merchant instance from its array configuration.
120
     *
121
     * @param string $id     ID
122
     * @param array  $config merchant instance configuration
123
     * @return AbstractMerchant merchant instance
124
     */
125
    public function createMerchant($id, array $config)
126
    {
127
        return Helper::create(array_merge([
128
            'library'   => $this->merchantLibrary,
129
            'gateway'   => $id,
130
            'id'        => $id,
131
        ], $config));
132
    }
133
134
    /**
135
     * Method builds data for merchant request.
136
     *
137
     * @param string $merchant
138
     * @param array  $data     request data
139
     * @return array
140
     */
141
    public function prepareRequestData($merchant, array $data)
142
    {
143
        $data = array_merge([
144
            'merchant'      => $merchant,
145
            'description'   => Yii::$app->request->getServerName() . ' deposit: ' . $this->username,
0 ignored issues
show
Bug introduced by
The property username does not seem to exist. Did you mean _username?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
146
            'transactionId' => uniqid(),
147
        ], $data);
148
149
        return array_merge([
150
            'notifyUrl'     => $this->buildUrl('notify', $data),
151
            'returnUrl'     => $this->buildUrl('return', $data),
152
            'cancelUrl'     => $this->buildUrl('cancel', $data),
153
            'finishUrl'     => $this->buildUrl('finish', $data),
154
            'returnMethod'  => 'POST',
155
            'cancelMethod'  => 'POST',
156
        ], $data);
157
    }
158
159
    /**
160
     * @var string client login
161
     */
162
    protected $_username;
163
164
    /**
165
     * Sets [[_username]].
166
     *
167
     * @param $username
168
     */
169
    public function setUsername($username)
170
    {
171
        $this->_username = $username;
172
    }
173
174
    /**
175
     * Gets [[_username]] when defined, otherwise - `Yii::$app->user->identity->username`,
176
     * otherwise `Yii::$app->user->identity->getId()`.
177
     * @throws InvalidConfigException
178
     * @return string
179
     */
180
    public function getUsername()
181
    {
182
        if (isset($this->_username)) {
183
            return $this->_username;
184
        } elseif (($identity = Yii::$app->user->identity) !== null) {
185
            if ($identity->hasProperty('username')) {
186
                $this->_username = $identity->username;
187
            } else {
188
                $this->_username = $identity->getId();
189
            }
190
191
            return $this->_username;
192
        }
193
        throw new InvalidConfigException('Unable to determine username');
194
    }
195
196
    /**
197
     * @var string|array the URL that will be used for payment system notifications. Will be passed through [[Url::to()]]
198
     */
199
    public $notifyPage = 'notify';
200
    /**
201
     * @var string|array the URL that will be used to redirect client from the merchant after the success payment.
202
     * Will be passed through [[Url::to()]]
203
     */
204
    public $returnPage = 'return';
205
    /**
206
     * @var string|array the URL that will be used to redirect client from the merchant after the failed payment.
207
     * Will be passed through [[Url::to()]]
208
     */
209
    public $cancelPage = 'cancel';
210
    /**
211
     * @var string|array the URL that might be used to redirect used from the success or error page to the finish page.
212
     * Will be passed through [[Url::to()]]
213
     */
214
    public $finishPage = 'finish';
215
216
    /**
217
     * Builds URLs that will be passed in the request to the merchant.
218
     *
219
     * @param string $destination `notify`, `return`, `cancel`
220
     * @param array  $data data, that will be used to build URL. Only `merchant` and `transactionId` keys
221
     * will be used from the array
222
     * @return string URL
223
     */
224
    public function buildUrl($destination, array $data)
225
    {
226
        $page = array_merge([
227
            'username'      => $this->username,
0 ignored issues
show
Bug introduced by
The property username does not seem to exist. Did you mean _username?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
228
            'merchant'      => $data['merchant'],
229
            'transactionId' => $data['transactionId'],
230
        ], (array) $this->getPage($destination, $data));
231
232
        if (is_array($page)) {
233
            $page[0] = $this->localizePage($page[0]);
234
        } else {
235
            $page = $this->localizePage($page);
236
        }
237
238
        return Url::to($page, true);
239
    }
240
241
    /**
242
     * Builds url to `this_module/pay/$page` if page is not /full/page.
243
     * @param mixed $page
244
     * @return mixed
245
     */
246
    public function localizePage($page)
247
    {
248
        return is_string($page) && $page[0] !== '/' ? ('/' . $this->id . '/pay/' . $page) : $page;
249
    }
250
251
    public function getPage($destination, array $data)
252
    {
253
        $name = $destination . 'Page';
254
        if (isset($data[$name])) {
255
            return $data[$name];
256
        }
257
258
        return $this->hasProperty($name) ? $this->{$name} : $destination;
259
    }
260
261
    /**
262
     * Saves the $url to session with [[URL_PREFIX]] key, trailed with $name.
263
     *
264
     * @param array|string $url
265
     * @param string $name the trailing part for the URL save key. Defaults to `back`
266
     * @void
267
     */
268
    public function rememberUrl($url, $name = 'back')
269
    {
270
        Url::remember($url, static::URL_PREFIX . $name);
271
    }
272
273
    /**
274
     * Extracts the URL from session storage, saved with [[URL_PREFIX]] key, trailed with $name.
275
     *
276
     * @param string $name the trailing part for the URL save key. Defaults to `back`
277
     * @return string
278
     */
279
    public function previousUrl($name = 'back')
280
    {
281
        return Url::previous(static::URL_PREFIX . $name);
282
    }
283
284
    /**
285
     * @var PayController The Payment controller
286
     */
287
    protected $_payController;
288
289
    /**
290
     * @throws InvalidConfigException
291
     *
292
     * @return PayController
293
     */
294
    public function getPayController()
295
    {
296
        if ($this->_payController === null) {
297
            $this->_payController = $this->createControllerById('pay');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->createControllerById('pay') can also be of type object<yii\base\Controller>. However, the property $_payController is declared as type object<hiqdev\yii2\merch...trollers\PayController>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
298
        }
299
300
        return $this->_payController;
301
    }
302
303
    /**
304
     * Renders page, that contains list of payment systems, that might be choosen by user.
305
     * Should be implemented in `PayController`.
306
     *
307
     * @param array $params
308
     * @return \yii\web\Response
309
     */
310
    public function renderDeposit(array $params)
311
    {
312
        return $this->getPayController()->renderDeposit($params);
313
    }
314
315
    public function completeHistory(array $data)
316
    {
317
        $history = $this->readHistory($data);
318
        if (isset($history['_isCompleted']) && $history['_isCompleted']) {
319
            return $history;
320
        }
321
        $data['_isCompleted'] = !isset($data['_error']) && $data['id'];
322
        if ($data['_isCompleted']) {
323
            $data['_error'] = null;
324
        }
325
326
        return $this->updateHistory($data);
327
    }
328
329
    /**
330
     * Merges the existing history with the given $data.
331
     * @param array $data
332
     * @throws \yii\base\Exception
333
     * @return int data that were written to the file, or false on failure
334
     */
335
    public function updateHistory(array $data)
336
    {
337
        return $this->writeHistory(array_merge($this->readHistory($data), $data));
338
    }
339
340
    /**
341
     * Writes given data to history.
342
     * @param array $data
343
     * @throws \yii\base\Exception
344
     * @return int data that were written to the file, or false on failure
345
     */
346
    public function writeHistory(array $data)
347
    {
348
        $path = $this->getHistoryPath($data);
349
350
        FileHelper::createDirectory(dirname($path));
351
        $res = file_put_contents($path, Json::encode($data));
352
353
        return $res === false ? $res : $data;
354
    }
355
356
    /**
357
     * Reads history.
358
     *
359
     * @param string|array $data
360
     * @return array
361
     */
362
    public function readHistory($data)
363
    {
364
        $path = $this->getHistoryPath($data);
365
366
        return file_exists($path) ? Json::decode(file_get_contents($path)) : [];
367
    }
368
369
    /**
370
     * Returns path for the transaction log depending on $transactionId.
371
     *
372
     * @param string|array $data transactionId or array containing transactionId
373
     * @return bool|string Path to the transaction log
374
     */
375
    protected function getHistoryPath($data)
376
    {
377
        $transactionId = is_array($data) ? $data['transactionId'] : $data;
378
379
        return Yii::getAlias('@runtime/merchant/' . substr(md5($transactionId), 0, 2) . '/' . $transactionId . '.json');
380
    }
381
}
382