Completed
Push — master ( 6518f9...994d23 )
by Songda
01:44
created

src/Gateways/Wechat.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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