appId = $this->config['appid']; $this->secret = $this->config['secret']; } /** * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html * * @desc 使用授权code换取openid */ public function code2Openid(string $code): array { $url = 'https://api.weixin.qq.com/sns/jscode2session'; $res = Curl::requestCurl($url, 'GET', [], http_build_query([ 'appid' => $this->appId, 'secret' => $this->secret, 'js_code' => $code, 'grant_type' => 'authorization_code', ])); return $res ? json_decode($res, true) : []; } /** * @see https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html * * @desc 解密小程序信息 * * @param string $sessionKey session_key * @param string $encryptedData 加密字符 * @param string $iv 偏移量 */ public function decrypt($sessionKey, $encryptedData, $iv): array { if (24 !== \strlen($iv)) { // 偏移量错误 return ['code' => 41002, 'data' => '', 'msg' => 'iv解密失败']; } if (24 !== \strlen($sessionKey)) { // sessionkey 错误 return ['code' => 41001, 'data' => '', 'msg' => 'sessionKey解密失败']; } $data = openssl_decrypt(base64_decode($encryptedData, true), 'AES-128-CBC', base64_decode($sessionKey, true), OPENSSL_RAW_DATA, base64_decode($iv, true)); if (!$data) { return ['code' => 41001, 'data' => '', 'msg' => '解密失败']; } $userInfo = json_decode($data, true); if ($userInfo['watermark']['appid'] !== $this->appId) { return ['code' => 41003, 'data' => '', 'msg' => 'appid不匹配']; } $userInfo['nickName'] = !empty($userInfo['nickName']) ? $userInfo['nickName'] : ''; return ['code' => 0, 'data' => $userInfo, 'msg' => 'success']; } /** * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.createQRCode.html * * @desc 获取小程序码(限量) * * @param string $path 页面路径 * @param int $width 二维码宽度 */ public function qrcode(string $path, $width = 430) { $params = ['path' => $path, 'width' => $width]; $url = 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=' . $this->accessToken(); return Curl::requestCurl($url, 'POST', [], json_encode($params)); } /** * @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 * * @desc 小程序发货信息管理服务 */ public function upload_shipping_info($transaction_id, $out_trade_no, $openid) { $params = [ 'logistics_type' => 3, 'order_key' => ['order_number_type' => 2, 'transaction_id' => $transaction_id, 'out_trade_no' => $out_trade_no, 'mchid' => config('swdz.wechatPay.mch_id')], 'delivery_mode' => 'UNIFIED_DELIVERY', 'shipping_list' => [ 'tracking_no' => '', 'item_desc' => '购买小程序钻石次数', ], 'upload_time' => date('Y-m-d H:i:s'), 'payer' => [ 'openid' => $openid, ], ]; $url = 'https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token=' . $this->accessToken(); return Curl::requestCurl($url, 'POST', [], json_encode($params)); } /** * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html * * @desc 获取小程序码(不限量) * * @param string $scene 场景值 * @param array $extra 额外参数,详见 * * @return bool|mixed|string */ public function qrcodeUnlimited(string $scene, $extra = []) { $params = array_merge($extra, ['scene' => $scene]); $url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' . $this->accessToken(); return Curl::requestCurl($url, 'POST', [], json_encode($params)); } /** * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.generate.html * * @desc 生成短链接 * * @return bool|string */ public function urlLink(array $params) { $url = 'https://api.weixin.qq.com/wxa/generate_urllink?access_token=' . $this->accessToken(); return Curl::requestCurl($url, 'POST', [], json_encode($params)); } /** * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html * * @desc 发送模板消息 * * @param string $openid 小程序openid * @param string $template 模板ID * @param array $data 小程序数据 * @param string $page 跳转页面 * @param string $state 小程序状态 * * @return bool|mixed */ public function sendMiniMsg(string $openid, string $template, array $data, string $page = null, string $state = 'normal'): array { $params = [ 'touser' => $openid, 'template_id' => $template, 'miniprogram_state' => $state, 'data' => $data, ]; if ($page) { $params['page'] = $page; } $url = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' . $this->accessToken(); $res = Curl::requestCurl($url, 'POST', [], json_encode($params)); return $res ? json_decode($res, true) : []; } /** * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html * * @desc 获取全局唯一接口调用凭据 * * @param int $repeatCnt * * @return string */ public function accessToken(&$repeatCnt = 0) { $drive = $this->config['token_drive'] ?? 'redis'; $key = 'mini_' . $this->appId . '_accessToken'; switch ($drive) { case 'redis': $redis = Redis::instance(); $redis->select(15); $token = $redis->get($key); break; case 'db': default: $token = Db::table('configs')->where('c_key', $key)->where('c_expired', '>', time())->value('c_value'); break; } if ($token) { return $token; } $url = 'https://api.weixin.qq.com/cgi-bin/token'; $res = Curl::requestCurl($url, 'GET', [], http_build_query([ 'grant_type' => 'client_credential', 'appid' => $this->appId, 'secret' => $this->secret, ])); $data = json_decode($res, true); if (isset($data['access_token'])) { $token = $data['access_token']; switch ($drive) { case 'redis': $redis->setex($key, 7000, $token); break; case 'db': default: $isWin = Db::table('configs')->where('c_key', $key)->value('c_value'); if ($isWin) { Db::table('configs') ->where('c_key', $key) ->update([ 'c_value' => $token, 'c_expired' => time() + 7000, 'c_updated' => date('Y-m-d H:i:s'), ]) ; } else { Db::table('configs') ->insert([ 'c_key' => $key, 'c_value' => $token, 'c_expired' => time() + 7000, 'c_updated' => date('Y-m-d H:i:s'), ]) ; } break; } return $token; } if ($repeatCnt <= 3) { ++$repeatCnt; return $this->accessToken($repeatCnt); } return $data['errcode']; } }