Wechat::refund()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Yansongda\Pay\Gateways;
4
5
use Exception;
6
use Symfony\Component\HttpFoundation\RedirectResponse;
7
use Symfony\Component\HttpFoundation\Request;
8
use Symfony\Component\HttpFoundation\Response;
9
use Yansongda\Pay\Contracts\GatewayApplicationInterface;
10
use Yansongda\Pay\Contracts\GatewayInterface;
11
use Yansongda\Pay\Events;
12
use Yansongda\Pay\Exceptions\GatewayException;
13
use Yansongda\Pay\Exceptions\InvalidArgumentException;
14
use Yansongda\Pay\Exceptions\InvalidGatewayException;
15
use Yansongda\Pay\Exceptions\InvalidSignException;
16
use Yansongda\Pay\Gateways\Wechat\Support;
17
use Yansongda\Pay\Log;
18
use Yansongda\Supports\Collection;
19
use Yansongda\Supports\Config;
20
use Yansongda\Supports\Str;
21
22
/**
23
 * @method Response         app(array $config)          APP 支付
24
 * @method Collection       groupRedpack(array $config) 分裂红包
25
 * @method Collection       miniapp(array $config)      小程序支付
26
 * @method Collection       mp(array $config)           公众号支付
27
 * @method Collection       pos(array $config)          刷卡支付
28
 * @method Collection       redpack(array $config)      普通红包
29
 * @method Collection       scan(array $config)         扫码支付
30
 * @method Collection       transfer(array $config)     企业付款
31
 * @method RedirectResponse wap(array $config)          H5 支付
32
 */
