Completed
Push — master ( b5c521...3f3d20 )
by Songda
01:37
created

Wechat::download()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace Yansongda\Pay\Gateways;
4
5
use Symfony\Component\HttpFoundation\RedirectResponse;
6
use Symfony\Component\HttpFoundation\Request;
7
use Symfony\Component\HttpFoundation\Response;
8
use Yansongda\Pay\Contracts\GatewayApplicationInterface;
9
use Yansongda\Pay\Contracts\GatewayInterface;
10
use Yansongda\Pay\Events;
11
use Yansongda\Pay\Exceptions\GatewayException;
12
use Yansongda\Pay\Exceptions\InvalidGatewayException;
13
use Yansongda\Pay\Exceptions\InvalidSignException;
14
use Yansongda\Pay\Gateways\Wechat\Support;
15
use Yansongda\Pay\Log;
16
use Yansongda\Supports\Collection;
17
use Yansongda\Supports\Config;
18
use Yansongda\Supports\Str;
19
20
/**
21
 * @method Response app(array $config) APP 支付
22
 * @method Collection groupRedpack(array $config) 分裂红包
23
 * @method Collection miniapp(array $config) 小程序支付
24
 * @method Collection mp(array $config) 公众号支付
25
 * @method Collection pos(array $config) 刷卡支付
26
 * @method Collection redpack(array $config) 普通红包
27
 * @method Collection scan(array $config) 扫码支付
28
 * @method Collection transfer(array $config) 企业付款
29
 * @method RedirectResponse wap(array $config) H5 支付
30
 */
