Alipay::extend()   B
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8977
c 0
b 0
f 0
cc 6
nc 4
nop 3
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_DEV => 'https://openapi.alipaydev.com/gateway.do?charset=utf-8',
52
    ];
53
54
    /**
55
     * Alipay payload.
56
     *
57
     * @var array
58
     */
59
    protected $payload;
60
61
    /**
62
     * Alipay gateway.
63
     *
64
     * @var string
65
     */
66
    protected $gateway;
67
68
    /**
69
     * extends.
70
     *
71
     * @var array
72
     */
73
    protected $extends;
74
75
    /**
76
     * Bootstrap.
77
     *
78
     * @author yansongda <[email protected]>
79
     *
80
     * @param Config $config
81
     */
82
    public function __construct(Config $config)
83
    {
84
        $this->gateway = Support::create($config)->getBaseUri();
85
        $this->payload = [
86
            'app_id' => $config->get('app_id'),
87
            'method' => '',
88
            'format' => 'JSON',
89
            'charset' => 'utf-8',
90
            'sign_type' => 'RSA2',
91
            'version' => '1.0',
92
            'return_url' => $config->get('return_url'),
93
            'notify_url' => $config->get('notify_url'),
94
            'timestamp' => date('Y-m-d H:i:s'),
95
            'sign' => '',
96
            'biz_content' => '',
97
            'app_auth_token' => $config->get('app_auth_token'),
98
        ];
99
    }
100
101
    /**
102
     * Magic pay.
103
     *
104
     * @author yansongda <[email protected]>
105
     *
106
     * @param string $method
107
     * @param array  $params
108
     *
109
     * @throws GatewayException
110
     * @throws InvalidArgumentException
111
     * @throws InvalidConfigException
112
     * @throws InvalidGatewayException
113
     * @throws InvalidSignException
114
     *
115
     * @return Response|Collection
116
     */
117
    public function __call($method, $params)
118
    {
119
        if (isset($this->extends[$method])) {
120
            return $this->makeExtend($method, ...$params);
121
        }
122
123
        return $this->pay($method, ...$params);
124
    }
125
126
    /**
127
     * Pay an order.
128
     *
129
     * @author yansongda <[email protected]>
130
     *
131
     * @param string $gateway
132
     * @param array  $params
133
     *
134
     * @throws InvalidGatewayException
135
     *
136
     * @return Response|Collection
137
     */
138
    public function pay($gateway, $params = [])
139
    {
140
        Events::dispatch(new Events\PayStarting('Alipay', $gateway, $params));
141
142
        $this->payload['return_url'] = $params['return_url'] ?? $this->payload['return_url'];
143
        $this->payload['notify_url'] = $params['notify_url'] ?? $this->payload['notify_url'];
144
145
        unset($params['return_url'], $params['notify_url']);
146
147
        $this->payload['biz_content'] = json_encode($params);
148
149
        $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway';
150
151
        if (class_exists($gateway)) {
152
            return $this->makePay($gateway);
153
        }
154
155
        throw new InvalidGatewayException("Pay Gateway [{$gateway}] not exists");
156
    }
157
158
    /**
159
     * Verify sign.
160
     *
161
     * @author yansongda <[email protected]>
162
     *
163
     * @param array|null $data
164
     * @param bool       $refund
165
     *
166
     * @throws InvalidSignException
167
     * @throws InvalidConfigException
168
     *
169
     * @return Collection
170
     */
171
    public function verify($data = null, bool $refund = false): Collection
172
    {
173
        if (is_null($data)) {
174
            $request = Request::createFromGlobals();
175
176
            $data = $request->request->count() > 0 ? $request->request->all() : $request->query->all();
177
        }
178
179
        if (isset($data['fund_bill_list'])) {
180
            $data['fund_bill_list'] = htmlspecialchars_decode($data['fund_bill_list']);
181
        }
182
183
        Events::dispatch(new Events\RequestReceived('Alipay', '', $data));
184
185
        if (Support::verifySign($data)) {
186
            return new Collection($data);
187
        }
188
189
        Events::dispatch(new Events\SignFailed('Alipay', '', $data));
190
191
        throw new InvalidSignException('Alipay Sign Verify FAILED', $data);
192
    }
193
194
    /**
195
     * Query an order.
196
     *
197
     * @author yansongda <[email protected]>
198
     *
199
     * @param string|array $order
200
     * @param string       $type
201
     *
202
     * @throws GatewayException
203
     * @throws InvalidConfigException
204
     * @throws InvalidSignException
205
     *
206
     * @return Collection
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
     * @param array $order
233
     *
234
     * @throws GatewayException
235
     * @throws InvalidConfigException
236
     * @throws InvalidSignException
237
     *
238
     * @return Collection
239
     */
240
    public function refund(array $order): Collection
241
    {
242
        $this->payload['method'] = 'alipay.trade.refund';
243
        $this->payload['biz_content'] = json_encode($order);
244
        $this->payload['sign'] = Support::generateSign($this->payload);
245
246
        Events::dispatch(new Events\MethodCalled('Alipay', 'Refund', $this->gateway, $this->payload));
247
248
        return Support::requestApi($this->payload);
249
    }
250
251
    /**
252
     * Cancel an order.
253
     *
254
     * @author yansongda <[email protected]>
255
     *
256
     * @param array|string $order
257
     *
258
     * @throws GatewayException
259
     * @throws InvalidConfigException
260
     * @throws InvalidSignException
261
     *
262
     * @return Collection
263
     */