33
class Wechat implements GatewayApplicationInterface
34
{
35
    /**
36
     * 普通模式.
37
     */
38
    const MODE_NORMAL = 'normal';
39
40
    /**
41
     * 沙箱模式.
42
     */
43
    const MODE_DEV = 'dev';
44
45
    /**
46
     * 香港钱包 API.
47
     */
48
    const MODE_HK = 'hk';
49
50
    /**
51
     * 境外 API.
52
     */
53
    const MODE_US = 'us';
54
55
    /**
56
     * 服务商模式.
57
     */
58
    const MODE_SERVICE = 'service';
59
60
    /**
61
     * Const url.
62
     */
63
    const URL = [
64
        self::MODE_NORMAL => 'https://api.mch.weixin.qq.com/',
65
        self::MODE_DEV => 'https://api.mch.weixin.qq.com/sandboxnew/',
66
        self::MODE_HK => 'https://apihk.mch.weixin.qq.com/',
67
        self::MODE_SERVICE => 'https://api.mch.weixin.qq.com/',
68
        self::MODE_US => 'https://apius.mch.weixin.qq.com/',
69
    ];
70
71
    /**
72
     * Wechat payload.
73
     *
74
     * @var array
75
     */
76
    protected $payload;
77
78
    /**
79
     * Wechat gateway.
80
     *
81
     * @var string
82
     */
83
    protected $gateway;
84
85
    /**
86
     * Bootstrap.
87
     *
88
     * @author yansongda <[email protected]>
89
     *
90
     * @param Config $config
91
     *
92
     * @throws Exception
93
     */
94
    public function __construct(Config $config)
95
    {
96
        $this->gateway = Support::create($config)->getBaseUri();
97
        $this->payload = [
98
            'appid' => $config->get('app_id', ''),
99
            'mch_id' => $config->get('mch_id', ''),
100
            'nonce_str' => Str::random(),
101
            'notify_url' => $config->get('notify_url', ''),
102
            'sign' => '',
103
            'trade_type' => '',
104
            'spbill_create_ip' => Request::createFromGlobals()->getClientIp(),
105
        ];
106
107
        if ($config->get('mode', self::MODE_NORMAL) === static::MODE_SERVICE) {
108
            $this->payload = array_merge($this->payload, [
109
                'sub_mch_id' => $config->get('sub_mch_id'),
110
                'sub_appid' => $config->get('sub_app_id', ''),
111
            ]);
112
        }
113
    }
114
115
    /**
116
     * Magic pay.
117
     *
118
     * @author yansongda <[email protected]>
119
     *
120
     * @param string $method
121
     * @param string $params
122
     *
123
     * @throws InvalidGatewayException
124
     *
125
     * @return Response|Collection
126
     */
127
    public function __call($method, $params)
128
    {
129
        return self::pay($method, ...$params);
0 ignored issues
show
Documentation introduced by yansongda
$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...
130
    }
131
132
    /**
133
     * Pay an order.
134
     *
135
     * @author yansongda <[email protected]>
136
     *
137
     * @param string $gateway
138
     * @param array  $params
139
     *
140
     * @throws InvalidGatewayException
141
     *
142
     * @return Response|Collection
143
     */
144
    public function pay($gateway, $params = [])
145
    {
146
        Events::dispatch(new Events\PayStarting('Wechat', $gateway, $params));
147
148
        $this->payload = array_merge($this->payload, $params);
149
150
        $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway';
151
152
        if (class_exists($gateway)) {
153
            return $this->makePay($gateway);
154
        }
155
156
        throw new InvalidGatewayException("Pay Gateway [{$gateway}] Not Exists");
157
    }
158
159
    /**
160
     * Verify data.
161
     *
162
     * @author yansongda <[email protected]>
163
     *
164
     * @param string|null $content
165
     * @param bool        $refund
166
     *
167
     * @throws InvalidSignException
168
     * @throws InvalidArgumentException
169
     *
170
     * @return Collection
171
     */
172
    public function verify($content = null, bool $refund = false): Collection
173
    {
174
        $content = $content ?? Request::createFromGlobals()->getContent();
175
176
        Events::dispatch(new Events\RequestReceived('Wechat', '', [$content]));
177
178
        $data = Support::fromXml($content);
0 ignored issues
show
Bug introduced by yansongda
It seems like $content defined by $content ?? \Symfony\Com...Globals()->getContent() on line 174 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...
179
        if ($refund) {
180
            $decrypt_data = Support::decryptRefundContents($data['req_info']);
181
            $data = array_merge(Support::fromXml($decrypt_data), $data);
182
        }
183
184
        Log::debug('Resolved The Received Wechat Request Data', $data);
185
186
        if ($refund || Support::generateSign($data) === $data['sign']) {
187
            return new Collection($data);
188
        }
189
190
        Events::dispatch(new Events\SignFailed('Wechat', '', $data));
191
192
        throw new InvalidSignException('Wechat Sign Verify FAILED', $data);
193
    }
194
195
    /**
196
     * Query an order.
197
     *
198
     * @author yansongda <[email protected]>
199
     *
200
     * @param string|array $order
201
     * @param string       $type
202
     *
203
     * @throws GatewayException
204
     * @throws InvalidSignException
205
     * @throws InvalidArgumentException
206
     *
207
     * @return Collection
208
     */
209
    public function find($order, string $type = 'wap'): Collection
210
    {
211
        if ('wap' != $type) {
212
            unset($this->payload['spbill_create_ip']);
213
        }
214
215
        $gateway = get_class($this).'\\'.Str::studly($type).'Gateway';
216
217
        if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) {
218
            throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method");
219
        }
220
221
        $config = call_user_func([new $gateway(), 'find'], $order);
222
223
        $this->payload = Support::filterPayload($this->payload, $config['order']);
224
225
        Events::dispatch(new Events\MethodCalled('Wechat', 'Find', $this->gateway, $this->payload));
226
227
        return Support::requestApi(
228
            $config['endpoint'],
229
            $this->payload,
230
            $config['cert']
231
        );
232
    }
233
234
    /**
235
     * Refund an order.
236
     *
237
     * @author yansongda <[email protected]>
238
     *
239
     * @param array $order
240
     *
241
     * @throws GatewayException
242
     * @throws InvalidSignException
243
     * @throws InvalidArgumentException
244
     *
245
     * @return Collection
246
     */
247
    public function refund(array $order): Collection
