Completed
Push — master ( c3ded3...e46555 )
by Songda
13s
created

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