31
class Wechat implements GatewayApplicationInterface
32
{
33
    /**
34
     * 普通模式.
35
     */
36
    const MODE_NORMAL = 'normal';
37
38
    /**
39
     * 沙箱模式.
40
     */
41
    const MODE_DEV = 'dev';
42
43
    /**
44
     * 香港钱包 API.
45
     */
46
    const MODE_HK = 'hk';
47
48
    /**
49
     * 境外 API.
50
     */
51
    const MODE_US = 'us';
52
53
    /**
54
     * 服务商模式.
55
     */
56
    const MODE_SERVICE = 'service';
57
58
    /**
59
     * Const url.
60
     */
61
    const URL = [
62
        self::MODE_NORMAL  => 'https://api.mch.weixin.qq.com/',
63
        self::MODE_DEV     => 'https://api.mch.weixin.qq.com/sandboxnew/',
64
        self::MODE_HK      => 'https://apihk.mch.weixin.qq.com/',
65
        self::MODE_SERVICE => 'https://api.mch.weixin.qq.com/',
66
        self::MODE_US      => 'https://apius.mch.weixin.qq.com/',
67
    ];
68
69
    /**
70
     * Wechat payload.
71
     *
72
     * @var array
73
     */
74
    protected $payload;
75
76
    /**
77
     * Wechat gateway.
78
     *
79
     * @var string
80
     */
81
    protected $gateway;
82
83
    /**
84
     * Bootstrap.
85
     *
86
     * @author yansongda <[email protected]>
87
     *
88
     * @param Config $config
89
     *
90
     * @throws \Exception
91
     */
92
    public function __construct(Config $config)
93
    {
94
        $this->gateway = Support::create($config)->getBaseUri();
95
        $this->payload = [
96
            'appid'            => $config->get('app_id', ''),
97
            'mch_id'           => $config->get('mch_id', ''),
98
            'nonce_str'        => Str::random(),
99
            'notify_url'       => $config->get('notify_url', ''),
100
            'sign'             => '',
101
            'trade_type'       => '',
102
            'spbill_create_ip' => Request::createFromGlobals()->getClientIp(),
103
        ];
104
105
        if ($config->get('mode', self::MODE_NORMAL) === static::MODE_SERVICE) {
106
            $this->payload = array_merge($this->payload, [
107
                'sub_mch_id' => $config->get('sub_mch_id'),
108
                'sub_appid'  => $config->get('sub_app_id', ''),
109
            ]);
110
        }
111
    }
112
113
    /**
114
     * Magic pay.
115
     *
116
     * @author yansongda <[email protected]>
117
     *
118
     * @param string $method
119
     * @param string $params
120
     *
121
     * @throws InvalidGatewayException
122
     *
123
     * @return Response|Collection
124
     */
125
    public function __call($method, $params)
126
    {
127
        return self::pay($method, ...$params);
0 ignored issues
show
Documentation introduced by
$params is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
128
    }
129
130
    /**
131
     * Pay an order.
132
     *
133
     * @author yansongda <[email protected]>
134
     *
135
     * @param string $gateway
136
     * @param array  $params
137
     *
138
     * @throws InvalidGatewayException
139
     *
140
     * @return Response|Collection
141
     */
142
    public function pay($gateway, $params = [])
143
    {
144
        Events::dispatch(Events::PAY_STARTING, new Events\PayStarting('Wechat', $gateway, $params));
145
146
        $this->payload = array_merge($this->payload, $params);
147
148
        $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway';
149
150
        if (class_exists($gateway)) {
151
            return $this->makePay($gateway);
152
        }
153
154
        throw new InvalidGatewayException("Pay Gateway [{$gateway}] Not Exists");
155
    }
156
157
    /**
158
     * Verify data.
159
     *
160
     * @author yansongda <[email protected]>
161
     *
162
     * @param string|null $content
163
     * @param bool        $refund
164
     *
165
     * @throws InvalidSignException
166
     * @throws \Yansongda\Pay\Exceptions\InvalidArgumentException
167
     *
168
     * @return Collection
169
     */
170
    public function verify($content = null, $refund = false): Collection
171
    {
172
        $content = $content ?? Request::createFromGlobals()->getContent();
173
174
        Events::dispatch(Events::REQUEST_RECEIVED, new Events\RequestReceived('Wechat', '', [$content]));
175
176
        $data = Support::fromXml($content);
0 ignored issues
show
Bug introduced by
It seems like $content defined by $content ?? \Symfony\Com...Globals()->getContent() on line 172 can also be of type resource; however, Yansongda\Pay\Gateways\Wechat\Support::fromXml() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
177
        if ($refund) {
178
            $decrypt_data = Support::decryptRefundContents($data['req_info']);
179
            $data = array_merge(Support::fromXml($decrypt_data), $data);
180
        }
181
182
        Log::debug('Resolved The Received Wechat Request Data', $data);
183
184
        if ($refund || Support::generateSign($data) === $data['sign']) {
185
            return new Collection($data);
186
        }
187
188
        Events::dispatch(Events::SIGN_FAILED, new Events\SignFailed('Wechat', '', $data));
189
190
        throw new InvalidSignException('Wechat Sign Verify FAILED', $data);
191
    }
192
193
    /**
194
     * Query an order.
195
     *
196
     * @author yansongda <[email protected]>
197
     *
198
     * @param string|array $order
199
     * @param bool         $refund
200
     *
201
     * @throws GatewayException
202
     * @throws InvalidSignException
203
     * @throws \Yansongda\Pay\Exceptions\InvalidArgumentException
204
     *
205
     * @return Collection
206
     */
207
    public function find($order, $refund = false): Collection
208
    {
209
        if ($refund) {
210
            unset($this->payload['spbill_create_ip']);
211
        }
212
213
        $this->payload = Support::filterPayload($this->payload, $order);
214
215
        Events::dispatch(Events::METHOD_CALLED, new Events\MethodCalled('Wechat', 'Find', $this->gateway, $this->payload));
216
217
        return Support::requestApi(
218
            $refund ? 'pay/refundquery' : 'pay/orderquery',
219
            $this->payload
220
        );
221
    }
222
223
    /**
224
     * Refund an order.
225
     *
226
     * @author yansongda <[email protected]>
227
     *
228
     * @param array $order
229
     *
230
     * @throws GatewayException
231
     * @throws InvalidSignException
232
     * @throws \Yansongda\Pay\Exceptions\InvalidArgumentException
233
     *
234
     * @return Collection
235
     */
236 View Code Duplication
    public function refund($order): Collection
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
237
    {
238
        $this->payload = Support::filterPayload($this->payload, $order, true);
239
240
        Events::dispatch(Events::METHOD_CALLED, new Events\MethodCalled('Wechat', 'Refund', $this->gateway, $this->payload));
241
242
        return Support::requestApi(
243
            'secapi/pay/refund',
244
            $this->payload,
245
            true
246
        );
247
    }
248
249
    /**
250
     * Cancel an order.
251
     *
252
     * @author yansongda <[email protected]>
253
     *
254
     * @param array $order
255
     *
256
     * @throws GatewayException
257
     * @throws InvalidSignException
258
     * @throws \Yansongda\Pay\Exceptions\InvalidArgumentException
259
     *
260
     * @return Collection
261
     */
262 View Code Duplication
    public function cancel($order): Collection
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263
    {
264
        unset($this->payload['spbill_create_ip']);
265
266
        $this->payload = Support::filterPayload($this->payload, $order, true);
267
268
        Events::dispatch(Events::METHOD_CALLED, new Events\MethodCalled('Wechat', 'Cancel', $this->gateway, $this->payload));
269
270
        return Support::requestApi(
271
            'secapi/pay/reverse',
272
            $this->payload,
273
            true
274
        );
275
    }
276
277
    /**
278
     * Close an order.
279
     *
280
     * @author yansongda <[email protected]>
281
     *
282
     * @param string|array $order
283
     *
284
     * @throws GatewayException
285
     * @throws InvalidSignException
286
     * @throws \Yansongda\Pay\Exceptions\InvalidArgumentException
287
     *
288
     * @return Collection
289
     */
290
    public function close($order): Collection
291
    {
292
        unset($this->payload['spbill_create_ip']);
293
294
        $this->payload = Support::filterPayload($this->payload, $order);
295
296
        Events::dispatch(Events::METHOD_CALLED, new Events\MethodCalled('Wechat', 'Close', $this->gateway, $this->payload));
297
298
        return Support::requestApi('pay/closeorder', $this->payload);
299
    }
300
301
    /**
302
     * Echo success to server.
303
     *
304
     * @author yansongda <[email protected]>
305
     *
306
     * @throws \Yansongda\Pay\Exceptions\InvalidArgumentException
307
     *
308
     * @return Response
309
     */
310
    public function success(): Response
311
    {
312
        Events::dispatch(Events::METHOD_CALLED, new Events\MethodCalled('Wechat', 'Success', $this->gateway));
313
314
        return Response::create(
315
            Support::toXml(['return_code' => 'SUCCESS', 'return_msg' => 'OK']),
316
            200,
317
            ['Content-Type' => 'application/xml']
318
        );
319
    }
320
321
    /**
322
     * Download the bill.
323
     *
324
     * @author yansongda <[email protected]>
325
     *
326
     * @param array $params
327
     *
328
     * @throws GatewayException
329
     * @throws \Yansongda\Pay\Exceptions\InvalidArgumentException
330
     *
331
     * @return string
332
     */
333
    public function download(array $params): string
334
    {
335
        unset($this->payload['spbill_create_ip']);
336
337
        $this->payload = Support::filterPayload($this->payload, $params, true);
338
339
        Events::dispatch(Events::METHOD_CALLED, new Events\MethodCalled('Wechat', 'Download', $this->gateway, $this->payload));
340
341
        $result = Support::getInstance()->post(
342
            'pay/downloadbill',
343
            Support::getInstance()->toXml($this->payload)
344
        );
345
346
        if (is_array($result)) {
347
            throw new GatewayException('Get Wechat API Error: '.$result['return_msg'], $result);
348
        }
349
350
        return $result;
351
    }
352
353
    /**
354
     * Make pay gateway.
355
     *
356
     * @author yansongda <[email protected]>
357
     *
358
     * @param string $gateway
359
     *
360
     * @throws InvalidGatewayException
361
     *
362
     * @return Response|Collection
363
     */
364 View Code Duplication
    protected function makePay($gateway)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
365
    {
366
        $app = new $gateway();
367
368
        if ($app instanceof GatewayInterface) {
369
            return $app->pay($this->gateway, array_filter($this->payload, function ($value) {
370
                return $value !== '' && !is_null($value);
371
            }));
372
        }
373
374
        throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface");
375
    }
376
}
377