Completed
Push — master ( 9c8aae...f44329 )
by Dmitry
03:19
created

Module::rememberUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
crap 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\controllers\PayController;
17
use Yii;
18
use yii\base\InvalidConfigException;
19
use yii\helpers\FileHelper;
20
use yii\helpers\Json;
21
use yii\helpers\Url;
22
23
/**
24
 * Merchant Module.
25
 *
26
 * Example application configuration:
27
 *
28
 * ```php
29
 * 'modules' => [
30
 *     'merchant' => [
31
 *         'class'         => 'hiqdev\yii2\merchant\Module',
32
 *         'notifyPage'    => '/my/notify/page',
33
 *         'collection'    => [
34
 *             'PayPal' => [
35
 *                 'purse'     => $params['paypal_purse'],
36
 *                 'secret'    => $params['paypal_secret'],   /// NEVER keep secret in source control
37
 *             ],
38
 *             'webmoney_usd' => [
39
 *                 'gateway'   => 'WebMoney',
40
 *                 'purse'     => $params['webmoney_purse'],
41
 *                 'secret'    => $params['webmoney_secret'], /// NEVER keep secret in source control
42
 *             ],
43
 *         ],
44
 *     ],
45
 * ],
46
 * ```
47
 *
48
 * @var string returns username for usage in merchant
49
 */
