AlipayMiniForm.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <?php
  2. namespace api\modules\v1\forms\user;
  3. use common\enums\StatusEnum;
  4. use common\events\user\UserLoginEvent;
  5. use common\events\user\UserRegisterEvent;
  6. use common\helpers\ArrayHelper;
  7. use common\helpers\sms\Sms;
  8. use common\helpers\StringHelper;
  9. use common\models\base\User;
  10. use common\models\user\UserInfo;
  11. use common\traits\ErrorTrait;
  12. use yii\base\Model;
  13. use Alipay\EasySDK\Kernel\Factory;
  14. use Alipay\EasySDK\Kernel\Util\ResponseChecker;
  15. use Alipay\EasySDK\Kernel\Config;
  16. use Exception;
  17. use Yii;
  18. class AlipayMiniForm extends Model
  19. {
  20. use ErrorTrait;
  21. public $mall_id;
  22. public $config=[];
  23. public function __construct($mall_id)
  24. {
  25. $this->mall_id = $mall_id;
  26. $this->config = \Yii::$app->services->setting->mall->get($mall_id, 'alipay_mini');
  27. $option = $this->getOptions();
  28. Factory::setOptions($option);
  29. }
  30. private function getOptions()
  31. {
  32. $options = new Config();
  33. $options->protocol = 'https';
  34. $options->gatewayHost = 'openapi.alipay.com';
  35. $options->signType = 'RSA2';
  36. $options->appId = $this->config['app_id'];
  37. // 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
  38. $options->merchantPrivateKey = $this->config['merchantPrivateKey'];
  39. if ($this->config['is_use_cert']) {
  40. $options->alipayCertPath = dirname(Yii::$app->basePath).'/'.$this->config['alipayCertPath'];
  41. $options->alipayRootCertPath = dirname(Yii::$app->basePath).'/'.$this->config['alipayRootCertPath'];
  42. $options->merchantCertPath = dirname(Yii::$app->basePath).'/'.$this->config['merchantCertPath'];
  43. } else {
  44. //注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
  45. $options->alipayPublicKey = file_get_contents(dirname(Yii::$app->basePath).'/'.$this->config['alipayCertPath']);
  46. }
  47. //可设置异步通知接收服务地址(可选)
  48. $options->notifyUrl = '';
  49. //可设置AES密钥,调用AES加解密相关接口时需要(可选)
  50. $options->encryptKey = $this->config['encryptKey'];
  51. return $options;
  52. }
  53. /**
  54. *
  55. * @param $code
  56. * @throws \Exception
  57. * @return array|bool
  58. */
  59. public function authorize($code, $parent_id){
  60. $option = $this->getOptions();
  61. Yii::error('authorize option:'.json_encode($option));
  62. Factory::setOptions($option);
  63. try {
  64. $result = Factory::base()->oauth()->getToken($code);
  65. Yii::error('authorize result:'.json_encode($result));
  66. $responseChecker = new ResponseChecker();
  67. //3. 处理响应或异常
  68. if (!$responseChecker->success($result)) {
  69. $this->setError($result->msg.",".$result->subMsg);
  70. return false;
  71. }
  72. $return_data['openid'] = $result->userId; //支付宝的会员ID
  73. $return_data['access_token'] = '';
  74. //支付宝用户id
  75. $user_info = $this->checkIsAuthorized($result->userId);
  76. if(!$user_info){
  77. if(!$result->accessToken) throw new Exception('授权失败');
  78. $user_info = $this->getUserInfoShare($result->accessToken, $parent_id);
  79. if (empty($user_info)) {
  80. $this->setError($this->getError());
  81. return false;
  82. }
  83. }
  84. if($user_info){
  85. if($user_info['user_id'] > 0){
  86. //支付宝的会员ID已存在用户表中
  87. $user = \common\models\user\User::findOne($user_info['user_id']);
  88. \Yii::$app->user->login($user);
  89. $res = \Yii::$app->services->apiAccessToken->getAccessToken($user, 'api');
  90. $return_data['access_token'] = $res['access_token'];
  91. // 触发登录成功事件,事务提交完成后触发
  92. $event = new UserLoginEvent($user['mall_id']);
  93. \Yii::$app->services->events->dispatch($event, ['mall_id' => $user['mall_id'], 'nickname' => $user['nickname'], 'user_id' => $user['id'], 'platform' =>$user['platform'], 'ip' => ArrayHelper::get_client_ip()], $event->listeners);
  94. }
  95. }
  96. return $return_data;
  97. } catch (Exception $e) {
  98. $this->setError('请求失败:'.$e->getMessage());
  99. return false;
  100. }
  101. }
  102. /**
  103. * 通过token获取支付宝会员信息
  104. * @param $token
  105. * @return array|bool|mixed|void
  106. * @throws \yii\base\Exception
  107. * @throws \yii\db\Exception
  108. */
  109. public function getUserInfoShare($token, $parent_id)
  110. {
  111. //设置系统参数(OpenAPI中非biz_content里的参数)
  112. $textParams = array(
  113. 'auth_token' => $token
  114. );
  115. //设置业务参数(OpenAPI中biz_content里的参数)
  116. $extendParams =[];
  117. $bizParams = [];
  118. $result = Factory::util()->generic()->execute("alipay.user.info.share",$textParams, $bizParams);
  119. $responseChecker = new ResponseChecker();
  120. //3. 处理响应或异常
  121. if (!$responseChecker->success($result)) {
  122. $this->setError($result->msg.",".$result->subMsg);
  123. return false;
  124. }
  125. Yii::error('httpBody:'.$result->httpBody);
  126. $bodyJson = json_decode($result->httpBody, true);
  127. $user_info = [];
  128. if($bodyJson['alipay_user_info_share_response']){
  129. //avatar nick_name user_id 暂时没看到mobile
  130. $platform_data = $bodyJson['alipay_user_info_share_response'];
  131. $model = UserInfo::setData([
  132. 'user_id' => 0,
  133. 'unionid' => '',
  134. 'openid' => $bodyJson['alipay_user_info_share_response']['user_id'] ?? 0,
  135. 'platform' => \common\models\user\User::PLATFORM_MP_ALI,
  136. 'mall_id' => $this->mall_id,
  137. 'platform_data' => $bodyJson['alipay_user_info_share_response']
  138. ]);
  139. if(!$model) throw new Exception(UserInfo::getStaticError());
  140. $user_info_id = $model->id;
  141. //后台设置支付宝小程序是否有授权手机号的能力
  142. $can_auth_phone = $this->config['can_auth_phone'] ?? false;
  143. if(!$can_auth_phone){
  144. //直接注册用户
  145. $password = StringHelper::random(6,true);
  146. $params['password'] = \Yii::$app->getSecurity()->generatePasswordHash($password);
  147. $params["username"] = $platform_data['nick_name'] ? (string)$platform_data['nick_name'] : '';
  148. $params["openid"] = $platform_data["user_id"]; //支付宝open_id
  149. $params["unionid"] = '';
  150. $params["nickname"] = $platform_data['nick_name'] ? (string)$platform_data['nick_name'] : '';
  151. $params["headimgurl"] = $platform_data["avatar"] ?? '';
  152. $params["avatar_url"] = $platform_data["avatar"] ?? '';
  153. $params['platform'] = \common\models\user\User::PLATFORM_MP_ALI;
  154. $params['last_login_at'] = time();
  155. $params['mall_id'] = $this->mall_id;
  156. $params['parent_id'] = $parent_id ?? 0; // 直推人
  157. $user = \common\models\user\User::setData($params);
  158. if ($user == false) throw new Exception(User::getStaticError());
  159. // 小程序授权手机触发注册事件
  160. $event = new UserRegisterEvent($params['mall_id']);
  161. $data = ['user_id' => $user->id, 'platform' => $user->platform];
  162. \Yii::$app->services->events->dispatch($event, $data, $event->listeners);
  163. if(!UserInfo::updateAll(['user_id' => $user->id ],['id' => $user_info_id])) throw new Exception('update error');
  164. // $model->user_id = $user->id;
  165. // $res = $model->save();
  166. // if ($res == false) throw new Exception(UserInfo::getStaticError());
  167. }
  168. $user_info = UserInfo::getOne([['id' => $model->id]],true);
  169. }
  170. return $user_info;
  171. }
  172. /**
  173. * @desc:授权
  174. * @Author: hua
  175. * @Date: 2021/10/25
  176. * @Time: 11:21
  177. * @Copyright: copyright (c) 2021 广东七件事集团
  178. * @param $params
  179. * @return array|false
  180. */
  181. public function checkIsAuthorized($alipay_user_id,$is_array = true)
  182. {
  183. $where = [['mall_id' => $this->mall_id], ['status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]]];
  184. $where[] = ['openid' => $alipay_user_id];
  185. $where[] = ['platform' => \common\models\user\User::PLATFORM_MP_ALI];
  186. $user_info = UserInfo::getOne($where,$is_array);
  187. return $user_info;
  188. }
  189. /**
  190. * 支付宝encryptedData 解密
  191. * @param $encryptedData
  192. * @return false|void
  193. * @throws
  194. */
  195. public function decryptData($encryptedData)
  196. {
  197. Yii::error('$encryptedData:'.$encryptedData);
  198. $result = Factory::util()->aes()->decrypt($encryptedData);
  199. Yii::error('decryptData:'.$result);
  200. $result = json_decode($result);
  201. $responseChecker = new ResponseChecker();
  202. //3. 处理响应或异常
  203. if (!$responseChecker->success($result)) {
  204. $this->setError($result->msg,",".$result->subMsg);
  205. return false;
  206. }
  207. if(!$result->mobile){
  208. $this->setError('支付宝小程序权限:获取不到会员手机号');
  209. return false;
  210. }
  211. $mobile = $result->mobile;
  212. return $mobile;
  213. }
  214. public function authorizedMobilePhone($params)
  215. {
  216. try{
  217. $mobile = $this->decryptData($params['encryptData']);
  218. if(!$mobile){
  219. throw new Exception($this->getError());
  220. }
  221. $where = [['mobile' => $mobile], ['mall_id' => $this->mall_id], ['status' => [StatusEnum::DISABLED, StatusEnum::ENABLED]]];
  222. $user = \common\models\user\User::getOne($where);
  223. $user_info = $this->checkIsAuthorized($params['openid'],false);
  224. $platform_data = json_decode($user_info->platform_data, true);
  225. if (!empty($user)) {
  226. $res = UserInfo::findOne(['mall_id' => $this->mall_id, 'user_id' => $user->id,'platform'=>$user_info->platform,'status'=>StatusEnum::ENABLED]);
  227. if (!empty($res)) throw new \yii\db\Exception('该手机号码已经被注册');
  228. $user_info->user_id = $user->id;
  229. $res = $user_info->save();
  230. if ($res == false) throw new Exception(UserInfo::getStaticError());
  231. $user->nickname = $platform_data['nick_name'] ? (string)$platform_data['nick_name'] : '';
  232. $user->avatar_url = $platform_data['avatar'] ?? '';
  233. $user->platform = $user_info->platform;
  234. $res = $user->save();
  235. if ($res == false) throw new Exception(\common\models\user\User::getStaticError());
  236. }else{
  237. if($user_info && $user_info->user_id > 0){
  238. //存在用户,直接修改用户的手机号
  239. $user = \common\models\user\User::findOne(['id' => $user_info->user_id]);
  240. $user->mobile = $mobile;
  241. $user->nickname = $platform_data['nick_name'] ? (string)$platform_data['nick_name'] : '';
  242. $user->avatar_url = $platform_data['avatar'] ?? '';
  243. $user->platform = $user_info->platform;
  244. $res = $user->save();
  245. if ($res == false) throw new Exception('手机号修改失败');
  246. }else{
  247. $password = substr($mobile,-6,6);
  248. $params['password'] = \Yii::$app->getSecurity()->generatePasswordHash($password);
  249. $params["username"] = $mobile;
  250. $params["openid"] = $platform_data["user_id"];
  251. $params["unionid"] = $platform_data["unionid"] ?? '';
  252. $params["nickname"] = $platform_data['nick_name'] ? (string)$platform_data['nick_name'] : '';
  253. $params["headimgurl"] = $platform_data["avatar"] ?? '';
  254. $params["avatar_url"] = $platform_data["avatar"] ?? '';
  255. $params['platform'] = $user_info->platform;
  256. $params['last_login_at'] = time();
  257. $params['mall_id'] = $this->mall_id;
  258. $params['mobile'] = $mobile;
  259. $user = \common\models\user\User::setData($params);
  260. if ($user == false) throw new \yii\db\Exception(User::getStaticError());
  261. $user_info->user_id = $user->id;
  262. $res = $user_info->save();
  263. if ($res == false) throw new Exception(UserInfo::getStaticError());
  264. }
  265. // 小程序授权手机触发注册事件
  266. $event = new UserRegisterEvent($this->mall_id);
  267. $data = ['user_id' => $user->id, 'platform' => $user->platform];
  268. \Yii::$app->services->events->dispatch($event, $data, $event->listeners);
  269. }
  270. \Yii::$app->user->login($user);
  271. // 触发登录成功事件,事务提交完成后触发
  272. $event = new UserLoginEvent($user->mall_id);
  273. \Yii::$app->services->events->dispatch($event, ['mall_id' => $user->mall_id, 'nickname' => $user->nickname, 'user_id' => $user->id, 'platform' => $user->platform, 'ip' => ArrayHelper::get_client_ip()], $event->listeners);
  274. return \Yii::$app->services->apiAccessToken->getAccessToken($user, 'api');
  275. }catch(Exception $e){
  276. $this->setError($e->getMessage());
  277. Yii::error('authorizedMobilePhone error:'.$e->getMessage());
  278. return false;
  279. }
  280. }
  281. /**
  282. * 获取小程序二维码链接
  283. * @param $url
  284. * @param $query
  285. * @param $desc
  286. * @return bool|string
  287. * @throws Exception
  288. */
  289. public function qrcode($url,$query,$desc)
  290. {
  291. $desc = $desc ?: '海报分享';
  292. $result = Factory::base()->qrcode()->create($url,$query,$desc);
  293. $responseChecker = new ResponseChecker();
  294. //3. 处理响应或异常
  295. if (!$responseChecker->success($result)) {
  296. \Yii::error($result->msg.",".$result->subMsg);
  297. $this->setError($result->msg.",".$result->subMsg);
  298. return false;
  299. }
  300. return $result->qrCodeUrl;
  301. }
  302. public function bindMobileBySms($openid,$mobile,$captcha)
  303. {
  304. try{
  305. //手机验证码验证
  306. if (YII_ENV == 'prod') {
  307. //短信验证码
  308. if (!Sms::checkValidateCode($mobile, $captcha,Sms::SMS_TYPE_MOBILE_BIND)) throw new \Exception('验证码不正确');
  309. }
  310. //查询user_info
  311. $user_info = $this->checkIsAuthorized($openid,false);
  312. if(!$user_info) throw new Exception('查询不到授权信息');
  313. $where = [['mobile' => $mobile], ['mall_id' => $this->mall_id], ['status' => [StatusEnum::DISABLED, StatusEnum::ENABLED]]];
  314. $user = \common\models\user\User::getOne($where);
  315. $user_info = $this->checkIsAuthorized($openid,false);
  316. $platform_data = json_decode($user_info->platform_data, true);
  317. if (!empty($user)) {
  318. $res = UserInfo::findOne(['mall_id' => $this->mall_id, 'user_id' => $user->id, 'platform' => $user_info->platform, 'status' => StatusEnum::ENABLED]);
  319. if (!empty($res)) throw new \Exception('该手机号码已经被注册');
  320. $user_info->user_id = $user->id;
  321. $res = $user_info->save();
  322. if ($res == false) throw new Exception(UserInfo::getStaticError());
  323. $user->nickname = $platform_data['nick_name'] ? (string)$platform_data['nick_name'] : '';
  324. $user->avatar_url = $platform_data['avatar'] ?? '';
  325. $user->platform = $user_info->platform;
  326. $res = $user->save();
  327. if ($res == false) throw new Exception(\common\models\user\User::getStaticError());
  328. }else{
  329. if($user_info->user_id > 0){
  330. //查询对应用户信息
  331. $user = User::find()->where(['id' => $user_info->user_id,'mall_id' => $this->mall_id,'status' => StatusEnum::ENABLED])->one();
  332. if($user->mobile) throw new Exception('该用户已绑定手机号');
  333. $user->mobile = $mobile;
  334. $user->platform = $user_info->platform;
  335. $res = $user->save();
  336. if (!$res) throw new Exception('手机号修改失败');
  337. }else{
  338. $platform_data = json_decode($user_info->platform_data, true);
  339. $password = substr($mobile,-6,6);
  340. $params['password'] = \Yii::$app->getSecurity()->generatePasswordHash($password);
  341. $params["username"] = $mobile;
  342. $params["openid"] = $platform_data["user_id"];
  343. $params["unionid"] = $platform_data["unionid"] ?? '';
  344. $params["nickname"] = $platform_data['nick_name'] ? (string)$platform_data['nick_name'] : '';
  345. $params["headimgurl"] = $platform_data["avatar"] ?? '';
  346. $params["avatar_url"] = $platform_data["avatar"] ?? '';
  347. $params['platform'] = $user_info->platform;
  348. $params['last_login_at'] = time();
  349. $params['mall_id'] = $this->mall_id;
  350. $params['mobile'] = $mobile;
  351. $user = \common\models\user\User::setData($params);
  352. if (!$user) throw new \Exception(\common\models\base\User::getStaticError());
  353. $user_info->user_id = $user->id;
  354. $res = $user_info->save();
  355. if (!$res) throw new Exception(UserInfo::getStaticError());
  356. // 小程序授权手机触发注册事件
  357. $event = new UserRegisterEvent($this->mall_id);
  358. $data = ['user_id' => $user->id, 'platform' => $user->platform];
  359. \Yii::$app->services->events->dispatch($event, $data, $event->listeners);
  360. }
  361. }
  362. //支付宝的会员ID已存在用户表中
  363. $user = \common\models\user\User::findOne($user_info->user_id);
  364. \Yii::$app->user->login($user);
  365. $res = \Yii::$app->services->apiAccessToken->getAccessToken($user, 'api');
  366. $return_data['access_token'] = $res['access_token'];
  367. // 触发登录成功事件,事务提交完成后触发
  368. $event = new UserLoginEvent($user['mall_id']);
  369. \Yii::$app->services->events->dispatch($event, ['mall_id' => $user['mall_id'], 'nickname' => $user['nickname'], 'user_id' => $user['id'], 'platform' =>$user['platform'], 'ip' => ArrayHelper::get_client_ip()], $event->listeners);
  370. return $return_data;
  371. }catch (Exception $e){
  372. $this->setError($e->getMessage());
  373. return false;
  374. }
  375. }
  376. }