Mini.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <?php
  2. namespace App\libs\wechat\auth\Gateways;
  3. use App\libs\cache\redis\Redis;
  4. use App\libs\helpers\Curl;
  5. use Illuminate\Support\Facades\DB;
  6. /**
  7. * @desc 微信小程序类
  8. */
  9. class Mini extends Base
  10. {
  11. private $appId;
  12. private $secret;
  13. public function __construct()
  14. {
  15. parent::__construct();
  16. $this->appId = $this->config['appid'];
  17. $this->secret = $this->config['secret'];
  18. }
  19. /**
  20. * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
  21. *
  22. * @desc 使用授权code换取openid
  23. */
  24. public function code2Openid(string $code): array
  25. {
  26. $url = 'https://api.weixin.qq.com/sns/jscode2session';
  27. $res = Curl::requestCurl($url, 'GET', [], http_build_query([
  28. 'appid' => $this->appId,
  29. 'secret' => $this->secret,
  30. 'js_code' => $code,
  31. 'grant_type' => 'authorization_code',
  32. ]));
  33. return $res ? json_decode($res, true) : [];
  34. }
  35. /**
  36. * @see https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
  37. *
  38. * @desc 解密小程序信息
  39. *
  40. * @param string $sessionKey session_key
  41. * @param string $encryptedData 加密字符
  42. * @param string $iv 偏移量
  43. */
  44. public function decrypt($sessionKey, $encryptedData, $iv): array
  45. {
  46. if (24 !== \strlen($iv)) {
  47. // 偏移量错误
  48. return ['code' => 41002, 'data' => '', 'msg' => 'iv解密失败'];
  49. }
  50. if (24 !== \strlen($sessionKey)) {
  51. // sessionkey 错误
  52. return ['code' => 41001, 'data' => '', 'msg' => 'sessionKey解密失败'];
  53. }
  54. $data = openssl_decrypt(base64_decode($encryptedData, true), 'AES-128-CBC', base64_decode($sessionKey, true), OPENSSL_RAW_DATA, base64_decode($iv, true));
  55. if (!$data) {
  56. return ['code' => 41001, 'data' => '', 'msg' => '解密失败'];
  57. }
  58. $userInfo = json_decode($data, true);
  59. if ($userInfo['watermark']['appid'] !== $this->appId) {
  60. return ['code' => 41003, 'data' => '', 'msg' => 'appid不匹配'];
  61. }
  62. $userInfo['nickName'] = !empty($userInfo['nickName']) ? $userInfo['nickName'] : '';
  63. return ['code' => 0, 'data' => $userInfo, 'msg' => 'success'];
  64. }
  65. /**
  66. * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.createQRCode.html
  67. *
  68. * @desc 获取小程序码(限量)
  69. *
  70. * @param string $path 页面路径
  71. * @param int $width 二维码宽度
  72. */
  73. public function qrcode(string $path, $width = 430)
  74. {
  75. $params = ['path' => $path, 'width' => $width];
  76. $url = 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=' . $this->accessToken();
  77. return Curl::requestCurl($url, 'POST', [], json_encode($params));
  78. }
  79. /**
  80. * @see https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html#%E4%B8%80%E3%80%81%E5%8F%91%E8%B4%A7%E4%BF%A1%E6%81%AF%E5%BD%95%E5%85%A5%E6%8E%A5%E5%8F%A3
  81. *
  82. * @desc 小程序发货信息管理服务
  83. */
  84. public function upload_shipping_info($transaction_id, $out_trade_no, $openid)
  85. {
  86. $params = [
  87. 'logistics_type' => 3,
  88. 'order_key' => ['order_number_type' => 2, 'transaction_id' => $transaction_id, 'out_trade_no' => $out_trade_no, 'mchid' => config('swdz.wechatPay.mch_id')],
  89. 'delivery_mode' => 'UNIFIED_DELIVERY',
  90. 'shipping_list' => [
  91. 'tracking_no' => '',
  92. 'item_desc' => '购买小程序钻石次数',
  93. ],
  94. 'upload_time' => date('Y-m-d H:i:s'),
  95. 'payer' => [
  96. 'openid' => $openid,
  97. ],
  98. ];
  99. $url = 'https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token=' . $this->accessToken();
  100. return Curl::requestCurl($url, 'POST', [], json_encode($params));
  101. }
  102. /**
  103. * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html
  104. *
  105. * @desc 获取小程序码(不限量)
  106. *
  107. * @param string $scene 场景值
  108. * @param array $extra 额外参数,详见
  109. *
  110. * @return bool|mixed|string
  111. */
  112. public function qrcodeUnlimited(string $scene, $extra = [])
  113. {
  114. $params = array_merge($extra, ['scene' => $scene]);
  115. $url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' . $this->accessToken();
  116. return Curl::requestCurl($url, 'POST', [], json_encode($params));
  117. }
  118. /**
  119. * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.generate.html
  120. *
  121. * @desc 生成短链接
  122. *
  123. * @return bool|string
  124. */
  125. public function urlLink(array $params)
  126. {
  127. $url = 'https://api.weixin.qq.com/wxa/generate_urllink?access_token=' . $this->accessToken();
  128. return Curl::requestCurl($url, 'POST', [], json_encode($params));
  129. }
  130. /**
  131. * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
  132. *
  133. * @desc 发送模板消息
  134. *
  135. * @param string $openid 小程序openid
  136. * @param string $template 模板ID
  137. * @param array $data 小程序数据
  138. * @param string $page 跳转页面
  139. * @param string $state 小程序状态
  140. *
  141. * @return bool|mixed
  142. */
  143. public function sendMiniMsg(string $openid, string $template, array $data, string $page = null, string $state = 'normal'): array
  144. {
  145. $params = [
  146. 'touser' => $openid,
  147. 'template_id' => $template,
  148. 'miniprogram_state' => $state,
  149. 'data' => $data,
  150. ];
  151. if ($page) {
  152. $params['page'] = $page;
  153. }
  154. $url = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' . $this->accessToken();
  155. $res = Curl::requestCurl($url, 'POST', [], json_encode($params));
  156. return $res ? json_decode($res, true) : [];
  157. }
  158. /**
  159. * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
  160. *
  161. * @desc 获取全局唯一接口调用凭据
  162. *
  163. * @param int $repeatCnt
  164. *
  165. * @return string
  166. */
  167. public function accessToken(&$repeatCnt = 0)
  168. {
  169. $drive = $this->config['token_drive'] ?? 'redis';
  170. $key = 'mini_' . $this->appId . '_accessToken';
  171. switch ($drive) {
  172. case 'redis':
  173. $redis = Redis::instance();
  174. $redis->select(15);
  175. $token = $redis->get($key);
  176. break;
  177. case 'db':
  178. default:
  179. $token = Db::table('configs')->where('c_key', $key)->where('c_expired', '>', time())->value('c_value');
  180. break;
  181. }
  182. if ($token) {
  183. return $token;
  184. }
  185. $url = 'https://api.weixin.qq.com/cgi-bin/token';
  186. $res = Curl::requestCurl($url, 'GET', [], http_build_query([
  187. 'grant_type' => 'client_credential',
  188. 'appid' => $this->appId,
  189. 'secret' => $this->secret,
  190. ]));
  191. $data = json_decode($res, true);
  192. if (isset($data['access_token'])) {
  193. $token = $data['access_token'];
  194. switch ($drive) {
  195. case 'redis':
  196. $redis->setex($key, 7000, $token);
  197. break;
  198. case 'db':
  199. default:
  200. $isWin = Db::table('configs')->where('c_key', $key)->value('c_value');
  201. if ($isWin) {
  202. Db::table('configs')
  203. ->where('c_key', $key)
  204. ->update([
  205. 'c_value' => $token,
  206. 'c_expired' => time() + 7000,
  207. 'c_updated' => date('Y-m-d H:i:s'),
  208. ])
  209. ;
  210. } else {
  211. Db::table('configs')
  212. ->insert([
  213. 'c_key' => $key,
  214. 'c_value' => $token,
  215. 'c_expired' => time() + 7000,
  216. 'c_updated' => date('Y-m-d H:i:s'),
  217. ])
  218. ;
  219. }
  220. break;
  221. }
  222. return $token;
  223. }
  224. if ($repeatCnt <= 3) {
  225. ++$repeatCnt;
  226. return $this->accessToken($repeatCnt);
  227. }
  228. return $data['errcode'];
  229. }
  230. }