Alipay   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 16
dl 0
loc 395
rs 9.28
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A success() 0 6 1
A refund() 0 10 1
A find() 0 18 3
A __construct() 0 23 3
A __call() 0 8 2
A pay() 0 19 2
A verify() 0 22 5
A cancel() 0 10 2
A close() 0 10 2
A download() 0 12 3
B extend() 0 25 6
A makePay() 0 12 3
B makeExtend() 0 28 6
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\Events;
10
use Yansongda\Pay\Exceptions\GatewayException;
11
use Yansongda\Pay\Exceptions\InvalidArgumentException;
12
use Yansongda\Pay\Exceptions\InvalidConfigException;
13
use Yansongda\Pay\Exceptions\InvalidGatewayException;
14
use Yansongda\Pay\Exceptions\InvalidSignException;
15
use Yansongda\Pay\Gateways\Alipay\Support;
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 pos(array $config)      刷卡支付
23
 * @method Collection scan(array $config)     扫码支付
24
 * @method Collection transfer(array $config) 帐户转账
25
 * @method Response   wap(array $config)      手机网站支付
26
 * @method Response   web(array $config)      电脑支付
27
 * @method Collection mini(array $config)     小程序支付
28
 */
29
class Alipay implements GatewayApplicationInterface
30
{
31
    /**
32
     * Const mode_normal.
33
     */
34
    const MODE_NORMAL = 'normal';
35
36
    /**
37
     * Const mode_dev.
38
     */
39
    const MODE_DEV = 'dev';
40
41
    /**
42
     * Const mode_service.
43
     */
44
    const MODE_SERVICE = 'service';
45
46
    /**
47
     * Const url.
48
     */
49
    const URL = [
50
        self::MODE_NORMAL => 'https://openapi.alipay.com/gateway.do?charset=utf-8',
51
        self::MODE_SERVICE => 'https://openapi.alipay.com/gateway.do?charset=utf-8',
52
        self::MODE_DEV => 'https://openapi.alipaydev.com/gateway.do?charset=utf-8',
53
    ];
54
55
    /**
56
     * Alipay payload.
57
     *
58
     * @var array
59
     */
60
    protected $payload;
61
62
    /**
63
     * Alipay gateway.
64
     *
65
     * @var string
66
     */
67
    protected $gateway;
68
69
    /**
70
     * extends.
71
     *
72
     * @var array
73
     */
74
    protected $extends;
75
76
    /**
77
     * Bootstrap.
78
     *
79
     * @author yansongda <[email protected]>
80
     *
81
     * @throws \Exception
82
     */
83
    public function __construct(Config $config)
84
    {
85
        $this->gateway = Support::create($config)->getBaseUri();
86
        $this->payload = [
87
            'app_id' => $config->get('app_id'),
88
            'method' => '',
89
            'format' => 'JSON',
90
            'charset' => 'utf-8',
91
            'sign_type' => 'RSA2',
92
            'version' => '1.0',
93
            'return_url' => $config->get('return_url'),
94
            'notify_url' => $config->get('notify_url'),
95
            'timestamp' => date('Y-m-d H:i:s'),
96
            'sign' => '',
97
            'biz_content' => '',
98
            'app_auth_token' => $config->get('app_auth_token'),
99
        ];
100
101
        if ($config->get('app_cert_public_key') && $config->get('alipay_root_cert')) {
102
            $this->payload['app_cert_sn'] = Support::getCertSN($config->get('app_cert_public_key'));
103
            $this->payload['alipay_root_cert_sn'] = Support::getRootCertSN($config->get('alipay_root_cert'));
104
        }
105
    }
106
107
    /**
108
     * Magic pay.
109
     *
110
     * @author yansongda <[email protected]>
111
     *
112
     * @param string $method
113
     * @param array  $params
114
     *
115
     * @throws GatewayException
116
     * @throws InvalidArgumentException
117
     * @throws InvalidConfigException
118
     * @throws InvalidGatewayException
119
     * @throws InvalidSignException
120
     *
121
     * @return Response|Collection
122
     */
123
    public function __call($method, $params)
124
    {
125
        if (isset($this->extends[$method])) {
126
            return $this->makeExtend($method, ...$params);
127
        }
128
129
        return $this->pay($method, ...$params);
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('Alipay', $gateway, $params));
147
148
        $this->payload['return_url'] = $params['return_url'] ?? $this->payload['return_url'];
149
        $this->payload['notify_url'] = $params['notify_url'] ?? $this->payload['notify_url'];
150
151
        unset($params['return_url'], $params['notify_url']);
152
153
        $this->payload['biz_content'] = json_encode($params);
154
155
        $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway';
156
157
        if (class_exists($gateway)) {
158
            return $this->makePay($gateway);
159
        }
160
161
        throw new InvalidGatewayException("Pay Gateway [{$gateway}] not exists");
162
    }
