Completed
Push — master ( c25059...aa3601 )
by i
04:27
created

RequestTrait::send()   C

Complexity

Conditions 7
Paths 24

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 24
nop 5
dl 0
loc 30
ccs 0
cts 17
cp 0
crap 56
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace Nilnice\Payment\Wechat\Traits;
4
5
use GuzzleHttp\Client;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Collection;
8
use Nilnice\Payment\Constant;
9
use Nilnice\Payment\Exception\GatewayException;
10
use Nilnice\Payment\Exception\InvalidSignException;
11
use Psr\Http\Message\ResponseInterface;
12
13
trait RequestTrait
14
{
15
    /**
16
     * Send a Wechat interface request.
17
     *
18
     * @param string      $gateway
19
     * @param array       $array
20
     * @param string      $key
21
     * @param string|null $certClient
22
     * @param string|null $certKey
23
     *
24
     * @return \Illuminate\Support\Collection
25
     * @throws \Nilnice\Payment\Exception\GatewayException
26
     * @throws \InvalidArgumentException
27
     * @throws \Nilnice\Payment\Exception\InvalidKeyException
28
     * @throws \Nilnice\Payment\Exception\InvalidSignException
29
     * @throws \RuntimeException
30
     */
31
    public function send(
32
        string $gateway,
33
        array $array,
34
        string $key,
35
        string $certClient = null,
36
        string $certKey = null
37
    ) : Collection {
38
        $cert = ($certClient !== null && $certKey !== null)
39
            ? ['cert' => $certClient, 'ssl_key' => $certKey]
40
            : [];
41
        $result = $this->post($gateway, self::toXml($array), $cert);
42
        $result = \is_array($result) ? $result : self::fromXml($result);
43
        $code = Arr::get($result, 'result_code');
0 ignored issues
show
Unused Code introduced by
The assignment to $code is dead and can be removed.
Loading history...
44
45
        if (Arr::get($result, 'return_code') !== 'SUCCESS'
46
            || Arr::get($result, 'result_code') !== 'SUCCESS'
47
        ) {
48
            throw new GatewayException(
49
                'Wxpay API Error: ' . $result['return_msg'],
50
                20000
51
            );
52
        }
53
54
        if (self::generateSign($result, $key) === $result['sign']) {
55
            return new Collection($result);
56
        }
57
58
        throw new InvalidSignException(
59
            'Invalid Wxpay [signature] verify.',
60
            3
61
        );
62
    }
63
64
    /**
65
     * Send a post request.
66
     *
67
     * @param string $gateway
68
     * @param mixed  $parameter
69
     * @param array  ...$options
70
     *
71
     * @return mixed
72
     *
73
     * @throws \RuntimeException
74
     */
75
    public function post(
76
        string $gateway,
77
        $parameter = null,
78
        ...$options
79
    ) {
80
        $options = $options[0] ?? [];
81
        if (\is_array($parameter)) {
82
            $options['form_params'] = $parameter;
83
        } else {
84
            $options['body'] = $parameter;
85
        }
86
87
        return $this->request('post', $gateway, $options);
88
    }
89
90
    /**
91
     * Send a request.
92
     *
93
     * @param string $method
94
     * @param string $gateway
95
     * @param array  $options
96
     *
97
     * @return mixed
98
     *
99
     * @throws \RuntimeException
100
     */
101
    public function request(
102
        string $method,
103
        string $gateway,
104
        array $options = []
105
    ) {
106
        if (property_exists($this, 'config')) {
107
            $baseuri = $this->config->get('env') === 'dev'
108
                ? Constant::WX_PAY_DEV_URI
109
                : Constant::WX_PAY_PRO_URI;
110
        }
111
        $baseuri = $baseuri ?? '';
112
        $timeout = property_exists($this, 'timeout') ? $this->timeout : 5.0;
113
        $config = ['base_uri' => $baseuri, 'timeout' => $timeout];
114
115
        $client = new Client($config);
116
        $response = $client->{$method}($gateway, $options);
117
118
        return $this->jsonResponse($response);
119
    }
120
121
    /**
122
     * Convert array to xml.
123
     *
124
     * @param array $array
125
     *
126
     * @return string
127
     *
128
     * @throws \InvalidArgumentException
129
     */
130
    public static function toXml(array $array) : string
131
    {
132
        if (empty($array)) {
133
            throw new \InvalidArgumentException('Invalid [array] argument.', 2);
134
        }
135
136
        $xml = '<xml>';
137
        foreach ($array as $key => $val) {
138
            $xml .= is_numeric($val)
139
                ? "<{$key}>{$val}</{$key}>"
140
                : "<{$key}><![CDATA[{$val}]]></{$key}>";
141
        }
142
        $xml .= '</xml>';
143
144
        return $xml;
145
    }
146
147
    /**
148
     * Convert xml to array.
149
     *
150
     * @param string $xml
151
     *
152
     * @return array
153
     *
154
     * @throws \InvalidArgumentException
155
     */
156
    public static function fromXml(string $xml) : array
157
    {
158
        if (! $xml) {
159
            throw new \InvalidArgumentException('Invalid [xml] argument.', 3);
160
        }
161
        libxml_disable_entity_loader(true);
162
        $array = simplexml_load_string(
163
            $xml,
164
            'SimpleXMLElement',
165
            LIBXML_NOCDATA
166
        );
167
        $array = json_decode(
168
            json_encode($array, JSON_UNESCAPED_UNICODE),
169
            true
170
        );
171
172
        return $array;
173
    }
174
175
    /**
176
     * Decodes a json/javascript/xml response contents.
177
     *
178
     * @param \Psr\Http\Message\ResponseInterface $response
179
     *
180
     * @return mixed
181
     *
182
     * @throws \RuntimeException
183
     */
184
    protected function jsonResponse(ResponseInterface $response)
185
    {
186
        $type = $response->getHeaderLine('Content-Type');
187
        $content = $response->getBody()->getContents();
188
189
        if (false !== self::contains($type, 'json')) {
0 ignored issues
show
introduced by
The condition false !== self::contains($type, 'json') can never be false.
Loading history...
190
            $content = json_decode($content, true);
191
        }
192
193
        return $content;
194
    }
195
196
    /**
197
     * Find the position of the first occurrence of a substring in a string.
198
     *
199
     * @param string $needle
200
     * @param string $haystack
201
     * @param bool   $isStrict
202
     *
203
     * @return bool|int
204
     */
205
    private static function contains(
206
        string $needle,
207
        string $haystack,
208
        bool $isStrict = false
209
    ) {
210
        return $isStrict
211
            ? strpos($haystack, $needle)
212
            : stripos($haystack, $needle);
213
    }
214
}
215