diff --git a/GDZZ.Application/Entity/UserScope.cs b/GDZZ.Application/Entity/UserScope.cs new file mode 100644 index 0000000..d615c9d --- /dev/null +++ b/GDZZ.Application/Entity/UserScope.cs @@ -0,0 +1,23 @@ +using System; +using SqlSugar; +using System.ComponentModel; +using GDZZ.Core.Entity; +namespace GDZZ.Application +{ + /// + /// 业务用户关联表 + /// + [SugarTable("baseuser_sysuser_scope")] + [Description("业务用户关联表")] + public class UserScope + { + /// + /// 业务用户ID + /// + public long BaseUserID { get; set; } + /// + /// 系统用户ID + /// + public long SysUserID { get; set; } + } +} \ No newline at end of file diff --git a/GDZZ.Application/Enum/MerchantErrorEnum.cs b/GDZZ.Application/Enum/MerchantErrorEnum.cs new file mode 100644 index 0000000..8adafb3 --- /dev/null +++ b/GDZZ.Application/Enum/MerchantErrorEnum.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GDZZ.Application.Enum +{ + public enum MerchantErrorEnum + { + + + /// + /// 系统错误 + /// + [Description("系统错误")] SYSTEM_ERROR = 0, + + /// + /// 商户号和appid没有绑定关系 + /// + [Description("商户号和appid没有绑定关系")] APPID_MCHID_NOT_MATCH = 1, + + /// + /// 系统错误 + /// + [Description("参数错误")] PARAM_ERROR = 2, + /// + /// 系统错误 + /// + [Description("请求参数符合参数格式,但不符合业务规则")] INVALID_REQUEST = 3, + /// + /// 系统错误 + /// + [Description("商户信息不合法")] NO_AUTH = 4, + /// + /// 系统错误 + /// + [Description("资金不足")] NOT_ENOUGH = 5, + /// + /// 系统错误 + /// + [Description("商户账户付款受限")] ACCOUNTERROR = 6, + /// + /// 系统错误 + /// + [Description("超出商户单日转账额度")] QUOTA_EXCEED =7, + + /// + /// 系统错误 + /// + [Description("频率超限")] FREQUENCY_LIMITED = 0, + + } +} diff --git a/GDZZ.Application/GDZZ.Application.csproj b/GDZZ.Application/GDZZ.Application.csproj index 2f209e6..1fd7d9f 100644 --- a/GDZZ.Application/GDZZ.Application.csproj +++ b/GDZZ.Application/GDZZ.Application.csproj @@ -7,6 +7,8 @@ + + diff --git a/GDZZ.Application/GDZZ.Application.xml b/GDZZ.Application/GDZZ.Application.xml index cb79e1f..ec3d039 100644 --- a/GDZZ.Application/GDZZ.Application.xml +++ b/GDZZ.Application/GDZZ.Application.xml @@ -804,6 +804,21 @@ 求职联系费用 + + + 业务用户关联表 + + + + + 业务用户ID + + + + + 系统用户ID + + 活动类型 @@ -834,6 +849,51 @@ 解除合作 + + + 系统错误 + + + + + 商户号和appid没有绑定关系 + + + + + 系统错误 + + + + + 系统错误 + + + + + 系统错误 + + + + + 系统错误 + + + + + 系统错误 + + + + + 系统错误 + + + + + 系统错误 + + 热招 @@ -1667,7 +1727,7 @@ 退款消息 - + 商户ID @@ -4260,16 +4320,16 @@ JsApiPay 的摘要说明 - + V3版本请求接口 微信的接口地址 post请求的数据,json格式 apiclient_key.pem中的内容,不要-----BEGIN PRIVATE KEY----- -----END PRIVATE KEY----- - 发起请求的商户(包括直连商户、服务商或渠道商)的商户号 mchid + 发起请求的商户(包括直连商户、服务商或渠道商)的商户号 mchid 商户证书号 - + diff --git a/GDZZ.Application/Help/CacheService.cs b/GDZZ.Application/Help/CacheService.cs index 29fe629..65d5a9a 100644 --- a/GDZZ.Application/Help/CacheService.cs +++ b/GDZZ.Application/Help/CacheService.cs @@ -57,10 +57,10 @@ namespace GDZZ.Application.Help /// /// [NonAction] - public async Task DelLiveHistoryService(long UserID) + public async Task DelLiveHistoryService(long UserID) { string cacheKey = SystemConst.LIVE_HISTORYLIST + $"{UserID}"; - await _redisCache.DelAsync(cacheKey); + return await _redisCache.DelAsync(cacheKey)>0; } #endregion diff --git a/GDZZ.Application/Help/ICacheService.cs b/GDZZ.Application/Help/ICacheService.cs index afab3e7..7165046 100644 --- a/GDZZ.Application/Help/ICacheService.cs +++ b/GDZZ.Application/Help/ICacheService.cs @@ -13,7 +13,7 @@ namespace GDZZ.Application.Help public interface ICacheService { #region 聊天接口 - Task DelLiveHistoryService(long UserID); + Task DelLiveHistoryService(long UserID); Task> GetLiveHistoryService(long UserID); Task SetLiveHistoryService(long UserID, List liveMessageLists); #endregion diff --git a/GDZZ.Application/Service/Auth/AuthService.cs b/GDZZ.Application/Service/Auth/AuthService.cs index 8cde910..ee5f56b 100644 --- a/GDZZ.Application/Service/Auth/AuthService.cs +++ b/GDZZ.Application/Service/Auth/AuthService.cs @@ -33,6 +33,8 @@ namespace GDZZ.Application.Service.Auth #region 仓储 private readonly SqlSugarRepository Baseuser; // wx用户仓储 private readonly SqlSugarRepository _sysUserRep; // 用户表仓储 + private readonly SqlSugarRepository UserScope; //用户业务关联仓储 _ + private readonly SqlSugarRepository _sysTenantRep; //租户仓储 private readonly SqlSugarRepository Self; //职业仓储 private readonly SqlSugarRepository CompanyRep; @@ -59,6 +61,7 @@ namespace GDZZ.Application.Service.Auth public AuthService( IOptions options, + SqlSugarRepository UserScope, SqlSugarRepository Baseuser, SqlSugarRepository sysTenantRep, SqlSugarRepository sysUserRep, @@ -75,6 +78,7 @@ namespace GDZZ.Application.Service.Auth IEventPublisher eventPublisher, IHttpContextAccessor httpContextAccessor) { + this.UserScope = UserScope; this.CompanyRep= CompanyRep; this._eventPublisher= eventPublisher; this._sysUserRep = sysUserRep; @@ -102,133 +106,144 @@ namespace GDZZ.Application.Service.Auth public async Task SignInAsync(PhoneModel phoneModel) { AuthUserOut authUserOut = new AuthUserOut(); - Company company = new Company(); - - //读取凭证 - var tokenModel = await this._wechatOAuth.GetCode2SessionAsync(phoneModel.Code); - //解析电话 - var phoneInfo = MiniProgramUtil.AESDecrypt(phoneModel.EncryptedDataStr, tokenModel.SessionKey, phoneModel.Iv); - - - //查询系统用户 - var sysUser = this._sysUserRep.AsQueryable() - .Filter("TenantId", true) - .First(x => x.Phone == phoneInfo.PhoneNumber); - - var wxUser = await this.Baseuser.AsQueryable() - .Filter("TenantId", true) - .Where(x => x.OpenID == tokenModel.OpenId).SingleAsync(); - - - - //账号不存在 生成系统账号 - if (sysUser.IsEmpty()) + try { - sysUser = await this._sysUserRep.InsertReturnEntityAsync(new SysUser() + this.UserScope.BeginTran(); //开启事务 + //读取凭证 + var tokenModel = await this._wechatOAuth.GetCode2SessionAsync(phoneModel.Code); + //解析电话 + var phoneInfo = MiniProgramUtil.AESDecrypt(phoneModel.EncryptedDataStr, tokenModel.SessionKey, phoneModel.Iv); + //系统用户 + var sysUser = this._sysUserRep.AsQueryable() + .Filter("TenantId", true) + .First(x => x.Phone == phoneInfo.PhoneNumber); + //业务用户 + var wxUser = await this.Baseuser.AsQueryable() + .Filter("TenantId", true) + .Where(x => x.OpenID == tokenModel.OpenId).SingleAsync(); + + //账号不存在 生成系统账号 + if (sysUser.IsEmpty()) { - Account = phoneInfo.PurePhoneNumber, - AdminType = AdminType.None, - Avatar = "https://gdzongzhi.com/assets/img/logo.png", - Birthday = DateTime.Now, - CreatedTime = DateTime.Now, - CreatedUserId = null, - CreatedUserName = null, - Sex = Gender.UNKNOWN, - Status = CommonStatus.ENABLE, - Email = null, - IsDeleted = false, - Name = phoneInfo.PhoneNumber, - Password = MD5Encryption.Encrypt("123456"), - TenantId = 392820661919813, - Phone = phoneInfo.PhoneNumber, - NickName = "", - Tel = null, - }); - } - - if (wxUser.IsEmpty()) - { - wxUser = await this.Baseuser.InsertReturnEntityAsync(new BaseUser() + //wxUser = await this.UserScope.InsertAsync(new UserScope) + sysUser = await this._sysUserRep.InsertReturnEntityAsync(new SysUser() + { + Account = phoneInfo.PurePhoneNumber, + AdminType = AdminType.None, + Avatar = "https://gdzongzhi.com/assets/img/logo.png", + Birthday = DateTime.Now, + CreatedTime = DateTime.Now, + CreatedUserId = null, + CreatedUserName = null, + Sex = Gender.UNKNOWN, + Status = CommonStatus.ENABLE, + Email = null, + IsDeleted = false, + Name = phoneInfo.PhoneNumber, + Password = MD5Encryption.Encrypt("123456"), + TenantId = 392820661919813, + Phone = phoneInfo.PhoneNumber, + NickName = "", + Tel = null, + }); + } + + if (wxUser.IsEmpty()) { - UnionId = tokenModel.Unionid, - CreatedUserId = sysUser.Id, - CreatedTime = DateTime.Now, - CreatedUserName = sysUser.Name, - AvatarUrl = "https://gdzongzhi.com/assets/img/logo.png", - Status = (int)CommonStatus.ENABLE, - OpenID = tokenModel.OpenId, - UserName = phoneModel.Phone.ToString(), - }); - } - //区分账号类型 - switch (phoneModel.LogInType) - { - case (int)UserEnum.JOB: - wxUser.Type = (int)UserEnum.JOB; - await this.Baseuser.UpdateAsync(wxUser); - break; - case (int)UserEnum.HEADHUNTERS: - wxUser.Type = (int)UserEnum.HEADHUNTERS; - await this.Baseuser.UpdateAsync(wxUser); - break; - case (int)UserEnum.ADVERTISE: - wxUser.Type = (int)UserEnum.ADVERTISE; - await this.Baseuser.UpdateAsync(wxUser); - //获取公司信息 - company = await this.CompanyRep.FirstOrDefaultAsync(x => x.Id == wxUser.CompanyID); - authUserOut.companyDto = company.Adapt(); - break; - default: - break; - } + wxUser = await this.Baseuser.InsertReturnEntityAsync(new BaseUser() + { + UnionId = tokenModel.Unionid, + CreatedUserId = sysUser.Id, + CreatedTime = DateTime.Now, + CreatedUserName = sysUser.Name, + AvatarUrl = "https://gdzongzhi.com/assets/img/logo.png", + Status = (int)CommonStatus.ENABLE, + OpenID = tokenModel.OpenId, + UserName = phoneModel.Phone.ToString(), + }); + } + //区分账号类型 + switch (phoneModel.LogInType) + { + case (int)UserEnum.JOB: + wxUser.Type = (int)UserEnum.JOB; + await this.Baseuser.UpdateAsync(wxUser); + break; + case (int)UserEnum.HEADHUNTERS: + wxUser.Type = (int)UserEnum.HEADHUNTERS; + await this.Baseuser.UpdateAsync(wxUser); + break; + case (int)UserEnum.ADVERTISE: + wxUser.Type = (int)UserEnum.ADVERTISE; + await this.Baseuser.UpdateAsync(wxUser); + //获取公司信息 + company = await this.CompanyRep.FirstOrDefaultAsync(x => x.Id == wxUser.CompanyID); + authUserOut.companyDto = company.Adapt(); + break; + default: + break; + } - if (wxUser.IsEmpty() || sysUser.IsEmpty()) - throw Oops.Oh(ErrorCode.xg1002); - //判断是否存在邀请 - if(phoneModel.Scene != null) - { - //判断当前用户是否被邀请过 - var invi = await this.invitaitionRey.FirstOrDefaultAsync(x => x.UserID == UserManager.UserId); - if (invi.IsNullOrZero()) + if (wxUser.IsEmpty() || sysUser.IsEmpty()) + throw Oops.Oh(ErrorCode.xg1002); + var userc = await this.UserScope.FirstOrDefaultAsync(x => x.BaseUserID == wxUser.Id && x.SysUserID == sysUser.Id); + if (userc.IsNullOrZero()) { - var invres = await this.invitaitionRey.FirstOrDefaultAsync(x => x.InviteID == phoneModel.Scene); - //未被邀请 - var invrey = await this.invitaitionRey.InsertAsync(new InviteUserPos() + await this.UserScope.InsertAsync(new UserScope() { - UserID = UserManager.UserId, - InviteUserID = (long)phoneModel.Scene, - InviteID = invres.InviteID - + SysUserID = sysUser.Id, + BaseUserID = wxUser.Id, }); - if (invrey > 0) + + } + + + + //判断是否存在邀请 + if (phoneModel.Scene != null) + { + //判断当前用户是否被邀请过 + var invi = await this.invitaitionRey.FirstOrDefaultAsync(x => x.UserID == UserManager.UserId); + if (invi.IsNullOrZero()) { - //附加奖励给邀请人 - UtilService utilService = new UtilService(this.rechargeRep); - utilService.Reward((long)phoneModel.Scene, 1); + var invres = await this.invitaitionRey.FirstOrDefaultAsync(x => x.InviteID == phoneModel.Scene); + //未被邀请 + var invrey = await this.invitaitionRey.InsertAsync(new InviteUserPos() + { + UserID = UserManager.UserId, + InviteUserID = (long)phoneModel.Scene, + InviteID = invres.InviteID + + }); + if (invrey > 0) + { + //附加奖励给邀请人 + UtilService utilService = new UtilService(this.rechargeRep); + utilService.Reward((long)phoneModel.Scene, 1); + } } + } - } - - var Self = await this.Self.FirstOrDefaultAsync(x => x.CreatedUserId == sysUser.Id); + var Self = await this.Self.FirstOrDefaultAsync(x => x.CreatedUserId == sysUser.Id); - // 获取加密后的密码 - var encryptPassword = MD5Encryption.Encrypt(sysUser.Password); + // 获取加密后的密码 + var encryptPassword = MD5Encryption.Encrypt(sysUser.Password); - // 验证账号是否被冻结 - if (sysUser.Status == CommonStatus.DISABLE) - throw Oops.Oh(ErrorCode.D1017); - //获取对应租户 - var tenant = this._sysTenantRep.Single(sysUser.TenantId); + // 验证账号是否被冻结 + if (sysUser.Status == CommonStatus.DISABLE) + throw Oops.Oh(ErrorCode.D1017); + //获取对应租户 + var tenant = this._sysTenantRep.Single(sysUser.TenantId); - if (tenant.IsNullOrZero()) - throw Oops.Oh(ErrorCode.F1001); + if (tenant.IsNullOrZero()) + throw Oops.Oh(ErrorCode.F1001); - // 生成Token令牌 - authUserOut.Token = JWTEncryption.Encrypt(new Dictionary + // 生成Token令牌 + authUserOut.Token = JWTEncryption.Encrypt(new Dictionary { {ClaimConst.CLAINM_USERID, sysUser.Id}, {ClaimConst.TENANT_ID, sysUser.TenantId}, @@ -239,31 +254,38 @@ namespace GDZZ.Application.Service.Auth { ClaimConst.CLAINM_TENANT_NAME, tenant.Name }, }); - // 设置Swagger自动登录 - _httpContextAccessor.HttpContext.SigninToSwagger(authUserOut.Token); - - // 生成刷新Token令牌 - var refreshToken = JWTEncryption.GenerateRefreshToken(authUserOut.Token, 30); - - // 设置刷新Token令牌 - _httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken; - - var httpContext = App.HttpContext; - await _eventPublisher.PublishAsync(new ChannelEventSource("Update:UserLoginInfo", - new SysUser { Id = sysUser.Id, LastLoginIp = httpContext.GetLocalIpAddressToIPv4(), LastLoginTime = DateTime.Now })); + // 设置Swagger自动登录 + _httpContextAccessor.HttpContext.SigninToSwagger(authUserOut.Token); + + // 生成刷新Token令牌 + var refreshToken = JWTEncryption.GenerateRefreshToken(authUserOut.Token, 30); + + // 设置刷新Token令牌 + _httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken; + + var httpContext = App.HttpContext; + await _eventPublisher.PublishAsync(new ChannelEventSource("Update:UserLoginInfo", + new SysUser { Id = sysUser.Id, LastLoginIp = httpContext.GetLocalIpAddressToIPv4(), LastLoginTime = DateTime.Now })); + authUserOut.Avatar = sysUser.Avatar; + authUserOut.Phone = sysUser.Phone; + authUserOut.Sex = sysUser.Sex; + authUserOut.UserId = sysUser.Id; + authUserOut.UserName = sysUser.Name; + authUserOut.Self = Self.IsEmpty() ? null : Self.Name; + authUserOut.Type = (UserEnum)wxUser.Type; + authUserOut.OpenID = tokenModel.OpenId; + authUserOut.Tenant = tenant.Adapt(); + authUserOut.Describe = wxUser.Describe; + await this.cacheService.SetUserInfoAsync(authUserOut, authUserOut.UserId); + this.UserScope.CurrentCommitTran(); + } + catch (Exception) + { + this.UserScope.CurrentRollbackTran(); + throw; + } - authUserOut.Avatar = sysUser.Avatar; - authUserOut.Phone = sysUser.Phone; - authUserOut.Sex = sysUser.Sex; - authUserOut.UserId = sysUser.Id; - authUserOut.UserName = sysUser.Name; - authUserOut.Self = Self.IsEmpty() ? null : Self.Name; - authUserOut.Type = (UserEnum)wxUser.Type; - authUserOut.OpenID = tokenModel.OpenId; - authUserOut.Tenant = tenant.Adapt(); - authUserOut.Describe = wxUser.Describe; - await this.cacheService.SetUserInfoAsync(authUserOut, authUserOut.UserId); return authUserOut; } diff --git a/GDZZ.Application/Service/Auth/DTO/WxRefundInput.cs b/GDZZ.Application/Service/Auth/DTO/WxRefundInput.cs index 60f9905..675e425 100644 --- a/GDZZ.Application/Service/Auth/DTO/WxRefundInput.cs +++ b/GDZZ.Application/Service/Auth/DTO/WxRefundInput.cs @@ -39,11 +39,11 @@ namespace GDZZ.Application /// /// 商户ID /// - public string MerchantID { get; set; } + public string OpenID { get; set; } /// /// 转账金额 /// - public dynamic TransferAmount { get; set; } + public decimal TransferAmount { get; set; } } } diff --git a/GDZZ.Application/Service/LiveHistoryContacts/Dto/LiveHistoryContactsInput.cs b/GDZZ.Application/Service/LiveHistoryContacts/Dto/LiveHistoryContactsInput.cs index e6d16bf..46dfd88 100644 --- a/GDZZ.Application/Service/LiveHistoryContacts/Dto/LiveHistoryContactsInput.cs +++ b/GDZZ.Application/Service/LiveHistoryContacts/Dto/LiveHistoryContactsInput.cs @@ -133,7 +133,7 @@ namespace GDZZ.Application /// /// 公司ID /// - public long CompanyID { get; set; } + public long? CompanyID { get; set; } } } diff --git a/GDZZ.Application/Service/LiveHistoryContacts/LiveHistoryContactsService.cs b/GDZZ.Application/Service/LiveHistoryContacts/LiveHistoryContactsService.cs index b6e1c6b..1e24175 100644 --- a/GDZZ.Application/Service/LiveHistoryContacts/LiveHistoryContactsService.cs +++ b/GDZZ.Application/Service/LiveHistoryContacts/LiveHistoryContactsService.cs @@ -80,12 +80,20 @@ namespace GDZZ.Application public async Task AddLive(AddLiveFriendInput input) { - var baseUser = await this.Baseuser.AsQueryable().Filter("TenantId", true).Where(x => x.CompanyID == input.CompanyID).SingleAsync(); - if (baseUser.IsNullOrZero()) - throw Oops.Oh(ErrorCode.B1002); - var user = await this._sysUserRep.Where(x => x.Id == baseUser.CreatedUserId).SingleAsync(); - if (user.IsNullOrZero()) - throw Oops.Oh(ErrorCode.xg1002); + SysUser user = new SysUser(); + if (input.CompanyID.IsNullOrZero()) + { + user = await this._sysUserRep.FirstOrDefaultAsync(x => x.Id == input.UserId); + if (user.IsNullOrZero()) + throw Oops.Oh(ErrorCode.xg1002); + } + else + { + var buser = await this.Baseuser.FirstOrDefaultAsync(x => x.CompanyID == input.CompanyID); + user = await this._sysUserRep.Where(x => x.Id == buser.CreatedUserId).SingleAsync(); + if (user.IsNullOrZero()) + throw Oops.Oh(ErrorCode.xg1002); + } //判断是是好友 var fends =await this.liveUserFriend.Where(x => x.FriendID == UserManager.UserId && x.CreatedUserId == user.Id).FirstAsync(); @@ -111,7 +119,9 @@ namespace GDZZ.Application await this.liveUserFriend.InsertAsync(item); } } - await this.cacheService.DelLiveHistoryService(UserManager.UserId); + + if(!await this.cacheService.DelLiveHistoryService(UserManager.UserId)) + throw Oops.Oh(ErrorCode.B1004); return user; } @@ -186,7 +196,6 @@ namespace GDZZ.Application Id = item.FriendID, Index = sysUser.Name.Substring(0, 1), LastSendTime = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000, - }; //查询聊天记录 diff --git a/GDZZ.Application/Service/WXPay/Dto/Merchant.cs b/GDZZ.Application/Service/WXPay/Dto/Merchant.cs new file mode 100644 index 0000000..bfc6b9d --- /dev/null +++ b/GDZZ.Application/Service/WXPay/Dto/Merchant.cs @@ -0,0 +1,20 @@ +using GDZZ.Application.Enum; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GDZZ.Application +{ + public class Merchant + { + public string out_batch_no { get; set; } + public string batch_id { get; set; } + public DateTime create_time { get; set; } + + public string code { get; set; } + + public string message { get; set; } + } +} diff --git a/GDZZ.Application/Service/WXPay/JsApiPay.cs b/GDZZ.Application/Service/WXPay/JsApiPay.cs index 483fbb7..00ce4b8 100644 --- a/GDZZ.Application/Service/WXPay/JsApiPay.cs +++ b/GDZZ.Application/Service/WXPay/JsApiPay.cs @@ -6,19 +6,31 @@ using System.Runtime.Serialization; using System.IO; using System.Text; using System.Net; - +using Flurl; +using Flurl.Http; using Furion.Logging; using SqlSugar; using Enyim.Caching; using System.Security.Cryptography; +using static System.Net.Mime.MediaTypeNames; +using Microsoft.Extensions.Hosting.Internal; +using Enyim.Caching.Configuration; +using Furion.RemoteRequest; +using Furion.RemoteRequest.Extensions; +using System.Net.Http; +using System.Threading.Tasks; +using System.Security.Policy; + /// ///JsApiPay 的摘要说明 /// -public static class JsApiPay +public class JsApiPay : IHttpDispatchProxy { - public static string WithDrawsToWx(string appid, string mchid, string serialNo, string openID, string partnerTradeNo, decimal totalFee) + const string PrivateKey = "App_Data/cert/apiclient_key.pem"; + + public async Task WithDrawsToWx(string url, string appid, string mchid, string serialNo, string openID, string partnerTradeNo, decimal totalFee, string hostting) { SortedDictionary dic = new SortedDictionary(); @@ -39,10 +51,7 @@ public static class JsApiPay list.Add(dic1); dic.Add("transfer_detail_list", list); - var url = "https://api.mch.weixin.qq.com/v3/transfer/batches"; - string transactionsResponse = WxV3PostJson(url, Newtonsoft.Json.JsonConvert.SerializeObject(dic), mchid, serialNo); - //Log.Info("商户转账到零钱返回:" , transactionsResponse); - return transactionsResponse; ; + return await WxV3PostJson(url, Newtonsoft.Json.JsonConvert.SerializeObject(dic), mchid, serialNo, hostting); ; ; } /// /// V3版本请求接口 @@ -50,52 +59,49 @@ public static class JsApiPay /// 微信的接口地址 /// post请求的数据,json格式 /// apiclient_key.pem中的内容,不要-----BEGIN PRIVATE KEY----- -----END PRIVATE KEY----- - /// 发起请求的商户(包括直连商户、服务商或渠道商)的商户号 mchid + /// 发起请求的商户(包括直连商户、服务商或渠道商)的商户号 mchid /// 商户证书号 - /// + /// /// - public static string WxV3PostJson(string url, string postData, string mchId, string serialNo) + public async Task WxV3PostJson(string url, string postData, string mchId, string serialNo, string hostting) { - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + string Authorization = GetAuthorization(url, "POST", postData, mchId, serialNo, hostting); + var request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; request.ContentType = "application/json;charset=UTF-8"; request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36"; request.Accept = "application/json"; - //Log.Info("发起开始申请商户转账到零钱请求", "发起请求1"); - string Authorization = GetAuthorization(url, "POST", postData, mchId, serialNo); request.Headers.Add("Authorization", Authorization); - //Log.Info("返回申请商户转账到零钱结果", Authorization); - byte[] paramJsonBytes; - paramJsonBytes = System.Text.Encoding.UTF8.GetBytes(postData); - request.ContentLength = paramJsonBytes.Length; + byte[] byteData = System.Text.Encoding.UTF8.GetBytes(postData); + request.ContentLength = byteData.Length; Stream writer; try { writer = request.GetRequestStream(); } - catch (Exception) + catch (Exception e) { - writer = null; Console.Write("连接服务器失败!"); + throw; } - writer.Write(paramJsonBytes, 0, paramJsonBytes.Length); + writer.Write(byteData, 0, byteData.Length); writer.Close(); + string responseString = ""; HttpWebResponse response; try { response = (HttpWebResponse)request.GetResponse(); } - catch (WebException ex) + catch (WebException e) { - response = ex.Response as HttpWebResponse; + response = e.Response as HttpWebResponse; } Stream resStream = response.GetResponseStream(); StreamReader reader = new StreamReader(resStream); - string text = reader.ReadToEnd(); - return text; + responseString = reader.ReadToEnd(); + return responseString; } - private static string GetAuthorization(string url, string method, string jsonParame, string mchId, string serialNo) + private string GetAuthorization(string url, string method, string jsonParame, string mchId, string serialNo, string hostting) { var uri = new Uri(url); string urlPath = uri.PathAndQuery; @@ -107,8 +113,8 @@ public static class JsApiPay //Log.Info("请求message:", message); //string signTxt = Sign(message, privateKey); - string signTxt = Sign(message); - + string signTxt = Sign(message, hostting); + //Authorization和格式 string authorzationTxt = string.Format("WECHATPAY2-SHA256-RSA2048 mchid=\"{0}\",nonce_str=\"{1}\",timestamp=\"{2}\",serial_no=\"{3}\",signature=\"{4}\"", @@ -122,17 +128,31 @@ public static class JsApiPay } - private static string Sign(string message) + private string Sign(string message, string hostting) { + + string base64X509Cert = ""; + string pemPublicCert = Path.Combine(hostting, PrivateKey); + using (FileStream fs = new FileStream(pemPublicCert, FileMode.Open, FileAccess.Read)) + { + using (StreamReader sr = new StreamReader(fs)) + { + base64X509Cert = sr.ReadToEnd().Trim(); + } + } + base64X509Cert = base64X509Cert.Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", "").Replace("\r", "").Replace("\n", ""); + // NOTE: 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY----- // 亦不包括结尾的-----END PRIVATE KEY----- - string privateKey = "{你的私钥}"; - byte[] keyData = Convert.FromBase64String(privateKey); + + byte[] keyData = Convert.FromBase64String(base64X509Cert); var rsa = RSA.Create(); rsa.ImportPkcs8PrivateKey(keyData, out _); byte[] data = System.Text.Encoding.UTF8.GetBytes(message); - return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)); + var sigdata = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + var res = Convert.ToBase64String(sigdata); + return res; } } diff --git a/GDZZ.Application/Service/WXPay/WXPayService.cs b/GDZZ.Application/Service/WXPay/WXPayService.cs index df80216..112db6e 100644 --- a/GDZZ.Application/Service/WXPay/WXPayService.cs +++ b/GDZZ.Application/Service/WXPay/WXPayService.cs @@ -27,6 +27,8 @@ using Senparc.CO2NET.Cache.Redis; using GDZZ.Core.Service; using System.Linq.Dynamic.Core.Tokenizer; using System.Diagnostics; +using Microsoft.AspNetCore.Hosting; +using Furion.JsonSerialization; namespace GDZZ.Application.Service.WXPay { @@ -35,7 +37,7 @@ namespace GDZZ.Application.Service.WXPay { - + private readonly IHostingEnvironment hostingEnvironment; private readonly SqlSugarRepository Baseuser; // wx用户仓储 private readonly SqlSugarRepository _sysUserRep; // 用户表仓储 private readonly SqlSugarRepository _sysTenantRep; //租户仓储 @@ -71,6 +73,7 @@ namespace GDZZ.Application.Service.WXPay SqlSugarRepository payTakeRep, SqlSugarRepository refundRep, WechatOAuth wechatOAuth, + IHostingEnvironment hostingEnvironment, IHttpContextAccessor _httpContextAccessor, IEventPublisher eventPublisher) { @@ -85,6 +88,7 @@ namespace GDZZ.Application.Service.WXPay this.payTakeRep = payTakeRep; this._wechatOAuth = wechatOAuth; this._oauthConfig = options.Value.SenparcWeixin; + this.hostingEnvironment = hostingEnvironment; this._httpContextAccessor = _httpContextAccessor; } @@ -128,6 +132,7 @@ namespace GDZZ.Application.Service.WXPay public async Task UpBalance(decimal Consume,long? ResumeID) { + this.balance.CurrentBeginTran(); var comrep = await this.ComsumeRep.Where(x=>x.ResumeID== ResumeID).FirstAsync(); if(!comrep.IsNullOrZero()) //已经消费过了 return ""; @@ -424,19 +429,21 @@ namespace GDZZ.Application.Service.WXPay /// /// [HttpPost] - [Route("Mini/v1/GetBalance")] + [Route("Mini/v1/MerchantTransfer")] public async Task MerchantTransfer(TransferInput wxRefundInput) { + var hostting = this.hostingEnvironment.ContentRootPath; //读取商户信息 - var appid = Config.SenparcWeixinSetting.TenPayV3_AppId; var mchid = Config.SenparcWeixinSetting.TenPayV3_MchId; var serialno = Config.SenparcWeixinSetting.TenPayV3_SerialNumber; - string partnerTradeNo = "xcx" + DateTime.Now.ToString("yyyyMMddHHmmfff"); - string result = JsApiPay.WithDrawsToWx(appid, mchid, serialno, wxRefundInput.MerchantID, partnerTradeNo, Convert.ToInt32(wxRefundInput.TransferAmount * 100)); + string url = string.Format("https://api.mch.weixin.qq.com/v3/transfer/batches"); + string partnerTradeNo = "tk" + DateTime.Now.ToString("yyyyMMddHHmmfff"); + JsApiPay jsApiPay = new JsApiPay(); + string result =await jsApiPay.WithDrawsToWx(url, appid, mchid, serialno, wxRefundInput.OpenID, partnerTradeNo, + Convert.ToInt32(wxRefundInput.TransferAmount * 100), hostting); return result; - } diff --git a/GDZZ.Core/ConfigOption/ConfigOptions.cs b/GDZZ.Core/ConfigOption/ConfigOptions.cs index b578492..509d03c 100644 --- a/GDZZ.Core/ConfigOption/ConfigOptions.cs +++ b/GDZZ.Core/ConfigOption/ConfigOptions.cs @@ -244,6 +244,8 @@ public class SenparcWeixinSetting public string TenPayV3_PrivateKey { get; set; } public string TenPayV3_SerialNumber { get; set; } public string TenPayV3_ApiV3Key { get; set; } + + public string TenPayv3_WxTransfer { get; set; } } diff --git a/GDZZ.Core/Enum/ErrorCode.cs b/GDZZ.Core/Enum/ErrorCode.cs index 854bc8c..9a3cd5c 100644 --- a/GDZZ.Core/Enum/ErrorCode.cs +++ b/GDZZ.Core/Enum/ErrorCode.cs @@ -431,4 +431,10 @@ public enum ErrorCode /// [ErrorCodeItemMetadata("手机已经存在")] B1003, + + /// + /// 数据删除失败 + /// + [ErrorCodeItemMetadata("数据删除失败")] + B1004, } diff --git a/GDZZ.Core/GDZZ.Core.xml b/GDZZ.Core/GDZZ.Core.xml index 0164721..e35e13f 100644 --- a/GDZZ.Core/GDZZ.Core.xml +++ b/GDZZ.Core/GDZZ.Core.xml @@ -2620,6 +2620,11 @@ 手机已经存在 + + + 数据删除失败 + + 文件扩展枚举 diff --git a/GDZZ.Core/Manager/UserManager.cs b/GDZZ.Core/Manager/UserManager.cs index 4c905c2..72ec7be 100644 --- a/GDZZ.Core/Manager/UserManager.cs +++ b/GDZZ.Core/Manager/UserManager.cs @@ -18,8 +18,6 @@ public static class UserManager /// public static string Account => App.User.FindFirst(ClaimConst.CLAINM_ACCOUNT)?.Value; - - /// /// 租户ID /// diff --git a/GDZZ.Web.Core/applicationconfig.json b/GDZZ.Web.Core/applicationconfig.json index e054e83..2d9da2b 100644 --- a/GDZZ.Web.Core/applicationconfig.json +++ b/GDZZ.Web.Core/applicationconfig.json @@ -158,9 +158,9 @@ * 1、支持明文私钥(无换行字符) * 2、私钥文件路径(如:~/App_Data/cert/apiclient_key.pem),注意:必须放在 App_Data 等受保护的目录下,避免泄露 */ - "TenPayV3_PrivateKey": "#{TenPayV3_PrivateKey}#", //(新)证书私钥 - "TenPayV3_SerialNumber": "56FA7394CDD74ACB0329480EAB7185BE7A2CB081", //(新)证书序列号 - "TenPayV3_ApiV3Key": "#{TenPayV3_APIv3Key}#", //(新)APIv3 密钥 + "TenPayV3_PrivateKey": "/App_Data/cert/apiclient_key.pem", //(新)证书私钥 + "TenPayV3_SerialNumber": "133833F7E1F4083BF191A81938EF25AC659F23D6", //(新)证书序列号 + "TenPayV3_ApiV3Key": "ZZRZJKvAstv4SJapStuyHhCOzqrrjSUD", //(新)APIv3 密钥 //如果不设置TenPayV3_WxOpenTenpayNotify,默认在 TenPayV3_TenpayNotify 的值最后加上 "WxOpen" "TenPayV3_WxOpenTenpayNotify": "https://admin.gdzongzhi.com/api/Mini/NotifyUrlWxOpen", //http://YourDomainName/TenpayV3/PayNotifyUrlWxOpen diff --git a/GDZZ.Web.Entry/App_Data/cert/apiclient_cert.p12 b/GDZZ.Web.Entry/App_Data/cert/apiclient_cert.p12 new file mode 100644 index 0000000..049d408 Binary files /dev/null and b/GDZZ.Web.Entry/App_Data/cert/apiclient_cert.p12 differ diff --git a/GDZZ.Web.Entry/App_Data/cert/apiclient_cert.pem b/GDZZ.Web.Entry/App_Data/cert/apiclient_cert.pem new file mode 100644 index 0000000..c41b4b7 --- /dev/null +++ b/GDZZ.Web.Entry/App_Data/cert/apiclient_cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIUEzgz9+H0CDvxkagZOO8lrGWfI9YwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT +FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg +Q0EwHhcNMjQwMTIyMDgyMjIyWhcNMjkwMTIwMDgyMjIyWjCBijETMBEGA1UEAwwK +MTY0MDM2MzE2NzEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTYwNAYDVQQL +DC3lub/kuJznnIHnurXmmbrkvIHkuJrnrqHnkIblkqjor6LmnInpmZDlhazlj7gx +CzAJBgNVBAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMGnNPWyzoN6ldrf0B8coEu0naNSCJZvXRYKKOi8VH17 +uNwbuxdYP4G0rWQC1hSbqc7Qz3iFEW5PqKA2ZqoDBdhFNqZTSVMOcw8Dagz0ya1m +a3EdjXSGp7prHNXRSWQtVGYVPqPmg1qrt+Uptfkd75Z1EmIxDMoN8ERLToOtmfi5 +vTWWS0TZ9xaE99qGVirb4yjDr9qrutSCx/C1xNHssAnT3m2x5bYHiOEcDGtVp6iG +DqvUBpLaqSa3IxJlQZSZP3mdrV7i2o8tlw8heSQJ+o7VWWrP0V/Y1CgPhsT0HBK4 +PRQRPfpHGv98W5tf+XBPorrjYqBFAc6TpoZHKLTY6BcCAwEAAaOBuTCBtjAJBgNV +HRMEAjAAMAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0 +cDovL2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIw +RTUwREJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0 +MjJFMTJCMjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUA +A4IBAQCTh90ch63bv8M0xPwCk31nuM5YZL3TsOxN8ZB+3odVEjY/zWuypSgK2aM7 +VuvroBsivAlLKefqw/U0JXw2oHNGveWW9Bq71K2KJmb8E+8yLzF9jae1mwCQmBo0 ++r0D2OWDdXKrJ0eFAu4Ug6LYIu7x//JoUW3e0jY+BgiOZgkOl7LdI0bmGHiQ8LMr +g0BgUSWc7TDd9r9UH86Rq9E0leA+5ftIN3QDfpIsxcoKXFG1hlFEj6hHQP25aKS2 +ViGl35FrUyOC+/2MAyJqjEpfTGOrX3s5HyXDyM19Qqd+oCiLjc0vblWY0Zk8Y22a +DIzWjsG1cpBTaRqxkLaquCkyR26G +-----END CERTIFICATE----- diff --git a/GDZZ.Web.Entry/App_Data/cert/apiclient_key.pem b/GDZZ.Web.Entry/App_Data/cert/apiclient_key.pem new file mode 100644 index 0000000..e096e30 --- /dev/null +++ b/GDZZ.Web.Entry/App_Data/cert/apiclient_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDBpzT1ss6DepXa +39AfHKBLtJ2jUgiWb10WCijovFR9e7jcG7sXWD+BtK1kAtYUm6nO0M94hRFuT6ig +NmaqAwXYRTamU0lTDnMPA2oM9MmtZmtxHY10hqe6axzV0UlkLVRmFT6j5oNaq7fl +KbX5He+WdRJiMQzKDfBES06DrZn4ub01lktE2fcWhPfahlYq2+Mow6/aq7rUgsfw +tcTR7LAJ095tseW2B4jhHAxrVaeohg6r1AaS2qkmtyMSZUGUmT95na1e4tqPLZcP +IXkkCfqO1Vlqz9Ff2NQoD4bE9BwSuD0UET36Rxr/fFubX/lwT6K642KgRQHOk6aG +Ryi02OgXAgMBAAECggEACBBXYzfL46uyG8ggGXuOrThbLBbZZrJCdQ19QJu/BO6m +9vtsof85vcPxSG3ZzkfhHUySpxkbbbWBdxJs2f2AO84+BVUIg53haqmgu+Nhlofi +R3aMkmKdD2UwcTLi1HoSvqF510ddBuSJptBC1JnLhT9gwZf7SVqlO6LLJS6Qb8vx +hMqNlvG3EqBVGSTuE8mTjcga3lmI9eAxz8rL9sZ59lj17k2JTxW6/Q3QjeTRqIpA +UTm+SOjVYn++2kHB3Ix8RYD5vClsZ7BpMS0f3fko6qt/SuRHS0wZF2RoELia+ohv +AyON+jKGSGRVivCiMPISDE7UGaA/cxKrolTok63QWQKBgQD8v14vWxZVp15VPosb +1quBUDajQLBjfNIifoU1Aqc7VD2zLJ79LN7Pngv84p55TAS3+MTcu/elYGeUeZuS +X/JexTJmSE30FtmGhpCuf/9VXyj7rN6sYbJkj+1ENFFv/qM0NBxagk/aVewM+PHs +tIetibw+qZ0iiabnSUrqBMMVkwKBgQDEJSm4hkzGdL3I1oOyVbju/gMXcMM3IMxZ +RpsmUQ8qhjid9KvBvC/AmWJFyDAkAkVownAcvSwoJAMSNUuApghEPDqRvvNnoc72 +lmtbwF5s6veyadZMVnrTc/ipFZdZg9ya+Qs0Kocbnceo2fJ+R5+HBtt+Wd8GE0oc +24//+C+17QKBgQCFdsxWd2QI4POYUgmFLsur4l6nwG4kavJP0r2mq3sBgk9+gO6H +xJz3x36PEGAcrz7CozPZV8zC4HBx3/F9zvqefsVJa572aOZ++ioGa4K6YyCyHawM +HR7lqXbiEDp8yFsIIwhh5vQh2ENo6kBd/Uq0Icps0IYwib2/3l0XzGHzzwKBgEsa +h70V/3PF451xNgAk/qjULk7daIJFVrmgZWvogcwglLE2rEWETyyKDqz1mClRjU4t +lUwLy0qbb2mbaouaB3RJM4v297BorpyQwA7ju8QsvCdeiyWzv1gUAdSMZeVqrh/Q +2E9jMVSLt5WZzlY4CodjQsxAkTr8S9Z848h5OZuJAoGBAKaaRH/9FY7igN5okg6s +4GkoxvhOtUZ2quMCK79xKnq7SaMXz07qEhBK/a9ajYvzlbOCw9ZcX3pe641IbE+K +03Fo7Uy/dJEtKE9rqA0lefGR9GpMbb6PdeHh9RvOKftMhrk8p8rL/GwUc2eFzgse +BfWBXzr2mpCy3arBimmjeyx/ +-----END PRIVATE KEY-----