发布:2022/4/19 17:13:37作者:管理员 来源:本站 浏览次数:1732
1. 前言
经过一个多周的焦傲、摧残,终于完成了微信支付及退款,做一下总结,主要是参数、签名、数据接收问题有几个小点要注意,本文基于c#进行开发。
2. 项目背景
项目为商城,这就需要支付功能,主要做的就是支付模块,所以就要实现现在主流的支付方式,如微信支付、支付宝支付…
项目业务:系统暂时要求点击支付时网站弹出生成的微信支付二维码,由用户进行扫描购买,数据回调记录数据库。
微信支付平台:转到
给出的支付方式如下图,本文主要讲Native支付模式二
在这里插入图片描述
3.Native支付
Native支付场景介绍→转到
扫码支付模式二文档→转到
准备工作
(1)申请 微信商户号 微信公众号 (这里具体步骤不描述,因为我做的商城,这里有商家去微信平台申请);
(2)开通 Native支付(点击开通,很简单,但是是商家的事,不解释);
(3)微信认证证书(商家提供,仅退款时跟撤销订单时需要);
①证书路径,注意应该填写绝对路径;
②证书文件不能放在web服务器虚拟目录,应放在有访问权限控制的目录中,防止被他人下载;
③建议将证书文件名改为复杂且不容易猜测的文件;
④商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。
下载微信官方提供的工具类Demo,地址:转到
在这里插入图片描述
解压并进入WxPayAPI文件夹,将 business example lib 三个文件夹复制到咱们项目的工程中(本项目使用VS[Visual Studio 2015] ,如果不会添加 ,VS将复制过来的文件或文件夹显示到解决方案管理)
在这里插入图片描述在这里插入图片描述
下面来说一下这三个文件夹
(1)business文件夹中主要是 NativePay.cs 类,推荐其他文件也不要删除;
(2)example文件夹中ResultNotifyPage.aspx页面,将此文件放在工程根目录下,MakeQRCode.aspx页面保留,其他文件建议删除;
(3)lib文件夹全部保留,因为这里面是所依赖的工具类。
在这里插入图片描述在这里插入图片描述
后端进行页面配置
(1)对于lib文件夹,仅需修改DemoConfig.cs[作用:配置微信商户号和公众号、回调地址等相关信息];(我是商城网站,商家总后台设置参数,这里从数据库调出,根据个人需求)
在这里插入图片描述Appsert公众帐号secert(仅JSAPI支付的时候需要配置)给出了设置了也没关系,
在这里插入图片描述支付只需要这私四个参数:
在这里插入图片描述在这里插入图片描述
(2)对于business文件夹,仅需修改NativePay.cs文件[作用:用来调用微信接口根据我们提供的数据生成二维码链接]
点击支付ajax提交到后台的一个静态方法:(仅供借鉴payment为支付类型(微信、支付宝),indentid订单ID)
$.ajax({
contentType: "application/json",
url: "pay.aspx/Pay_btn",
type: "post",
data: JSON.stringify({"payment":payment,"indentid":indentid}),
dataType: "json",
success: function (date) {
if (payment == "1") {
alert(date.d);
} else {
window.location.href="Wx_Payment-" + indentid + "-" + date.d + ".html";
}
}
});
后台静态方法里,Encrypt()加密方法
NativePay nativePay = new NativePay();//定义这个类
string url2 = nativePay.GetPayUrl(DESEncrypt.Decrypt(indentid));//indentid订单ID(返回二维码的url)
string imgurl = "WX/example/MakeQRCode.aspx?data=" + HttpUtility.UrlEncode(url2);//(将url加入二维码生成页面)
return DESEncrypt.Encrypt(imgurl);//(加密返回给前台)
nativepay类(直接看模式二,根据个人逻辑写入参数):
例:
public string GetPayUrl(string indentid)
{
// Log.Info(this.GetType().ToString(), "Native pay mode 2 url is producing...");
IndentBLL indentbll = new IndentBLL();//订单类
IndenterBLL indenterbll = new IndenterBLL();//订单明细类
Indent showindent = indentbll.GetModel(int.Parse(indentid));//通过订单ID获取当前订单
BasicBLL basicbll = new BasicBLL();//网站信息设置类
DataTable showbasic = new DataTable();
showbasic = basicbll.GetAllList().Tables[0];//获取网站信息设置
WxPayData data = new WxPayData();
data.SetValue("body", showbasic.Rows[0]["Webname"].ToString());//商品描述(我这里给的是网站标题中文,默认是只能英文字符串,如何改下边说到)
data.SetValue("attach", showindent.OrderNo);//附加数据,如商家数据包,自身系统生成的订单号
data.SetValue("out_trade_no", showindent.OrderNo);//随机字符串(系统生成的订单号)
data.SetValue("total_fee",Convert.ToInt32((showindent.Total*100)).ToString());//总金额
data.SetValue("time_start", DateTime.Now.ToString("yyyyMMddHHmmss"));//交易起始时间
data.SetValue("time_expire", DateTime.Now.AddMinutes(10).ToString("yyyyMMddHHmmss"));//交易结束时间
data.SetValue("goods_tag", showindent.UserID);//商品标记 会员用户名
data.SetValue("trade_type", "NATIVE");//交易类型
data.SetValue("product_id", indentid);//商品ID(订单ID)
WxPayData result = WxPayApi.UnifiedOrder(data);//调用统一下单接口
string url = result.GetValue("code_url").ToString();//获得统一下单接口返回的二维码链接
//Log.Info(this.GetType().ToString(), "Get native pay mode 2 url : " + url);
return url;
}
注:1. body,传入值必须为英文,负责签名错误;2.total_fee商品金额 单位是分!!!单位是分!!!单位是分!!!3.trade_type交易类型 一定为NATIVE。
(3)返回的url做了一个页面Wx_Payment.aspx接收传入加密的订单ID(indnetid)和加密的imgurl;
局部代码获取两个值,Decrypt()为解密方法:
string indentid = Request.QueryString["key1"];
imgurl =DESEncrypt.Decrypt(Request.QueryString["key2"]);
showindent = indentbll.GetModel(int.Parse(DESEncrypt.Decrypt(indentid)));//查询订单
showindenter = indenterbll.GetList("IndentID="+ int.Parse(DESEncrypt.Decrypt(indentid))).Tables[0];//查询订单明细
在这里插入图片描述效果:
在这里插入图片描述(4)回调,用户扫码支付成功后微信平台异步回调ResultNotifyPage.aspx
后台调用了ResultNotify 类:
protected void Page_Load(object sender, EventArgs e)
{
//File.WriteAllText(Server.MapPath("/log.txt"), Request.Url.ToString() + "==" + DateTime.Now.ToString());
ResultNotify resultNotify = new ResultNotify(this);
resultNotify.ProcessNotify();
}
ResultNotify 类里:
public override void ProcessNotify()
{
WxPayData notifyData = GetNotifyData();
HelpClass helpclass = new HelpClass();//自己写的帮助类
//File.WriteAllText(HttpContext.Current.Server.MapPath("/log2.txt"), notifyData.ToXml() + "==" + DateTime.Now.ToString());
//File.WriteAllText(HttpContext.Current.Server.MapPath("/log3.txt"), notifyData.IsSet("transaction_id").ToString() + "==" + DateTime.Now.ToString());
//检查支付结果中transaction_id是否存在
if (!notifyData.IsSet("transaction_id"))
{
//若transaction_id不存在,则立即返回结果给微信支付后台
WxPayData res = new WxPayData();
res.SetValue("return_code", "FAIL");
res.SetValue("return_msg", "支付结果中微信订单号不存在");
//File.WriteAllText(HttpContext.Current.Server.MapPath("/log4.txt"), res.ToXml() + "==" + DateTime.Now.ToString());
page.Response.Write(res.ToXml());
page.Response.End();
}
string transaction_id = notifyData.GetValue("transaction_id").ToString();//获取微信平台返回的交易号
string orderno= notifyData.GetValue("out_trade_no").ToString();//返回的有传入的系统自动生成的订单号
//File.WriteAllText(HttpContext.Current.Server.MapPath("/log5.txt"), transaction_id + "="+ QueryOrder(transaction_id).ToString() + "=" + DateTime.Now.ToString());
//查询订单,判断订单真实性
if (!QueryOrder(transaction_id))
{
//若订单查询失败,则立即返回结果给微信支付后台
WxPayData res = new WxPayData();
res.SetValue("return_code", "FAIL");
res.SetValue("return_msg", "订单查询失败");
page.Response.Write(res.ToXml());
page.Response.End();
}
//查询订单成功
else
{
//File.WriteAllText(HttpContext.Current.Server.MapPath("/log6.txt"), "=sql=" + DateTime.Now.ToString());
WxPayData res = new WxPayData();
res.SetValue("return_code", "SUCCESS");
res.SetValue("return_msg", "OK");
helpclass.Update_table("Indent", "PaymentType=2,PaymentNumber='" + transaction_id + "',PaymentTime='" + DateTime.Now.ToString() + "',PaymentStatus=1,OrdStatus=2", "OrderNo='" + orderno + "'");//通过订单编号修改支付状态,支付类型,处理结果,微信交易号,订单状态,支付时间等(修改数据库信息)
page.Response.Write(res.ToXml());
page.Response.End();
}
}
订单表一个支付状态字段,在用户扫码页面隔段时间判断一下是否支付,付过了就给支付页面关了,不能用户付完之后一直还停留在扫码二维码页面,然后二维码扫码页面(Wx_Payment.aspx)ajax一秒一提交订单ID(indentid)到后台判断订单支付状态,付过了跳转到订单详情页;
<script>
window.setInterval(function () {
var entid = $("#entid").val();
$.ajax({
contentType: "application/json",
url: "Wx_Payment.aspx/Status",
type: "post",
data: JSON.stringify({ "indentid": entid }),
dataType: "json",
success: function (date) {
if (date.d == true) {
window.location.href = "order_info-" + entid + ".html";
}
}
});
}, 1000);
</script>
后台:
[WebMethod]
public static bool Status(string indentid)
{
IndentBLL indentbll = new IndentBLL();
Indent showindent = indentbll.GetModel(int.Parse(DESEncrypt.Decrypt(indentid)));
if (showindent.PaymentStatus == 1)//PaymentStatus支付状态
{
return true;
}
else
{
return false;
}
}
(5)传参时body设置中文签名错误问题,参考:https://blog.csdn.net/YuanMxy/article/details/89331113
找到Data类的CalcHMACSHA256Hash方法将var enc = Encoding.Default;改为var enc = Encoding.UTF8;
微信支付、支付参数还可以参考:https://blog.csdn.net/YuanMxy/article/details/89359573?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158632862519724845049815%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=158632862519724845049815&biz_id=14&utm_source=distribute.pc_search_result.none-task-blog-soetl_SOETL-18
(7)退款,此时需要证书和证书密码,在DemoConfig.cs加入
用户点击退款
$('.tuibtn').click(function () {
var id = $(this).attr("tid");//获取要退款订单的ID
$.ajax({
contentType: "application/json",
url: "SelectIndent.aspx/TuiKuan",
type: "post",
data: JSON.stringify({ "indentid": id }),//订单ID传入后台的静态方法
dataType: "json",
success: function (date) {
if (date.d == "ok") {
alert("退款成功!");
window.location.reload();
} else if (date.d == "no") {
alert("退款失败!");
}
else {
alert("退款失败!" + date.d);
}
}
});
});
后台:
[WebMethod]
public static string TuiKuan(string indentid)
{
HelpClass helpclass = new HelpClass();
IndentBLL indentbll = new IndentBLL();
Indent showindent = indentbll.GetModel(int.Parse(indentid));//获取当前订单
try
{
WxPayData result = Refund.Run(showindent.PaymentNumber,showindent.OrderNo,Convert.ToInt32(showindent.Total*100).ToString(),Convert.ToInt32((Convert.ToDecimal(showindent.Refund_fee)*100)).ToString());//调起退款
string result_code = result.GetValue("result_code").ToString();//业务结果(SUCCESS)
string out_trade_no= result.GetValue("out_trade_no").ToString();//商户订单号(系统自己生产的订单号)
string out_refund_no= result.GetValue("out_refund_no").ToString();//商户退款订单号(自己生成的返回回来了)
string refund_id= result.GetValue("refund_id").ToString();//微信退款单号
string refund_fee= result.GetValue("refund_fee").ToString();//实际退款金额
if(result_code== "SUCCESS")//证明退款成功了
{
helpclass.Update_table("Indent", "Payrefund_bool='1',Refund_id='" + refund_id + "',Refund_no='" + out_refund_no + "',Yesrun_fee='" + (Convert.ToDecimal(refund_fee)/100).ToString() + "'", "OrderNo='" + out_trade_no + "'");//保存处理结果(记录到数据库,修改当前订单,自己的逻辑代码)
IndenterBLL indenterbll = new IndenterBLL();
DataTable showindenter = new DataTable();
GoodsBLL goodsbll = new GoodsBLL();
showindenter = indenterbll.GetList("IndentID=" + indentid).Tables[0];//获取当前订单的订单详情
foreach (DataRow item in showindenter.Rows)
{
if (goodsbll.GetList("ID=" + int.Parse(item["GoodID"].ToString())).Tables[0].Rows.Count > 0)
{
Goods goods = goodsbll.GetModel(int.Parse(item["GoodID"].ToString()));
helpclass.Update_table("Goods", "Sales=" + (goods.Sales - (int.Parse(item["GoodCount"].ToString()))) + ",Inventory=" + (goods.Inventory + (int.Parse(item["GoodCount"].ToString()))), "ID=" + (int.Parse(item["GoodID"].ToString())));//修改商品的销售量和库存量
}
}
return "ok";
}
else
{
return "no";
}
}
catch (WxPayException ex)
{
return ex.ToString();
}
catch (Exception ex)
{
return ex.ToString();
}
}
Refund类的 Run方法:
/***
* 申请退款完整业务流程逻辑
* @param transaction_id 微信订单号(优先使用)
* @param out_trade_no 商户订单号
* @param total_fee 订单总金额
* @param refund_fee 退款金额
* @return 退款结果(xml格式)
*/
public static WxPayData Run(string transaction_id, string out_trade_no, string total_fee, string refund_fee)
{
//File.WriteAllText(HttpContext.Current.Server.MapPath("/log.txt"), transaction_id + "==" + out_trade_no+"=="+ total_fee+"=="+ refund_fee+"=="+DateTime.Now.ToString());
WxPayData data = new WxPayData();
if (!string.IsNullOrEmpty(transaction_id))//微信订单号存在的条件下,则已微信订单号为准
{
data.SetValue("transaction_id", transaction_id);
}
else//微信订单号不存在,才根据商户订单号去退款
{
data.SetValue("out_trade_no", out_trade_no);
}
data.SetValue("total_fee", int.Parse(total_fee));//订单总金额
data.SetValue("refund_fee", int.Parse(refund_fee));//退款金额
data.SetValue("out_refund_no", WxPayApi.GenerateOutTradeNo());//随机生成商户退款单号
data.SetValue("op_user_id", WxPayConfig.GetConfig().GetMchID());//操作员,默认为商户号
WxPayData result = WxPayApi.Refund(data);//提交退款申请给API,接收返回数据
//File.WriteAllText(HttpContext.Current.Server.MapPath("/log1.txt"), result.ToXml()+"==" + DateTime.Now.ToString());
return result;
}
WxPayApi的Refund():
/**
*
* 申请退款
* @param WxPayData inputObj 提交给申请退款API的参数
* @param int timeOut 超时时间
* @throws WxPayException
* @return 成功时返回接口调用结果,其他抛异常
*/
public static WxPayData Refund(WxPayData inputObj, int timeOut = 6)
{
string url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
//检测必填参数
if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
{
throw new WxPayException("退款申请接口中,out_trade_no、transaction_id至少填一个!");
}
else if (!inputObj.IsSet("out_refund_no"))
{
throw new WxPayException("退款申请接口中,缺少必填参数out_refund_no!");
}
else if (!inputObj.IsSet("total_fee"))
{
throw new WxPayException("退款申请接口中,缺少必填参数total_fee!");
}
else if (!inputObj.IsSet("refund_fee"))
{
throw new WxPayException("退款申请接口中,缺少必填参数refund_fee!");
}
else if (!inputObj.IsSet("op_user_id"))
{
throw new WxPayException("退款申请接口中,缺少必填参数op_user_id!");
}
inputObj.SetValue("appid", WxPayConfig.GetConfig().GetAppID());//公众账号ID
inputObj.SetValue("mch_id", WxPayConfig.GetConfig().GetMchID());//商户号
inputObj.SetValue("nonce_str", Guid.NewGuid().ToString().Replace("-", ""));//随机字符串
inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
inputObj.SetValue("sign", inputObj.MakeSign());//签名
string xml = inputObj.ToXml();
var start = DateTime.Now;
string response = HttpService.Post(xml, url, true, timeOut);//调用HTTP通信接口提交数据到API
var end = DateTime.Now;
int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时
//将xml格式的结果转换为对象以返回
WxPayData result = new WxPayData();
result.FromXml(response);
ReportCostTime(url, timeCost, result);//测速上报
return result;
}
Post():
public static string Post(string xml, string url, bool isUseCert, int timeout)
{
System.GC.Collect();//垃圾回收,回收没有正常关闭的http连接
string result = "";//返回结果
HttpWebRequest request = null;
HttpWebResponse response = null;
Stream reqStream = null;
try
{
//设置最大连接数
ServicePointManager.DefaultConnectionLimit = 200;
//设置https验证方式
if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback =
new RemoteCertificateValidationCallback(CheckValidationResult);
}
/***************************************************************
* 下面设置HttpWebRequest的相关属性
* ************************************************************/
request = (HttpWebRequest)WebRequest.Create(url);
request.UserAgent = USER_AGENT;
request.Method = "POST";
request.Timeout = timeout * 1000;
//设置代理服务器
//WebProxy proxy = new WebProxy(); //定义一个网关对象
//proxy.Address = new Uri(WxPayConfig.PROXY_URL); //网关服务器端口:端口
//request.Proxy = proxy;
//设置POST的数据类型和长度
request.ContentType = "text/xml";
byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
request.ContentLength = data.Length;
//是否使用证书
if (isUseCert)
{
string path = HttpContext.Current.Request.PhysicalApplicationPath;
X509Certificate2 cert = new X509Certificate2(path + WxPayConfig.GetConfig().GetSSlCertPath(), WxPayConfig.GetConfig().GetSSlCertPassword());//将证书相对地址拼成了绝对地址
request.ClientCertificates.Add(cert);
}
//往服务器写入数据
reqStream = request.GetRequestStream();
reqStream.Write(data, 0, data.Length);
reqStream.Close();
//获取服务端返回
response = (HttpWebResponse)request.GetResponse();
//获取服务端返回数据
StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
result = sr.ReadToEnd().Trim();
sr.Close();
}
catch (System.Threading.ThreadAbortException e)
{
Log.Error("HttpService", "Thread - caught ThreadAbortException - resetting.");
Log.Error("Exception message: {0}", e.Message);
System.Threading.Thread.ResetAbort();
}
catch (WebException e)
{
Log.Error("HttpService", e.ToString());
if (e.Status == WebExceptionStatus.ProtocolError)
{
Log.Error("HttpService", "StatusCode : " + ((HttpWebResponse)e.Response).StatusCode);
Log.Error("HttpService", "StatusDescription : " + ((HttpWebResponse)e.Response).StatusDescription);
}
throw new WxPayException(e.ToString());
}
catch (Exception e)
{
Log.Error("HttpService", e.ToString());
throw new WxPayException(e.ToString());
}
finally
{
//关闭连接和流
if (response != null)
{
response.Close();
}
if(request != null)
{
request.Abort();
}
}
return result;
}
注:WxPayData 支付或退款时传进去的参数,成功后微信平台有的参数不一定传回,付款时统一下单传进和返回的参数见:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1,退款时见:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_4
© Copyright 2014 - 2024 柏港建站平台 ejk5.com. 渝ICP备16000791号-4