Completed
Push — master ( 30db8e...c8145a )
by Dmitry
06:20
created

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