Issues (32)

src/WechatPay.php (14 issues)

1
<?php
2
/**
3
 * WechatPay
4
 *
5
 * @license MIT
6
 * @author zhangv
7
 */
8
namespace zhangv\wechat\pay;
9
10
use \Exception;
11
use zhangv\wechat\pay\util\HttpClient;
12
use zhangv\wechat\pay\util\OAuth;
13
use zhangv\wechat\pay\cache\CacheProvider;
14
use zhangv\wechat\pay\cache\JsonFileCacheProvider;
15
16
/**
17
 * Class WechatPay
18
 * @package zhangv\wechat
19
 * @author zhangv
20
 * @license MIT
21
 *
22
 * @method static service\App       App(array $config)
23
 * @method static service\Jsapi     Jsapi(array $config)
24
 * @method static service\Micro     Micro(array $config)
25
 * @method static service\Mweb      Mweb(array $config)
26
 * @method static service\Native    Native(array $config)
27
 * @method static service\Weapp     Weapp(array $config)
28
 * @method static service\Mchpay    Mchpay(array $config)
29
 * @method static service\Redpack   Redpack(array $config)
30
 * @method static service\Coupon    Coupon(array $config)
31
 */
32
class WechatPay {
33
	const TRADETYPE_JSAPI = 'JSAPI',TRADETYPE_NATIVE = 'NATIVE',TRADETYPE_APP = 'APP',TRADETYPE_MWEB = 'MWEB';
34
	const SIGNTYPE_MD5 = 'MD5', SIGNTYPE_HMACSHA256 = 'HMAC-SHA256';
35
	const CHECKNAME_FORCECHECK = 'FORCE_CHECK',CHECKNAME_NOCHECK = 'NO_CHECK';
36
	const ACCOUNTTYPE_BASIC = 'Basic',ACCOUNTTYPE_OPERATION = 'Operation',ACCOUNTTYPE_FEES = 'Fees';
37
	const API_ENDPOINT = 'https://api.mch.weixin.qq.com/';
38
	/** 支付 */
39
	const URL_UNIFIEDORDER = 'pay/unifiedorder';
40
	const URL_ORDERQUERY = 'pay/orderquery';
41
	const URL_CLOSEORDER = 'pay/closeorder';
42
	const URL_REFUND = 'secapi/pay/refund';
43
	const URL_REFUNDQUERY = 'pay/refundquery';
44
	const URL_DOWNLOADBILL = 'pay/downloadbill';
45
	const URL_DOWNLOAD_FUND_FLOW = 'pay/downloadfundflow';
46
	const URL_REPORT = 'payitil/report';
47
	const URL_SHORTURL = 'tools/shorturl';
48
	const URL_MICROPAY = 'pay/micropay';
49
	const URL_BATCHQUERYCOMMENT = 'billcommentsp/batchquerycomment';
50
	const URL_REVERSE = 'secapi/pay/reverse';
51
	const URL_AUTHCODETOOPENID = 'tools/authcodetoopenid';
52
	/** 红包 */
53
	const URL_GETHBINFO = 'mmpaymkttransfers/gethbinfo';
54
	const URL_SENDREDPACK = 'mmpaymkttransfers/sendredpack';
55
	const URL_SENDGROUPREDPACK = 'mmpaymkttransfers/sendgroupredpack';
56
	/** 企业付款 */
57
	const URL_TRANSFER_WALLET = 'mmpaymkttransfers/promotion/transfers';
58
	const URL_QUERY_TRANSFER_WALLET = 'mmpaymkttransfers/gettransferinfo';
59
	const URL_TRANSFER_BANKCARD = 'mmpaysptrans/pay_bank';
60
	const URL_QUERY_TRANSFER_BANKCARD = 'mmpaysptrans/query_bank';
61
	/** 代金券 */
62
	const URL_SEND_COUPON = 'mmpaymkttransfers/send_coupon';
63
	const URL_QUERY_COUPON_STOCK = 'mmpaymkttransfers/query_coupon_stock';
64
	const URL_QUERY_COUPON_INFO = 'mmpaymkttransfers/querycouponsinfo';
65
	/** Sandbox获取测试公钥 */
66
	const URL_GETPUBLICKEY = 'https://fraud.mch.weixin.qq.com/risk/getpublickey';
67
	public static $BANKCODE = [
68
		'工商银行' => '1002', '农业银行' => '1005', '中国银行' => '1026', '建设银行' => '1003', '招商银行' => '1001',
69
		'邮储银行' => '1066', '交通银行' => '1020', '浦发银行' => '1004', '民生银行' => '1006', '兴业银行' => '1009',
70
		'平安银行' => '1010', '中信银行' => '1021', '华夏银行' => '1025', '广发银行' => '1027', '光大银行' => '1022',
71
		'北京银行' => '1032', '宁波银行' => '1056',
72
	];
73
74
	public $getSignKeyUrl = "sandboxnew/pay/getsignkey";
75
	public $sandbox = false;
76
77
	/** @var string */
78
	public $returnCode;
79
	/** @var string */
80
	public $returnMsg;
81
	/** @var string */
82
	public $resultCode;
83
	/** @var string */
84
	public $errCode;
85
	/** @var string */
86
	public $errCodeDes;
87
	/** @var string */
88
	public $requestXML = null;
89
	/** @var string */
90
	public $responseXML = null;
91
	/** @var array */
92
	public $requestArray = null;
93
	/** @var array */
94
	public $responseArray = null;
95
	/** @var array */
96
	protected $config;
97
	/** @var HttpClient */
98
	protected $httpClient = null;
99
	/** @var OAuth */
100
	protected $oauth = null;
101
	/** @var string */
102
	public $publicKey = null;
103
	/** @var CacheProvider */
104
	public $cacheProvider = null;
105
106
	/**
107
	 * @param $config array 配置
108
	 */
109 54
	public function __construct(array $config) {
110 54
		$this->config = $config;
111 54
		$this->httpClient = new HttpClient(5);
112 54
		$this->cacheProvider = new JsonFileCacheProvider();
113 54
	}
114
115
	/**
116
	 * @param string $name
117
	 * @param string $config
118
	 * @return mixed
119
	 */
120 30
	private static function load($name, $config) {
121 30
		$service = __NAMESPACE__ . "\\service\\{$name}";
122 30
		return new $service($config);
123
	}
124
125
	/**
126
	 * @param string $name
127
	 * @param array  $config
128
	 *
129
	 * @return mixed
130
	 */
131 30
	public static function __callStatic($name, $config) {
132 30
		return self::load($name, ...$config);
133
	}
134
135 1
	public function setOAuth($oauth){
136 1
		$this->oauth = $oauth;
137 1
	}
138
139 1
	public function getOAuth(){
140 1
		if(!$this->oauth){
141 1
			$this->oauth = new OAuth($this->config['app_id'],$this->config['app_secret']);
142
		}
143 1
		return $this->oauth;
144
	}
145
146 1
	public function setConfig($config){
147 1
		$this->config = $config;
148 1
	}
149
150 3
	public function getConfig(){
151 3
		return $this->config;
152
	}
153
154 39
	public function setHttpClient($httpClient){
155 39
		$this->httpClient = $httpClient;
156 39
	}
157
158 54
	public function setCacheProvider($cacheProvider){
159 54
		$this->cacheProvider = $cacheProvider;
160 54
	}
161
162 1
	public function getCacheProvider(){
163 1
		return $this->cacheProvider;
164
	}
165
166
	/**
167
	 * 统一下单接口
168
	 * @param array $params
169
	 * @throws Exception
170
	 * @return array
171
	 */
172 8
	public function unifiedOrder($params) {
173 8
		$data = array();
174 8
		$data["appid"] = $this->config["app_id"];
175 8
		$data["device_info"] = (isset($params['device_info'])&&trim($params['device_info'])!='')?$params['device_info']:null;
176 8
		$data["body"] = $params['body'];
177 8
		$data["detail"] = isset($params['detail'])?$params['detail']:null;//optional
178 8
		$data["attach"] = isset($params['attach'])?$params['attach']:null;//optional
179 8
		$data["out_trade_no"] = isset($params['out_trade_no'])?$params['out_trade_no']:null;
180 8
		$data["fee_type"] = isset($params['fee_type'])?$params['fee_type']:'CNY';
181 8
		$data["total_fee"]    = $params['total_fee'];
182 8
		$data["spbill_create_ip"] = $params['spbill_create_ip'];
183 8
		$data["time_start"] = isset($params['time_start'])?$params['time_start']:null;//optional
184 8
		$data["time_expire"] = isset($params['time_expire'])?$params['time_expire']:null;//optional
185 8
		$data["goods_tag"] = isset($params['goods_tag'])?$params['goods_tag']:null;
186 8
		$data["notify_url"] = $this->config["notify_url"];
187 8
		$data["trade_type"] = $params['trade_type'];
188 8
		if($params['trade_type'] == WechatPay::TRADETYPE_NATIVE){
189 1
			if(!isset($params['product_id'])) throw new Exception('product_id is required when trade_type is NATIVE');
190 1
			$data["product_id"] = $params['product_id'];
191
		}
192 8
		if($params['trade_type'] == WechatPay::TRADETYPE_JSAPI){
193 5
			if(!isset($params['openid'])) throw new Exception('openid is required when trade_type is JSAPI');
194 5
			$data["openid"] = $params['openid'];
195
		}
196 8
		$result = $this->post(self::URL_UNIFIEDORDER, $data);
197 6
		return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type string|true which is incompatible with the documented return type array.
Loading history...
198
	}
199
200
	/**
201
	 * 查询订单(根据微信订单号)
202
	 * @param $transaction_id string 微信订单号
203
	 * @return array
204
	 * @throws Exception
205
	 */
206 2
	public function queryOrderByTransactionId($transaction_id){
207 2
		$data = array();
208 2
		$data["appid"] = $this->config["app_id"];
209 2
		$data["transaction_id"] = $transaction_id;
210 2
		$result = $this->post(self::URL_ORDERQUERY, $data);
211 2
		return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type string|true which is incompatible with the documented return type array.
Loading history...
212
	}
213
214
	/**
215
	 * 查询订单(根据商户订单号)
216
	 * @param $out_trade_no string 商户订单号
217
	 * @return array
218
	 * @throws Exception
219
	 */
220 1
	public function queryOrderByOutTradeNo($out_trade_no){
221 1
		$data = array();
222 1
		$data["appid"] = $this->config["app_id"];
223 1
		$data["out_trade_no"] = $out_trade_no;
224 1
		return $this->post(self::URL_ORDERQUERY, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self::URL_ORDERQUERY, $data) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
225
	}
226
227
	/**
228
	 * 查询退款(根据微信订单号)
229
	 * @param $transaction_id string 微信交易号
230
	 * @param $offset int 偏移
231
	 * @return array
232
	 * @throws Exception
233
	 */
234 1
	public function queryRefundByTransactionId($transaction_id,$offset = 0){
235 1
		$data = array();
236 1
		$data["appid"] = $this->config["app_id"];
237 1
		$data["transaction_id"] = $transaction_id;
238 1
		$data["offset"] = $offset;
239 1
		return $this->post(self::URL_REFUNDQUERY, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self::URL_REFUNDQUERY, $data) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
240
	}
241
242
	/**
243
	 * 查询退款(根据商户订单号)
244
	 * @param $out_trade_no string 商户交易号
245
	 * @param $offset int 偏移
246
	 * @return array
247
	 * @throws Exception
248
	 */
249 1
	public function queryRefundByOutTradeNo($out_trade_no,$offset = 0){
250 1
		$data = array();
251 1
		$data["appid"] = $this->config["app_id"];
252 1
		$data["out_trade_no"] = $out_trade_no;
253 1
		$data["offset"] = $offset;
254 1
		return $this->post(self::URL_REFUNDQUERY, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self::URL_REFUNDQUERY, $data) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
255
	}
256
257
	/**
258
	 * 查询退款(根据微信退款单号)
259
	 * @param $refund_id string 微信退款单号
260
	 * @param $offset int 偏移
261
	 * @return array
262
	 * @throws Exception
263
	 */
264 1
	public function queryRefundByRefundId($refund_id,$offset = 0){
265 1
		$data = array();
266 1
		$data["appid"] = $this->config["app_id"];
267 1
		$data["refund_id"] = $refund_id;
268 1
		$data["offset"] = $offset;
269 1
		return $this->post(self::URL_REFUNDQUERY, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self::URL_REFUNDQUERY, $data) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
270
	}
271
272
	/**
273
	 * 查询退款(根据商户退款单号)
274
	 * @param $out_refund_no string 商户退款单号
275
	 * @param $offset int 偏移
276
	 * @return array
277
	 * @throws Exception
278
	 */
279 1
	public function queryRefundByOutRefundNo($out_refund_no,$offset = 0){
280 1
		$data = array();
281 1
		$data["appid"] = $this->config["app_id"];
282 1
		$data["out_refund_no"] = $out_refund_no;
283 1
		$data["offset"] = $offset;
284 1
		return $this->post(self::URL_REFUNDQUERY, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self::URL_REFUNDQUERY, $data) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
285
	}
286
287
	/**
288
	 * 关闭订单
289
	 * @param $out_trade_no string 商户订单号
290
	 * @return array
291
	 * @throws Exception
292
	 */
293 1
	public function closeOrder($out_trade_no){
294 1
		$data = array();
295 1
		$data["appid"] = $this->config["app_id"];
296 1
		$data["out_trade_no"] = $out_trade_no;
297 1
		return $this->post(self::URL_CLOSEORDER, $data,false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self:...OSEORDER, $data, false) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
298
	}
299
300
	/**
301
	 * 退款 - 使用商户订单号
302
	 * @param $out_trade_no string 商户订单号
303
	 * @param $out_refund_no string 商户退款单号
304
	 * @param $total_fee int 总金额(单位:分)
305
	 * @param $refund_fee int 退款金额(单位:分)
306
	 * @param $ext array 扩展数组
307
	 * @return array
308
	 * @throws Exception
309
	 */
310 1
	public function refundByOutTradeNo($out_trade_no,$out_refund_no,$total_fee,$refund_fee,$ext = array()){
311 1
		$data = ($ext && is_array($ext))?$ext:array();
312 1
		$data["appid"] = $this->config["app_id"];
313 1
		$data["out_trade_no"] = $out_trade_no;
314 1
		$data["out_refund_no"] = $out_refund_no;
315 1
		$data["total_fee"] = $total_fee;
316 1
		$data["refund_fee"] = $refund_fee;
317 1
		return $this->post(self::URL_REFUND, $data,true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self::URL_REFUND, $data, true) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
318
	}
319
320
	/**
321
	 * 退款 - 使用微信订单号
322
	 * @param $transaction_id string 微信订单号
323
	 * @param $out_refund_no string 商户退款单号
324
	 * @param $total_fee int 总金额(单位:分)
325
	 * @param $refund_fee int 退款金额(单位:分)
326
	 * @param $ext array 扩展数组
327
	 * @return array
328
	 * @throws Exception
329
	 */
330 1
	public function refundByTransactionId($transaction_id,$out_refund_no,$total_fee,$refund_fee,$ext = array()){
331 1
		$data = ($ext && is_array($ext))?$ext:array();
332 1
		$data["appid"] = $this->config["app_id"];
333 1
		$data["transaction_id"] = $transaction_id;
334 1
		$data["out_refund_no"] = $out_refund_no;
335 1
		$data["total_fee"] = $total_fee;
336 1
		$data["refund_fee"] = $refund_fee;
337 1
		return $this->post(self::URL_REFUND, $data,true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self::URL_REFUND, $data, true) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
338
	}
339
340
	/**
341
	 * 下载对账单
342
	 * @param $bill_date string 下载对账单的日期,格式:20140603
343
	 * @param $bill_type string 类型 ALL|SUCCESS
344
	 * @return array
345
	 * @throws Exception
346
	 */
347 1
	public function downloadBill($bill_date,$bill_type = 'ALL'){
348 1
		$data = array();
349 1
		$data["appid"] = $this->config["app_id"];
350 1
		$data["bill_date"] = $bill_date;
351 1
		$data["bill_type"] = $bill_type;
352 1
		return $this->post(self::URL_DOWNLOADBILL, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self::URL_DOWNLOADBILL, $data) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
353
	}
354
355
	/**
356
	 * 下载资金账单
357
	 * @param $bill_date string 资金账单日期,格式:20140603
358
	 * @param $account_type string 资金账户类型 Basic|Operation|Fees
359
	 * @param $tar_type string 压缩账单
360
	 * @return array
361
	 * @throws Exception
362
	 */
363 1
	public function downloadFundFlow($bill_date,$account_type = self::ACCOUNTTYPE_BASIC,$tar_type = 'GZIP'){
364 1
		$data = array();
365 1
		$data["appid"] = $this->config["app_id"];
366 1
		$data["bill_date"] = $bill_date;
367 1
		$data["account_type"] = $account_type;
368 1
		$data["tar_type"] = $tar_type;
369 1
		return $this->post(self::URL_DOWNLOAD_FUND_FLOW, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self:...NLOAD_FUND_FLOW, $data) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
370
	}
371
372
	/**
373
	 * 拉取订单评价数据
374
	 * @param string $begin_time 开始时间,格式为yyyyMMddHHmmss
375
	 * @param string $end_time 结束时间,格式为yyyyMMddHHmmss
376
	 * @param int $offset 偏移
377
	 * @param int $limit 条数
378
	 * @return array
379
	 * @throws Exception
380
	 */
381 1
	public function batchQueryComment($begin_time,$end_time,$offset = 0,$limit = 200){
382 1
		$data = array();
383 1
		$data["appid"] = $this->config["app_id"];
384 1
		$data["begin_time"] = $begin_time;
385 1
		$data["end_time"] = $end_time;
386 1
		$data["offset"] = $offset;
387 1
		$data["limit"] = $limit;
388 1
		$data["sign"] = $this->sign($data,WechatPay::SIGNTYPE_HMACSHA256);
389 1
		return $this->post(self::URL_BATCHQUERYCOMMENT, $data, true); //cert is required
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self:...RYCOMMENT, $data, true) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
390
	}