163
164
    /**
165
     * Verify sign.
166
     *
167
     * @author yansongda <[email protected]>
168
     *
169
     * @param array|null $data
170
     *
171
     * @throws InvalidSignException
172
     * @throws InvalidConfigException
173
     */
174
    public function verify($data = null, bool $refund = false): Collection
175
    {
176
        if (is_null($data)) {
177
            $request = Request::createFromGlobals();
178
179
            $data = $request->request->count() > 0 ? $request->request->all() : $request->query->all();
180
        }
181
182
        if (isset($data['fund_bill_list'])) {
183
            $data['fund_bill_list'] = htmlspecialchars_decode($data['fund_bill_list']);
184
        }
185
186
        Events::dispatch(new Events\RequestReceived('Alipay', '', $data));
187
188
        if (Support::verifySign($data)) {
189
            return new Collection($data);
190
        }
191
192
        Events::dispatch(new Events\SignFailed('Alipay', '', $data));
193
194
        throw new InvalidSignException('Alipay Sign Verify FAILED', $data);
195
    }
196
197
    /**
198
     * Query an order.
199
     *
200
     * @author yansongda <[email protected]>
201
     *
202
     * @param string|array $order
203
     *
204
     * @throws GatewayException
205
     * @throws InvalidConfigException
206
     * @throws InvalidSignException
207
     */
208
    public function find($order, string $type = 'wap'): Collection
209
    {
210
        $gateway = get_class($this).'\\'.Str::studly($type).'Gateway';
211
212
        if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) {
213
            throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method");
214
        }
215
216
        $config = call_user_func([new $gateway(), 'find'], $order);
217
218
        $this->payload['method'] = $config['method'];
219
        $this->payload['biz_content'] = $config['biz_content'];
220
        $this->payload['sign'] = Support::generateSign($this->payload);
221
222
        Events::dispatch(new Events\MethodCalled('Alipay', 'Find', $this->gateway, $this->payload));
223
224
        return Support::requestApi($this->payload);
225
    }
226
227
    /**
228
     * Refund an order.
229
     *
230
     * @author yansongda <[email protected]>
231
     *
232
     * @throws GatewayException
233
     * @throws InvalidConfigException
234
     * @throws InvalidSignException
235
     */
236
    public function refund(array $order): Collection
237
    {
238
        $this->payload['method'] = 'alipay.trade.refund';
239
        $this->payload['biz_content'] = json_encode($order);
240
        $this->payload['sign'] = Support::generateSign($this->payload);
241
242
        Events::dispatch(new Events\MethodCalled('Alipay', 'Refund', $this->gateway, $this->payload));
243
244
        return Support::requestApi($this->payload);
245
    }
246
247
    /**
248
     * Cancel an order.
249
     *
250
     * @author yansongda <[email protected]>
251
     *
252
     * @param array|string $order
253
     *
254
     * @throws GatewayException
255
     * @throws InvalidConfigException
256
     * @throws InvalidSignException
257
     */
258
    public function cancel($order): Collection
259
    {
260
        $this->payload['method'] = 'alipay.trade.cancel';
261
        $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]);
262
        $this->payload['sign'] = Support::generateSign($this->payload);
263
264
        Events::dispatch(new Events\MethodCalled('Alipay', 'Cancel', $this->gateway, $this->payload));
265
266
        return Support::requestApi($this->payload);
267
    }
268
269
    /**
270
     * Close an order.
271
     *
272
     * @param string|array $order
273
     *
274
     * @author yansongda <[email protected]>
275
     *
276
     * @throws GatewayException
277
     * @throws InvalidConfigException
278
     * @throws InvalidSignException
279
     */
280
    public function close($order): Collection
281
    {
282
        $this->payload['method'] = 'alipay.trade.close';
283
        $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]);
284
        $this->payload['sign'] = Support::generateSign($this->payload);
285
286
        Events::dispatch(new Events\MethodCalled('Alipay', 'Close', $this->gateway, $this->payload));
