Completed
Pull Request — master (#666)
by mingyoung
03:57
created

API::refundByTransactionId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 6
dl 0
loc 10
ccs 0
cts 2
cp 0
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the overtrue/wechat.
5
 *
6
 * (c) overtrue <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
/**
13
 * API.php.
14
 *
15
 * @author    overtrue <[email protected]>
16
 * @copyright 2015 overtrue <[email protected]>
17
 *
18
 * @see      https://github.com/overtrue
19
 * @see      http://overtrue.me
20
 */
21
22
namespace EasyWeChat\Payment;
23
24
use Doctrine\Common\Cache\FilesystemCache;
25
use EasyWeChat\Core\AbstractAPI;
26
use EasyWeChat\Core\Exception;
27
use EasyWeChat\Support\Collection;
28
use EasyWeChat\Support\XML;
29
use Psr\Http\Message\ResponseInterface;
30
31
/**
32
 * Class API.
33
 */
34
class API extends AbstractAPI
35
{
36
    /**
37
     * Merchant instance.
38
     *
39
     * @var Merchant
40
     */
41
    protected $merchant;
42
43
    /**
44
     * Sandbox box mode.
45
     *
46
     * @var bool
47
     */
48
    protected $sandboxEnabled = false;
49
50
    /**
51
     * Sandbox sign key.
52
     *
53
     * @var string
54
     */
55
    protected $sandboxSignKey;
56
57
    /**
58
     * Cache.
59
     *
60
     * @var Cache
61
     */
62
    protected $cache;
63
64
    const API_HOST = 'https://api.mch.weixin.qq.com';
65
66
    // api
67
    const API_PAY_ORDER = '/pay/micropay';
68
    const API_PREPARE_ORDER = '/pay/unifiedorder';
69
    const API_QUERY = '/pay/orderquery';
70
    const API_CLOSE = '/pay/closeorder';
71
    const API_REVERSE = '/secapi/pay/reverse';
72
    const API_REFUND = '/secapi/pay/refund';
73
    const API_QUERY_REFUND = '/pay/refundquery';
74
    const API_DOWNLOAD_BILL = '/pay/downloadbill';
75
    const API_REPORT = '/payitil/report';
76
77
    const API_URL_SHORTEN = 'https://api.mch.weixin.qq.com/tools/shorturl';
78
    const API_AUTH_CODE_TO_OPENID = 'https://api.mch.weixin.qq.com/tools/authcodetoopenid';
79
    const API_SANDBOX_SIGN_KEY = 'https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey';
80
81
    // order id types.
82
    const TRANSACTION_ID = 'transaction_id';
83
    const OUT_TRADE_NO = 'out_trade_no';
84
    const OUT_REFUND_NO = 'out_refund_no';
85
    const REFUND_ID = 'refund_id';
86
87
    // bill types.
88
    const BILL_TYPE_ALL = 'ALL';
89
    const BILL_TYPE_SUCCESS = 'SUCCESS';
90
    const BILL_TYPE_REFUND = 'REFUND';
91
    const BILL_TYPE_REVOKED = 'REVOKED';
92
93
    /**
94
     * API constructor.
95
     *
96
     * @param \EasyWeChat\Payment\Merchant   $merchant
97
     * @param \EasyWeChat\Payment\Cache|null $cache
98
     */
99 12
    public function __construct(Merchant $merchant, Cache $cache = null)
100
    {
101 12
        $this->merchant = $merchant;
102 12
        $this->cache = $cache;
103 12
    }
104
105
    /**
106
     * Pay the order.
107
     *
108
     * @param Order $order
109
     *
110
     * @return \EasyWeChat\Support\Collection
111
     */
112 1
    public function pay(Order $order)
113
    {
114 1
        return $this->request($this->wrapApi(self::API_PAY_ORDER), $order->all());
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request($this->wr...ORDER), $order->all()); of type EasyWeChat\Support\Colle...ssage\ResponseInterface adds the type Psr\Http\Message\ResponseInterface to the return on line 114 which is incompatible with the return type documented by EasyWeChat\Payment\API::pay of type EasyWeChat\Support\Collection.
Loading history...
115
    }
116
117
    /**
118
     * Prepare order to pay.
119
     *
120
     * @param Order $order
121
     *
122
     * @return \EasyWeChat\Support\Collection
123
     */
124 1
    public function prepare(Order $order)
125
    {
126 1
        $order->notify_url = $order->get('notify_url', $this->merchant->notify_url);
127 1
        if (is_null($order->spbill_create_ip)) {
128 1
            $order->spbill_create_ip = ($order->trade_type === Order::NATIVE) ? get_server_ip() : get_client_ip();
129 1
        }
130
131 1
        return $this->request($this->wrapApi(self::API_PREPARE_ORDER), $order->all());
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request($this->wr...ORDER), $order->all()); of type EasyWeChat\Support\Colle...ssage\ResponseInterface adds the type Psr\Http\Message\ResponseInterface to the return on line 131 which is incompatible with the return type documented by EasyWeChat\Payment\API::prepare of type EasyWeChat\Support\Collection.
Loading history...
132
    }
