weixin.pay.class.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <?php
  2. /**
  3. * [WeEngine System] Copyright (c) 2014 WE7.CC
  4. * WeEngine is NOT a free software, it under the license terms, visited http://www.we7.cc/ for more details.
  5. */
  6. defined('IN_IA') or exit('Access Denied');
  7. class WeiXinPay extends pay {
  8. public $wxpay;
  9. public function __construct($module = '') {
  10. global $_W;
  11. $setting = uni_setting($_W['uniacid']);
  12. $wxpay = $setting['payment']['wechat'];
  13. if (3 == intval($wxpay['switch'])) {
  14. $oauth_account = uni_setting($wxpay['service'], array('payment'));
  15. $oauth_acid = pdo_getcolumn('uni_account', array('uniacid' => $wxpay['service']), 'default_acid');
  16. $oauth_appid = pdo_getcolumn('account_wechats', array('acid' => $oauth_acid), 'key');
  17. $this->wxpay = array(
  18. 'appid' => $oauth_appid,
  19. 'mch_id' => $oauth_account['payment']['wechat_facilitator']['mchid'],
  20. 'sub_mch_id' => $wxpay['sub_mch_id'],
  21. 'key' => $oauth_account['payment']['wechat_facilitator']['signkey'],
  22. 'notify_url' => $_W['siteroot'] . 'payment/wechat/notify.php',
  23. );
  24. } else {
  25. $this->wxpay = array(
  26. 'appid' => $_W['account']['key'],
  27. 'mch_id' => $wxpay['mchid'],
  28. 'key' => !empty($wxpay['apikey']) ? $wxpay['apikey'] : $wxpay['signkey'],
  29. 'notify_url' => $_W['siteroot'] . 'payment/wechat/notify.php',
  30. );
  31. }
  32. }
  33. public function array2url($params) {
  34. $str = '';
  35. $ignore = array('coupon_refund_fee', 'coupon_refund_count');
  36. foreach ($params as $key => $val) {
  37. if ((empty($val) || is_array($val)) && !in_array($key, $ignore)) {
  38. continue;
  39. }
  40. $str .= "{$key}={$val}&";
  41. }
  42. $str = trim($str, '&');
  43. return $str;
  44. }
  45. public function bulidSign($params) {
  46. unset($params['sign']);
  47. ksort($params);
  48. $string = $this->array2url($params);
  49. $string = $string . "&key={$this->wxpay['key']}";
  50. $string = md5($string);
  51. $result = strtoupper($string);
  52. return $result;
  53. }
  54. public function parseResult($result) {
  55. if ('<xml>' != substr($result, 0, 5)) {
  56. return $result;
  57. }
  58. $result = json_decode(json_encode(isimplexml_load_string($result, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
  59. if (!is_array($result)) {
  60. return error(-1, 'xml结构错误');
  61. }
  62. if ((isset($result['return_code']) && 'SUCCESS' != $result['return_code']) || (isset($result['result_code']) && 'SUCCESS' != $result['result_code']) || ('ERROR' == $result['err_code'] && !empty($result['err_code_des']))) {
  63. $msg = empty($result['return_msg']) ? $result['err_code_des'] : $result['return_msg'];
  64. return error(-1, $msg);
  65. }
  66. if (!empty($result['sign']) && $this->bulidsign($result) != $result['sign']) {
  67. return error(-1, '验证签名出错');
  68. }
  69. return $result;
  70. }
  71. public function requestApi($url, $params, $extra = array()) {
  72. load()->func('communication');
  73. $xml = array2xml($params);
  74. $response = ihttp_request($url, $xml, $extra);
  75. if (is_error($response)) {
  76. return $response;
  77. }
  78. $result = $this->parseResult($response['content']);
  79. return $result;
  80. }
  81. public function shortUrl($url) {
  82. $params = array(
  83. 'appid' => $this->wxpay['appid'],
  84. 'mch_id' => $this->wxpay['mch_id'],
  85. 'long_url' => $url,
  86. 'nonce_str' => random(32),
  87. );
  88. $params['sign'] = $this->bulidSign($params);
  89. $result = $this->requestApi('https://api.mch.weixin.qq.com/tools/shorturl', $params);
  90. if (is_error($result)) {
  91. return $result;
  92. }
  93. return $result['short_url'];
  94. }
  95. public function bulidNativePayurl($product_id, $short_url = true) {
  96. $params = array(
  97. 'appid' => $this->wxpay['appid'],
  98. 'mch_id' => $this->wxpay['mch_id'],
  99. 'time_stamp' => TIMESTAMP,
  100. 'nonce_str' => random(32),
  101. 'product_id' => $product_id,
  102. );
  103. $params['sign'] = $this->bulidSign($params);
  104. $url = 'weixin://wxpay/bizpayurl?' . $this->array2url($params);
  105. if ($short_url) {
  106. $url = $this->shortUrl($url);
  107. }
  108. return $url;
  109. }
  110. public function paylog2NativeUrl($params) {
  111. $result = $this->buildPayLog($params);
  112. if (is_error($result)) {
  113. return $result;
  114. }
  115. $url = $this->bulidNativePayurl($result);
  116. if (is_error($url)) {
  117. return $url;
  118. }
  119. return array('url' => $url, 'product_id' => $result);
  120. }
  121. public function buildUnifiedOrder($params) {
  122. global $_W;
  123. if (empty($params['out_trade_no'])) {
  124. return error(-1, '缺少统一支付接口必填参数out_trade_no:商户订单号');
  125. }
  126. if (empty($params['body'])) {
  127. return error(-1, '缺少统一支付接口必填参数body:商品描述');
  128. }
  129. if (empty($params['total_fee'])) {
  130. return error(-1, '缺少统一支付接口必填参数total_fee:总金额');
  131. }
  132. if (empty($params['trade_type'])) {
  133. return error(-1, '缺少统一支付接口必填参数trade_type:交易类型');
  134. }
  135. if ('JSAPI' == $params['trade_type'] && empty($params['openid'])) {
  136. return error(-1, '统一支付接口中,缺少必填参数openid!交易类型为JSAPI时,openid为必填参数!');
  137. }
  138. if ('NATIVE' == $params['trade_type'] && empty($params['product_id'])) {
  139. return error(-1, '统一支付接口中,缺少必填参数product_id!交易类型为NATIVE时,product_id为必填参数!');
  140. }
  141. if (empty($params['notify_url'])) {
  142. $params['notify_url'] = $this->wxpay['notify_url'];
  143. }
  144. $params['appid'] = $this->wxpay['appid'];
  145. $params['mch_id'] = $this->wxpay['mch_id'];
  146. $params['spbill_create_ip'] = $_W['clientip'];
  147. $params['nonce_str'] = random(32);
  148. $params['sign'] = $this->bulidSign($params);
  149. $result = $this->requestApi('https://api.mch.weixin.qq.com/pay/unifiedorder', $params);
  150. if (is_error($result)) {
  151. return $result;
  152. }
  153. return $result;
  154. }
  155. public function buildMicroOrder($params) {
  156. global $_W;
  157. if (empty($params['out_trade_no'])) {
  158. return error(-1, '缺少刷卡支付接口必填参数out_trade_no:商户订单号');
  159. }
  160. if (empty($params['body'])) {
  161. return error(-1, '缺少刷卡支付接口必填参数body:商品描述');
  162. }
  163. if (empty($params['total_fee'])) {
  164. return error(-1, '缺少刷卡支付接口必填参数total_fee:总金额');
  165. }
  166. if (empty($params['auth_code'])) {
  167. return error(-1, '缺少刷卡支付接口必填参数auth_code:授权码');
  168. }
  169. $uniontid = $params['uniontid'];
  170. unset($params['uniontid']);
  171. $params['appid'] = $this->wxpay['appid'];
  172. $params['mch_id'] = $this->wxpay['mch_id'];
  173. $params['spbill_create_ip'] = $_W['clientip'];
  174. $params['nonce_str'] = random(32);
  175. if (!empty($this->wxpay['sub_mch_id'])) {
  176. $params['sub_mch_id'] = $this->wxpay['sub_mch_id'];
  177. }
  178. $params['sign'] = $this->bulidSign($params);
  179. $result = $this->requestApi('https://api.mch.weixin.qq.com/pay/micropay', $params);
  180. if (is_error($result)) {
  181. return $result;
  182. }
  183. if ('SUCCESS' != $result['result_code']) {
  184. return array('errno' => -10, 'message' => $result['err_code_des'], 'uniontid' => $uniontid);
  185. }
  186. return $result;
  187. }
  188. public function NoticeMicroSuccessOrder($result) {
  189. if (empty($result['out_trade_no'])) {
  190. return array('errno' => -1, 'message' => '交易单号错误');
  191. }
  192. $pay_log = pdo_get('core_paylog', array('uniontid' => $result['out_trade_no']));
  193. if (empty($pay_log)) {
  194. return array('errno' => -1, 'message' => '交易日志不存在');
  195. }
  196. $order = pdo_get('paycenter_order', array('uniontid' => $result['out_trade_no']));
  197. if (empty($order)) {
  198. return array('errno' => -1, 'message' => '交易订单不存在');
  199. }
  200. $data = array(
  201. 'status' => 1,
  202. 'openid' => $result['openid'],
  203. );
  204. pdo_update('core_paylog', $data, array('uniontid' => $result['out_trade_no']));
  205. $data['trade_type'] = strtolower($result['trade_type']);
  206. $data['paytime'] = strtotime($result['time_end']);
  207. $data['uniontid'] = $result['out_trade_no'];
  208. $data['follow'] = 'Y' == $result['is_subscribe'] ? 1 : 0;
  209. pdo_update('paycenter_order', $data, array('uniontid' => $result['out_trade_no']));
  210. if (!$order['credit_status'] && $order['uid'] > 0) {
  211. load()->model('mc');
  212. $member_credit = mc_credit_fetch($order['uid']);
  213. $message = '';
  214. if ($member_credit['credit1'] < $order['credit1']) {
  215. $message = '会员账户积分少于需扣除积分';
  216. }
  217. if ($member_credit['credit2'] < $order['credit2']) {
  218. $message = '会员账户余额少于需扣除余额';
  219. }
  220. if (!empty($message)) {
  221. return array('errno' => -10, 'message' => "该订单需要扣除会员积分:{$order['credit1']}, 扣除余额{$order['credit2']}.出错:{$message}.你需要和会员沟通解决该问题.");
  222. }
  223. if ($order['credit1'] > 0) {
  224. $status = mc_credit_update($order['uid'], 'credit1', -$order['credit1'], array(0, "会员刷卡消费,使用积分抵现,扣除{$order['credit1']}积分", 'system', $order['clerk_id'], $order['store_id'], $order['clerk_type']));
  225. }
  226. if ($order['credit2'] > 0) {
  227. $status = mc_credit_update($order['uid'], 'credit2', -$order['credit2'], array(0, "会员刷卡消费,使用余额支付,扣除{$order['credit2']}余额", 'system', $order['clerk_id'], $order['store_id'], $order['clerk_type']));
  228. }
  229. }
  230. pdo_update('paycenter_order', array('credit_status' => 1), array('id' => $order['id']));
  231. return true;
  232. }
  233. public function buildJsApiPrepayid($product_id) {
  234. $order = pdo_get('core_paylog', array('plid' => $product_id));
  235. if (empty($order)) {
  236. return error(-1, '订单不存在');
  237. }
  238. if (1 == $order['status']) {
  239. return error(-1, '该订单已经支付,请勿重复支付');
  240. }
  241. $jspai = array(
  242. 'out_trade_no' => $order['uniontid'],
  243. 'trade_type' => 'JSAPI',
  244. 'openid' => $order['openid'],
  245. 'body' => $order['body'],
  246. 'total_fee' => $order['fee'] * 100,
  247. 'attach' => $order['uniacid'],
  248. );
  249. $result = $this->buildUnifiedOrder($jspai);
  250. if (is_error($result)) {
  251. return $result;
  252. }
  253. $jspai = array(
  254. 'appId' => $this->wxpay['appid'],
  255. 'timeStamp' => TIMESTAMP,
  256. 'nonceStr' => random(32),
  257. 'package' => 'prepay_id=' . $result['prepay_id'],
  258. 'signType' => 'MD5',
  259. );
  260. $jspai['paySign'] = $this->bulidSign($jspai);
  261. $jspai = <<<EOF
  262. <script type="text/javascript">
  263. document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() {
  264. WeixinJSBridge.invoke(
  265. 'getBrandWCPayRequest', {
  266. appId:'{$jspai['appId']}',
  267. timeStamp:'{$jspai['timeStamp']}',
  268. nonceStr:'{$jspai['nonceStr']}',
  269. package:'{$jspai['package']}',
  270. signType:'MD5',
  271. paySign:'{$jspai['paySign']}'
  272. },
  273. function(res){
  274. if(res.err_msg == 'get_brand_wcpay_request:ok' ) {
  275. alert('支付成功')
  276. } else {
  277. }
  278. }
  279. );
  280. }, false);
  281. </script>
  282. EOF;
  283. return $jspai;
  284. }
  285. public function buildNativePrepayid($product_id) {
  286. $order = pdo_get('core_paylog', array('plid' => $product_id));
  287. if (empty($order)) {
  288. return error(-1, '订单不存在');
  289. }
  290. if (1 == $order['status']) {
  291. return error(-1, '该订单已经支付,请勿重复支付');
  292. }
  293. $data = array(
  294. 'body' => $order['body'],
  295. 'out_trade_no' => $order['uniontid'],
  296. 'total_fee' => $order['fee'] * 100,
  297. 'trade_type' => 'NATIVE',
  298. 'product_id' => $order['plid'],
  299. 'attach' => $order['uniacid'],
  300. );
  301. $result = $this->buildUnifiedOrder($data);
  302. if (is_error($result)) {
  303. return $result;
  304. }
  305. $params = array(
  306. 'return_code' => 'SUCCESS',
  307. 'appid' => $this->wxpay['appid'],
  308. 'mch_id' => $this->wxpay['mch_id'],
  309. 'prepay_id' => $result['prepay_id'],
  310. 'nonce_str' => random(32),
  311. 'result_code' => 'SUCCESS',
  312. 'code_url' => $result['code_url'],
  313. );
  314. $params['sign'] = $this->bulidSign($params);
  315. return $params;
  316. }
  317. public function replyErrorNotify($msg) {
  318. $result = array(
  319. 'return_code' => 'FAIL',
  320. 'return_msg' => $msg,
  321. );
  322. echo array2xml($result);
  323. }
  324. public function closeOrder($trade_no) {
  325. $params = array(
  326. 'appid' => $this->wxpay['appid'],
  327. 'mch_id' => $this->wxpay['mch_id'],
  328. 'nonce_str' => random(32),
  329. 'out_trade_no' => trim($trade_no),
  330. );
  331. $params['sign'] = $this->bulidSign($params);
  332. $result = $this->requestApi('https://api.mch.weixin.qq.com/pay/closeorder', $params);
  333. if (is_error($result)) {
  334. return $result;
  335. }
  336. if ('SUCCESS' == $result['result_code']) {
  337. pdo_update('paycenter_order', array('status' => 'CLOSED'), array('tradeno' => $result['out_trade_no']));
  338. }
  339. return true;
  340. }
  341. public function queryOrder($id, $type = 1) {
  342. $params = array(
  343. 'appid' => $this->wxpay['appid'],
  344. 'mch_id' => $this->wxpay['mch_id'],
  345. 'nonce_str' => random(32),
  346. );
  347. if (1 == $type) {
  348. $params['transaction_id'] = $id;
  349. } else {
  350. $params['out_trade_no'] = $id;
  351. }
  352. $params['sign'] = $this->bulidSign($params);
  353. $result = $this->requestApi('https://api.mch.weixin.qq.com/pay/orderquery', $params);
  354. if (is_error($result)) {
  355. return $result;
  356. }
  357. if ('SUCCESS' != $result['result_code']) {
  358. return error(-1, $result['err_code_des']);
  359. }
  360. $result['total_fee'] = $result['total_fee'] / 100;
  361. return $result;
  362. }
  363. public function downloadBill($date, $type = 'ALL') {
  364. $params = array(
  365. 'appid' => $this->wxpay['appid'],
  366. 'mch_id' => $this->wxpay['mch_id'],
  367. 'nonce_str' => random(32),
  368. 'bill_date' => $date,
  369. 'bill_type' => $type,
  370. );
  371. $params['sign'] = $this->bulidSign($params);
  372. $result = $this->requestApi('https://api.mch.weixin.qq.com/pay/downloadbill', $params);
  373. return $result;
  374. }
  375. public function refundOrder($date, $type = 'ALL') {
  376. $params = array(
  377. 'appid' => $this->wxpay['appid'],
  378. 'mch_id' => $this->wxpay['mch_id'],
  379. 'nonce_str' => random(32),
  380. 'bill_date' => $date,
  381. 'bill_type' => $type,
  382. );
  383. $params['sign'] = $this->bulidSign($params);
  384. $result = $this->requestApi('https://api.mch.weixin.qq.com/pay/downloadbill', $params);
  385. return $result;
  386. }
  387. public function refund($params, $module = '') {
  388. global $_W;
  389. $params['sign'] = $this->bulidSign($params);
  390. $cert_root = ATTACHMENT_ROOT . $_W['uniacid'] . md5(complex_authkey()) . '_wechat_refund_all.pem';
  391. $result = $this->requestApi('https://api.mch.weixin.qq.com/secapi/pay/refund', $params, array(CURLOPT_SSLCERT => $cert_root));
  392. return $result;
  393. }
  394. }