WechatService.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. <?php
  2. namespace liuniu;
  3. use app\admin\model\Company;
  4. use app\admin\model\User;
  5. use app\admin\model\WechatPlanRecord;
  6. use app\api\controller\Lave;
  7. use app\common\model\LaveMonth;
  8. use app\common\model\WechatContext;
  9. use EasyWeChat\Factory;
  10. use EasyWeChat\Kernel\Messages\Article;
  11. use EasyWeChat\Kernel\Messages\Image;
  12. use EasyWeChat\Kernel\Messages\News;
  13. use EasyWeChat\Kernel\Messages\Text;
  14. use EasyWeChat\Kernel\Messages\Video;
  15. use think\Hook;
  16. use think\Request;
  17. use think\Response;
  18. class WechatService
  19. {
  20. private static $instance = null;
  21. private static $app = null;
  22. public static function options($cid)
  23. {
  24. $info = Company::where('id', $cid)->find();
  25. $config = [
  26. 'app_id' => isset($info['wechat_appid']) ? trim($info['wechat_appid']) : '',
  27. 'secret' => isset($info['wechat_appsecret']) ? trim($info['wechat_appsecret']) : '',
  28. 'token' => isset($info['wechat_token']) ? trim($info['wechat_token']) : '',
  29. 'guzzle' => [
  30. 'timeout' => 10.0, // 超时时间(秒)
  31. 'verify' => false
  32. ],
  33. ];
  34. if (isset($info['wechat_encode']) && (int)$info['wechat_encode'] > 0 && isset($info['wechat_encodingaeskey']) && !empty($info['wechat_encodingaeskey']))
  35. $config['aes_key'] = $info['wechat_encodingaeskey'];
  36. if (isset($info['pay_weixin_open']) && $info['pay_weixin_open'] == 1) {
  37. $config1 = [
  38. 'mch_id' => trim($info['pay_weixin_mchid']),
  39. 'key' => trim($info['pay_weixin_key']),
  40. 'cert_path' => realpath('.' . $info['pay_weixin_client_certfile']),
  41. 'key_path' => realpath('.' . $info['pay_weixin_client_keyfile']),
  42. 'notify_url' => Request::instance()->domain() . "/api/wechat/notify/" . $cid
  43. ];
  44. $config = array_merge($config, $config1);
  45. }
  46. return $config;
  47. }
  48. public static function application($cache = false, $cid = 0)
  49. {
  50. (self::$instance[$cid] === null || $cache === true) && (self::$instance[$cid] = Factory::officialAccount(self::options($cid)));
  51. return self::$instance[$cid];
  52. }
  53. /**
  54. * 支付接口
  55. * @param false $cache
  56. * @param int $cid
  57. * @return \EasyWeChat\Payment\Application|mixed
  58. */
  59. public static function payment($cache = false, $cid = 0)
  60. {
  61. (self::$app[$cid] === null || $cache === true) && (self::$app[$cid] = Factory::payment(self::options($cid)));
  62. return self::$app[$cid];
  63. }
  64. public static function payment2($cache = false, $cid = 0) {
  65. // 使用 isset() 检查下标,避免直接访问未定义键
  66. if (!isset(self::$app[$cid])) {
  67. $options = self::options($cid);
  68. // 校验必要配置是否存在
  69. // if (empty($options['app_id']) || empty($options['mch_id'])) {
  70. // throw new \Exception("微信支付配置缺失: cid={$cid}");
  71. // }
  72. self::$app[$cid] = Factory::payment($options);
  73. }
  74. return self::$app[$cid];
  75. }
  76. public static function serve($cid = 0): Response
  77. {
  78. $wechat = self::application(true, $cid);
  79. $server = $wechat->server;
  80. self::hook($server);
  81. $response = $server->serve();
  82. return Response($response->getContent());
  83. }
  84. /**
  85. * 监听行为
  86. * @param Guard $server
  87. */
  88. private static function hook($server)
  89. {
  90. $server->push(function ($message) {
  91. switch ($message['MsgType']) {
  92. case 'event':
  93. switch (strtolower($message['Event'])) {
  94. case 'subscribe':
  95. $response = WechatReply::reply('subscribe');
  96. if (isset($message->EventKey)) {
  97. if ($message->EventKey && ($qrInfo = QrcodeService::getQrcode($message->Ticket, 'ticket'))) {
  98. QrcodeService::scanQrcode($message->Ticket, 'ticket');
  99. if (strtolower($qrInfo['third_type']) == 'spread') {
  100. }
  101. }
  102. }
  103. break;
  104. case 'unsubscribe':
  105. event('WechatEventUnsubscribeBefore', [$message]);
  106. break;
  107. case 'scan':
  108. $response = WechatReply::reply('subscribe');
  109. if ($message->EventKey && ($qrInfo = QrcodeService::getQrcode($message->Ticket, 'ticket'))) {
  110. QrcodeService::scanQrcode($message->Ticket, 'ticket');
  111. if (strtolower($qrInfo['third_type']) == 'spread') {
  112. }
  113. }
  114. break;
  115. case 'location':
  116. $response = MessageRepositories::wechatEventLocation($message);
  117. break;
  118. case 'click':
  119. $response = WechatReply::reply($message->EventKey);
  120. break;
  121. case 'view':
  122. $response = MessageRepositories::wechatEventView($message);
  123. break;
  124. }
  125. break;
  126. case 'text':
  127. @file_put_contents("tt.txt", '1');
  128. $response = self::textMessage('绑定推荐人失败');
  129. @file_put_contents("tt.txt", '2', 8);
  130. @file_put_contents("tt.txt", json_encode($response), 8);
  131. break;
  132. $response = WechatReply::reply($message->Content);
  133. break;
  134. case 'image':
  135. $response = MessageRepositories::wechatMessageImage($message);
  136. break;
  137. case 'voice':
  138. $response = MessageRepositories::wechatMessageVoice($message);
  139. break;
  140. case 'video':
  141. $response = MessageRepositories::wechatMessageVideo($message);
  142. break;
  143. case 'location':
  144. $response = MessageRepositories::wechatMessageLocation($message);
  145. break;
  146. case 'link':
  147. $response = MessageRepositories::wechatMessageLink($message);
  148. break;
  149. // ... 其它消息
  150. default:
  151. $response = MessageRepositories::wechatMessageOther($message);
  152. break;
  153. }
  154. return $response ?? false;
  155. });
  156. }
  157. /**
  158. * 多客服消息转发
  159. * @param string $account
  160. * @return \EasyWeChat\Message\Transfer
  161. */
  162. public static function transfer($account = '')
  163. {
  164. $transfer = new \EasyWeChat\Message\Transfer();
  165. return empty($account) ? $transfer : $transfer->to($account);
  166. }
  167. /**
  168. * 上传永久素材接口
  169. * @return \EasyWeChat\Material\Material
  170. */
  171. public static function materialService($cid = 0)
  172. {
  173. return self::application(false, $cid)->material;
  174. }
  175. /**
  176. * 上传临时素材接口
  177. * @return \EasyWeChat\Material\Temporary
  178. */
  179. public static function materialTemporaryService($cid = 0)
  180. {
  181. return self::application(false, $cid)->media;
  182. }
  183. /**
  184. * 用户接口
  185. * @return \EasyWeChat\User\User
  186. */
  187. public static function userService($cid = 0)
  188. {
  189. return self::application(false, $cid)->user;
  190. }
  191. /**
  192. * 客服消息接口
  193. * @param null $to
  194. * @param null $message
  195. */
  196. public static function staffService($cid = 0)
  197. {
  198. return self::application(false, $cid)->staff;
  199. }
  200. /**
  201. * 微信公众号菜单接口
  202. * @return \EasyWeChat\Menu\Menu
  203. */
  204. public static function menuService($cid = 0)
  205. {
  206. return self::application(false, $cid)->menu;
  207. }
  208. /**
  209. * 微信二维码生成接口
  210. * @return \EasyWeChat\QRCode\QRCode
  211. */
  212. public static function qrcodeService($cid = 0)
  213. {
  214. return self::application(false, $cid)->qrcode;
  215. }
  216. /**
  217. * 微信永久二维码生成接口 小于10万个
  218. * @return \EasyWeChat\QRCode\QRCode
  219. */
  220. public static function qrcodeForeverService($sceneValue, $cid = 0)
  221. {
  222. return self::application(false, $cid)->qrcode->forever($sceneValue);
  223. }
  224. /**
  225. * 微信临时二维码生成接口 30天有效期
  226. * @return \EasyWeChat\QRCode\QRCode
  227. */
  228. public static function qrcodeTempService($sceneValue, $expireSeconds = 2592000, $cid = 0)
  229. {
  230. return self::application(false, $cid)->qrcode->temporary($sceneValue, $expireSeconds);
  231. }
  232. /**
  233. * 短链接生成接口
  234. * @return \EasyWeChat\Url\Url
  235. */
  236. public static function urlService($cid = 0)
  237. {
  238. return self::application(false, $cid)->url;
  239. }
  240. /**
  241. * 用户授权
  242. * @return \Overtrue\Socialite\Providers\WeChatProvider
  243. */
  244. public static function oauthService($cid = 0)
  245. {
  246. return self::application(false, $cid)->oauth;
  247. }
  248. /**
  249. * 模板消息接口
  250. * @return \EasyWeChat\Notice\Notice
  251. */
  252. public static function noticeService($cid = 0)
  253. {
  254. return self::application(false, $cid)->template_message;
  255. }
  256. public static function sendTemplate($openid, $templateId, array $data, $url = null, $cid = 0)
  257. {
  258. $notice = self::noticeService($cid);
  259. return $notice->send([
  260. 'touser' => $openid,
  261. 'template_id' => $templateId,
  262. 'url' => $url,
  263. 'data' => $data,
  264. ]);
  265. }
  266. public static function userTagService($cid = 0)
  267. {
  268. return self::application(false, $cid)->user_tag;
  269. }
  270. /**
  271. * 生成支付订单对象
  272. * @param $openid
  273. * @param $out_trade_no
  274. * @param $total_fee
  275. * @param $attach
  276. * @param $body
  277. * @param string $detail
  278. * @param string $trade_type
  279. * @param array $options
  280. * @return Order
  281. */
  282. public static function paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [], $cid = 0)
  283. {
  284. $total_fee = bcmul($total_fee, 100, 0);
  285. $order = array_merge(compact('out_trade_no', 'total_fee', 'attach', 'body', 'detail', 'trade_type', 'openid'), $options);
  286. if ($order['detail'] == '') unset($order['detail']);
  287. @file_put_contents("quanju.txt", json_encode($order)."-微信支付传值\r\n", 8);
  288. $result = self::payment(false, $cid)->order->unify(
  289. $order
  290. );
  291. return $result;
  292. }
  293. /**
  294. * 使用商户订单号退款
  295. * @param $orderNo
  296. * @param $opt
  297. */
  298. public static function payOrderRefund($cid, $orderNo, array $opt)
  299. {
  300. if (!isset($opt['pay_price'])) exception('缺少pay_price');
  301. $totalFee = floatval(bcmul($opt['pay_price'], 100, 0));
  302. $refundFee = isset($opt['refund_price']) ? floatval(bcmul($opt['refund_price'], 100, 0)) : null;
  303. $refundReason = isset($opt['desc']) ? $opt['desc'] : '';
  304. $refundNo = isset($opt['refund_id']) ? $opt['refund_id'] : $orderNo;
  305. $opUserId = isset($opt['op_user_id']) ? $opt['op_user_id'] : null;
  306. $type = isset($opt['type']) ? $opt['type'] : 'out_trade_no';
  307. /*仅针对老资金流商户使用
  308. REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款)
  309. REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款*/
  310. $refundAccount = isset($opt['refund_account']) ? $opt['refund_account'] : 'REFUND_SOURCE_UNSETTLED_FUNDS';
  311. try {
  312. $res = self::payment('false', $cid)->byOutTradeNumber($orderNo, $refundNo, $totalFee, $refundFee, ['refund_desc' => $refundReason]);
  313. if ($res->return_code == 'FAIL') exception('退款失败:' . $res->return_msg);
  314. if (isset($res->err_code)) exception('退款失败:' . $res->err_code_des);
  315. } catch (\Exception $e) {
  316. exception($e->getMessage());
  317. }
  318. return true;
  319. }
  320. /**
  321. * 微信支付成功回调接口
  322. */
  323. public static function handleNotify($cid)
  324. {
  325. $response = self::payment(true, $cid)->handlePaidNotify(function ($notify, $successful) use ($cid) {
  326. if ($successful && isset($notify['out_trade_no'])) {
  327. if (isset($notify['attach']) && $notify['attach']) {
  328. if (($count = strpos($notify['out_trade_no'], '_')) !== false) {
  329. $notify['out_trade_no'] = substr($notify['out_trade_no'], $count + 1);
  330. }
  331. $params = [$cid, $notify['out_trade_no']];
  332. Hook::exec("\\liuniu\\repositories\\PaymentRepositories", "wechat" . ucfirst($notify['attach']), $params);
  333. }
  334. $data = ['eventkey' => 'notify', 'command' => '', 'refreshtime' => time(), 'openid' => $notify['openid'], 'message' => json_encode($notify)];
  335. $wechatContext = WechatContext::create($data, true);
  336. return true;
  337. }
  338. @file_put_contents("quanju.txt", json_encode($notify)."-签约返回内容\r\n", 8);
  339. // @file_put_contents("quanju.txt", json_encode($successful)."-这是什么\r\n", 8);
  340. if (isset($notify['plan_id'])) {
  341. if (isset($notify['change_type']) && $notify['change_type']) {
  342. @file_put_contents("quanju.txt", $notify['change_type']."-状态\r\n", 8);
  343. if ($notify['change_type']=='ADD'){
  344. $cs=WechatPlanRecord::where('contract_code',$notify['contract_code'])->Update(['contract_id' => $notify['contract_id'],'is_signing'=>0]);
  345. if (($count = strpos($notify['contract_code'], '_')) !== false) {
  346. $notify['contract_code'] = substr($notify['contract_code'], $count + 1);
  347. }
  348. $params = [$cid, $notify['contract_code']];
  349. Hook::exec("\\liuniu\\repositories\\PaymentRepositories", "wechat" . ucfirst('MonthLave'), $params);
  350. @file_put_contents("quanju.txt", json_encode($params)."-不知道行不行\r\n", 8);
  351. $tf = Lave::paySignPap($notify['contract_code']);
  352. @file_put_contents("quanju3.txt", $tf."-第一次扣款\r\n", 8);
  353. }elseif ($notify['change_type']=='DELETE'){
  354. @file_put_contents("quanju.txt", "-关闭签约\r\n", 8);
  355. $cs=WechatPlanRecord::where('contract_code',$notify['contract_code'])->Update(['is_signing' => 1,'deletetime'=>time()]);
  356. }
  357. @file_put_contents("quanju.txt", $cs."-修改记录\r\n", 8);
  358. }
  359. $data = ['eventkey' => 'notify', 'command' => '', 'refreshtime' => time(), 'openid' => $notify['openid'], 'message' => json_encode($notify)];
  360. $wechatContext = WechatContext::create($data, true);
  361. return true;
  362. }
  363. if (isset($notify['nonce_str'])) {
  364. $cz = LaveMonth::Where(['contract_code' => $notify['nonce_str']])->find();
  365. @file_put_contents("quanju.txt", json_encode($cz)."-随机字符搜索\r\n", 8);
  366. if (!empty($cz)){
  367. $params = [$cid, $notify['nonce_str']];
  368. Hook::exec("\\liuniu\\repositories\\PaymentRepositories", "wechat" . ucfirst('MonthLavePay'), $params);
  369. }
  370. $data = ['eventkey' => 'notify', 'command' => '', 'refreshtime' => time(), 'openid' => $notify['openid'], 'message' => json_encode($notify)];
  371. $wechatContext = WechatContext::create($data, true);
  372. return true;
  373. }
  374. });
  375. $response->send();
  376. }
  377. /**
  378. * jsSdk
  379. * @return \EasyWeChat\Js\Js
  380. */
  381. public static function jsService($cid = 0)
  382. {
  383. return self::payment(false, $cid)->jssdk;
  384. }
  385. public static function WeixinJSBridge($cid, $prepayId)
  386. {
  387. $json = self::jsService($cid)->bridgeConfig($prepayId);
  388. return $json;
  389. }
  390. public static function jspay($cid, $prepayId)
  391. {
  392. $json = self::jsService($cid)->sdkConfig($prepayId);
  393. return $json;
  394. }
  395. public static function jsSdk($url = '', $cid = 0)
  396. {
  397. $apiList = ['editAddress', 'openAddress', 'updateTimelineShareData', 'updateAppMessageShareData', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone', 'startRecord', 'stopRecord', 'onVoiceRecordEnd', 'playVoice', 'pauseVoice', 'stopVoice', 'onVoicePlayEnd', 'uploadVoice', 'downloadVoice', 'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'translateVoice', 'getNetworkType', 'openLocation', 'getLocation', 'hideOptionMenu', 'showOptionMenu', 'hideMenuItems', 'showMenuItems', 'hideAllNonBaseMenuItem', 'showAllNonBaseMenuItem', 'closeWindow', 'scanQRCode', 'chooseWXPay', 'openProductSpecificView', 'addCard', 'chooseCard', 'openCard'];
  398. $jsService = self::jsService($cid);
  399. if ($url) $jsService->setUrl($url);
  400. try {
  401. return $jsService->buildConfig($apiList, false);
  402. } catch (\Exception $e) {
  403. // var_dump($e->getMessage());
  404. return '{}';
  405. }
  406. }
  407. /**
  408. * 回复文本消息
  409. * @param string $content 文本内容
  410. * @return Text
  411. */
  412. public static function textMessage($content)
  413. {
  414. return new Text($content);
  415. }
  416. /**
  417. * 回复图片消息
  418. * @param string $media_id 媒体资源 ID
  419. * @return Image
  420. */
  421. public static function imageMessage($media_id)
  422. {
  423. return new Image('media_id');
  424. }
  425. /**
  426. * 回复视频消息
  427. * @param string $media_id 媒体资源 ID
  428. * @param string $title 标题
  429. * @param string $description 描述
  430. * @param null $thumb_media_id 封面资源 ID
  431. * @return Video
  432. */
  433. public static function videoMessage($media_id, $title = '', $description = '...', $thumb_media_id = null)
  434. {
  435. return new Video(compact('media_id', 'title', 'description', 'thumb_media_id'));
  436. }
  437. /**
  438. * 回复声音消息
  439. * @param string $media_id 媒体资源 ID
  440. * @return Voice
  441. */
  442. public static function voiceMessage($media_id)
  443. {
  444. return new Voice(compact('media_id'));
  445. }
  446. /**
  447. * 回复图文消息
  448. * @param string|array $title 标题
  449. * @param string $description 描述
  450. * @param string $url URL
  451. * @param string $image 图片链接
  452. */
  453. public static function newsMessage($title, $description = '...', $url = '', $image = '')
  454. {
  455. if (is_array($title)) {
  456. if (isset($title[0]) && is_array($title[0])) {
  457. $newsList = [];
  458. foreach ($title as $news) {
  459. $newsList[] = self::newsMessage($news);
  460. }
  461. return $newsList;
  462. } else {
  463. $data = $title;
  464. }
  465. } else {
  466. $data = compact('title', 'description', 'url', 'image');
  467. }
  468. return new News($data);
  469. }
  470. /**
  471. * 回复文章消息
  472. * @param string|array $title 标题
  473. * @param string $thumb_media_id 图文消息的封面图片素材id(必须是永久 media_ID)
  474. * @param string $source_url 图文消息的原文地址,即点击“阅读原文”后的URL
  475. * @param string $content 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS
  476. * @param string $author 作者
  477. * @param string $digest 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空
  478. * @param int $show_cover_pic 是否显示封面,0为false,即不显示,1为true,即显示
  479. * @param int $need_open_comment 是否打开评论,0不打开,1打开
  480. * @param int $only_fans_can_comment 是否粉丝才可评论,0所有人可评论,1粉丝才可评论
  481. * @return Article
  482. */
  483. public static function articleMessage($title, $thumb_media_id, $source_url, $content = '', $author = '', $digest = '', $show_cover_pic = 0, $need_open_comment = 0, $only_fans_can_comment = 1)
  484. {
  485. $data = is_array($title) ? $title : compact('title', 'thumb_media_id', 'source_url', 'content', 'author', 'digest', 'show_cover_pic', 'need_open_comment', 'only_fans_can_comment');
  486. return new Article($data);
  487. }
  488. /**
  489. * 作为客服消息发送
  490. * @param $to
  491. * @param $message
  492. * @return bool
  493. */
  494. public static function staffTo($to, $message)
  495. {
  496. $staff = self::staffService();
  497. $staff = is_callable($message) ? $staff->message($message()) : $staff->message($message);
  498. $res = $staff->to($to)->send();
  499. return $res;
  500. }
  501. /**
  502. * 获得用户信息
  503. * @param array|string $openid
  504. * @return \EasyWeChat\Support\Collection
  505. */
  506. public static function getUserInfo($cid, $openid)
  507. {
  508. $userService = self::userService($cid);
  509. $userInfo = is_array($openid) ? $userService->select($openid) : $userService->get($openid);
  510. return $userInfo;
  511. }
  512. /**
  513. * 生成支付签约订单对象
  514. * @param $openid
  515. * @param $out_trade_no
  516. * @param $total_fee
  517. * @param $attach
  518. * @param $body
  519. * @param string $detail
  520. * @param string $trade_type
  521. * @param array $options
  522. * @return Order
  523. */
  524. public static function paysignedOrder($openid, $out_trade_no, $total_fee, $attach, $body,$contract_code, $plan_id,$spbill_create_ip,$detail = '', $trade_type = 'JSAPI', $options = [], $cid = 0,$contract_display_account='')
  525. {
  526. $total_fee = bcmul($total_fee, 100, 0);
  527. $order = array_merge(compact('out_trade_no', 'total_fee', 'attach', 'body', 'detail', 'trade_type', 'openid','contract_code','plan_id','spbill_create_ip','contract_display_account'), $options);
  528. if ($order['detail'] == '') unset($order['detail']);
  529. $order['contract_notify_url']=Request::instance()->domain() . "/api/wechat/notify/" . $cid;
  530. $result = self::payment(false, $cid)->order->unify(
  531. $order,true
  532. );
  533. // var_dump($result);die();
  534. @file_put_contents("quanju.txt", json_encode($result) . "-签约返回结果\r\n", 8);
  535. return $result;
  536. }
  537. /**
  538. * 纯签约订单对象
  539. * @param $openid
  540. * @param $out_trade_no
  541. * @param $total_fee
  542. * @param $attach
  543. * @param $body
  544. * @param string $detail
  545. * @param string $trade_type
  546. * @param array $options
  547. */
  548. public static function signedOrder($contract_code, $plan_id, $cid = 0,$contract_display_account='',$nickname='',$return_web = '')
  549. {
  550. // $total_fee = bcmul($total_fee, 100, 0);
  551. // $timestamp=time();
  552. // $version = '1.0';
  553. $company = Company::where('id',$cid)->find();
  554. if (empty($company)){
  555. return'企业不存在';
  556. }
  557. // 微信支付配置参数
  558. // $mch_id = "1623907696";
  559. $mch_id = $company['pay_weixin_mchid'];
  560. // $key = "1wm55KpF5tgZFW1TYs6TBX9MWBpI5FmT";
  561. $key = $company['wechat_encodingaeskey'];
  562. $app_id = $company['wechat_appid'];
  563. // 使用动态时间戳
  564. $current_timestamp = time();
  565. // $contract_code = preg_replace("/[^0-9]/", "", $contract_code);
  566. // if ($return_web != ''){
  567. // $return_web ='http://red.igxys.com/'.$return_web;
  568. // }else{
  569. // $return_web = '1';
  570. // }
  571. $params = [
  572. // 'appid' => 'wx5681205d1ef4d9d3',
  573. 'appid' => $app_id,
  574. 'mch_id' => $mch_id,
  575. 'plan_id' => $plan_id,
  576. 'contract_code' => $contract_code,
  577. 'request_serial' => $current_timestamp, // 使用时间戳作为序列号
  578. 'contract_display_account' => $contract_display_account,
  579. 'notify_url' => 'http://red.igxys.com/api/wechat/notify/'.$cid,
  580. 'timestamp' => $current_timestamp, // 动态时间戳
  581. 'version' => '1.0',
  582. 'return_web' => $return_web
  583. ];
  584. // 关键步骤1:先对需要编码的参数进行原始值存储
  585. $raw_params = $params;
  586. // 关键步骤2:对需要URL编码的参数值进行编码(签名前)
  587. // $params['contract_display_account'] = urlencode($params['contract_display_account']);
  588. // 步骤3:参数按ASCII排序
  589. ksort($params);
  590. // 步骤4:构建待签名字符串(使用编码后的值)
  591. $stringA = "";
  592. foreach ($params as $k => $v) {
  593. $stringA .= $k . '=' . $v . '&';
  594. }
  595. $stringSignTemp = $stringA . 'key=' . $key;
  596. // 关键步骤5:正确生成HMAC-SHA256签名
  597. // $sign = strtoupper(hash_hmac('sha256', $stringSignTemp, '')); // 注意:第三个参数为空字符串
  598. $sign = self::generateWechatMD5Sign($params, $key);
  599. $params['sign'] = $sign;
  600. $params['notify_url'] = rawurlencode($params['notify_url']);
  601. $params['contract_display_account'] = rawurlencode($params['contract_display_account']);
  602. // 构建最终URL
  603. $queryString = "";
  604. foreach ($params as $k => $v) {
  605. // 已编码的参数不需要再次编码
  606. $queryString .= $k . '=' . $v . '&';
  607. }
  608. $url = 'https://api.mch.weixin.qq.com/papay/entrustweb?' . rtrim($queryString, '&');
  609. // 记录请求URL
  610. @file_put_contents("quanju3.txt", $url . "-请求URL\r\n", 8);
  611. // return $url;
  612. //// 发送请求(禁止自动重定向)
  613. // $ch = curl_init();
  614. // curl_setopt($ch, CURLOPT_URL, $url);
  615. // curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  616. // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
  617. // curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  618. // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // 关键:禁止自动重定向
  619. // curl_setopt($ch, CURLOPT_HEADER, true); // 获取响应头
  620. //
  621. // $response = curl_exec($ch);
  622. // $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  623. //
  624. //// 记录完整响应
  625. // @file_put_contents("quanju3.txt", "HTTP状态码: " . $http_code . "\n响应内容: " . $response . "\n", FILE_APPEND);
  626. //
  627. //// 分析响应
  628. // if ($http_code == 302) {
  629. // // 解析重定向地址
  630. // preg_match('/Location: (.*)/i', $response, $matches);
  631. // $redirect_url = trim($matches[1] ?? '');
  632. //
  633. // if ($redirect_url) {
  634. // // 成功重定向到微信签约页面
  635. // @file_put_contents("quanju3.txt", "签约URL: " . $redirect_url . "\n", FILE_APPEND);
  636. // } else {
  637. // @file_put_contents("quanju3.txt", "错误:未找到重定向地址\n", FILE_APPEND);
  638. // }
  639. // } else {
  640. // // 提取可能的错误信息
  641. // preg_match('/<error_description><!\[CDATA\[(.*?)\]\]><\/error_description>/', $response, $error_matches);
  642. // $error_msg = $error_matches[1] ?? '未知错误';
  643. //
  644. // @file_put_contents("quanju3.txt", "错误信息: " . $error_msg . "\n", FILE_APPEND);
  645. // }
  646. //
  647. // curl_close($ch);
  648. // if (curl_errno($ch)) {
  649. // echo 'CURL Error: ' . curl_error($ch);
  650. // } else {
  651. // echo "Response:\n" . $response;
  652. // }
  653. // $request_serial = time();
  654. // $timestamp = time();
  655. // $order = array_merge(compact( 'contract_code','plan_id','contract_display_account','timestamp','version','request_serial'), $options);
  656. //// if ($order['detail'] == '') unset($order['detail']);
  657. // $order['notify_url']=Request::instance()->domain() . "/api/wechat/notify/" . $cid;
  658. // $result = self::payment(false, $cid)->contract->web(
  659. // $order
  660. // );
  661. // var_dump($result);die();
  662. // @file_put_contents("quanju3.txt", json_encode($response) . "-签约返回结果测试\r\n", 8);
  663. return $url;
  664. }
  665. public static function generateWechatMD5Sign($params, $key) {
  666. // 1. 过滤空值和签名参数
  667. $filteredParams = array_filter($params, function($value, $key) {
  668. return $value !== '' && $key !== 'sign';
  669. }, ARRAY_FILTER_USE_BOTH);
  670. // 2. 按键名ASCII字典序排序
  671. ksort($filteredParams);
  672. // 3. 拼接键值对
  673. $stringA = '';
  674. foreach ($filteredParams as $k => $v) {
  675. $stringA .= "{$k}={$v}&";
  676. }
  677. // 4. 拼接API密钥
  678. $stringSignTemp = $stringA . "key={$key}";
  679. // 5. MD5加密并转为大写
  680. return strtoupper(md5($stringSignTemp));
  681. }
  682. /**
  683. * 签约申请扣款
  684. * @param $openid
  685. * @param $out_trade_no
  686. * @param $total_fee
  687. * @param $attach
  688. * @param $body
  689. * @param string $detail
  690. * @param string $trade_type
  691. * @param array $options
  692. * @return Order
  693. */
  694. public static function papPayApply($mch_id,$out_trade_no, $total_fee, $attach, $detail = '', $trade_type = 'PAP', $options = [], $cid = 0,$contract_id='',$body='月捐款',$nonce_str='')
  695. {
  696. $total_fee = bcmul($total_fee, 100, 0);
  697. $order = array_merge(compact('mch_id','out_trade_no', 'total_fee', 'attach', 'detail', 'trade_type','contract_id','body','nonce_str'), $options);
  698. if ($order['detail'] == '') unset($order['detail']);
  699. $order['notify_url']=Request::instance()->domain() . "/api/wechat/notify/" . $cid;
  700. $result = self::payment2(false, $cid)->contract->apply(
  701. $order
  702. );
  703. // var_dump($result);die();
  704. return $result;
  705. }
  706. /**
  707. * 解除签约
  708. */
  709. public static function deleteSign($mch_id,$contract_code,$plan_id,$version='1.0',$options=[],$cid=0)
  710. {
  711. $contract_termination_remark = '月捐款解约';
  712. $order = array_merge(compact('mch_id','contract_code','plan_id','version','contract_termination_remark'), $options);
  713. // if ($order['detail'] == '') unset($order['detail']);
  714. // $order['notify_url']=Request::instance()->domain() . "/api/wechat/notify/" . $cid;
  715. $result = self::payment(false, $cid)->contract->delete(
  716. $order
  717. );
  718. return $result;
  719. }
  720. /**
  721. * 查询签约
  722. */
  723. public static function querySign($mch_id,$contract_code,$pan_id,$version='1.0',$options=[],$cid=0)
  724. {
  725. $appid='wx5681205d1ef4d9d3';
  726. $order = array_merge(compact('appid','mch_id','contract_code','pan_id','version'), $options);
  727. // if ($order['detail'] == '') unset($order['detail']);
  728. // $order['notify_url']=Request::instance()->domain() . "/api/wechat/notify/" . $cid;
  729. $result = self::payment(false, $cid)->contract->query(
  730. $order
  731. );
  732. @file_put_contents("quanju.txt", json_encode($result) . "-查询签约状态返回结果\r\n", 8);
  733. return $result;
  734. }
  735. /**
  736. * 查询签约订单
  737. */
  738. public static function querySignOrder($order_id,$cid=0)
  739. {
  740. @file_put_contents("quanju2.txt", $order_id . "-查询的订单id\r\n", 8);
  741. @file_put_contents("quanju2.txt", $cid . "-cid\r\n", 8);
  742. $result = self::payment2(false, $cid)->order->queryByOutTradeNumber(
  743. $order_id
  744. );
  745. @file_put_contents("quanju2.txt", json_encode($result) . "-查询签约状态返回结果\r\n", 8);
  746. if ($result['return_code'] == 'SUCCESS' && $result['result_code'] !='FAIL'){
  747. if (!empty($result['trade_state_desc'])){
  748. if ($result['trade_state_desc'] == '支付成功'){
  749. $time=strtotime($result['time_end']);
  750. LaveMonth::where(['order_id' => $order_id])->update(['paid' => 1,'paytime'=>$time]); //修改订单状态
  751. }
  752. }
  753. }
  754. return $result;
  755. }
  756. /**
  757. * 查询签约关系
  758. */
  759. public static function querycontract($mch_id,$contract_id,$version='1.0',$options=[],$cid=0)
  760. {
  761. $appid='wx5681205d1ef4d9d3';
  762. $order = array_merge(compact('appid','mch_id','contract_id','version'), $options);
  763. // if ($order['detail'] == '') unset($order['detail']);
  764. // $order['notify_url']=Request::instance()->domain() . "/api/wechat/notify/" . $cid;
  765. $result = self::payment(false, $cid)->contract->querycontract(
  766. $order
  767. );
  768. // @file_put_contents("quanju.txt", json_encode($result) . "-查询签约状态返回结果\r\n", 8);
  769. return $result;
  770. }
  771. }