133
134
    /**
135
     * Query order.
136
     *
137
     * @param string $orderNo
138
     * @param string $type
139
     *
140
     * @return \EasyWeChat\Support\Collection
141
     */
142 1 View Code Duplication
    public function query($orderNo, $type = self::OUT_TRADE_NO)
143
    {
144
        $params = [
145 1
            $type => $orderNo,
146 1
        ];
147
148 1
        return $this->request($this->wrapApi(self::API_QUERY), $params);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request($this->wr...::API_QUERY), $params); of type EasyWeChat\Support\Colle...ssage\ResponseInterface adds the type Psr\Http\Message\ResponseInterface to the return on line 148 which is incompatible with the return type documented by EasyWeChat\Payment\API::query of type EasyWeChat\Support\Collection.
Loading history...
149
    }
150
151
    /**
152
     * Query order by transaction_id.
153
     *
154
     * @param string $transactionId
155
     *
156
     * @return \EasyWeChat\Support\Collection
157
     */
158 1
    public function queryByTransactionId($transactionId)
159
    {
160 1
        return $this->query($transactionId, self::TRANSACTION_ID);
161
    }
162
163
    /**
164
     * Close order by out_trade_no.
165
     *
166
     * @param $tradeNo
167
     *
168
     * @return \EasyWeChat\Support\Collection
169
     */
170 1
    public function close($tradeNo)
171
    {
172
        $params = [
173 1
            'out_trade_no' => $tradeNo,
174 1
        ];
175
176 1
        return $this->request($this->wrapApi(self::API_CLOSE), $params);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request($this->wr...::API_CLOSE), $params); of type EasyWeChat\Support\Colle...ssage\ResponseInterface adds the type Psr\Http\Message\ResponseInterface to the return on line 176 which is incompatible with the return type documented by EasyWeChat\Payment\API::close of type EasyWeChat\Support\Collection.
Loading history...
177
    }
178
179
    /**
180
     * Reverse order.
181
     *
182
     * @param string $orderNo
183
     * @param string $type
184
     *
185
     * @return \EasyWeChat\Support\Collection
186
     */
187 1
    public function reverse($orderNo, $type = self::OUT_TRADE_NO)
188
    {
189
        $params = [
190 1
            $type => $orderNo,
191 1
        ];
192
193 1
        return $this->safeRequest($this->wrapApi(self::API_REVERSE), $params);
194
    }
195
196
    /**
197
     * Reverse order by transaction_id.
198
     *
199
     * @param int $transactionId
200
     *
201
     * @return \EasyWeChat\Support\Collection
202
     */
203
    public function reverseByTransactionId($transactionId)
204
    {
205
        return $this->reverse($transactionId, self::TRANSACTION_ID);
206
    }
207
208
    /**
209
     * Make a refund request.
210
     *
211
     * @param string $orderNo
212
     * @param float  $totalFee
213
     * @param float  $refundFee
214
     * @param string $opUserId
215
     * @param string $type
216
     * @param string $refundAccount
217
     *
218
     * @return \EasyWeChat\Support\Collection
219
     */
