Pay.Class.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. <?php
  2. /**
  3. * 微信支付
  4. * Created by PhpStorm.
  5. * User: phperstar
  6. * Date: 2019/11/28
  7. * Time: 2:12 PM
  8. */
  9. namespace Util\WeiXin;
  10. use Mall\Framework\Core\ErrorCode;
  11. Use Mall\Framework\Core\ResultWrapper;
  12. class Pay
  13. {
  14. /**
  15. * 小程序 appId
  16. * @var string $appid
  17. */
  18. private $appid;
  19. /**
  20. * 商户号
  21. * @var string $mch_id
  22. */
  23. private $mch_id;
  24. /**
  25. * 商户支付密钥
  26. */
  27. private $partnerKey;
  28. /**
  29. * 微信支付异步通知地址
  30. */
  31. private $notifyUrl = PAY_NOTIFY_URL.'/common/WeiXinPayNotify/notify';
  32. /**
  33. * 公共的接口请求地址
  34. * @var string
  35. */
  36. private $apiUrl = 'https://api.mch.weixin.qq.com/pay/';
  37. /**
  38. * 基础得请求地址
  39. */
  40. private $baseUrl = 'https://api.mch.weixin.qq.com/';
  41. /**
  42. * Pay constructor.
  43. * @param string $appid
  44. * @param $mch_id
  45. * @param $partnerKey
  46. */
  47. public function __construct($appid='', $mch_id, $partnerKey)
  48. {
  49. $this->appid = $appid;
  50. $this->mch_id = $mch_id;
  51. $this->partnerKey = $partnerKey;
  52. }
  53. /**
  54. * 统一下单接口
  55. */
  56. public function unifiedorder($orderNo, $total_fee, $ip, $source, $shopName, $openid = '', $attach = '')
  57. {
  58. $url = $this->apiUrl.'unifiedorder';
  59. switch ($source){
  60. case 'byteDanceH5':
  61. $trade_type = 'MWEB';
  62. break;
  63. case 'H5':
  64. $trade_type = 'MWEB';
  65. break;
  66. case 'APP':
  67. $trade_type = 'APP';
  68. break;
  69. default:
  70. $trade_type = 'JSAPI';
  71. break;
  72. }
  73. $params = [
  74. 'appid' => $this->appid, // 小程序ID/微信开放平台审核通过的应用APPID
  75. 'mch_id' => $this->mch_id, // 商户号
  76. 'nonce_str' => md5(md5(time().'qianniao.vip')), // 32位随机字符串
  77. 'out_trade_no' => $orderNo, // 商户订单号
  78. 'total_fee' => yuanToFen($total_fee), // 订单总金额,单位分
  79. 'spbill_create_ip' => $ip, // 终端ip
  80. 'trade_type' => $trade_type, // 交易类型
  81. 'notify_url' => $this->notifyUrl, // 通知地址
  82. 'body' => $shopName.'-'.'线上商城',
  83. 'attach' => $attach, // 附加数据
  84. ];
  85. if($trade_type == 'JSAPI'){
  86. $params['openid'] = $openid;
  87. }
  88. $params['sign'] = self::getSign($params);
  89. $post_xml = arrayToWeiXinXml($params);
  90. $curl_content = request($url, $post_xml);
  91. $result = $this->commonResult($curl_content);
  92. if(!$result->isSuccess()){
  93. return ResultWrapper::fail($result->getData(), $result->getErrorCode());
  94. }
  95. $response_content = $result->getData();
  96. //获取统一下单返回的id
  97. $prepay_id = $response_content['prepay_id'];
  98. // H5支付返回的支付跳转链接
  99. $mweb_url = isset($response_content['mweb_url']) ? $response_content['mweb_url'] : '';
  100. switch ($source){
  101. case 'byteDanceH5':
  102. $payParams = $mweb_url; // 字节跳动小程序调用微信H5支付获取支付链接
  103. break;
  104. case 'H5':
  105. $payParams = self::payment($prepay_id);
  106. break;
  107. case 'APP':
  108. $payParams = self::payment($prepay_id);
  109. break;
  110. default:
  111. $payParams = self::paymentToMinprogram($prepay_id);
  112. break;
  113. }
  114. return ResultWrapper::success($payParams);
  115. }
  116. /**
  117. * 小程序调起支付用到的参数
  118. * 官方接口地址: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3
  119. */
  120. public function paymentToMinprogram($prepay_id)
  121. {
  122. unset($params);
  123. $params = [
  124. 'appId' => $this->appid, // 小程序ID
  125. 'timeStamp' => (string)time(), //时间戳
  126. 'nonceStr' => md5(md5(time().'qianniao.vip')), //32位随机字符串
  127. 'package' => 'prepay_id='.$prepay_id, // 数据包
  128. 'signType' => 'MD5', // 签名方式
  129. ];
  130. $params['paySign'] = self::getSign($params);
  131. return $params;
  132. }
  133. /**
  134. * 调起支付用到的参数
  135. * 官方接口地址: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12
  136. * @param $prepay_id
  137. */
  138. public function payment($prepay_id)
  139. {
  140. unset($parm);
  141. $parm = array(
  142. 'appid' => $this->appid,
  143. 'partnerid' => $this->mch_id,
  144. 'timestamp' => (string)time(), //时间戳
  145. 'noncestr' => md5(md5(time().'qianniao.vip')), //32位随机字符串
  146. 'prepayid' => $prepay_id, //统一下单接口返回的prepay_id 参数值
  147. 'package' => 'Sign=WXPay',
  148. );
  149. $parm['sign'] = self::getSign($parm);
  150. return $parm;
  151. }
  152. /**
  153. * 付款码支付
  154. * 官方文档地址: https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
  155. */
  156. public function micropay($orderNo, $total_fee, $ip, $shopName, $auth_code)
  157. {
  158. $url = $this->apiUrl.'micropay';
  159. $params = [
  160. 'appid' => $this->appid, // 小程序ID/微信开放平台审核通过的应用APPID
  161. 'mch_id' => $this->mch_id, // 商户号
  162. 'nonce_str' => md5(md5(time().'qianniao.vip')), // 32位随机字符串
  163. 'body' => $shopName.'-'.'线上商城',
  164. 'out_trade_no' => $orderNo, // 商户订单号
  165. 'total_fee' => yuanToFen($total_fee), // 订单总金额,单位分
  166. 'spbill_create_ip' => $ip, // 终端ip
  167. 'auth_code' => $auth_code,
  168. ];
  169. $params['sign'] = self::getSign($params);
  170. $post_xml = arrayToWeiXinXml($params);
  171. $curl_content = request($url, $post_xml);
  172. if($curl_content['httpcode'] != '200'){
  173. return ResultWrapper::fail($curl_content['errorMsg'], ErrorCode::$apiNotResult);
  174. }
  175. $response_content = (array)simplexml_load_string($curl_content['content'], 'SimpleXMLElement', LIBXML_NOCDATA);
  176. if($response_content['return_code'] != 'SUCCESS'){
  177. return ResultWrapper::fail($response_content['return_msg'], ErrorCode::$weixinPayError);
  178. }
  179. if($response_content['result_code'] != 'SUCCESS' && $response_content['err_code'] != 'USERPAYING' ){
  180. return ResultWrapper::fail($response_content['err_code_des'], ErrorCode::$weixinPayError);
  181. }
  182. // 20s内查询支付状态,如果有明确错误直接返回错误,其他情况继续查询。如果成功直接返回成功
  183. for($i=1; $i<=10; $i++)
  184. {
  185. $url = $this->apiUrl.'orderquery';
  186. $params = [
  187. 'appid' => $this->appid, // 小程序ID/微信开放平台审核通过的应用APPID
  188. 'mch_id' => $this->mch_id, // 商户号
  189. 'nonce_str' => md5(md5(time().'qianniao.vip')), // 32位随机字符串
  190. 'out_trade_no' => $orderNo, // 商户订单号
  191. ];
  192. $params['sign'] = self::getSign($params);
  193. $post_xml = arrayToWeiXinXml($params);
  194. $curl_content = request($url, $post_xml);
  195. if($curl_content['httpcode'] != '200'){
  196. return ResultWrapper::fail($curl_content['errorMsg'], ErrorCode::$apiNotResult);
  197. }
  198. $response_content = (array)simplexml_load_string($curl_content['content'], 'SimpleXMLElement', LIBXML_NOCDATA);
  199. if($response_content['return_code'] != 'SUCCESS'){
  200. return ResultWrapper::fail($response_content['return_msg'], ErrorCode::$weixinPayError);
  201. }
  202. if($response_content['result_code'] != 'SUCCESS' && $response_content['err_code'] == 'ORDERNOTEXIST'){
  203. return ResultWrapper::fail($response_content['err_code_des'], ErrorCode::$weixinPayError);
  204. }
  205. if($response_content['return_code'] == 'SUCCESS' && $response_content['result_code'] == 'SUCCESS' && $response_content['trade_state'] == 'SUCCESS'){
  206. return ResultWrapper::success($response_content['trade_state']);
  207. }
  208. sleep(2);
  209. }
  210. // 20s内没有支付则撤销订单
  211. for($i=1; $i<=10; $i++){
  212. $url = $this->baseUrl.'secapi/pay/reverse';
  213. $params = [
  214. 'appid' => $this->appid, // 小程序ID/微信开放平台审核通过的应用APPID
  215. 'mch_id' => $this->mch_id, // 商户号
  216. 'nonce_str' => md5(md5(time().'qianniao.vip')), // 32位随机字符串
  217. 'out_trade_no' => $orderNo, // 商户订单号
  218. ];
  219. $params['sign'] = self::getSign($params);
  220. $post_xml = arrayToWeiXinXml($params);
  221. $curl_content = request($url, $post_xml, 10, false, [], true);
  222. if($curl_content['httpcode'] != '200'){
  223. return ResultWrapper::fail($curl_content['errorMsg'], ErrorCode::$apiNotResult);
  224. }
  225. $response_content = (array)simplexml_load_string($curl_content['content'], 'SimpleXMLElement', LIBXML_NOCDATA);
  226. if($response_content['return_code'] != 'SUCCESS'){
  227. return ResultWrapper::fail($response_content['return_msg'], ErrorCode::$weixinPayError);
  228. }
  229. if($response_content['result_code'] != 'SUCCESS' && $response_content['recall'] == 'N'){
  230. return ResultWrapper::fail($response_content['err_code_des'], ErrorCode::$weixinPayError);
  231. }
  232. if($response_content['return_code'] == 'SUCCESS' && $response_content['result_code'] == 'SUCCESS' && $response_content['recall'] == 'N'){
  233. return ResultWrapper::success($response_content['result_code']);
  234. }
  235. }
  236. }
  237. /**
  238. * H5查询支付状态接口
  239. */
  240. public function orderquery($orderNo)
  241. {
  242. $url = $this->apiUrl.'orderquery';
  243. $params = [
  244. 'appid' => $this->appid, // 小程序ID/微信开放平台审核通过的应用APPID
  245. 'mch_id' => $this->mch_id, // 商户号
  246. 'nonce_str' => md5(md5(time().'qianniao.vip')), // 32位随机字符串
  247. 'out_trade_no' => $orderNo, // 商户订单号
  248. ];
  249. $params['sign'] = self::getSign($params);
  250. $post_xml = arrayToWeiXinXml($params);
  251. $curl_content = request($url, $post_xml);
  252. $result = $this->commonResult($curl_content);
  253. if(!$result->isSuccess()){
  254. return ResultWrapper::fail($result->getData(), $result->getErrorCode());
  255. }
  256. $response_content = $result->getData();
  257. $trade_state = $response_content['trade_state'];
  258. return ResultWrapper::success($trade_state);
  259. }
  260. /**
  261. * 申请退款API
  262. * 官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4
  263. */
  264. public function transfers($outerTradeNo, $out_refund_no, $refundMoney, $orderMoney, $sslData)
  265. {
  266. $url = $this->baseUrl.'secapi/pay/refund';
  267. $params = [
  268. 'appid' => $this->appid,
  269. 'mch_id' => $this->mch_id,
  270. 'nonce_str' => md5(md5(time().'qianniao.vip')), // 32位随机字符串
  271. 'transaction_id' => $outerTradeNo, // 微信交易号
  272. 'out_refund_no' => $out_refund_no, // 商户退款单号
  273. 'refund_fee' => yuanToFen($refundMoney), // 退款金额
  274. 'total_fee' => yuanToFen($orderMoney), // 订单总金额
  275. ];
  276. $params['sign'] = self::getSign($params);
  277. $post_xml = arrayToWeiXinXml($params);
  278. $curl_content = request($url, $post_xml, 10, false, [], true, $sslData);
  279. $result = $this->commonResult($curl_content);
  280. if(!$result->isSuccess()){
  281. return ResultWrapper::fail($result->getData(), $result->getErrorCode());
  282. }
  283. $response_content = $result->getData();
  284. return ResultWrapper::success($response_content['refund_id']);
  285. }
  286. /**
  287. * 公共处理返回结果方法
  288. */
  289. public function commonResult($curl_content)
  290. {
  291. if($curl_content['httpcode'] == '200'){
  292. $response_content = (array)simplexml_load_string($curl_content['content'], 'SimpleXMLElement', LIBXML_NOCDATA);
  293. if($response_content['return_code'] == 'SUCCESS'){
  294. if($response_content['result_code'] == 'SUCCESS'){
  295. return ResultWrapper::success($response_content);
  296. }else{
  297. return ResultWrapper::fail($response_content['err_code_des'], ErrorCode::$weixinPayError);
  298. }
  299. }else{
  300. return ResultWrapper::fail($response_content['return_msg'], ErrorCode::$weixinPayError);
  301. }
  302. }else{
  303. return ResultWrapper::fail($curl_content['errorMsg'], ErrorCode::$apiNotResult);
  304. }
  305. }
  306. /**
  307. * 生成签名方法
  308. * 官方接口地址: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
  309. * 官方签名测试地址: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=20_1
  310. * @param $parm
  311. * @return string
  312. */
  313. public function getSign($parm)
  314. {
  315. //非空参数值的参数按照参数名ASCII码从小到大排序(字典序)
  316. if(ksort($parm)){
  317. $stringA = '';
  318. //使用URL键值对的格式拼接成字符串stringA
  319. foreach($parm as $key => $value){
  320. if($value == 0 || !empty($value)){
  321. $stringA .=$key.'='.$value.'&';
  322. }
  323. }
  324. }else{
  325. echo "对参数排序出错";
  326. exit();
  327. }
  328. //在stringA最后拼接上key=商户支付密钥
  329. $stringSignTemp = $stringA.'key='.$this->partnerKey;
  330. //对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写
  331. $signValue = strtoupper(md5($stringSignTemp));
  332. return $signValue;
  333. }
  334. }