UserUtil.ets 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. import { cryptoFramework } from "@kit.CryptoArchitectureKit";
  2. import { LogUtil, PreferencesUtil, StrUtil, ToastUtil } from "@pura/harmony-utils";
  3. import { CommonConstants } from "../constants/CommonConstants";
  4. import { http } from "@kit.NetworkKit";
  5. import { bundleManager } from "@kit.AbilityKit";
  6. import { BusinessError } from '@kit.BasicServicesKit';
  7. import { hilog } from "@kit.PerformanceAnalysisKit";
  8. // 用户相关接口定义
  9. export interface WechatUserInfo {
  10. openid?: string;
  11. nickname?: string;
  12. sex?: number;
  13. language?: string;
  14. city?: string;
  15. province?: string;
  16. country?: string;
  17. headimgurl?: string;
  18. privilege?: string[];
  19. unionid?: string;
  20. }
  21. export interface Subscription {
  22. plan_id: string;
  23. plan_name: string;
  24. plan_description: string;
  25. price: string;
  26. start_date: string;
  27. end_date: string;
  28. status: string;
  29. auto_renew: string;
  30. features: string[];
  31. }
  32. export interface SubscriptionInfo {
  33. has_active_subscription: boolean;
  34. subscriptions: Subscription[];
  35. }
  36. export interface responseData {
  37. token_info?: object;
  38. user_info: WechatUserInfo;
  39. }
  40. export interface WechatUserInfoData {
  41. token_info?: object;
  42. user_info: WechatUserInfo;
  43. system_info?: SystemInfo;
  44. subscription_info?: SubscriptionInfo;
  45. }
  46. export interface WechatUserInfoApiResponse {
  47. code: number;
  48. msg: string;
  49. data: WechatUserInfoData;
  50. }
  51. export interface HuaweiUserInfo {
  52. openid: string;
  53. unionid?: string;
  54. nickname: string;
  55. headimgurl: string;
  56. }
  57. export interface SystemInfo {
  58. user_id: number | string;
  59. token: string;
  60. }
  61. export interface HuaweiUserInfoApiData {
  62. user_info: HuaweiUserInfo;
  63. system_info?: SystemInfo;
  64. subscription_info?: SubscriptionInfo;
  65. }
  66. export interface HuaweiUserInfoApiResponse {
  67. code: number;
  68. msg: string;
  69. data: HuaweiUserInfoApiData;
  70. }
  71. export interface VipFeature {
  72. icon: Resource;
  73. title: string;
  74. description: string;
  75. isVipOnly: boolean;
  76. }
  77. export interface VipPlanApi {
  78. id: string;
  79. name: string;
  80. description: string;
  81. price: string;
  82. duration: string;
  83. features: string;
  84. sort_order: string;
  85. status: string;
  86. created_at: string;
  87. updated_at: string;
  88. }
  89. export interface VipPlanListApiData {
  90. plans: VipPlanApi[];
  91. }
  92. export interface VipPlanListApiResponse {
  93. code: number;
  94. msg: string;
  95. data: VipPlanListApiData;
  96. }
  97. export interface WeChatPrepayId {
  98. prepay_id: string;
  99. }
  100. export interface UserInfoApiData {
  101. user_info: WechatUserInfo;
  102. system_info: SystemInfo;
  103. subscription_info: SubscriptionInfo;
  104. }
  105. export interface UserInfoApiResponse {
  106. code: number;
  107. msg: string;
  108. data: UserInfoApiData;
  109. }
  110. export default class UserUtil {
  111. // 微信是否安装
  112. static async isHasWX(): Promise<boolean> {
  113. try {
  114. let link = 'weixin://';
  115. let data = bundleManager.canOpenLink(link);
  116. if (data) {
  117. return true;
  118. } else {
  119. ToastUtil.showToast('微信未安装');
  120. return false;
  121. }
  122. } catch (err) {
  123. let message = (err as BusinessError).message;
  124. console.error('canOpenLink failed: %{public}s', message);
  125. return false;
  126. }
  127. }
  128. // 创建微信订单
  129. static async createWeChatOrder(app_id: string, mch_id: string, description: string, amount: number, out_trade_no_pre: string, uid: number, plan_id: string): Promise<string> {
  130. let requestUrl = CommonConstants.WX_PAY_API
  131. + '?app_id=' + encodeURIComponent(app_id.trim())
  132. + '&mch_id=' + encodeURIComponent(mch_id.trim())
  133. + '&description=' + encodeURIComponent(description.trim())
  134. + '&amount=' + encodeURIComponent(amount)
  135. + '&uid=' + encodeURIComponent(uid)
  136. + '&plan_id=' + encodeURIComponent(plan_id)
  137. + '&out_trade_no_pre=' + encodeURIComponent(out_trade_no_pre);
  138. const httpRequest: http.HttpRequest = http.createHttp();
  139. const options: http.HttpRequestOptions = {
  140. method: http.RequestMethod.GET,
  141. readTimeout: 6000,
  142. connectTimeout: 6000,
  143. };
  144. try {
  145. const response: http.HttpResponse = await httpRequest.request(requestUrl, options);
  146. if (response.responseCode === 200) {
  147. let responseData: WeChatPrepayId;
  148. if (typeof response.result === 'string') {
  149. responseData = JSON.parse(response.result) as WeChatPrepayId;
  150. } else if (response.result instanceof Object) {
  151. responseData = response.result as WeChatPrepayId;
  152. } else {
  153. throw new Error('Unexpected response format');
  154. }
  155. const prepayId: string = responseData.prepay_id;
  156. return prepayId;
  157. } else {
  158. ToastUtil.showToast('微信下单失败');
  159. return '';
  160. }
  161. } catch (error) {
  162. ToastUtil.showToast('微信下单异常');
  163. return '';
  164. }
  165. }
  166. // 构造签名串
  167. static constructSignatureString(appId: string, timestamp: string, nonceStr: string, prepayId: string): string {
  168. return `${appId}\n${timestamp}\n${nonceStr}\n${prepayId}\n`;
  169. }
  170. // 微信支付签名
  171. static async rsaSign2048( message: string): Promise<Uint8Array> {
  172. try {
  173. let keyGen = cryptoFramework.createAsyKeyGenerator("RSA2048")
  174. let key = keyGen.convertPemKeySync(null, CommonConstants.WX_PAY_RSA_PRIVATE_KEY)
  175. let sign = cryptoFramework.createSign('RSA2048|PKCS1|SHA256');
  176. let msgBlob: cryptoFramework.DataBlob = {data: StrUtil.strToUint8Array(message)}
  177. sign.initSync(key.priKey)
  178. let signature = await sign.sign(msgBlob)
  179. return signature.data
  180. } catch (error) {
  181. console.error(error, `onecold error code: ${error.code}`)
  182. return new Uint8Array()
  183. }
  184. }
  185. // 通过code换取用户信息
  186. static async getUserInfoByCode(code: string): Promise<UserInfoApiData |WechatUserInfoData| null> {
  187. try {
  188. const url = `https://pay.ss5.xyz/wechat/user_info?code=${encodeURIComponent(code)}`;
  189. const httpRequest = http.createHttp();
  190. const options: http.HttpRequestOptions = {
  191. method: http.RequestMethod.GET,
  192. readTimeout: 3000,
  193. connectTimeout: 3000,
  194. };
  195. const response: http.HttpResponse = await httpRequest.request(url, options);
  196. if (response.responseCode === 200) {
  197. let res = response.result as string;
  198. LogUtil.debug("getUserInfoByCode res =" + res)
  199. let json: WechatUserInfoApiResponse = JSON.parse(res) as WechatUserInfoApiResponse;
  200. if (json.code === 0 && json.data && json.data.user_info) {
  201. const userId = json.data.system_info?.user_id ? Number(json.data.system_info?.user_id) : 0;
  202. hilog.info(0x0000, 'UserUtil', `getUserInfoByCode: userId set to ${userId}`);
  203. const userName = json.data.user_info.nickname || '微信用户';
  204. const userAvatarUrl = json.data.user_info.headimgurl && json.data.user_info.headimgurl.length > 0 ? json.data.user_info.headimgurl : '';
  205. // 解析订阅信息
  206. const subInfo = json.data.subscription_info;
  207. let hasActiveSubscription = false;
  208. let subscriptionName = '';
  209. let subscriptionEndDate = '';
  210. if (subInfo && subInfo.has_active_subscription && subInfo.subscriptions && subInfo.subscriptions.length > 0) {
  211. hasActiveSubscription = true;
  212. subscriptionName = subInfo.subscriptions[0].plan_name;
  213. subscriptionEndDate = subInfo.subscriptions[0].end_date;
  214. }
  215. // 保存登录状态和用户信息
  216. PreferencesUtil.putSync('isLogin', true);
  217. PreferencesUtil.putSync('userId', userId);
  218. PreferencesUtil.putSync('userName', userName);
  219. PreferencesUtil.putSync('userAvatarUrl', userAvatarUrl);
  220. PreferencesUtil.putSync('subscriptionName', subscriptionName);
  221. PreferencesUtil.putSync('subscriptionEndDate', subscriptionEndDate);
  222. PreferencesUtil.putSync('hasActiveSubscription', hasActiveSubscription);
  223. PreferencesUtil.putSync('userToken', json.data.system_info?.token || '');
  224. ToastUtil.showToast('微信登录成功');
  225. return json.data;
  226. } else {
  227. ToastUtil.showToast('用户信息解析失败');
  228. return null;
  229. }
  230. } else {
  231. ToastUtil.showToast('获取用户信息失败');
  232. return null;
  233. }
  234. } catch (e) {
  235. ToastUtil.showToast('登录失败' + (e && e.message ? (': ' + e.message) : ''));
  236. return null;
  237. }
  238. }
  239. // 通过华为账号信息换取用户信息和会员状态
  240. static async getUserInfoByHuawei(openID: string, unionID: string, nickname: string, avatarUri: string): Promise<HuaweiUserInfoApiData | null> {
  241. try {
  242. const url = `https://pay.ss5.xyz/user/huawei?openID=${encodeURIComponent(openID)}&unionID=${encodeURIComponent(unionID)}&nickname=${encodeURIComponent(nickname)}&avatarUri=${encodeURIComponent(avatarUri)}`;
  243. const httpRequest = http.createHttp();
  244. const options: http.HttpRequestOptions = {
  245. method: http.RequestMethod.GET,
  246. readTimeout: 3000,
  247. connectTimeout: 3000,
  248. };
  249. const response: http.HttpResponse = await httpRequest.request(url, options);
  250. if (response.responseCode === 200) {
  251. let res = response.result as string;
  252. LogUtil.debug("getUserInfoByHuawei res =" + res)
  253. let json: HuaweiUserInfoApiResponse = JSON.parse(res) as HuaweiUserInfoApiResponse;
  254. if (json.code === 0 && json.data && json.data.user_info) {
  255. const userId = json.data.system_info?.user_id ? Number(json.data.system_info?.user_id) : 0;
  256. hilog.info(0x0000, 'UserUtil', `getUserInfoByHuawei: userId set to ${userId}`);
  257. const userName = json.data.user_info.nickname || '华为用户';
  258. const userAvatarUrl = json.data.user_info.headimgurl || '';
  259. // 处理会员信息
  260. const subInfo = json.data.subscription_info;
  261. let hasActiveSubscription = false;
  262. let subscriptionName = '';
  263. let subscriptionEndDate = '';
  264. if (subInfo && subInfo.has_active_subscription && subInfo.subscriptions && subInfo.subscriptions.length > 0) {
  265. hasActiveSubscription = true;
  266. subscriptionName = subInfo.subscriptions[0].plan_name;
  267. subscriptionEndDate = subInfo.subscriptions[0].end_date;
  268. }
  269. // 保存登录状态
  270. PreferencesUtil.putSync('isLogin', true);
  271. PreferencesUtil.putSync('userId', userId);
  272. PreferencesUtil.putSync('userName', userName);
  273. PreferencesUtil.putSync('userAvatarUrl', userAvatarUrl);
  274. PreferencesUtil.putSync('subscriptionName', subscriptionName);
  275. PreferencesUtil.putSync('subscriptionEndDate', subscriptionEndDate);
  276. PreferencesUtil.putSync('hasActiveSubscription', hasActiveSubscription);
  277. PreferencesUtil.putSync('userToken', json.data.system_info?.token || '');
  278. ToastUtil.showToast('华为登录成功');
  279. return json.data;
  280. } else {
  281. ToastUtil.showToast('用户信息解析失败');
  282. return null;
  283. }
  284. } else {
  285. ToastUtil.showToast('获取用户信息失败');
  286. return null;
  287. }
  288. } catch (e) {
  289. ToastUtil.showToast('登录失败' + (e && e.message ? (': ' + e.message) : ''));
  290. return null;
  291. }
  292. }
  293. // 刷新用户信息
  294. static async fetchUserInfo(token: string): Promise<UserInfoApiData | null> {
  295. try {
  296. const url = `https://pay.ss5.xyz/user/info?token=${encodeURIComponent(token)}`;
  297. const httpRequest = http.createHttp();
  298. const options: http.HttpRequestOptions = {
  299. method: http.RequestMethod.GET,
  300. readTimeout: 3000,
  301. connectTimeout: 3000,
  302. };
  303. const response: http.HttpResponse = await httpRequest.request(url, options);
  304. if (response.responseCode === 200) {
  305. let res = response.result as string;
  306. LogUtil.debug("fetchUserInfo res =" + res);
  307. let json: UserInfoApiResponse = JSON.parse(res) as UserInfoApiResponse;
  308. if (json.code === 0 && json.data) {
  309. const userInfo = json.data.user_info;
  310. const systemInfo = json.data.system_info;
  311. const subInfo = json.data.subscription_info;
  312. let hasActiveSubscription = false;
  313. let subscriptionName = '';
  314. let subscriptionEndDate = '';
  315. if (subInfo && subInfo.has_active_subscription && subInfo.subscriptions && subInfo.subscriptions.length > 0) {
  316. hasActiveSubscription = true;
  317. subscriptionName = subInfo.subscriptions[0].plan_name;
  318. subscriptionEndDate = subInfo.subscriptions[0].end_date;
  319. }
  320. // ToastUtil.showToast('用户信息已更新');
  321. return json.data;
  322. } else {
  323. ToastUtil.showToast('刷新用户信息失败');
  324. return null;
  325. }
  326. } else {
  327. ToastUtil.showToast('获取用户信息失败');
  328. return null;
  329. }
  330. } catch (e) {
  331. ToastUtil.showToast('获取用户信息异常' + (e && e.message ? (': ' + e.message) : ''));
  332. hilog.error(0x0000, 'UserUtil', `fetchUserInfo error: ${e.message}`);
  333. return null;
  334. }
  335. }
  336. // 华为登录错误处理
  337. static dealHuaweiLoginError(error: BusinessError): void {
  338. if (error.code === 1001) {
  339. ToastUtil.showToast('未登录华为账号');
  340. } else if (error.code === 1002) {
  341. ToastUtil.showToast('网络异常');
  342. } else if (error.code === 1003) {
  343. ToastUtil.showToast('内部错误');
  344. } else if (error.code === 1004) {
  345. ToastUtil.showToast('用户取消授权');
  346. } else if (error.code === 1005) {
  347. ToastUtil.showToast('系统服务异常');
  348. } else if (error.code === 1006) {
  349. ToastUtil.showToast('请求被拒绝');
  350. } else if (error.code === 1007) {
  351. ToastUtil.showToast('无权限');
  352. } else {
  353. ToastUtil.showToast('登录失败');
  354. }
  355. }
  356. }