391
392
	/**
393
	 * 支付结果通知处理
394
	 * @param $notify_data array|string 通知数据
395
	 * @param $callback callable 回调
396
	 * @return null
397
	 * @throws Exception
398
	 */
399 2
	public function onPaidNotify($notify_data,callable $callback = null){
400 2
		if(!is_array($notify_data)) $notify_data = $this->xml2array($notify_data);
401 2
		if(!$this->validateSign($notify_data)) throw new Exception('Invalid paid notify data');
402 1
		if($callback && is_callable($callback)){
403 1
			return call_user_func_array( $callback , [$notify_data] );
404
		}
405
		return null;
406
	}
407
408
	/**
409
	 * 退款结果通知处理
410
	 * @param string|array $notify_data 通知数据(XML/array)
411
	 * @param callable $callback 回调
412
	 * @return mixed
413
	 * @throws Exception
414
	 */
415 2
	public function onRefundedNotify($notify_data,callable $callback = null){
416 2
		if(!is_array($notify_data)) $notify_data = $this->xml2array($notify_data);
417 2
		if(!$this->validateSign($notify_data)) throw new Exception('Invalid refund notify data');
418 1
		if($callback && is_callable($callback)){
419 1
			return call_user_func_array( $callback ,[$notify_data] );
420
		}
421
		return null;
422
	}