264
    public function cancel($order): Collection
265
    {
266
        $this->payload['method'] = 'alipay.trade.cancel';
267
        $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]);
268
        $this->payload['sign'] = Support::generateSign($this->payload);
269
270
        Events::dispatch(new Events\MethodCalled('Alipay', 'Cancel', $this->gateway, $this->payload));
271
272
        return Support::requestApi($this->payload);
273
    }
274
275
    /**
276
     * Close an order.
277
     *
278
     * @param string|array $order
279
     *
280
     * @author yansongda <[email protected]>
281
     *
282
     * @throws GatewayException
283
     * @throws InvalidConfigException
284
     * @throws InvalidSignException
285
     *
286
     * @return Collection
287
     */
288
    public function close($order): Collection
289
    {
290
        $this->payload['method'] = 'alipay.trade.close';
291
        $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]);
292
        $this->payload['sign'] = Support::generateSign($this->payload);
293
294
        Events::dispatch(new Events\MethodCalled('Alipay', 'Close', $this->gateway, $this->payload));
295
296
        return Support::requestApi($this->payload);
297
    }
298
299
    /**
300
     * Download bill.
301
     *
302
     * @author yansongda <[email protected]>
303
     *
304
     * @param string|array $bill
305
     *
306
     * @throws GatewayException
307
     * @throws InvalidConfigException
308
     * @throws InvalidSignException
309
     *
310
     * @return string
311
     */
312
    public function download($bill): string
313
    {
314
        $this->payload['method'] = 'alipay.data.dataservice.bill.downloadurl.query';
315
        $this->payload['biz_content'] = json_encode(is_array($bill) ? $bill : ['bill_type' => 'trade', 'bill_date' => $bill]);
316
        $this->payload['sign'] = Support::generateSign($this->payload);
317
318
        Events::dispatch(new Events\MethodCalled('Alipay', 'Download', $this->gateway, $this->payload));
319
320
        $result = Support::requestApi($this->payload);
321
322
        return ($result instanceof Collection) ? $result->get('bill_download_url') : '';
323
    }
324
325
    /**
326
     * Reply success to alipay.
327
     *
328
     * @author yansongda <[email protected]>
329
     *
330
     * @return Response
331
     */
332
    public function success(): Response
333
    {
334
        Events::dispatch(new Events\MethodCalled('Alipay', 'Success', $this->gateway));
335
336
        return Response::create('success');
337
    }
338
339
    /**
340
     * extend.
341
     *
342
     * @author yansongda <[email protected]>
343
     *
344
     * @param string   $method
345
     * @param callable $function
346
     * @param bool     $now
347
     *
348
     * @throws GatewayException
349
     * @throws InvalidConfigException
350
     * @throws InvalidSignException
351
     * @throws InvalidArgumentException
352
     *
353
     * @return Collection|null
354
     */
355
    public function extend(string $method, callable $function, bool $now = true): ?Collection
356
    {
357
        if (!$now && !method_exists($this, $method)) {
358
            $this->extends[$method] = $function;
359
360
            return null;
361
        }
362
363
        $customize = $function($this->payload);
364
365
        if (!is_array($customize) && !($customize instanceof Collection)) {
366
            throw new InvalidArgumentException('Return Type Must Be Array Or Collection');
367
        }
368
369
        Events::dispatch(new Events\MethodCalled('Alipay', 'extend', $this->gateway, $customize));
370
371
        if (is_array($customize)) {
372
            $this->payload = $customize;
373
            $this->payload['sign'] = Support::generateSign($this->payload);
374
375
            return Support::requestApi($this->payload);
376
        }
377
378
        return $customize;
379
    }
380
381
    /**
382
     * Make pay gateway.
383
     *
384
     * @author yansongda <[email protected]>
385
     *
386
     * @param string $gateway
387
     *
388
     * @throws InvalidGatewayException
389
     *
390
     * @return Response|Collection
391
     */
392
    protected function makePay(string $gateway)
393
    {
394
        $app = new $gateway();
395
396
        if ($app instanceof GatewayInterface) {
397
            return $app->pay($this->gateway, array_filter($this->payload, function ($value) {
398
                return '' !== $value && !is_null($value);
399
            }));
400
        }
401
402
        throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface");
403
    }
404
405
    /**
406
     * makeExtend.
407
     *
408
     * @author yansongda <[email protected]>
409
     *
410
     * @param string $method
411
     * @param array  $params
412
     *
413
     * @throws GatewayException
414
     * @throws InvalidArgumentException
415
     * @throws InvalidConfigException
416
     * @throws InvalidSignException
417
     *
418
     * @return Collection
419
     */
420
    protected function makeExtend(string $method, array ...$params): Collection
421
    {
422
        $params = count($params) >= 1 ? $params[0] : $params;
423
424
        $function = $this->extends[$method];
425
426
        $customize = $function($this->payload, $params);
427
428
        if (!is_array($customize) && !($customize instanceof Collection)) {
429
            throw new InvalidArgumentException('Return Type Must Be Array Or Collection');
430
        }
431
432
        Events::dispatch(new Events\MethodCalled(
433
            'Alipay',
434
            'extend - '.$method,
435
            $this->gateway,
436
            is_array($customize) ? $customize : $customize->toArray()
437
        ));
438
439
        if (is_array($customize)) {
440
            $this->payload = $customize;
441
            $this->payload['sign'] = Support::generateSign($this->payload);
442
443
            return Support::requestApi($this->payload);
444
        }
445
446
        return $customize;
447
    }
448
}
449