<?php
namespace DtApp\ThinkLibrary\service\wechat;
use DtApp\ThinkLibrary\exception\DtaException;
use DtApp\ThinkLibrary\facade\Pregs;
use DtApp\ThinkLibrary\facade\Randoms;
use DtApp\ThinkLibrary\facade\Urls;
use DtApp\ThinkLibrary\facade\Xmls;
use DtApp\ThinkLibrary\Service;
use DtApp\ThinkLibrary\service\curl\HttpService;
use think\db\exception\DbException;
class WebAppService extends Service
{
private $app_id;
private $app_secret;
private $redirect_uri;
private $response_type = 'code';
private $scope = "snsapi_base";
private $state = "";
private $grant_type = "authorization_code";
private $cache = "file";
private $mch_key;
public function mchKey(string $mchKey)
{
$this->mch_key = $mchKey;
return $this;
}
private $mch_id;
public function mchId(string $mchId)
{
$this->mch_id = $mchId;
return $this;
}
public function appId(string $appId): self
{
$this->app_id = $appId;
return $this;
}
public function appSecret(string $appSecret): self
{
$this->app_secret = $appSecret;
return $this;
}
private function getConfig(): self
{
$this->cache = config('dtapp.wechat.webapp.cache');
$this->app_id = config('dtapp.wechat.webapp.app_id');
$this->app_secret = config('dtapp.wechat.webapp.app_secret');
return $this;
}
public function redirectUri(string $redirectUri)
{
if (empty(Pregs::isLink($redirectUri))) {
throw new DtaException("请检查redirectUri,是否正确");
}
$this->redirect_uri = Urls::lenCode($redirectUri);
return $this;
}
public function scope(string $scope): self
{
if ($scope === "snsapi_base") {
$this->scope = $scope;
} elseif ($scope === "snsapi_userinfo") {
$this->scope = $scope;
} else {
throw new DtaException("请检查scope参数");
}
return $this;
}
public function state(string $state): self
{
$this->state = $state;
return $this;
}
public function cache(string $cache): self
{
$this->cache = $cache;
return $this;
}
public function oauth2()
{
if (empty($this->app_id)) {
$this->getConfig();
}
if (strlen($this->state) > 128) {
throw new DtaException("请检查state参数,最多128字节");
}
$params = Urls::toParams([
'appid' => $this->app_id,
'redirect_uri' => $this->redirect_uri,
'response_type' => $this->response_type,
'scope' => $this->scope,
'state' => $this->state
]);
return header("Location:https://open.weixin.qq.com/connect/oauth2/authorize?$params#wechat_redirect");
}
public function accessToken(string $code, bool $is = true)
{
if (empty($this->app_id) || empty($this->app_secret)) {
$this->getConfig();
}
if (empty($this->app_id)) {
throw new DtaException('请检查app_id参数');
}
if (empty($this->app_secret)) {
throw new DtaException('请检查app_secret参数');
}
return HttpService::instance()
->url("https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->app_id}&secret={$this->app_secret}&code={$code}&grant_type={$this->grant_type}")
->toArray($is);
}
public function refreshToken(string $refreshToken, bool $is = true)
{
if (empty($this->app_id)) {
$this->getConfig();
}
if (empty($this->app_id)) {
throw new DtaException('请检查app_id参数');
}
$this->grant_type = "refresh_token";
return HttpService::instance()
->url("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid={$this->app_id}&grant_type={$this->grant_type}&refresh_token={$refreshToken}")
->toArray($is);
}
public function useInfo(string $accessToken, string $openid, $lang = "zh_CN", bool $is = true)
{
if (empty($this->app_id) || empty($this->app_secret)) {
$this->getConfig();
}
return HttpService::instance()
->url("https://api.weixin.qq.com/sns/userinfo?access_token={$accessToken}&openid={$openid}&lang={$lang}")
->toArray($is);
}
public function auth(string $accessToken, string $openid, bool $is = true)
{
if (empty($this->app_id) || empty($this->app_secret)) {
$this->getConfig();
}
return HttpService::instance()
->url("https://api.weixin.qq.com/sns/auth?access_token={$accessToken}&openid={$openid}")
->toArray($is);
}
public function share($url = '')
{
if (empty($this->app_id)) {
$this->getConfig();
}
if (empty($this->app_id)) {
throw new DtaException('请检查app_id参数');
}
$accessToken = $this->getAccessToken();
if (!isset($accessToken['access_token'])) {
throw new DtaException("获取access_token错误," . $accessToken['errmsg']);
}
$res = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$accessToken['access_token']}&type=jsapi")
->toArray();
if (!empty($res['errcode'])) {
$accessToken = $this->getAccessToken();
if (!isset($accessToken['access_token'])) {
throw new DtaException("获取access_token错误," . $accessToken['errmsg']);
}
$res = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$accessToken['access_token']}&type=jsapi")
->toArray();
if (!empty($res['errcode'])) {
throw new DtaException('accessToken已过期');
}
}
if (empty($url)) {
$protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] === 443) ? "https://" : "http://";
$url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
}
$timestamp = time();
$nonceStr = $this->createNonceStr();
$jsapiTicket = $res['ticket'];
$string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url";
return [
"appId" => $this->app_id,
"nonceStr" => $nonceStr,
"timestamp" => $timestamp,
"url" => $url,
"signature" => sha1($string),
"rawString" => $string
];
}
private function createNonceStr($length = 16): string
{
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= $chars[random_int(0, strlen($chars) - 1)];
}
return $str;
}
public function qrCode(array $data)
{
$accessToken = $this->getAccessToken();
return HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={$accessToken['access_token']}")
->data($data)
->post()
->toArray();
}
public function messageTemplateSend(array $data = [])
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->data($data)
->toArray();
}
public function setIndustry(string $access_token, array $data = [])
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->data($data)
->toArray();
}
public function shortUrl(string $long_url)
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/shorturl?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->data([
'action' => 'long2short',
'long_url' => $long_url
])
->toArray();
}
public function fiNihPageSet(array $data = [])
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/bizwifi/finishpage/set?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->post()
->data($data)
->toArray();
}
public function menuGet()
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->toArray();
}
public function menuAddConditional(array $data = [])
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/menu/addconditional?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->post()
->data($data)
->toArray();
}
public function menuDelConditional(array $data = [])
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/menu/delconditional?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->post()
->data($data)
->toArray();
}
public function menuTryMatch(array $data = [])
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/menu/trymatch?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->post()
->data($data)
->toArray();
}
public function menuDelete()
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->toArray();
}
public function getCurrentSelfmenuInfo()
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->toArray();
}
public function menuCreate(array $data = [])
{
$accessToken = $this->getAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token={$accessToken['access_token']}";
return HttpService::instance()
->url($url)
->post()
->data($data)
->toArray();
}
private function getAccessToken()
{
if (empty($this->cache) || empty($this->app_id) || empty($this->app_secret)) {
$this->getConfig();
}
if (empty($this->cache)) {
throw new DtaException('请检查cache参数');
}
if (empty($this->app_id)) {
throw new DtaException('请检查app_id参数');
}
if (empty($this->app_secret)) {
throw new DtaException('请检查app_secret参数');
}
$this->grant_type = "client_credential";
if ($this->cache === "file") {
$file = "{$this->app->getRootPath()}runtime/{$this->app_id}_access_token.json";
$accessToken = file_exists($file) ? json_decode(file_get_contents($file), true) : [];
if (empty($accessToken) || !is_array($accessToken)) {
$accessToken = [
'access_token' => '',
'expires_in' => '',
'expires_time' => '',
];
}
if (empty($accessToken['expires_time'])) {
$accessToken_res = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
->toArray();
$accessToken_res['expires_time'] = time() + 6000;
file_put_contents($file, json_encode($accessToken_res, JSON_UNESCAPED_UNICODE));
$accessToken = $accessToken_res;
} else if (!isset($accessToken['access_token'])) {
$accessToken_res = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
->toArray();
$accessToken_res['expires_time'] = time() + 6000;
file_put_contents($file, json_encode($accessToken_res, JSON_UNESCAPED_UNICODE));
$accessToken = $accessToken_res;
} else if ($accessToken['expires_time'] <= time()) {
$accessToken_res = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
->toArray();
$accessToken_res['expires_time'] = time() + 6000;
file_put_contents($file, json_encode($accessToken_res, JSON_UNESCAPED_UNICODE));
$accessToken = $accessToken_res;
}
if (isset($accessToken['access_token'])) {
$judge = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token={$accessToken['access_token']}")
->toArray();
if (!isset($judge['ip_list'])) {
$accessToken_res = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
->toArray();
$accessToken_res['expires_time'] = time() + 6000;
file_put_contents($file, json_encode($accessToken_res, JSON_UNESCAPED_UNICODE));
$accessToken = $accessToken_res;
}
} else {
$accessToken_res = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
->toArray();
$accessToken_res['expires_time'] = time() + 6000;
file_put_contents($file, json_encode($accessToken_res, JSON_UNESCAPED_UNICODE));
$accessToken = $accessToken_res;
}
return $accessToken;
}
if ($this->cache === "mysql") {
$access_token = [];
$file = "{$this->app_id}_access_token";
$cache_mysql_value = dtacache($file);
if (!empty($cache_mysql_value)) {
$access_token['access_token'] = $cache_mysql_value;
} else {
$accessToken_res = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
->toArray();
dtacache($file, $accessToken_res['access_token'], 6000);
$access_token['access_token'] = $accessToken_res['access_token'];
}
$judge = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token={$access_token['access_token']}")
->toArray();
if (!isset($judge['ip_list'])) {
$accessToken_res = HttpService::instance()
->url("https://api.weixin.qq.com/cgi-bin/token?grant_type={$this->grant_type}&appid={$this->app_id}&secret={$this->app_secret}")
->toArray();
dtacache($file, $accessToken_res['access_token'], 6000);
$access_token['access_token'] = $accessToken_res['access_token'];
}
return $access_token;
}
throw new DtaException("驱动方式错误");
}
public function payUnfIedOrder(array $array)
{
$array['appid'] = $this->app_id;
$array['mch_id'] = $this->mch_id;
$array['nonce_str'] = Randoms::generate(32, 3);
$array['sign_type'] = 'HMAC-SHA256';
$array['sign'] = $this->paySign($array);
$res = $this->postXmlCurl(Xmls::toXml($array));
return Xmls::toArray($res);
}
public function h5Pay(string $prepay_id)
{
$array['appId'] = $this->app_id;
$array['timeStamp'] = time();
$array['nonceStr'] = Randoms::generate(32, 3);
$array['package'] = "prepay_id={$prepay_id}";
$array['signType'] = 'HMAC-SHA256';
$array['paySign'] = $this->paySign($array);
return $array;
}
private function paySign(array $array, bool $hmacsha256 = true): string
{
ksort($array);
$stringA = Urls::toParams($array);
$stringSignTemp = "{$stringA}&key=" . $this->mch_key;
if ($hmacsha256) {
$str = hash_hmac("sha256", $stringSignTemp, $this->mch_key);
} else {
$str = md5($stringSignTemp);
}
return strtoupper($str);
}
private function postXmlCurl($xml)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_URL, "https://api.mch.weixin.qq.com/pay/unifiedorder");
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false) curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
$data = curl_exec($ch);
if ($data) {
curl_close($ch);
return $data;
}
$error = curl_errno($ch);
curl_close($ch);
return "curl error, error code " . $error;
}
}