423
424
	/**
425
	 * 验证数据签名
426
	 * @param $data array 数据数组
427
	 * @return boolean 数据校验结果
428
	 * @throws Exception
429
	 */
430 5
	public function validateSign($data) {
431 5
		if (!isset($data["sign"])) {
432 1
			return false;
433
		}
434 4
		$sign = $data["sign"];
435 4
		unset($data["sign"]);
436 4
		return $this->sign($data) == $sign;
437
	}
438
439
	/**
440
	 * 响应微信支付后台通知
441
	 * @param array $data
442
	 * @param string $return_code 返回状态码 SUCCESS/FAIL
443
	 * @param string $return_msg  返回信息
444
	 * @param bool $print
445
	 * @return string
446
	 */
447 1
	public function responseNotify($print = true,$data = [],$return_code="SUCCESS", $return_msg= 'OK') {
448 1
		$data["return_code"] = $return_code;
449 1
		if ($return_msg) {
450 1
			$data["return_msg"] = $return_msg;
451
		}
452 1
		$xml = $this->array2xml($data);
453 1
		if($print === true) print $xml;
454 1
		else return $xml;
455
	}
456
457
	/**
458
	 * 交易保障
459
	 * @param string $interface_url
460
	 * @param string $execution_time
461
	 * @param string $return_code
462
	 * @param string $result_code
463
	 * @param string $user_ip
464
	 * @param string $out_trade_no
465
	 * @param string $time
466
	 * @param string $device_info
467
	 * @param string $return_msg
468
	 * @param string $err_code
469
	 * @param string $err_code_des
470
	 * @return array
471
	 * @throws Exception
472
	 */
