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

520 lines
24 KiB
C#

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.

// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Furion.Logging.Extensions;
using Newtonsoft.Json;
namespace Admin.NET.Core.Service;
/// <summary>
/// 微信支付服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 210)]
public class SysWechatPayService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<SysWechatPay> _sysWechatPayRep;
private readonly SqlSugarRepository<SysWechatRefund> _sysWechatRefundRep;
private readonly WechatPayOptions _wechatPayOptions;
private readonly PayCallBackOptions _payCallBackOptions;
private readonly WechatTenpayClient _wechatTenpayClient;
public SysWechatPayService(SqlSugarRepository<SysWechatPay> sysWechatPayUserRep,
SqlSugarRepository<SysWechatRefund> sysWechatRefundRep,
IOptions<WechatPayOptions> wechatPayOptions,
IOptions<PayCallBackOptions> payCallBackOptions)
{
_sysWechatPayRep = sysWechatPayUserRep;
_sysWechatRefundRep = sysWechatRefundRep;
_wechatPayOptions = wechatPayOptions.Value;
_payCallBackOptions = payCallBackOptions.Value;
_wechatTenpayClient = CreateTenpayClient();
}
/// <summary>
/// 初始化微信支付客户端
/// </summary>
/// <returns></returns>
private WechatTenpayClient CreateTenpayClient()
{
var cerFilePath = Path.Combine(App.WebHostEnvironment.ContentRootPath, _wechatPayOptions.MerchantCertificatePrivateKey.Replace("/", Path.DirectorySeparatorChar.ToString()));
var tenpayClientOptions = new WechatTenpayClientOptions()
{
MerchantId = _wechatPayOptions.MerchantId,
MerchantV3Secret = _wechatPayOptions.MerchantV3Secret,
MerchantCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber,
MerchantCertificatePrivateKey = File.Exists(cerFilePath) ? File.ReadAllText(cerFilePath) : "",
PlatformCertificateManager = new InMemoryCertificateManager()
};
return new WechatTenpayClient(tenpayClientOptions);
}
/// <summary>
/// 分页查询支付列表 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
[ApiDescriptionSettings(Name = "Page")]
public async Task<SqlSugarPagedList<SysWechatPay>> Page(WechatPayPageInput input)
{
var query = _sysWechatPayRep.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.OutTradeNumber == input.Keyword || u.TransactionId == input.Keyword)
.WhereIF(input.CreateTimeRange != null && input.CreateTimeRange.Count > 0 && input.CreateTimeRange[0].HasValue, x => x.CreateTime >= input.CreateTimeRange[0])
.WhereIF(input.CreateTimeRange != null && input.CreateTimeRange.Count > 1 && input.CreateTimeRange[1].HasValue, x => x.CreateTime < ((DateTime)input.CreateTimeRange[1]).AddDays(1));
return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
}
/// <summary>
/// 查询退款信息列表
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpPost]
[DisplayName("根据支付id获取退款信息列表")]
public async Task<List<SysWechatRefund>> ListRefund([FromBody] string id)
{
var query = _sysWechatRefundRep.AsQueryable()
.Where(u => u.TransactionId == id);
return await query.ToListAsync();
}
/// <summary>
/// 生成JSAPI调起支付所需参数 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("生成JSAPI调起支付所需参数")]
public WechatPayParaOutput GenerateParametersForJsapiPay(WechatPayParaInput input)
{
var data = _wechatTenpayClient.GenerateParametersForJsapiPayRequest(_wechatPayOptions.AppId, input.PrepayId);
return new WechatPayParaOutput()
{
AppId = data["appId"],
TimeStamp = data["timeStamp"],
NonceStr = data["nonceStr"],
Package = data["package"],
SignType = data["signType"],
PaySign = data["paySign"]
};
}
/// <summary>
/// 微信支付下单(商户直连) 🔖
/// </summary>
[DisplayName("微信支付下单(商户直连)")]
public async Task<WechatPayTransactionOutput> CreatePayTransaction([FromBody] WechatPayTransactionInput input)
{
var request = new CreatePayTransactionJsapiRequest()
{
OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
AppId = _wechatPayOptions.AppId,
Description = input.Description,
Attachment = input.Attachment,
GoodsTag = input.GoodsTag,
ExpireTime = DateTimeOffset.Now.AddMinutes(10),
NotifyUrl = _payCallBackOptions.WechatPayUrl,
Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = input.Total },
Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = input.OpenId }
};
var response = await _wechatTenpayClient.ExecuteCreatePayTransactionJsapiAsync(request);
if (!response.IsSuccessful())
throw Oops.Oh(response.ErrorMessage);
var singInfo = this.GenerateParametersForJsapiPay(new WechatPayParaInput() { PrepayId = response.PrepayId });
// 保存订单信息
var wechatPay = new SysWechatPay()
{
AppId = _wechatPayOptions.AppId,
MerchantId = _wechatPayOptions.MerchantId,
OutTradeNumber = request.OutTradeNumber,
Description = input.Description,
Attachment = input.Attachment,
GoodsTag = input.GoodsTag,
Total = input.Total,
OpenId = input.OpenId,
TransactionId = "",
Tags = input.Tags,
BusinessId = input.BusinessId,
};
await _sysWechatPayRep.InsertAsync(wechatPay);
return new WechatPayTransactionOutput()
{
PrepayId = response.PrepayId,
OutTradeNumber = request.OutTradeNumber,
SingInfo = singInfo
};
}
/// <summary>
/// 微信支付下单(商户直连)Native
/// </summary>
[DisplayName("微信支付下单(商户直连)Native")]
public async Task<dynamic> CreatePayTransactionNative([FromBody] WechatPayTransactionInput input)
{
var request = new CreatePayTransactionNativeRequest()
{
OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
AppId = _wechatPayOptions.AppId,
Description = input.Description,
Attachment = input.Attachment,
GoodsTag = input.GoodsTag,
ExpireTime = DateTimeOffset.Now.AddMinutes(10),
NotifyUrl = _payCallBackOptions.WechatPayUrl,
Amount = new CreatePayTransactionNativeRequest.Types.Amount() { Total = input.Total },
//Payer = new CreatePayTransactionNativeRequest.Types.Payer() { OpenId = input.OpenId }
Scene = new CreatePayTransactionNativeRequest.Types.Scene() { ClientIp = "127.0.0.1" }
};
var response = await _wechatTenpayClient.ExecuteCreatePayTransactionNativeAsync(request);
if (!response.IsSuccessful())
{
JsonConvert.SerializeObject(response).LogInformation();
throw Oops.Oh(response.ErrorMessage);
}
// 保存订单信息
var wechatPay = new SysWechatPay()
{
AppId = _wechatPayOptions.AppId,
MerchantId = _wechatPayOptions.MerchantId,
OutTradeNumber = request.OutTradeNumber,
Description = input.Description,
Attachment = input.Attachment,
GoodsTag = input.GoodsTag,
Total = input.Total,
//OpenId = input.OpenId,
TransactionId = "",
QrcodeContent = response.QrcodeUrl,
Tags = input.Tags,
BusinessId = input.BusinessId,
};
await _sysWechatPayRep.InsertAsync(wechatPay);
return new
{
request.OutTradeNumber,
response.QrcodeUrl
};
}
/// <summary>
/// 微信支付下单(服务商模式) 🔖
/// </summary>
[DisplayName("微信支付下单(服务商模式)")]
public async Task<dynamic> CreatePayPartnerTransaction([FromBody] WechatPayTransactionInput input)
{
var request = new CreatePayPartnerTransactionJsapiRequest()
{
OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
AppId = _wechatPayOptions.AppId,
MerchantId = _wechatPayOptions.MerchantId,
SubAppId = _wechatPayOptions.AppId,
SubMerchantId = _wechatPayOptions.MerchantId,
Description = input.Description,
Attachment = input.Attachment,
GoodsTag = input.GoodsTag,
ExpireTime = DateTimeOffset.Now.AddMinutes(10),
NotifyUrl = _payCallBackOptions.WechatPayUrl,
Amount = new CreatePayPartnerTransactionJsapiRequest.Types.Amount() { Total = input.Total },
Payer = new CreatePayPartnerTransactionJsapiRequest.Types.Payer() { OpenId = input.OpenId }
};
var response = await _wechatTenpayClient.ExecuteCreatePayPartnerTransactionJsapiAsync(request);
if (!response.IsSuccessful())
throw Oops.Oh(response.ErrorMessage);
var singInfo = this.GenerateParametersForJsapiPay(new WechatPayParaInput() { PrepayId = response.PrepayId });
// 保存订单信息
var wechatPay = new SysWechatPay()
{
AppId = _wechatPayOptions.AppId,
MerchantId = _wechatPayOptions.MerchantId,
SubAppId = _wechatPayOptions.AppId,
SubMerchantId = _wechatPayOptions.MerchantId,
OutTradeNumber = request.OutTradeNumber,
Description = input.Description,
Attachment = input.Attachment,
GoodsTag = input.GoodsTag,
Total = input.Total,
OpenId = input.OpenId,
TransactionId = ""
};
await _sysWechatPayRep.InsertAsync(wechatPay);
return new
{
response.PrepayId,
request.OutTradeNumber,
singInfo
};
}
/// <summary>
/// 获取支付订单详情(本地库) 🔖
/// </summary>
/// <param name="tradeId"></param>
/// <returns></returns>
[DisplayName("获取支付订单详情(本地库)")]
public async Task<SysWechatPay> GetPayInfo(string tradeId)
{
return await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == tradeId);
}
/// <summary>
/// 获取支付订单详情(微信接口) 🔖
/// </summary>
/// <param name="tradeId"></param>
/// <returns></returns>
[DisplayName("获取支付订单详情(微信接口)")]
public async Task<SysWechatPay> GetPayInfoFromWechat(string tradeId)
{
var request = new GetPayTransactionByOutTradeNumberRequest();
request.OutTradeNumber = tradeId;
var response = await _wechatTenpayClient.ExecuteGetPayTransactionByOutTradeNumberAsync(request);
// 修改订单支付状态
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == response.OutTradeNumber
&& u.MerchantId == response.MerchantId);
// 如果状态不一致就更新数据库中的记录
if (wechatPay != null && wechatPay.TradeState != response.TradeState)
{
wechatPay.OpenId = response.Payer.OpenId;
wechatPay.TransactionId = response.TransactionId; // 支付订单号
wechatPay.TradeType = response.TradeType; // 交易类型
wechatPay.TradeState = response.TradeState; // 交易状态
wechatPay.TradeStateDescription = response.TradeStateDescription; // 交易状态描述
wechatPay.BankType = response.BankType; // 付款银行类型
wechatPay.Total = response.Amount.Total; // 订单总金额
wechatPay.PayerTotal = response.Amount.PayerTotal; // 用户支付金额
wechatPay.SuccessTime = response.SuccessTime.Value.DateTime; // 支付完成时间
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
}
wechatPay = new SysWechatPay()
{
AppId = _wechatPayOptions.AppId,
MerchantId = _wechatPayOptions.MerchantId,
SubAppId = _wechatPayOptions.AppId,
SubMerchantId = _wechatPayOptions.MerchantId,
OutTradeNumber = request.OutTradeNumber,
Attachment = response.Attachment,
Total = response.Amount.Total, // 订单总金额
TransactionId = response.TransactionId,
TradeType = response.TradeType, // 交易类型
TradeState = response.TradeState, // 交易状态
TradeStateDescription = response.TradeStateDescription, // 交易状态描述
BankType = response.BankType, // 付款银行类型
PayerTotal = response.Amount.PayerTotal, // 用户支付金额
SuccessTime = response.SuccessTime.Value.DateTime // 支付完成时间
};
return wechatPay;
}
/// <summary>
/// 退款申请
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("退款申请")]
[HttpPost]
public async Task<dynamic> CreateRefundDomestic([FromBody] WechatPayRefundDomesticInput input)
{
// refund/domestic/refunds
var request = new CreateRefundDomesticRefundRequest()
{
Amount = new CreateRefundDomesticRefundRequest.Types.Amount()
{
Refund = input.Refund,
Total = input.Total,
Currency = "CNY"
},
OutTradeNumber = input.TradeId,
OutRefundNumber = "R" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
NotifyUrl = _payCallBackOptions.WechatRefundUrl, // 应采用WechatRefundUrl参数如与WechatPayUrl入口相同也应分开设置参数
Reason = input.Reason,
};
var response = await _wechatTenpayClient.ExecuteCreateRefundDomesticRefundAsync(request);
if (string.IsNullOrEmpty(response.ErrorCode))
{
// 成功了,这里应该保存退款订单信息
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == response.OutTradeNumber);
// 保存订单信息
if (wechatPay != null)
{
var wechatRefund = new SysWechatRefund()
{
WechatPayId = wechatPay.Id,
TransactionId = response.TransactionId,
Refund = input.Refund,
Reason = input.Reason,
OutRefundNumber = request.OutRefundNumber,
Channel = response.Channel,
UserReceivedAccount = response.UserReceivedAccount,
};
await _sysWechatRefundRep.InsertAsync(wechatRefund);
}
}
else
{
throw Oops.Bah($"[{response.ErrorCode}]{response.ErrorMessage}");
}
return response;
}
/// <summary>
/// 获取退款订单详情(微信接口)
/// </summary>
/// <param name="refundId"></param>
/// <returns></returns>
[DisplayName("获取退款订单详情(微信接口)")]
public async Task<SysWechatRefund> GetRefundInfoFromWechat(string refundId)
{
var request = new GetRefundDomesticRefundByOutRefundNumberRequest();
request.OutRefundNumber = refundId;
var response = await _wechatTenpayClient.ExecuteGetRefundDomesticRefundByOutRefundNumberAsync(request);
// 修改订单支付状态
var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutRefundNumber == refundId);
// 如果状态不一致就更新数据库中的记录
if (wechatRefund != null && wechatRefund.TradeState != response.Status)
{
wechatRefund.TransactionId = response.TransactionId; // 支付订单号
wechatRefund.TradeState = response.Status; // 交易状态
wechatRefund.SuccessTime = response.SuccessTime.Value.DateTime; // 支付完成时间
await _sysWechatRefundRep.AsUpdateable(wechatRefund).IgnoreColumns(true).ExecuteCommandAsync();
// 有退款,刷新一下订单状态
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.Id == wechatRefund.WechatPayId);
if (wechatPay != null)
await GetPayInfoFromWechat(wechatPay.OutTradeNumber);
}
wechatRefund = new SysWechatRefund()
{
TransactionId = response.TransactionId,
Refund = response.Amount.Refund,
OutRefundNumber = request.OutRefundNumber,
Channel = response.Channel,
UserReceivedAccount = response.UserReceivedAccount,
TradeState = response.Status, // 交易状态
SuccessTime = response.SuccessTime.Value.DateTime, // 支付完成时间
};
return wechatRefund;
}
/// <summary>
/// 微信支付成功回调(商户直连)
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[DisplayName("微信支付成功回调(商户直连)")]
public async Task<WechatPayOutput> PayCallBack()
{
using var ms = new MemoryStream();
await App.HttpContext.Request.Body.CopyToAsync(ms);
var b = ms.ToArray();
var callbackJson = Encoding.UTF8.GetString(b);
var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson);
if ("TRANSACTION.SUCCESS".Equals(callbackModel.EventType))
{
try
{
var callbackPayResource = _wechatTenpayClient.DecryptEventResource<TransactionResource>(callbackModel);
// 修改订单支付状态
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackPayResource.OutTradeNumber
&& u.MerchantId == callbackPayResource.MerchantId);
if (wechatPay == null) return null;
wechatPay.OpenId = callbackPayResource.Payer.OpenId; // 支付者标识
//wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号
//wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号
wechatPay.TransactionId = callbackPayResource.TransactionId; // 支付订单号
wechatPay.TradeType = callbackPayResource.TradeType; // 交易类型
wechatPay.TradeState = callbackPayResource.TradeState; // 交易状态
wechatPay.TradeStateDescription = callbackPayResource.TradeStateDescription; // 交易状态描述
wechatPay.BankType = callbackPayResource.BankType; // 付款银行类型
wechatPay.Total = callbackPayResource.Amount.Total; // 订单总金额
wechatPay.PayerTotal = callbackPayResource.Amount.PayerTotal; // 用户支付金额
wechatPay.SuccessTime = callbackPayResource.SuccessTime.DateTime; // 支付完成时间
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
return new WechatPayOutput()
{
Total = wechatPay.Total,
Attachment = wechatPay.Attachment,
GoodsTag = wechatPay.GoodsTag
};
}
catch (Exception ex)
{
"微信支付回调时出错:".LogError(ex);
}
}
else if ("REFUND.SUCCESS".Equals(callbackModel.EventType))
{
//参考https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/refund-result-notice.html
try
{
var callbackRefundResource = _wechatTenpayClient.DecryptEventResource<RefundResource>(callbackModel);
// 修改订单支付状态
var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutRefundNumber == callbackRefundResource.OutRefundNumber);
if (wechatRefund == null) return null;
wechatRefund.TradeState = callbackRefundResource.RefundStatus; // 交易状态
wechatRefund.SuccessTime = callbackRefundResource.SuccessTime.Value.DateTime; // 支付完成时间
await _sysWechatRefundRep.AsUpdateable(wechatRefund).IgnoreColumns(true).ExecuteCommandAsync();
// 有退款,刷新一下订单状态
await GetPayInfoFromWechat(callbackRefundResource.OutTradeNumber);
}
catch (Exception ex)
{
"微信退款回调时出错:".LogError(ex);
}
}
else
{
callbackModel.EventType.LogInformation();
}
return null;
}
/// <summary>
/// 微信支付成功回调(服务商模式) 🔖
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[DisplayName("微信支付成功回调(服务商模式)")]
public async Task PayPartnerCallBack()
{
using var ms = new MemoryStream();
await App.HttpContext.Request.Body.CopyToAsync(ms);
var b = ms.ToArray();
var callbackJson = Encoding.UTF8.GetString(b);
var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson);
if ("TRANSACTION.SUCCESS".Equals(callbackModel.EventType))
{
var callbackResource = _wechatTenpayClient.DecryptEventResource<PartnerTransactionResource>(callbackModel);
// 修改订单支付状态
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber
&& u.MerchantId == callbackResource.MerchantId);
if (wechatPay == null) return;
//wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识
//wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号
//wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号
wechatPay.TransactionId = callbackResource.TransactionId; // 支付订单号
wechatPay.TradeType = callbackResource.TradeType; // 交易类型
wechatPay.TradeState = callbackResource.TradeState; // 交易状态
wechatPay.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述
wechatPay.BankType = callbackResource.BankType; // 付款银行类型
wechatPay.Total = callbackResource.Amount.Total; // 订单总金额
wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额
wechatPay.SuccessTime = callbackResource.SuccessTime.DateTime; // 支付完成时间
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
}
}
}