248
    {
249
        $this->payload = Support::filterPayload($this->payload, $order, true);
250
251
        Events::dispatch(new Events\MethodCalled('Wechat', 'Refund', $this->gateway, $this->payload));
252
253
        return Support::requestApi(
254
            'secapi/pay/refund',
255
            $this->payload,
256
            true
257
        );
258
    }
259
260
    /**
261
     * Cancel an order.
262
     *
263
     * @author yansongda <[email protected]>
264
     *
265
     * @param array $order
266
     *
267
     * @throws GatewayException
268
     * @throws InvalidSignException
269
     * @throws InvalidArgumentException
270
     *
271
     * @return Collection
272
     */
273 View Code Duplication
    public function cancel($order): Collection
0 ignored issues
show
Duplication introduced by yansongda
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...
274
    {
275
        unset($this->payload['spbill_create_ip']);
276
277
        $this->payload = Support::filterPayload($this->payload, $order, true);
278
279
        Events::dispatch(new Events\MethodCalled('Wechat', 'Cancel', $this->gateway, $this->payload));
280
281
        return Support::requestApi(
282
            'secapi/pay/reverse',
283
            $this->payload,
284
            true
285
        );
286
    }
287
288
    /**
289
     * Close an order.
290
     *
291
     * @author yansongda <[email protected]>
292
     *
293
     * @param string|array $order
294
     *
295
     * @throws GatewayException
296
     * @throws InvalidSignException
297
     * @throws InvalidArgumentException
298
     *
299
     * @return Collection
300
     */
301 View Code Duplication
    public function close($order): Collection
0 ignored issues
show
Duplication introduced by yansongda
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...
302
    {
303
        unset($this->payload['spbill_create_ip']);
304
305
        $this->payload = Support::filterPayload($this->payload, $order);
306
307
        Events::dispatch(new Events\MethodCalled('Wechat', 'Close', $this->gateway, $this->payload));
308
309
        return Support::requestApi('pay/closeorder', $this->payload);
310
    }
311
312
    /**
313
     * Echo success to server.
314
     *
315
     * @author yansongda <[email protected]>
316
     *
317
     * @throws InvalidArgumentException
318
     *
319
     * @return Response
320
     */
321
    public function success(): Response
322
    {
323
        Events::dispatch(new Events\MethodCalled('Wechat', 'Success', $this->gateway));
324
325
        return Response::create(
326
            Support::toXml(['return_code' => 'SUCCESS', 'return_msg' => 'OK']),
327
            200,
328
            ['Content-Type' => 'application/xml']
329
        );
330
    }
331
332
    /**
333
     * Download the bill.
334
     *
335
     * @author yansongda <[email protected]>
336
     *
337
     * @param array $params
338
     *
339
     * @throws GatewayException
340
     * @throws InvalidArgumentException
341
     *
342
     * @return string
343
     */
344
    public function download(array $params): string
345
    {
346
        unset($this->payload['spbill_create_ip']);
347
348
        $this->payload = Support::filterPayload($this->payload, $params, true);
349
350
        Events::dispatch(new Events\MethodCalled('Wechat', 'Download', $this->gateway, $this->payload));
351
352
        $result = Support::getInstance()->post(
353
            'pay/downloadbill',
354
            Support::getInstance()->toXml($this->payload)
355
        );
356
357
        if (is_array($result)) {
358
            throw new GatewayException('Get Wechat API Error: '.$result['return_msg'], $result);
359
        }
360
361
        return $result;
362
    }
363
364
    /**
365
     * Make pay gateway.
366
     *
367
     * @author yansongda <[email protected]>
368
     *
369
     * @param string $gateway
370
     *
371
     * @throws InvalidGatewayException
372
     *
373
     * @return Response|Collection
374
     */
375
    protected function makePay($gateway)
376
    {
377
        $app = new $gateway();
378
379
        if ($app instanceof GatewayInterface) {
380
            return $app->pay($this->gateway, array_filter($this->payload, function ($value) {
381
                return '' !== $value && !is_null($value);
382
            }));
383
        }
384
385
        throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface");
386
    }
387
}
388