emoticon_api/Infrastructure/WeChat/TenPay/Pay.cs
2023-10-19 20:50:53 +08:00

536 lines
19 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.

using Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Senparc.CO2NET.Extensions;
using Senparc.CO2NET.HttpUtility;
using Senparc.CO2NET.Utilities;
using Senparc.Weixin;
using Senparc.Weixin.Entities;
using Senparc.Weixin.Exceptions;
using Senparc.Weixin.Helpers;
using Senparc.Weixin.MP.AdvancedAPIs.MerChant;
using Senparc.Weixin.TenPayV3;
using Senparc.Weixin.TenPayV3.Apis;
using Senparc.Weixin.TenPayV3.Apis.BasePay;
using Senparc.Weixin.TenPayV3.Apis.Entities;
using Senparc.Weixin.TenPayV3.Entities;
using Senparc.Weixin.TenPayV3.Helpers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Senparc.Weixin.TenPayV3.Apis.BasePay.Entities;
namespace Infrastructure.WeChat.TenPay
{
/// <summary>
/// 微信支付基础类
/// </summary>
public class Pay
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// 用于初始化BasePayApis
/// </summary>
private readonly ISenparcWeixinSettingForTenpayV3 _tenpayV3Setting;
public static HttpContext HttpContext => HttpContextLocal.Current();
private readonly BasePayApis _basePayApis;
private readonly SenparcHttpClient _httpClient;
/// <summary>
/// trade_no 和 transaction_id 对照表
/// TODO可以放入缓存设置有效时间
/// </summary>
//public static ConcurrentDictionary<string, string> TradeNumberToTransactionId = new ConcurrentDictionary<string, string>();
public TenPayV3Info TenPayV3Info;
public Pay(SenparcHttpClient httpClient)
{
var TenPayV3_AppId = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_AppId");
var TenPayV3_AppSecret = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_AppSecret");
var TenPayV3_MchId = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_MchId");
var TenPayV3_MchName = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_MchName");
var TenPayV3_Key = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_Key");
var TenPayV3_CertPath = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_CertPath");
var TenPayV3_CertSecret = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_CertPath");
var TenPayV3_TenpayNotify = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_TenpayNotify");
var TenPayV3_WxOpenNotify = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_WxOpenNotify");
var TenPayV3_PrivateKey = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_PrivateKey");
var TenPayV3_SerialNumber = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_SerialNumber");
var TenPayV3_ApiV3Key = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_ApiV3Key");
var tenPayV3Info = new TenPayV3Info(TenPayV3_AppId, TenPayV3_AppSecret, TenPayV3_MchId, TenPayV3_Key, TenPayV3_CertPath, TenPayV3_CertSecret,TenPayV3_TenpayNotify, TenPayV3_WxOpenNotify,TenPayV3_PrivateKey, TenPayV3_SerialNumber, TenPayV3_ApiV3Key);
TenPayV3InfoCollection.Register(tenPayV3Info, TenPayV3_MchName);
this.TenPayV3Info = tenPayV3Info;
_tenpayV3Setting = Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting;
_basePayApis = new BasePayApis(_tenpayV3Setting);
this._httpClient = httpClient;
}
public async Task<JsApiUiPackage> PrePay(string openId,string orderNo,int type,int price)
{
string sp_billno = orderNo;//out_trade_no
//调用下单接口下单
var TenPayV3_MchName = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_MchName");
var name = TenPayV3_MchName;
try
{
var appId = TenPayV3Info.AppId;
var notifyUrl = TenPayV3Info.TenPayV3_WxOpenNotify;
//请求信息
TransactionsRequestData jsApiRequestData = new(
appId,
TenPayV3Info.MchId,
name,
sp_billno,
new TenpayDateTime(DateTime.Now.AddMinutes(1), false),
null,
notifyUrl,
null,
new() { currency = "CNY", total = price },
new(openId),
null,
null,
null
);
logger.Info("支付参数:{0}", jsApiRequestData.ToJson());
//请求接口
var basePayApis2 = new Senparc.Weixin.TenPayV3.TenPayHttpClient.BasePayApis2(_httpClient, _tenpayV3Setting);
var result = await basePayApis2.JsApiAsync(jsApiRequestData);
logger.Info("支付结果:{@UnifiedorderResult}", result);
if (result.VerifySignSuccess != true)
{
throw new WeixinException("获取 prepay_id 结果校验出错!");
}
//获取 UI 信息包
var jsApiUiPackage = TenPaySignHelper.GetJsApiUiPackage(appId, result.prepay_id);
return jsApiUiPackage;
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// JS-SDK支付回调地址在下单接口中设置的 notify_url
/// </summary>
/// <returns></returns>
public async Task<OrderReturnJson> PayNotifyUrl()
{
try
{
//获取微信服务器异步发送的支付通知信息
var resHandler = new TenPayNotifyHandler(HttpContext);
var orderReturnJson = await resHandler.AesGcmDecryptGetObjectAsync<OrderReturnJson>();
//演示记录 transaction_id实际开发中需要记录到数据库以便退款和后续跟踪
//TradeNumberToTransactionId[orderReturnJson.out_trade_no] = orderReturnJson.transaction_id;
//获取支付状态
string trade_state = orderReturnJson.trade_state;
//验证请求是否从微信发过来(安全)
NotifyReturnData returnData = new();
//验证可靠的支付状态
if (orderReturnJson.VerifySignSuccess == true && trade_state == "SUCCESS")
{
returnData.code = "SUCCESS";//正确的订单处理
logger.Info("回调成功啦!", returnData.code.ToString());
/* 提示:
* 1、直到这里才能认为交易真正成功了可以进行数据库操作但是别忘了返回规定格式的消息
* 2、上述判断已经具有比较高的安全性以外还可以对访问 IP 进行判断进一步加强安全性。
* 3、下面演示的是发送支付成功的模板消息提示非必须。
*/
}
else
{
returnData.code = "FAILD";//错误的订单处理
returnData.message = "验证失败";
//此处可以给用户发送支付失败提示等
}
#region
var notifyJson = orderReturnJson.ToJson().ToString();
await payLog("WechatPay", "支付通知信息:" + notifyJson + "");
#endregion
//https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml
return orderReturnJson;
}
catch (Exception ex)
{
WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex));
throw;
}
}
/// <summary>
/// 订单查询
/// </summary>
/// <returns></returns>
public async Task<OrderReturnJson> OrderQuery(string out_trade_no = null, string transaction_id = null)
{
//out_trade_no transaction_id 两个参数不能都为空
if (out_trade_no is null && transaction_id is null)
{
throw new ArgumentNullException(nameof(out_trade_no) + " or " + nameof(transaction_id));
}
OrderReturnJson result = null;
//选择方式查询订单
if (out_trade_no is not null)
{
result = await _basePayApis.OrderQueryByOutTradeNoAsync(out_trade_no, TenPayV3Info.MchId);
}
if (transaction_id is not null)
{
result = await _basePayApis.OrderQueryByTransactionIdAsync(transaction_id, TenPayV3Info.MchId);
}
return result;
}
/// <summary>
/// 关闭订单
/// </summary>
/// <returns></returns>
public async Task<ReturnJsonBase> CloseOrder(string out_trade_no)
{
//out_trade_no transaction_id 两个参数不能都为空
if (out_trade_no is null)
{
throw new ArgumentNullException(nameof(out_trade_no));
}
ReturnJsonBase result = null;
result = await _basePayApis.CloseOrderAsync(out_trade_no, TenPayV3Info.MchId);
return result;
}
/// <summary>
/// 退款申请接口
/// </summary>
/// <returns></returns>
public async Task<RefundReturnJson> Refund(string transactionId, decimal totalFee,string? paymentRefundNumber,string reason)
{
try
{
string outRefundNo;
await payLog("WechatRefund", "1asdasdas");
string nonceStr = TenPayV3Util.GetNoncestr();
await payLog("WechatRefund", "2 退款微信单号transactionId" + transactionId + "/n 订单金额:" + totalFee);
if (!string.IsNullOrEmpty(paymentRefundNumber))
{
outRefundNo = paymentRefundNumber;
}
else
{
outRefundNo = CreateNo_Recharge();
}
int refundFee = Convert.ToInt32(totalFee);
string opUserId = TenPayV3Info.MchId;
var notifyUrl = AppSettings.GetConfig("SenparcWeixinSetting:TenPayV3_RefundNotify");
//var dataInfo = new TenPayV3RefundRequestData(TenPayV3Info.AppId, TenPayV3Info.MchId, TenPayV3Info.Key,
// null, nonceStr, null, outTradeNo, outRefundNo, totalFee, refundFee, opUserId, null, notifyUrl: notifyUrl);
//TODO:该接口参数二选一传入
var dataInfo = new RefundRequsetData(transactionId, null, outRefundNo, reason, notifyUrl, null, new RefundRequsetData.Amount(refundFee, null, refundFee, "CNY"), null);
await payLog("WechatRefund", "2.5 DataInfo" + dataInfo.ToJson());
//#region 新方法Senparc.Weixin v6.4.4+
//var result = TenPayOldV3.Refund(_serviceProvider, dataInfo);//证书地址、密码,在配置文件中设置,并在注册微信支付信息时自动记录
//#endregion
var result = await _basePayApis.RefundAsync(dataInfo);
await payLog("WechatRefund", "3 退款结果Result" + result.ToJson());
return result;
//return Json(result, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
await payLog("WechatRefund", "报错:" + ex.Message);
WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex));
throw;
}
}
/// <summary>
/// 退款通知地址
/// </summary>
/// <returns></returns>
public async Task<RefundNotifyJson> RefundNotifyUrl()
{
await payLog("WechatRefund", "允许被访问IP" + HttpContext.UserHostAddress()?.ToString());
NotifyReturnData returnData = new();
var resHandler = new TenPayNotifyHandler(HttpContext);
var refundNotifyJson = await resHandler.AesGcmDecryptGetObjectAsync<RefundNotifyJson>();
try
{
await payLog("WechatRefund", "退款支付结果:" + refundNotifyJson.ToJson());
string refund_status = refundNotifyJson.refund_status;
if (/*refundNotifyJson.VerifySignSuccess == true &*/ refund_status == "SUCCESS")
{
returnData.code = "SUCCESS";
returnData.message = "OK";
//填写逻辑
await payLog("WechatRefund", "验证通过");
}
else
{
returnData.code = "FAILD";
returnData.message = "验证失败";
await payLog("WechatRefund", "验证失败");
}
return refundNotifyJson;
//进行后续业务处理
}
catch (Exception ex)
{
returnData.code = "FAILD";
returnData.message = ex.Message;
WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex));
}
return refundNotifyJson;
//https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_3.shtml
}
#region
/// <summary>
/// 小程序支付返回的参数
/// </summary>
public class PayParams
{
/// <summary>
/// 产品Guid
/// </summary>
public JsApiUiPackage jsApiUiPackage { get; set; }
/// <summary>
/// 系统订单号
/// </summary>
public string outTradeNo { get; set; }
/// <summary>
///订单下单时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
///订单结束时间
/// </summary>
public DateTime OverTime { get; set; }
}
/// <summary>
/// 小程序支付接口的参数
/// </summary>
public class PayDto
{
/// <summary>
/// 客户Guid
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 支付类型
/// </summary>
public int PayType { get; set; }
/// <summary>
/// 小程序用户OpenId
/// </summary>
public string OpenId { get; set; }
/// <summary>
/// 核销前价格
/// </summary>
public decimal BeforeMoney { get; set; }
/// <summary>
/// 核销后价格/操作金额
/// </summary>
public decimal Money { get; set; }
/// <summary>
/// 留言
/// </summary>
public string Remark { get; set; }
}
/// <summary>
/// 小程序支付订单查询的参数
/// </summary>
public class OrderQueryDto
{
/// <summary>
/// 系统订单号
/// </summary>
public string? outTradeNo { get; set; }
/// <summary>
/// 微信支付订单号
/// </summary>
public string? transactionId { get; set; }
}
/// <summary>
/// 小程序支付需要的参数
/// </summary>
public class PayRequesEntity
{
/// <summary>
/// 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间
/// </summary>
public string timeStamp { get; set; }
/// <summary>
/// 随机字符串长度为32个字符以下。
/// </summary>
public string nonceStr { get; set; }
/// <summary>
/// 统一下单接口返回的 prepay_id 参数值
/// </summary>
public string package { get; set; }
/// <summary>
/// 签名算法
/// </summary>
public string signType { get; set; }
/// <summary>
/// 签名
/// </summary>
public string paySign { get; set; }
}
#endregion
#region
/// <summary>
/// 微信支付日志
/// </summary>
/// <param name="dirName"></param>
public async Task payLog(string dirName,string data)
{
var logDir = ServerUtility.ContentRootMapPath(string.Format("~/App_Data/{0}/{1}", dirName , 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))
{
await fileStream.WriteAsync(Encoding.Default.GetBytes(data), 0, Encoding.Default.GetByteCount(data));
fileStream.Close();
}
}
#endregion
/// <summary>
/// 申请资金账单接口
/// </summary>
/// <param name="date">日期格式如2021-08-27</param>
/// <returns></returns>
public async Task<string> FundflowBill(string date)
{
var filePath = $"{date}-FundflowBill.csv";
Console.WriteLine("FilePath:" + filePath);
using (var fs = new FileStream(filePath, FileMode.OpenOrCreate))
{
BasePayApis basePayApis = new BasePayApis();
var result = await _basePayApis.FundflowBillQueryAsync(date, fs);
fs.Flush();
}
return "已经下载倒指定目录,文件名:" + filePath;
}
public static object _lock = new object();
public static int count = 1;
/// <summary>
/// 退款订单号生成
/// </summary>
/// <returns></returns>
public static string CreateNo_Recharge()
{
lock (_lock)
{
if (count >= 10000)
{
count = 1;
}
var number = "R" + DateTime.Now.ToString("yyMMddHHmmssfff") + count.ToString("0000");
count++;
return number;
}
}
}
}