50
class Module extends \yii\base\Module
51
{
52
    /**
53
     * The URL prefix that will be used as a key to save current URL in the session.
54
     *
55
     * @see rememberUrl()
56
     * @see previousUrl()
57
     * @see \yii\helpers\BaseUrl::remember()
58
     * @see \yii\helpers\BaseUrl::previous()
59
     */
60
    const URL_PREFIX = 'merchant_url_';
61
62
    /**
63
     * @var string merchant library name. Defaults to `Omnipay`
64
     */
65
    public $merchantLibrary = 'Omnipay';
66
67
    /**
68
     * @var string merchant collection class name. Defaults to [[hiqdev\yii2\merchant\Collection]]
69
     */
70
    public $collectionClass = 'hiqdev\yii2\merchant\Collection';
71
72
    /**
73
     * @var string Deposit model class name. Defaults to [[hiqdev\yii2\merchant\models\Deposit]]
74
     */
75
    public $depositClass = 'hiqdev\yii2\merchant\models\Deposit';
76
77
    /**
78
     * {@inheritdoc}
79
     */
80 3
    public function init()
81
    {
82 3
        parent::init();
83 3
        $this->registerTranslations();
84 3
    }
85
86
    /**
87
     * Registers message sources for Merchant module.
88
     *
89
     * @void
90
     */
91 3
    public function registerTranslations()
92
    {
93 3
        Yii::$app->i18n->translations['merchant'] = [
94
            'class'          => 'yii\i18n\PhpMessageSource',
95
            'sourceLanguage' => 'en-US',
96
            'basePath'       => '@hiqdev/yii2/merchant/messages',
97
            'fileMap'        => [
98
                'merchant' => 'merchant.php',
99
            ],
100
        ];
101 3
    }
102
103
    /**
104
     * @var array|Closure list of merchants
105
     */
106
    protected $_collection = [];
107
108
    /**
109
     * @param array|Closure $collection list of merchants or callback
110
     */
111 3
    public function setCollection($collection)
112
    {
113 3
        $this->_collection = $collection;
114 3
    }
115
116
    /**
117
     * @param array $params parameters for collection
118
     * @return AbstractMerchant[] list of merchants
119
     */
120 3
    public function getCollection(array $params = [])
121
    {
122 3
        if (!is_object($this->_collection)) {
123 3
            $this->_collection = Yii::createObject(array_merge([
124 3
                'class'  => $this->collectionClass,
125 3
                'module' => $this,
126 3
                'params' => $params,
127 3
            ], (array) $this->_collection));
128
        }
129
130 3
        return $this->_collection;
131
    }
132
133
    /**
134
     * @param string $id merchant id
135
     * @param array $params parameters for collection
136
     * @return AbstractMerchant merchant instance
137
     */
138
    public function getMerchant($id, array $params = [])
139
    {
140
        return $this->getCollection($params)->get($id);
141
    }
142
143
    /**
144
     * Checks if merchant exists in the hub.
145
     *
146
     * @param string $id merchant id
147
     * @return bool whether merchant exist
148
     */
149
    public function hasMerchant($id)
150
    {
151
        return $this->getCollection()->has($id);
152
    }
153
154
    /**
155
     * Creates merchant instance from its array configuration.
156
     *
157
     * @param string $id     ID
158
     * @param array  $config merchant instance configuration
159
     * @return AbstractMerchant merchant instance
160
     */
161 2
    public function createMerchant($id, array $config)
162
    {
163 2
        return Helper::create(array_merge([
164 2
            'library'   => $this->merchantLibrary,
165 2
            'gateway'   => $id,
166 2
            'id'        => $id,
167
        ], $config));
168
    }
169
170
    /**
171
     * Method builds data for merchant request.
172
     *
173
     * @param string $merchant
174
     * @param array  $data     request data
175
     * @return array
176
     */
177
    public function prepareRequestData($merchant, array $data)
178
    {
179
        $data = array_merge([
180
            'merchant'      => $merchant,
181
            '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...
182
            'transactionId' => uniqid(),
183
        ], $data);
184
185
        return array_merge([
186
            'notifyUrl'     => $this->buildUrl('notify', $data),
187
            'returnUrl'     => $this->buildUrl('return', $data),
188
            'cancelUrl'     => $this->buildUrl('cancel', $data),
189
            'finishUrl'     => $this->buildUrl('finish', $data),
190
            'returnMethod'  => 'POST',
191
            'cancelMethod'  => 'POST',
192
        ], $data);
193
    }
194
195
    /**
196
     * @var string client login
197
     */
198
    protected $_username;
199
200
    /**
201
     * Sets [[_username]].
202
     *
203
     * @param $username
204
     */
205 3
    public function setUsername($username)
206
    {
207 3
        $this->_username = $username;
208 3
    }
209
210
    /**
211
     * Gets [[_username]] when defined, otherwise - `Yii::$app->user->identity->username`,
212
     * otherwise `Yii::$app->user->identity->getId()`.
213
     * @throws InvalidConfigException
214
     * @return string
215
     */
216
    public function getUsername()
217
    {
218
        if (isset($this->_username)) {
219
            return $this->_username;
220
        } elseif (($identity = Yii::$app->user->identity) !== null) {
221
            if ($identity->hasProperty('username')) {
222
                $this->_username = $identity->username;
223
            } else {
224
                $this->_username = $identity->getId();
225
            }
226
227
            return $this->_username;
228
        }
229
        throw new InvalidConfigException('Unable to determine username');
230
    }
231
232
    /**
233
     * @var string|array the URL that will be used for payment system notifications. Will be passed through [[Url::to()]]
234
     */
235
    public $notifyPage = 'notify';
236
    /**
237
     * @var string|array the URL that will be used to redirect client from the merchant after the success payment.
238
     * Will be passed through [[Url::to()]]
239
     */
240
    public $returnPage = 'return';
241
    /**
242
     * @var string|array the URL that will be used to redirect client from the merchant after the failed payment.
243
     * Will be passed through [[Url::to()]]
244
     */
245
    public $cancelPage = 'cancel';
246
    /**
247
     * @var string|array the URL that might be used to redirect used from the success or error page to the finish page.
248
     * Will be passed through [[Url::to()]]
249
     */
250
    public $finishPage = 'finish';
251
252
    /**
253
     * Builds URLs that will be passed in the request to the merchant.
254
     *
255
     * @param string $destination `notify`, `return`, `cancel`
256
     * @param array  $data data, that will be used to build URL. Only `merchant` and `transactionId` keys
257
     * will be used from the array
258
     * @return string URL
259
     */
260
    public function buildUrl($destination, array $data)
261
    {
262
        $page = array_merge([
263
            '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...
264
            'merchant'      => $data['merchant'],
265
            'transactionId' => $data['transactionId'],
266
        ], (array) $this->getPage($destination, $data));
267
268
        if (is_array($page)) {
269
            $page[0] = $this->localizePage($page[0]);
270
        } else {
271
            $page = $this->localizePage($page);
272
        }
273
274
        return Url::to($page, true);
275
    }
276
277
    /**
278
     * Builds url to `this_module/pay/$page` if page is not /full/page.
279
     * @param mixed $page
280
     * @return mixed
281
     */
282
    public function localizePage($page)
283
    {
284
        return is_string($page) && $page[0] !== '/' ? ('/' . $this->id . '/pay/' . $page) : $page;
285
    }
286
287
    public function getPage($destination, array $data)
288
    {
289
        $name = $destination . 'Page';
290
        if (isset($data[$name])) {
291
            return $data[$name];
292
        }
293
294
        return $this->hasProperty($name) ? $this->{$name} : $destination;
295
    }
296
297
    /**
298
     * Saves the $url to session with [[URL_PREFIX]] key, trailed with $name.
299
     *
300
     * @param array|string $url
301
     * @param string $name the trailing part for the URL save key. Defaults to `back`
302
     * @void
303
     */
304
    public function rememberUrl($url, $name = 'back')
305
    {
306
        Url::remember($url, static::URL_PREFIX . $name);
307
    }
308
309
    /**
310
     * Extracts the URL from session storage, saved with [[URL_PREFIX]] key, trailed with $name.
311
     *
312
     * @param string $name the trailing part for the URL save key. Defaults to `back`
313
     * @return string
314
     */
315
    public function previousUrl($name = 'back')
316
    {
317
        return Url::previous(static::URL_PREFIX . $name);
318
    }
319
320
    /**
321
     * @var PayController The Payment controller
322
     */
323
    protected $_payController;
324
325
    /**
326
     * @throws InvalidConfigException
327
     *
328
     * @return PayController
329
     */
330
    public function getPayController()
331
    {
332
        if ($this->_payController === null) {
333
            $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...
334
        }
335
336
        return $this->_payController;
337
    }
338
339
    /**
340
     * Renders page, that contains list of payment systems, that might be choosen by user.
341
     * Should be implemented in `PayController`.
342
     *
343
     * @param array $params
344
     * @return \yii\web\Response
345
     */
346
    public function renderDeposit(array $params)
347
    {
348
        return $this->getPayController()->renderDeposit($params);
349
    }
350
351
    public function completeHistory(array $data)
352
    {
353
        $history = $this->readHistory($data);
354
        if (isset($history['_isCompleted']) && $history['_isCompleted']) {
355
            return $history;
356
        }
357
        $data['_isCompleted'] = !isset($data['_error']) && $data['id'];
358
        if ($data['_isCompleted']) {
359
            $data['_error'] = null;
360
        }
361
362
        return $this->updateHistory($data);
363
    }
364
365
    /**
366
     * Merges the existing history with the given $data.
367
     * @param array $data
368
     * @throws \yii\base\Exception
369
     * @return int data that were written to the file, or false on failure
370
     */
371
    public function updateHistory(array $data)
372
    {
373
        return $this->writeHistory(array_merge($this->readHistory($data), $data));
374
    }
375
376
    /**
377
     * Writes given data to history.
378
     * @param array $data
379
     * @throws \yii\base\Exception
380
     * @return int data that were written to the file, or false on failure
381
     */
382
    public function writeHistory(array $data)
383
    {
384
        $path = $this->getHistoryPath($data);
385
386
        FileHelper::createDirectory(dirname($path));
387
        $res = file_put_contents($path, Json::encode($data));
388
389
        return $res === false ? $res : $data;
390
    }
391
392
    /**
393
     * Reads history.
394
     *
395
     * @param string|array $data
396
     * @return array
397
     */
398
    public function readHistory($data)
399
    {
400
        $path = $this->getHistoryPath($data);
401
402
        return file_exists($path) ? Json::decode(file_get_contents($path)) : [];
403
    }
404
405
    /**
406
     * Returns path for the transaction log depending on $transactionId.
407
     *
408
     * @param string|array $data transactionId or array containing transactionId
409
     * @return bool|string Path to the transaction log
410
     */
411
    protected function getHistoryPath($data)
412
    {
413
        $transactionId = is_array($data) ? $data['transactionId'] : $data;
414
415
        return Yii::getAlias('@runtime/merchant/' . substr(md5($transactionId), 0, 2) . '/' . $transactionId . '.json');
416
    }
417
}
418