220 1
    public function refund(
221
        $orderNo,
222
        $refundNo,
223
        $totalFee,
224
        $refundFee = null,
225
        $opUserId = null,
226
        $type = self::OUT_TRADE_NO,
227
        $refundAccount = 'REFUND_SOURCE_UNSETTLED_FUNDS'
228
        ) {
229
        $params = [
230 1
            $type => $orderNo,
231 1
            'out_refund_no' => $refundNo,
232 1
            'total_fee' => $totalFee,
233 1
            'refund_fee' => $refundFee ?: $totalFee,
234 1
            'refund_fee_type' => $this->merchant->fee_type,
235 1
            'refund_account' => $refundAccount,
236 1
            'op_user_id' => $opUserId ?: $this->merchant->merchant_id,
237 1
        ];
238
239 1
        return $this->safeRequest($this->wrapApi(self::API_REFUND), $params);
240
    }
241
242
    /**
243
     * Refund by transaction id.
244
     *
245
     * @param string $orderNo
246
     * @param float  $totalFee
247
     * @param float  $refundFee
248
     * @param string $opUserId
249
     * @param string $refundAccount
250
     *
251
     * @return \EasyWeChat\Support\Collection
252
     */
253
    public function refundByTransactionId(
254
        $orderNo,
255
        $refundNo,
256
        $totalFee,
257
        $refundFee = null,
258
        $opUserId = null,
259
        $refundAccount = 'REFUND_SOURCE_UNSETTLED_FUNDS'
260
        ) {
261
        return $this->refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, self::TRANSACTION_ID, $refundAccount);
262
    }
263
264
    /**
265
     * Query refund status.
266
     *
267
     * @param string $orderNo
268
     * @param string $type
269
     *
270
     * @return \EasyWeChat\Support\Collection
271
     */
272 1 View Code Duplication
    public function queryRefund($orderNo, $type = self::OUT_TRADE_NO)
273
    {
274
        $params = [
275 1
            $type => $orderNo,
276 1
        ];
277
278 1
        return $this->request($this->wrapApi(self::API_QUERY_REFUND), $params);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request($this->wr...UERY_REFUND), $params); of type EasyWeChat\Support\Colle...ssage\ResponseInterface adds the type Psr\Http\Message\ResponseInterface to the return on line 278 which is incompatible with the return type documented by EasyWeChat\Payment\API::queryRefund of type EasyWeChat\Support\Collection.
Loading history...
279
    }
280
281
    /**
282
     * Query refund status by out_refund_no.
283
     *
284
     * @param string $refundNo
285
     *
286
     * @return \EasyWeChat\Support\Collection
287
     */
288
    public function queryRefundByRefundNo($refundNo)
289
    {
290
        return $this->queryRefund($refundNo, self::OUT_REFUND_NO);
291
    }
292
293
    /**
294
     * Query refund status by transaction_id.
295
     *
296
     * @param string $transactionId
297
     *
298
     * @return \EasyWeChat\Support\Collection
299
     */
300
    public function queryRefundByTransactionId($transactionId)
301
    {
302
        return $this->queryRefund($transactionId, self::TRANSACTION_ID);
303
    }
304
305
    /**
306
     * Query refund status by refund_id.
307
     *
308
     * @param string $refundId
309
     *
310
     * @return \EasyWeChat\Support\Collection
311
     */
312
    public function queryRefundByRefundId($refundId)
313
    {
314
        return $this->queryRefund($refundId, self::REFUND_ID);
315
    }
316
317
    /**
318
     * Download bill history as a table file.
319
     *
320
     * @param string $date
321
     * @param string $type
322
     *
323
     * @return \Psr\Http\Message\ResponseInterface
324
     */
325 1
    public function downloadBill($date, $type = self::BILL_TYPE_ALL)
326
    {
327
        $params = [
328 1
            'bill_date' => $date,
329 1
            'bill_type' => $type,
330 1
        ];
331
332 1
        return $this->request($this->wrapApi(self::API_DOWNLOAD_BILL), $params, 'post', [\GuzzleHttp\RequestOptions::STREAM => true], true)->getBody();
0 ignored issues
show
Bug introduced by
The method getBody does only exist in Psr\Http\Message\ResponseInterface, but not in EasyWeChat\Support\Collection.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
333
    }