473 1
	public function report($interface_url,$execution_time,$return_code,$result_code,$user_ip,$out_trade_no = null,$time = null,$device_info = null,
474
	                       $return_msg = null,$err_code = null,$err_code_des = null){
475 1
		$data = array();
476 1
		$data["appid"] = $this->config["app_id"];
477 1
		$data["interface_url"] = $interface_url;
478 1
		$data["execution_time"] = $execution_time;
479 1
		$data["return_code"] = $return_code;
480 1
		$data["result_code"] = $result_code;
481 1
		$data["user_ip"] = $user_ip;
482 1
		if($out_trade_no) $data["out_trade_no"] = $out_trade_no;
483 1
		if($time) $data["time"] = $time;
484 1
		if($device_info) $data["device_info"] = $device_info;
485 1
		if($return_msg) $data["return_msg"] = $return_msg;
486 1
		if($err_code) $data["err_code"] = $err_code;
487 1
		if($err_code_des) $data["err_code_des"] = $err_code_des;
488 1
		return $this->post(self::URL_REPORT, $data, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post(self:...L_REPORT, $data, false) also could return the type string|true which is incompatible with the documented return type array.
Loading history...
489
	}
490
491
	/**
492
	 * 转换短链接
493
	 * @param $longurl
494
	 * @return string
495
	 * @throws Exception
496
	 */
497 1
	public function shortUrl($longurl){
498 1
		$data = array();
499 1
		$data["appid"] = $this->config["app_id"];
500 1
		$data["long_url"] = $longurl;
501 1
		$result = $this->post(self::URL_SHORTURL,$data,false);
502 1
		return $result['short_url'];
503
	}
504
505
	/**
506
	 * sandbox环境获取验签秘钥
507
	 * @return array
508
	 * @throws Exception
509
	 */
510 1
	public function getSignKey(){
511 1
		$data = array();
512 1
		$data["mch_id"] = $this->config["mch_id"];
513 1
		$result = $this->post($this->getSignKeyUrl,$data,false);
514 1
		return $result['sandbox_signkey'];
515
	}
516
517
	/**
518
	 * 获取JSAPI所需要的页面参数
519
	 * @param string $url
520
	 * @param string $ticket
521
	 * @return array
522
	 */
523 1
	public function getSignPackage($url, $ticket = null){
524 1
		if(!$ticket) $ticket = $this->getTicket();
525 1
		$timestamp = time();
526 1
		$nonceStr = $this->getNonceStr();
527 1
		$rawString = "jsapi_ticket=$ticket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";
528 1
		$signature = sha1($rawString);
529
530
		$signPackage = array(
531 1
			"appId" => $this->config['app_id'],
532 1
			"nonceStr" => $nonceStr,
533 1
			"timestamp" => $timestamp,
534 1
			"url" => $url,
535 1
			"signature" => $signature,
536 1
			"rawString" => $rawString
537
		);
538 1
		return $signPackage;
539
	}
540
541
	/**
542
	 * 获取JSAPI Ticket
543
	 * @param boolean $cache
544
	 * @return string
545
	 */
546 1
	public function getTicket($cache = true){
547 1
		$ticket = null;
548 1
		$cacheKey = 'jsapi_ticket';
549 1
		if($cache === true){
550 1
			$data = $this->cacheProvider->get($cacheKey);
551 1
			if ($data && $data->expires_at > time()) {
552 1
				$ticket = $data->ticket;
553
			}
554
		}
555 1
		if(!$ticket){
556 1
			$data = $this->getOAuth()->getTicket();
557 1
			if($cache === true){
558 1
				$this->cacheProvider->set($cacheKey,$data,time() + $data->expires_in);
559
			}
560 1
			$ticket = $data->ticket;
561
		}
562 1
		return $ticket;
563
	}
564
565 39
	protected function post($url, $data,$cert = true) {
566 39
		if(!isset($data['mch_id']) && !isset($data['mchid'])) $data["mch_id"] = $this->config["mch_id"];
567 39
		if(!isset($data['nonce_str'])) $data["nonce_str"] = $this->getNonceStr();
568 39
		if(!isset($data['sign'])) $data['sign'] = $this->sign($data);
569 39
		$this->requestXML = $this->responseXML = null;
570 39
		$this->requestArray = $this->responseArray = null;
571
572 39
		$this->requestArray = $data;
573 39
		$this->requestXML = $this->array2xml($data);
574
		$opts = [
575 39
			CURLOPT_SSL_VERIFYPEER => false,
576 39
			CURLOPT_SSL_VERIFYHOST => false,
577 39
			CURLOPT_RETURNTRANSFER => true,
578 39
			CURLOPT_TIMEOUT => 10
579
		];
580 39
		if($cert == true){
581 32
			$opts[CURLOPT_SSLCERTTYPE] = 'PEM';
582 32
			$opts[CURLOPT_SSLCERT] = $this->config['ssl_cert_path'];
583 32
			$opts[CURLOPT_SSLKEYTYPE] = 'PEM';
584 32
			$opts[CURLOPT_SSLKEY] = $this->config['ssl_key_path'];
585
		}
586 39
		$processResponse = true;
587 39
		if(in_array($url,[self::URL_DOWNLOADBILL,self::URL_DOWNLOAD_FUND_FLOW,self::URL_BATCHQUERYCOMMENT])){
588 3
			$processResponse = false;
589
		}
590 39
		if($this->sandbox === true) $url = "sandboxnew/{$url}";
591
592 39
		$content = $this->httpClient->post(self::API_ENDPOINT . $url,$this->requestXML,[],$opts);
593 39
		if(!$content) throw new Exception("Empty response with {$this->requestXML}");
594
595 39
		$this->responseXML = $content;
596 39
		if($processResponse)
597 36
			return $this->processResponseXML($this->responseXML);
598 3
		else return $this->responseXML;
599
	}
600
601
	/**
602
	 * @param $responseXML
603
	 * @return array
604
	 * @throws Exception
605
	 */
606 36
	private function processResponseXML($responseXML){
607 36
		$result = $this->xml2array($responseXML);
608 36
		$this->responseArray = $result;
609 36
		if(empty($result['return_code'])){
610 1
			throw new Exception("No return code presents in {$this->responseXML}");
611
		}
612 35
		$this->returnCode = $result["return_code"];
613 35
		$this->returnMsg = isset($result['return_msg'])?$result['return_msg']:'';
614
615 35
		if ($this->returnCode == "SUCCESS") {
616 29
			if(isset($result['result_code']) && $result['result_code'] == "FAIL") {
617 3
				$this->resultCode = $result['result_code'];
618 3
				$this->errCode = $result['err_code'];
619 3
				$this->errCodeDes = $result['err_code_des'];
620
				//应该在外部进行业务处理,这里可能会返回正在支付中的状态
621
				//throw new Exception("[$this->errCode]$this->errCodeDes, with request XML: {$this->requestXML}");
622
			}
623 29
			return $result;
624 6
		} else if($this->returnCode == 'FAIL'){
625 6
			throw new Exception($this->returnMsg);
626
		}
627
	}
628
629 48
	public function sign($data,$sign_type = WechatPay::SIGNTYPE_MD5) {
630 48
		ksort($data);
631 48
		$string1 = "";
632 48
		foreach ($data as $k => $v) {
633 48
			if ($v && trim($v)!='') {
634 48
				$string1 .= "$k=$v&";
635
			}
636
		}
637 48
		$stringSignTemp = $string1 . "key=" . $this->config["api_key"];
638 48
		if($sign_type == WechatPay::SIGNTYPE_MD5){
639 46
			$sign = strtoupper(md5($stringSignTemp));
640 3
		}elseif($sign_type == WechatPay::SIGNTYPE_HMACSHA256){
641 2
			$sign = strtoupper(hash_hmac('sha256',$stringSignTemp,$this->config["api_key"]));
642 1
		}else throw new Exception("Not supported sign type - $sign_type");
643 47
		return $sign;
644
	}
645
646 40
	private function array2xml($array) {
647 40
		$xml = "<xml>" . PHP_EOL;
648 40
		foreach ($array as $k => $v) {
649 40
			if($v && trim($v)!='')
650 40
				$xml .= "<$k><![CDATA[$v]]></$k>" . PHP_EOL;
651
		}
652 40
		$xml .= "</xml>";
653 40
		return $xml;
654
	}
655
656 38
	private function xml2array($xml) {
657 38
		$array = [];
658 38
		$tmp = (array) simplexml_load_string($xml);
659 38
		foreach ( $tmp as $k => $v) {
660 38
			$array[$k] = (string) $v;
661
		}
662 38
		return $array;
663
	}
664
665 43
	protected function getNonceStr() {
666 43
		return substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"),0,32);
667
	}
668
669
}