You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

511 lines
19 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using GDZZ.Application.Entity;
using GDZZ.Core.Entity;
using GDZZ.Core;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using Furion.EventBus;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Http;
using Furion.DependencyInjection;
using Furion.DynamicApiController;
using GDZZ.Application.Service.WXPay.Dto;
using TenPayOldV3 = Senparc.Weixin.TenPay.V3.TenPayV3;
using Senparc.Weixin;
using Senparc.Weixin.TenPay.V3;
using Senparc.Weixin.TenPay;
using Senparc.CO2NET.Utilities;
using Senparc.Weixin.Sample.CommonService.TemplateMessage;
using System.IO;
using System.Text;
using Senparc.Weixin.Exceptions;
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using Mapster;
using Furion.FriendlyException;
using Senparc.CO2NET.Cache.Redis;
using GDZZ.Core.Service;
using System.Linq.Dynamic.Core.Tokenizer;
using System.Diagnostics;
namespace GDZZ.Application.Service.WXPay
{
[ApiDescriptionSettings("Application", Name = "WXPay", Order = 1)]
public class WXPayService : IWXPayService, IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<BaseUser> Baseuser; // wx用户仓储
private readonly SqlSugarRepository<SysUser> _sysUserRep; // 用户表仓储
private readonly SqlSugarRepository<SysTenant> _sysTenantRep; //租户仓储
private readonly SqlSugarRepository<SeIF> self; //职业仓储
private readonly SqlSugarRepository<Consume> ComsumeRep; //消费记录仓储
private readonly SqlSugarRepository<Balance> balance; //余额仓储
private readonly SqlSugarRepository<MiniRecharge> rechargeRep; //充值仓储
private readonly SqlSugarRepository<MiniPayTake> payTakeRep; //支付仓储
private readonly SqlSugarRepository<ReFund> refundRep; //退款仓储
private readonly WechatOAuth _wechatOAuth; //微信权限服务
private readonly IHttpContextAccessor _httpContextAccessor; //http服务
/// <summary>
/// 获取配置文件
/// </summary>
private readonly SenparcWeixinSetting _oauthConfig;
public WXPayService(
IOptions<OAuthOptions> options,
SqlSugarRepository<BaseUser> Baseuser,
SqlSugarRepository<SysTenant> sysTenantRep,
SqlSugarRepository<SysUser> sysUserRep,
SqlSugarRepository<Balance> balance,
SqlSugarRepository<Consume> ComsumeRep,
SqlSugarRepository<SeIF> Self,
SqlSugarRepository<MiniRecharge> rechargeRep,
SqlSugarRepository<MiniPayTake> payTakeRep,
SqlSugarRepository<ReFund> refundRep,
WechatOAuth wechatOAuth,
IHttpContextAccessor _httpContextAccessor,
IEventPublisher eventPublisher)
{
this.self = Self;
this.refundRep= refundRep;
this.balance = balance;
this.Baseuser = Baseuser;
this.ComsumeRep = ComsumeRep;
this._sysUserRep = sysUserRep;
this._sysTenantRep = sysTenantRep;
this.rechargeRep = rechargeRep;
this.payTakeRep = payTakeRep;
this._wechatOAuth = wechatOAuth;
this._oauthConfig = options.Value.SenparcWeixin;
this._httpContextAccessor = _httpContextAccessor;
}
/// <summary>
/// 查询余额
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("Mini/v1/GetUserBalance")]
public async Task<BalanceOut> GetUserBalance()
{
var balan = await this.balance.AsQueryable().Filter("TenantId", true).SingleAsync(x => x.UserID == UserManager.UserId);
if (balan == null)
return null;
return new BalanceOut()
{
Amount = balan.Amount,
UserID = UserManager.UserId,
};
}
/// <summary>
/// 充值记录查询
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("Mini/v1/GetRechargeList")]
public async Task<List<RechargeOut>> GetRechargeList()
{
var rechargeLiist =await this.rechargeRep.Where(x => x.CreatedUserId == UserManager.UserId).ToListAsync();
return rechargeLiist.Adapt<List<RechargeOut>>();
}
/// <summary>
/// 修改余额
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("Mini/v1/UpBalance")]
public async Task<dynamic> UpBalance(decimal Consume,long? ResumeID)
{
var comrep = await this.ComsumeRep.Where(x=>x.ResumeID== ResumeID).FirstAsync();
if(!comrep.IsNullOrZero()) //已经消费过了
return "";
var ban = await this.balance.Where(x => x.UserID == UserManager.UserId).FirstAsync();
if (ban == null)
throw Oops.Oh(ErrorCode.xg1002);
ban.Amount -= Consume;
if (ban.Amount < 0)
throw Oops.Oh(ErrorCode.B1001);
await this.ComsumeRep.InsertAsync(new Consume()
{
CAmount= Consume,
Surplus = ban.Amount,
Sort =(int)ConsumeEnum.Contact,
ResumeID = ResumeID
});
return await this.balance.AsUpdateable(ban).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync()>0;
}
/// <summary>
/// 查询消费记录
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("Mini/v1/GetBalance")]
public async Task<List<ConsumeOut>> GetBalance()
{
var comsume = await this.ComsumeRep.Where(x => x.CreatedUserId == UserManager.UserId).OrderByDescending(x=>x.CreatedTime).ToListAsync();
return comsume.Adapt<List<ConsumeOut>>();
}
#region 小程序支付
/// <summary>
///微信小程序支付
/// </summary>
/// <param name="authUserInput">商品Id</param>
/// <returns></returns>
[HttpPost]
[Route("Mini/v1/wxpay")]
public async Task<dynamic> WxPay(AuthUserInput authUserInput)
{
var recharge = await this.rechargeRep.InsertReturnEntityAsync(new MiniRecharge()
{
PaymentMoney = authUserInput.Money,
Status = RechargeEnum.NoFinis,
Type = RechargeTypeEnum.StoredValue,
TotalPrice = authUserInput.Money
});
var payTake = await this.payTakeRep.InsertReturnEntityAsync(new MiniPayTake()
{
PaymentMoney = authUserInput.Money,
OrderId = recharge.Id,
PaySn = "",
PayStatus = PayStatusEnum.NotPaying
});
int pMoney = (int)(authUserInput.Money * 100);
string sp_billno =
$"{Config.SenparcWeixinSetting.TenPayV3_MchId}{SystemTime.Now:yyyyMMddHHmmss}{TenPayV3Util.BuildRandomStr(6)}";
string timeStamp = TenPayV3Util.GetTimestamp();
string nonceStr = TenPayV3Util.GetNoncestr();
TenPayV3UnifiedorderRequestData xmlDataInfo = new TenPayV3UnifiedorderRequestData(Config.SenparcWeixinSetting.WxOpenAppId,
Config.SenparcWeixinSetting.TenPayV3_MchId, "余额充值", sp_billno,
pMoney,
"127.0.0.1",
Config.SenparcWeixinSetting.TenPayV3_TenpayNotify, TenPayV3Type.JSAPI, authUserInput.OpenID,
Config.SenparcWeixinSetting.TenPayV3_Key, nonceStr, null, null, null, null, payTake.OrderId.ToString());
Console.WriteLine(xmlDataInfo.PackageRequestHandler.ParseXML());
var result = TenPayOldV3.Unifiedorder(xmlDataInfo);//调用统一订单接口
string packageStr = "prepay_id=" + result.prepay_id;
return new
{
success = true,
result.prepay_id,
appId = Config.SenparcWeixinSetting.WxOpenAppId,
timeStamp,
nonceStr,
package = packageStr,
signType = "MD5",
paySign = TenPayV3.GetJsPaySign(Config.SenparcWeixinSetting.WxOpenAppId, timeStamp, nonceStr,
packageStr, Config.SenparcWeixinSetting.TenPayV3_Key)
};
}
/// <summary>
/// 微信小程序支付回调
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("/Mini/NotifyUrl")]
[UnifyResult(typeof(string))]
[AllowAnonymous]
public async Task<ActionResult> NotifyUrl()
{
ResponseHandler resHandler = new ResponseHandler(this._httpContextAccessor.HttpContext);
ContentResult content = new ContentResult();
string return_code = resHandler.GetParameter("return_code");
string return_msg = resHandler.GetParameter("return_msg");
resHandler.SetKey(Config.SenparcWeixinSetting.TenPayV3_Key);
try
{
//验证请求是否从微信发过来(安全)
if (resHandler.IsTenpaySign() && return_code.ToUpper() == "SUCCESS")
{
//res = "success";//正确的订单处理
//直到这里,才能认为交易真正成功了,可以进行数据库操作,但是别忘了返回规定格式的消息!
Console.WriteLine("回调成功");
//attach
var paymentId = long.Parse(resHandler.GetParameter("attach"));
//业务处理
var paytake = await this.payTakeRep.Where(x => x.OrderId == paymentId).SingleAsync();
if (!paytake.IsEmpty())
{
paytake.PayStatus = PayStatusEnum.Paying;
this.payTakeRep.Update(paytake);
}
var recharge = await this.rechargeRep.Where(x => x.Id == paymentId).SingleAsync();
if (!recharge.IsEmpty())
{
recharge.Status = RechargeEnum.Finish;
this.rechargeRep.Update(recharge);
var balan = await this.balance.AsQueryable().Filter("TenantId", true).Where(x => x.UserID == paytake.CreatedUserId).SingleAsync();
if (!balan.IsEmpty())
{
balan.Amount = recharge.PaymentMoney;
this.balance.Update(balan);
}
else
{
this.balance.Insert(new Balance()
{
Amount = recharge.PaymentMoney,
UserID = (long)paytake.CreatedUserId
});
}
}
content.Content = string.Format(@"<xml>
<return_code><![CDATA[{0}]]></return_code>
<return_msg><![CDATA[{1}]]></return_msg>
</xml>", return_code, return_msg);
this.SendTemplate(0, return_code, resHandler);
}
else
{
content.Content = "回调失败";
}
this.LogRecord(resHandler);
}
catch (Exception ex)
{
content.Content = ex.Message;
}
return content;
}
#endregion
#region 小程序退款
/// <summary>
///微信小程序退款
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("Mini/v1/refund")]
public async Task<dynamic> WxRefund(WxRefundInput wxRefundInput)
{
//查询订单
var order = this.payTakeRep.FirstOrDefault(x => x.OrderId == wxRefundInput.OrderID);
if (order == null)
return new Exception("查询无此订单,退款失败");
//生成退款单
var res = await this.refundRep.InsertReturnEntityAsync(new ReFund()
{
State = RefundStatusEnum.REDIND,
Amount = order.PaymentMoney,
Message = "退款审核中",
PayNo = order.Id.ToString(),
});
if (res == null)
return new Exception("生成退款单失败");
string notifyUrl = string.Format(Config.SenparcWeixinSetting.TenPayV3_TenpayNotify, "RefundNotifyUrl");
string opUserId = Config.SenparcWeixinSetting.TenPayV3_MchId;
//退款金额
int refundFee = (int)(order.PaymentMoney * 100);
//支付时的总金额
int totalFee = (int)(order.PaymentMoney * 100);
string outRefundNo = res.Id.ToString();//新退款单号
string outTradeNo = order.OrderId.ToString();
var nonceStr = TenPayV3Util.GetNoncestr();
var dataInfo = new TenPayV3RefundRequestData(Config.SenparcWeixinSetting.WxOpenAppId, Config.SenparcWeixinSetting.TenPayV3_MchId,
Config.SenparcWeixinSetting.TenPayV3_Key, null, nonceStr, null, outTradeNo, outRefundNo, totalFee, refundFee, opUserId, null,
notifyUrl: notifyUrl);
return new
{
message = "退款请求完成",
};
}
/// <summary>
/// 退款回调
/// </summary>
[HttpPost("RefundNotifyUrl")]
[UnifyResult(typeof(string))]
public async Task<ActionResult> RefundNotifyUrl()
{
ResponseHandler resHandler = new ResponseHandler(this._httpContextAccessor.HttpContext);
ContentResult content = new ContentResult();
string return_code = resHandler.GetParameter("return_code");
string return_msg = resHandler.GetParameter("return_msg");
var mch_key = Senparc.Weixin.Config.SenparcWeixinSetting.TenPayV3_Key;
try
{
if (return_code.ToUpper() == "SUCCESS")
{
string req_info = resHandler.GetParameter("req_info");
var decodeReqInfo = TenPayV3Util.DecodeRefundReqInfo(req_info, mch_key);
var decodeDoc = System.Xml.Linq.XDocument.Parse(decodeReqInfo);
var refundNotifyXml = decodeDoc.Serialize();
//获取接口中需要用到的信息
string out_trade_no = decodeDoc.Root.Element("out_trade_no").Value;
string transaction_id = decodeDoc.Root.Element("transaction_id").Value;
string refund_id = decodeDoc.Root.Element("refund_id").Value;
int total_fee = int.Parse(decodeDoc.Root.Element("total_fee").Value);
int refund_fee = int.Parse(decodeDoc.Root.Element("refund_fee").Value);
string out_refund_no = decodeDoc.Root.Element("out_refund_no").Value;
var refun = this.refundRep.FirstOrDefault(x => x.PayNo == out_refund_no);
refun.PayNo = out_refund_no;
refun.Amount = ((float)refund_fee.ParseToInt() / 100).ParseToDecimal();
refun.Message = "退款完成";
refun.RefundID = refund_id;
refun.refundNo = out_refund_no;
refun.TransactionNo = transaction_id;
refun.State = RefundStatusEnum.RESUCCESS;
if (await this.refundRep.UpdateAsync(refun) > 0)
{
content.Content = string.Format(@"<xml>
<return_code><![CDATA[{0}]]></return_code>
<return_msg><![CDATA[{1}]]></return_msg>
</xml>", return_code, return_msg);
return content;
}
}
else
{
content.Content = "回调处理失败!";
}
}
catch (Exception e)
{
content.Content = e.Message;
}
return content;
}
#endregion
#region 商户
/// <summary>
/// 商户转账
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("Mini/v1/GetBalance")]
public async Task<dynamic> MerchantTransfer(TransferInput wxRefundInput)
{
//读取商户信息
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));
return result;
}
#endregion
#region 附加服务
public void SendTemplate(int type, string return_code, ResponseHandler resHandler)
{
switch (type)
{
case 0:
//发送支付成功的模板消息
try
{
string appId = Config.SenparcWeixinSetting.TenPayV3_AppId;//与微信公众账号后台的AppId设置保持一致区分大小写。
string openId = resHandler.GetParameter("openid");
var templateData = new WeixinTemplate_PaySuccess("https://weixin.senparc.com", "微信支付 V2 购买商品", "状态:" + return_code);
Senparc.Weixin.WeixinTrace.SendCustomLog("支付成功模板消息参数", appId + " , " + openId);
var result = Senparc.Weixin.MP.AdvancedAPIs.TemplateApi.SendTemplateMessage(appId, openId, templateData);
}
catch (Exception ex)
{
Senparc.Weixin.WeixinTrace.SendCustomLog("支付成功模板消息", ex.ToString());
}
break;
default:
break;
}
}
private void LogRecord(ResponseHandler resHandler)
{
#region 记录日志
var logDir = ServerUtility.ContentRootMapPath(string.Format("~/App_Data/TenPayNotify/{0}", SystemTime.Now.ToString("yyyyMMdd")));
if (!Directory.Exists(logDir))
{
Directory.CreateDirectory(logDir);
}
var logPath = Path.Combine(logDir, string.Format("{0}-{1}-{2}.txt", SystemTime.Now.ToString("yyyyMMdd"), SystemTime.Now.ToString("HHmmss"), Guid.NewGuid().ToString("n").Substring(0, 8)));
using (var fileStream = System.IO.File.OpenWrite(logPath))
{
var notifyXml = resHandler.ParseXML();
fileStream.Write(Encoding.Default.GetBytes(notifyXml), 0, Encoding.Default.GetByteCount(notifyXml));
fileStream.Close();
}
#endregion
}
#endregion
}
}