334
335
    /**
336
     * Convert long url to short url.
337
     *
338
     * @param string $url
339
     *
340
     * @return \EasyWeChat\Support\Collection
341
     */
342 1
    public function urlShorten($url)
343
    {
344 1
        return $this->request(self::API_URL_SHORTEN, ['long_url' => $url]);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request(self::API...y('long_url' => $url)); of type EasyWeChat\Support\Colle...ssage\ResponseInterface adds the type Psr\Http\Message\ResponseInterface to the return on line 344 which is incompatible with the return type documented by EasyWeChat\Payment\API::urlShorten of type EasyWeChat\Support\Collection.
Loading history...
345
    }
346
347
    /**
348
     * Report API status to WeChat.
349
     *
350
     * @param string $api
351
     * @param int    $timeConsuming
352
     * @param string $resultCode
353
     * @param string $returnCode
354
     * @param array  $other         ex: err_code,err_code_des,out_trade_no,user_ip...
355
     *
356
     * @return \EasyWeChat\Support\Collection
357
     */
358
    public function report($api, $timeConsuming, $resultCode, $returnCode, array $other = [])
359
    {
360
        $params = array_merge([
361
            'interface_url' => $api,
362
            'execute_time_' => $timeConsuming,
363
            'return_code' => $returnCode,
364
            'return_msg' => null,
365
            'result_code' => $resultCode,
366
            'user_ip' => get_client_ip(),
367
            'time' => time(),
368
        ], $other);
369
370
        return $this->request($this->wrapApi(self::API_REPORT), $params);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request($this->wr...:API_REPORT), $params); of type EasyWeChat\Support\Colle...ssage\ResponseInterface adds the type Psr\Http\Message\ResponseInterface to the return on line 370 which is incompatible with the return type documented by EasyWeChat\Payment\API::report of type EasyWeChat\Support\Collection.
Loading history...
371
    }
372
373
    /**
374
     * Get openid by auth code.
375
     *
376
     * @param string $authCode
377
     *
378
     * @return \EasyWeChat\Support\Collection
379
     */
380 1
    public function authCodeToOpenId($authCode)
