Passed
Push — master ( 8679ac...a8b1e8 )
by i
03:51
created

RequestTrait::filterPayload()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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