Issues (9)

src/Wechat/Traits/RequestTrait.php (1 issue)

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