381
    {
382 1
        return $this->request(self::API_AUTH_CODE_TO_OPENID, ['auth_code' => $authCode]);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request(self::API...h_code' => $authCode)); of type EasyWeChat\Support\Colle...ssage\ResponseInterface adds the type Psr\Http\Message\ResponseInterface to the return on line 382 which is incompatible with the return type documented by EasyWeChat\Payment\API::authCodeToOpenId of type EasyWeChat\Support\Collection.
Loading history...
383
    }
384
385
    /**
386
     * Merchant setter.
387
     *
388
     * @param Merchant $merchant
389
     *
390
     * @return $this
391
     */
392 1
    public function setMerchant(Merchant $merchant)
393
    {
394 1
        $this->merchant = $merchant;
395 1
    }
396
397
    /**
398
     * Merchant getter.
399
     *
400
     * @return Merchant
401
     */
402 1
    public function getMerchant()
403
    {
404 1
        return $this->merchant;
405
    }
406
407
    /**
408
     * Set sandbox mode.
409
     *
410
     * @param bool $enabled
411
     *
412
     * @return $this
413
     */
414 10
    public function sandboxMode($enabled = false)
415
    {
416 10
        $this->sandboxEnabled = $enabled;
417
418 10
        return $this;
419
    }
420
421
    /**
422
     * Make a API request.
423
     *
424
     * @param string $api
425
     * @param array  $params
426
     * @param string $method
427
     * @param array  $options
428
     * @param bool   $returnResponse
429
     *
430
     * @return \EasyWeChat\Support\Collection|\Psr\Http\Message\ResponseInterface
431
     */
432 10
    protected function request($api, array $params, $method = 'post', array $options = [], $returnResponse = false)
433
    {
434 10
        $params = array_merge($params, $this->merchant->only(['sub_appid', 'sub_mch_id']));
435
436 10
        $params['appid'] = $this->merchant->app_id;
437 10
        $params['mch_id'] = $this->merchant->merchant_id;
438 10
        $params['device_info'] = $this->merchant->device_info;
439 10
        $params['nonce_str'] = uniqid();
440 10
        $params = array_filter($params);
441
442 10
        $params['sign'] = $this->generateSignKey($params);
443
444 10
        $options = array_merge([
445 10
            'body' => XML::build($params),
446 10
        ], $options);
447
448 10
        $response = $this->getHttp()->request($api, $method, $options);
449
450 10
        return $returnResponse ? $response : $this->parseResponse($response);
451
    }
452
453
    /**
454
     * Generate sign key.
455
     *
456
     * @param array $params
457
     *
458
     * @return string
459
     */
460 10
    protected function generateSignKey($params)
461
    {
462 10
        $key = ($this->sandboxEnabled && $this->sandboxSignKey) ? $this->sandboxSignKey : $this->merchant->key;
463
464 10
        return generate_sign($params, $key, 'md5');
465
    }
466
467
    /**
468
     * Request with SSL.
469
     *
470
     * @param string $api
471
     * @param array  $params
472
     * @param string $method
473
     *
474
     * @return \EasyWeChat\Support\Collection
475
     */
476 2
    protected function safeRequest($api, array $params, $method = 'post')
477
    {
478
        $options = [
479 2
            'cert' => $this->merchant->get('cert_path'),
480 2
            'ssl_key' => $this->merchant->get('key_path'),
481 2
        ];
482
483 2
        return $this->request($api, $params, $method, $options);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request($api, $params, $method, $options); of type Psr\Http\Message\Respons...Chat\Support\Collection adds the type Psr\Http\Message\ResponseInterface to the return on line 483 which is incompatible with the return type documented by EasyWeChat\Payment\API::safeRequest of type EasyWeChat\Support\Collection.
Loading history...
484
    }
485
486
    /**
487
     * Parse Response XML to array.
488
     *
489
     * @param ResponseInterface $response
490
     *
491
     * @return \EasyWeChat\Support\Collection
492
     */
493 9
    protected function parseResponse($response)
494
    {
495 9
        if ($response instanceof ResponseInterface) {
496
            $response = $response->getBody();
497
        }
498
499 9
        return new Collection((array) XML::parse($response));
500
    }
501
502
    /**
503
     * Wrap API.
504
     *
505
     * @param string $resource
506
     *
507
     * @return string
508
     */
509 8
    protected function wrapApi($resource)
510
    {
511 8
        return self::API_HOST.($this->sandboxEnabled ? '/sandboxnew' : '').$resource;
512
    }
513
514
    /**
515
     * Get sandbox sign key.
516
     */
517
    protected function getSandboxSignKey()
518
    {
519
        // Try to get sandbox_signkey from cache
520
        $cacheKey = 'sandbox_signkey'.$this->merchant->merchant_id.$this->merchant->sub_merchant_id;
521
        /** @var \Doctrine\Common\Cache\Cache $cache */
522
        $cache = $this->getCache();
523
        $this->sandboxSignKey = $cache->fetch($cacheKey);
524
525
        if (!$this->sandboxSignKey) {
526
            // Try to acquire a new sandbox_signkey from WeChat
527
            try {
528
                $result = $this->request(self::API_SANDBOX_SIGN_KEY, []);
529
                if ($result->return_code === 'SUCCESS') {
530
                    $cache->save($cacheKey, $result->sandbox_signkey);
531
                    $this->sandboxSignKey = $result->sandbox_signkey;
532
                } else {
533
                    throw new Exception($result->return_msg);
534
                }
535
            } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
536
            }
537
        }
538
    }
539
540
    /**
541
     * Return the cache manager.
542
     *
543
     * @return \Doctrine\Common\Cache\Cache
544
     */
545
    public function getCache()
546
    {
547
        return $this->cache ?: $this->cache = new FilesystemCache(sys_get_temp_dir());
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Doctrine\Common\Cac...che(sys_get_temp_dir()) of type object<Doctrine\Common\Cache\FilesystemCache> is incompatible with the declared type object<EasyWeChat\Payment\Cache> of property $cache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
548
    }
549
}
550