Completed
Push — master ( d2116f...60eecd )
by Dmitry
02:40
created

Module::saveTransaction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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