VistarStarDataIntegration/admin.net.pro/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs
2024-09-09 09:23:58 +08:00

536 lines
25 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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, Description = "微信支付")]
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> sysWechatPayRep,
SqlSugarRepository<SysWechatRefund> sysWechatRefundRep,
IOptions<WechatPayOptions> wechatPayOptions,
IOptions<PayCallBackOptions> payCallBackOptions)
{
_sysWechatPayRep = sysWechatPayRep;
this._sysWechatRefundRep = sysWechatRefundRep;
_wechatPayOptions = wechatPayOptions.Value;
_payCallBackOptions = payCallBackOptions.Value;
_wechatTenpayClient = CreateTenpayClient();
}
/// <summary>
/// 初始化微信支付客户端
/// </summary>
/// <returns></returns>
private WechatTenpayClient CreateTenpayClient()
{
var cerFilePath = App.WebHostEnvironment.ContentRootPath + _wechatPayOptions.MerchantCertificatePrivateKey;
if (!File.Exists(cerFilePath))
Log.Warning("商户证书文件不存在:" + cerFilePath);
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>
/// 生成JSAPI调起支付所需参数 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("生成JSAPI调起支付所需参数")]
public dynamic GenerateParametersForJsapiPay(WechatPayParaInput input)
{
return _wechatTenpayClient.GenerateParametersForJsapiPayRequest(_wechatPayOptions.AppId, input.PrepayId);
}
/// <summary>
/// 微信支付统一下单获取Id(商户直连) 🔖
/// </summary>
[DisplayName("微信支付统一下单获取Id(商户直连)")]
public async Task<CreatePayTransactionOutput> CreatePayTransaction([FromBody] WechatPayTransactionInput input)
{
string outTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000); // 微信需要的订单号(唯一)
// 检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断)
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OrderId == input.OrderId && u.OrderStatus == input.OrderStatus);
if (wechatPay != null)
{
outTradeNumber = wechatPay.OutTradeNumber;
}
var request = new CreatePayTransactionJsapiRequest()
{
OutTradeNumber = outTradeNumber,
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<Exception>(response.ErrorMessage);
if (wechatPay == null)
{
// 保存订单信息
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 = "",
OrderId = input.OrderId,
OrderStatus = input.OrderStatus,
Tags = input.Tags,
BusinessId = input.BusinessId,
};
await _sysWechatPayRep.InsertAsync(wechatPay);
}
var singInfo = GenerateParametersForJsapiPay(new WechatPayParaInput() { PrepayId = response.PrepayId });
return new CreatePayTransactionOutput
{
PrepayId = response.PrepayId,
OutTradeNumber = request.OutTradeNumber,
SingInfo = singInfo
};
}
/// <summary>
/// 微信支付统一下单(商户直连)Native
/// </summary>
[DisplayName("微信支付统一下单(商户直连)Native")]
public async Task<CreatePayTransactionNativeOutput> 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 CreatePayTransactionNativeOutput
{
OutTradeNumber = request.OutTradeNumber,
QrcodeUrl = response.QrcodeUrl
};
}
/// <summary>
/// 微信支付统一下单获取Id(服务商模式) 🔖
/// </summary>
[DisplayName("微信支付统一下单获取Id(服务商模式)")]
public async Task<CreatePayTransactionOutput> CreatePayPartnerTransaction([FromBody] WechatPayTransactionInput input)
{
string outTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000); // 微信需要的订单号(唯一)
//检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断)
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OrderId == input.OrderId && u.OrderStatus == input.OrderStatus);
if (wechatPay != null)
{
outTradeNumber = wechatPay.OutTradeNumber;
}
var request = new CreatePayPartnerTransactionJsapiRequest()
{
OutTradeNumber = outTradeNumber,
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<Exception>($"JSAPI 下单失败(状态码:{response.GetRawStatus()},错误代码:{response.ErrorCode},错误描述:{response.ErrorMessage}");
if (wechatPay == null)
{
// 保存订单信息
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 CreatePayTransactionOutput
{
PrepayId = response.PrepayId,
OutTradeNumber = request.OutTradeNumber
};
}
/// <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>
/// <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))
{
var callbackResource = _wechatTenpayClient.DecryptEventResource<TransactionResource>(callbackModel);
// 修改订单支付状态
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber
&& u.MerchantId == callbackResource.MerchantId);
if (wechatPay == null) return null;
//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; // 支付完成时间
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
return new WechatPayOutput()
{
Total = wechatPay.Total,
Attachment = wechatPay.Attachment,
GoodsTag = wechatPay.GoodsTag
};
}
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; // 支付完成时间
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
}
}
/// <summary>
/// 退款申请 🔖
/// https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/create.html
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("微信退款申请)")]
public async Task Refund(RefundRequestInput input)
{
var request = new CreateRefundDomesticRefundRequest()
{
OutTradeNumber = input.OutTradeNumber,
OutRefundNumber = "REFUND_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"),
Amount = new CreateRefundDomesticRefundRequest.Types.Amount()
{
Total = input.Total,
Refund = input.Refund
},
Reason = input.Reason,
};
var response = await _wechatTenpayClient.ExecuteCreateRefundDomesticRefundAsync(request);
if (!response.IsSuccessful())
throw Oops.Oh<Exception>($"JSAPI 退款申请失败(状态码:{response.GetRawStatus()},错误代码:{response.ErrorCode},错误描述:{response.ErrorMessage}");
var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutTradeNumber == input.OutTradeNumber);
if (wechatRefund == null)
{
// 保存退款申请信息
wechatRefund = new SysWechatRefund()
{
TransactionId = input.OutTradeNumber,
OutTradeNumber = request.OutTradeNumber,
OutRefundNo = request.OutTradeNumber, //每笔付款只退一次,所以这里直接用付款单号
Reason = request.Reason,
Refund = input.Refund,
Total = input.Total,
//NotifyUrl = "",
OrderId = input.OrderId,
OrderStatus = input.OrderStatus,
MerchantGoodsId = input.MerchantGoodsId,
GoodsName = input.GoodsName,
UnitPrice = input.UnitPrice,
RefundAmount = input.RefundAmount,
RefundQuantity = input.RefundQuantity,
Attachment = input.Attachment,
Remark = input.Remark
};
await _sysWechatRefundRep.InsertAsync(wechatRefund);
}
}
/// <summary>
/// 查询单笔退款(通过商户退款单号) 🔖
/// https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/query-by-out-refund-no.html
/// </summary>
/// <param name="outRefundNumber"></param>
/// <returns></returns>
[DisplayName("微信查询单笔退款)")]
public async Task<GetRefundDomesticRefundByOutRefundNumberResponse> GetRefundByOutRefundNumber(string outRefundNumber)
{
var request = new GetRefundDomesticRefundByOutRefundNumberRequest()
{
OutRefundNumber = outRefundNumber
};
return await _wechatTenpayClient.ExecuteGetRefundDomesticRefundByOutRefundNumberAsync(request);
}
/// <summary>
/// 微信支付订单号查询(校正) 🔖
/// https://api.mch.weixin.qq.com/v3/pay/transactions/id/{transaction_id}
/// </summary>
/// <param name="transactionId"></param>
/// <returns></returns>
[DisplayName("微信支付订单号查询(校正)")]
public async Task<WechatPayOutput> GetPayTransactionByIdAsync(string transactionId)
{
if (string.IsNullOrEmpty(transactionId))
throw Oops.Oh("TransactionId 不能为空");
if (string.IsNullOrEmpty(_wechatPayOptions.MerchantId) || string.IsNullOrEmpty(_wechatPayOptions.MerchantCertificateSerialNumber))
throw Oops.Oh("商户号或证书序列号不能为空,请检查支付配置");
var request = new GetPayTransactionByIdRequest()
{
MerchantId = _wechatPayOptions.MerchantId,
TransactionId = transactionId,
WechatpayCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber
};
var response = await _wechatTenpayClient.ExecuteGetPayTransactionByIdAsync(request);
if (response.TradeState == "SUCCESS" || response.TradeState == "CLOSED")
{
// 修正订单支付状态
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.TransactionId == request.TransactionId && u.MerchantId == request.MerchantId);
if (wechatPay != null && string.IsNullOrEmpty(wechatPay.TradeState))
{
wechatPay.TradeType = response.TradeType; // 交易类型
wechatPay.TradeState = response.TradeState; // 交易状态
wechatPay.TradeStateDescription = response.TradeStateDescription; // 交易状态描述
wechatPay.BankType = response.BankType; // 付款银行类型
wechatPay.PayerTotal = response.Amount?.PayerTotal; // 用户支付金额
wechatPay.SuccessTime = response.SuccessTime; // 支付完成时间
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
return wechatPay.Adapt<WechatPayOutput>();
}
}
return response.Adapt<WechatPayOutput>();
}
/// <summary>
/// 商户订单号查询(校正) 🔖
/// https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}
/// </summary>
/// <param name="outTradeNumber"></param>
/// <returns></returns>
[DisplayName("微信商户订单号查询(校正)")]
public async Task<WechatPayOutput> GetPayTransactionByOutTradeNumberAsync(string outTradeNumber)
{
if (string.IsNullOrEmpty(outTradeNumber))
throw Oops.Oh("商户订单号(OutTradeNumber)不能为空");
if (string.IsNullOrEmpty(_wechatPayOptions.MerchantId) || string.IsNullOrEmpty(_wechatPayOptions.MerchantCertificateSerialNumber))
throw Oops.Oh("商户号或证书序列号不能为空,请检查支付配置");
var request = new GetPayTransactionByOutTradeNumberRequest()
{
MerchantId = _wechatPayOptions.MerchantId,
OutTradeNumber = outTradeNumber,
WechatpayCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber
};
var response = await _wechatTenpayClient.ExecuteGetPayTransactionByOutTradeNumberAsync(request);
if (response.TradeState == "SUCCESS" || response.TradeState == "CLOSED")
{
// 修正订单支付状态
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == outTradeNumber && u.MerchantId == request.MerchantId);
if (wechatPay != null && string.IsNullOrEmpty(wechatPay.TradeState))
{
wechatPay.TransactionId = response.TransactionId; // 支付订单号
wechatPay.TradeType = response.TradeType; // 交易类型
wechatPay.TradeState = response.TradeState; // 交易状态
wechatPay.TradeStateDescription = response.TradeStateDescription; // 交易状态描述
wechatPay.BankType = response.BankType; // 付款银行类型
wechatPay.PayerTotal = response.Amount?.PayerTotal; // 用户支付金额
wechatPay.SuccessTime = response.SuccessTime; // 支付完成时间
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
return wechatPay.Adapt<WechatPayOutput>();
}
}
return response.Adapt<WechatPayOutput>();
}
/// <summary>
/// 获取支付记录分页列表 🔖
/// </summary>
/// <param name="input">PageSysWechatPayInput</param>
/// <returns></returns>
[DisplayName("获取支付记录分页列表")]
public async Task<SqlSugarPagedList<SysWechatPay>> PageAsync(PageSysWechatPayInput input)
{
var query = _sysWechatPayRep.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.OrderId), u => u.OrderId == input.OrderId)
.WhereIF(!string.IsNullOrWhiteSpace(input.OrderStatus), u => u.OrderStatus == input.OrderStatus)
.WhereIF(!string.IsNullOrWhiteSpace(input.OutTradeNumber), u => u.OutTradeNumber.Contains(input.OutTradeNumber.Trim()));
if (input.SuccessTimeRange != null && input.SuccessTimeRange.Count > 0)
{
DateTime? start = input.SuccessTimeRange[0];
query = query.WhereIF(start.HasValue, u => u.SuccessTime > start);
if (input.SuccessTimeRange.Count > 1 && input.SuccessTimeRange[1].HasValue)
{
var end = input.SuccessTimeRange[1].Value.AddDays(1);
query = query.Where(u => u.SuccessTime < end);
}
}
if (input.ExpireTimeRange != null && input.ExpireTimeRange.Count > 0)
{
DateTime? start = input.ExpireTimeRange[0];
query = query.WhereIF(start.HasValue, u => u.ExpireTime > start);
if (input.ExpireTimeRange.Count > 1 && input.ExpireTimeRange[1].HasValue)
{
var end = input.ExpireTimeRange[1].Value.AddDays(1);
query = query.Where(u => u.ExpireTime < end);
}
}
query = query.OrderByDescending(u => u.CreateTime);
return await query.ToPagedListAsync(input.Page, input.PageSize);
}
/// <summary>
/// 根据支付Id获取退款信息列表
/// </summary>
/// <param name="transactionId"></param>
/// <returns></returns>
[DisplayName("根据支付Id获取退款信息列表")]
public async Task<List<SysWechatRefund>> GetRefundList([FromQuery] string transactionId)
{
return await _sysWechatRefundRep.AsQueryable().Where(u => u.TransactionId == transactionId).ToListAsync();
}
}