287
288
        return Support::requestApi($this->payload);
289
    }
290
291
    /**
292
     * Download bill.
293
     *
294
     * @author yansongda <[email protected]>
295
     *
296
     * @param string|array $bill
297
     *
298
     * @throws GatewayException
299
     * @throws InvalidConfigException
300
     * @throws InvalidSignException
301
     */
302
    public function download($bill): string
303
    {
304
        $this->payload['method'] = 'alipay.data.dataservice.bill.downloadurl.query';
305
        $this->payload['biz_content'] = json_encode(is_array($bill) ? $bill : ['bill_type' => 'trade', 'bill_date' => $bill]);
306
        $this->payload['sign'] = Support::generateSign($this->payload);
307
308
        Events::dispatch(new Events\MethodCalled('Alipay', 'Download', $this->gateway, $this->payload));
309
310
        $result = Support::requestApi($this->payload);
311
312
        return ($result instanceof Collection) ? $result->get('bill_download_url') : '';
313
    }
314
315
    /**
316
     * Reply success to alipay.
317
     *
318
     * @author yansongda <[email protected]>
319
     */
320
    public function success(): Response
321
    {
322
        Events::dispatch(new Events\MethodCalled('Alipay', 'Success', $this->gateway));
323
324
        return new Response('success');
325
    }
326
327
    /**
328
     * extend.
329
     *
330
     * @author yansongda <[email protected]>
331
     *
332
     * @throws GatewayException
333
     * @throws InvalidConfigException
334
     * @throws InvalidSignException
335
     * @throws InvalidArgumentException
336
     */
337
    public function extend(string $method, callable $function, bool $now = true): ?Collection
338
    {
339
        if (!$now && !method_exists($this, $method)) {
340
            $this->extends[$method] = $function;
341
342
            return null;
343
        }
344
345
        $customize = $function($this->payload);
346
347
        if (!is_array($customize) && !($customize instanceof Collection)) {
348
            throw new InvalidArgumentException('Return Type Must Be Array Or Collection');
349
        }
350
351
        Events::dispatch(new Events\MethodCalled('Alipay', 'extend', $this->gateway, $customize));
0 ignored issues
show
Bug introduced by yansongda
It seems like $customize can also be of type object<Yansongda\Supports\Collection>; however, Yansongda\Pay\Events\MethodCalled::__construct() does only seem to accept array, 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...
352
353
        if (is_array($customize)) {
354
            $this->payload = $customize;
355
            $this->payload['sign'] = Support::generateSign($this->payload);
356
357
            return Support::requestApi($this->payload);
358
        }
359
360
        return $customize;
361
    }
362
363
    /**
364
     * Make pay gateway.
365
     *
366
     * @author yansongda <[email protected]>
367
     *
368
     * @throws InvalidGatewayException
369
     *
370
     * @return Response|Collection
371
     */
372
    protected function makePay(string $gateway)
373
    {
374
        $app = new $gateway();
375
376
        if ($app instanceof GatewayInterface) {
377
            return $app->pay($this->gateway, array_filter($this->payload, function ($value) {
378
                return '' !== $value && !is_null($value);
379
            }));
380
        }
381
382
        throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface");
383
    }
384
385
    /**
386
     * makeExtend.
387
     *
388
     * @author yansongda <[email protected]>
389
     *
390
     * @throws GatewayException
391
     * @throws InvalidArgumentException
392
     * @throws InvalidConfigException
393
     * @throws InvalidSignException
394
     */
395
    protected function makeExtend(string $method, array ...$params): Collection
396
    {
397
        $params = count($params) >= 1 ? $params[0] : $params;
398
399
        $function = $this->extends[$method];
400
401
        $customize = $function($this->payload, $params);
402
403
        if (!is_array($customize) && !($customize instanceof Collection)) {
404
            throw new InvalidArgumentException('Return Type Must Be Array Or Collection');
405
        }
406
407
        Events::dispatch(new Events\MethodCalled(
408
            'Alipay',
409
            'extend - '.$method,
410
            $this->gateway,
411
            is_array($customize) ? $customize : $customize->toArray()
412
        ));
413
414
        if (is_array($customize)) {
415
            $this->payload = $customize;
416
            $this->payload['sign'] = Support::generateSign($this->payload);
417
418
            return Support::requestApi($this->payload);
419
        }
420
421
        return $customize;
422
    }
423
}
424