C# 实现微信退款及对帐

目录

需求

基础准备

关键代码

操作界面

​编辑

退款订单类及方法

退款功能实现

对帐

支付商家后台相关要点

实时交易帐单查询

精确交易帐单查询

小结

需求

在招聘报名系统里,考务费支付是其中一个环节,支付方式很多种,比如银联、微信、支付宝等等。本次我们以微信支付进行举例,在考生注册账号、编写简历、报名职位、被初审核通过等一系列基础的条件的具备下,可以进入支付考务费的环节(笔试费用),我们会为其生成一个支付二维码,考生支付后(无论成功与否),都会记录其支付结果状态。

在实际的应用中,对于支付成功的考生,我们会遇到实现退款的需求,只要包括如下场景:

1、根据政策规定,某些符合全部或部分退款条件的考生。

2、其它未知原因,重复支付订单的考生。

3、其它不可抗力,需求进行退款的考生。

基础准备

在实现功能前,做为企业,我们需要申请一个微信服务号,并成为微信支付商家。

1、申请服务号

申请成功后会获得到 AppId 和 AppSecret 用于后续开发,如关联支付商户、网页授权登录等。

具体指引请参照微信公众平台首页:https://mp.weixin.qq.com/cgi-bin/loginpage

2、成为微信支付商家

申请成功后会获得 Mchid 和 paySignKey 用于微信支付、退款等,请在商家后台务必关联申请的公众号。

具体指引请参照微信支付平台首页:https://pay.weixin.qq.com/index.php/core/home/login

上述两个平台申请成功后,请登录微信支付商家平台,进行如下图操作:

在产品中心、AppID帐号管理、关联 AppID(即申请的服务号) 

另外一个重要配置是支付目录,我们写的支付程序需要在这里设置,如下图:

关键代码

操作界面

界面上会显示最近一笔的微信订单支付情况,包括订单号、交费时间、交费金额、退款金额。其中退款金额不能大于成功交费金额,否则会返回失败。另外,还可以显示微信交易跟踪日志列表信息,如果订单号、交易价格、openid、返回信息、交易状态等。

示例界面如下:

退款订单类及方法

实现微信退款,需要在支付商家平台申请退款证书,证书文件保存到自定义的目录中,在退款时指定路径。

退款示例代码如下:

                const string RefundOrderUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";   //退款申请API地址const string RefundQueryUrl = "https://api.mch.weixin.qq.com/pay/refundquery";  //退款查询API地址
//退款订单明细类
public class RefundOrderDetail{/// <summary>/// 返回状态码,SUCCESS/FAIL 此字段是通信标识,非交易标识,交易是否成功需要查看trade_state来判断/// </summary>public string return_code = "";/// <summary>/// 返回信息返回信息,如非空,为错误原因 签名失败 参数格式校验错误/// </summary>public string return_msg = "";/// <summary>/// 业务结果,SUCCESS/FAIL/// </summary>public string result_code = "";/// <summary>/// 错误代码/// </summary>public string err_code = "";/// <summary>/// 错误代码描述/// </summary>public string err_code_des = "";/// <summary>/// 公众号ID(微信分配的公众账号 ID)/// </summary>public string appid = "";/// <summary>/// 商户号(微信支付分配的商户号)/// </summary>public string mch_id = "";/// <summary>/// 微信支付分配的终端设备号/// </summary>public string device_info = "";/// <summary>/// 随机字符串,不长于32位/// </summary>public string nonce_str = "";/// <summary>/// 签名/// </summary>public string sign = "";/// <summary>/// 微信支付订单号/// </summary>public string transaction_id = "";/// <summary>/// 商户系统的订单号,与请求一致。/// </summary>public string out_trade_no = "";public string out_refund_no = "";public string refund_id = "";public string refund_fee = "";public string settlement_refund_fee = "";/// <summary>/// 订单总金额,单位为分/// </summary>public string total_fee = "";/// </summary>public string settlement_total_fee = "";public string fee_type = "";public string cash_fee = "";public string cash_fee_type = "";public string cash_refund_fee = "";public string coupon_type_0 = "";public string coupon_refund_fee = "";public string coupon_refund_fee_0 = "";public string coupon_refund_count = "";public string coupon_refund_id_0 = "";}
//退款订单类public class RefundOrder{/// <summary>/// 公众号ID(微信分配的公众账号 ID)/// </summary>public string appid = "";/// <summary>/// 商户号(微信支付分配的商户号)/// </summary>public string mch_id = "";/// <summary>/// 微信支付分配的终端设备号/// </summary>public string device_info = "";/// <summary>/// 随机字符串,不长于 32 位/// </summary>public string nonce_str = "";/// <summary>/// 签名public string sign = "";public string sign_type = "";/// <summary>/// 商户系统内部的订单号,32个字符内、可包含字母,确保在商户系统唯一,详细说明/// </summary>public string transaction_id = "";public string out_trade_no = "";public string out_refund_no = "";/// <summary>/// 订单总金额,单位为分,不能带小数点/// </summary>public int total_fee = 0;public int refund_fee = 0;public string  refund_fee_type = "";public string op_user_id = "";/// <summary>public string refund_account = "";/// <summary>}
//查询对帐订单类public class QueryOrder{/// <summary>/// 公共号ID(微信分配的公众账号 ID)/// </summary>public string appid = "";/// <summary>/// 商户号(微信支付分配的商户号)/// </summary>public string mch_id = "";/// <summary>/// 微信订单号,优先使用/// </summary>public string transaction_id = "";/// <summary>/// 商户系统内部订单号/// </summary>public string out_trade_no = "";/// <summary>/// 随机字符串,不长于 32 位/// </summary>public string nonce_str = "";/// <summary>/// 签名,参与签名参数:appid,mch_id,transaction_id,out_trade_no,nonce_str,key/// </summary>public string sign = "";}//申请退款方法,返回退款订单明细类
//参数refundorder为退款订单类, key 为支付签名KEY,cert为证书地址,password 为证书密码public RefundOrderDetail getRefundOrderDetail(RefundOrder refundorder, string key,string cert,string password){string post_data = getRefundOrderXml(refundorder, key);string request_data = PostXmlAndCertToUrl(RefundOrderUrl, post_data,cert,password);RefundOrderDetail orderdetail = new RefundOrderDetail();SortedDictionary<string, string> requestXML = GetInfoFromXml(request_data);foreach (KeyValuePair<string, string> k in requestXML){switch (k.Key){case "retuen_code":orderdetail.result_code = k.Value;break;case "return_msg":orderdetail.return_msg = k.Value;break;case "result_code":orderdetail.result_code = k.Value;break;case "err_code":orderdetail.err_code = k.Value;break;case "err_code_des":orderdetail.err_code_des = k.Value;break;case "appid":orderdetail.appid = k.Value;break;case "mch_id":orderdetail.mch_id = k.Value;break;case "device_info":orderdetail.device_info = k.Value;break;case "nonce_str":orderdetail.nonce_str = k.Value;break;case "sign":orderdetail.sign = k.Value;break;case "transaction_id":orderdetail.transaction_id = k.Value;break;case "out_trade_no":orderdetail.out_trade_no = k.Value;break;case "out_refund_no":orderdetail.out_refund_no = k.Value;break;case "refund_id":orderdetail.refund_id = k.Value;break;case "refund_fee":orderdetail.refund_fee = k.Value;break;case "total_fee":orderdetail.total_fee = k.Value;break;case "settlement_refund_fee":orderdetail.settlement_refund_fee = k.Value;break;case "settlement_total_fee":orderdetail.settlement_total_fee = k.Value;break;case "fee_type":orderdetail.fee_type = k.Value;break;case "cash_fee":orderdetail.cash_fee = k.Value;break;case "cash_fee_type ":orderdetail.cash_fee_type = k.Value;break;case "cash_refund_fee":orderdetail.cash_refund_fee = k.Value;break;case "coupon_type_0":orderdetail.coupon_type_0 = k.Value;break;case "coupon_refund_fee":orderdetail.coupon_refund_fee = k.Value;break;case "coupon_refund_fee_0":orderdetail.coupon_refund_fee_0 = k.Value;break;case "coupon_refund_count":orderdetail.coupon_refund_count = k.Value;break;case "coupon_refund_id_0":orderdetail.coupon_refund_id_0 = k.Value;break;default:break;}}return orderdetail;}protected string getRefundOrderXml(RefundOrder refundorder, string key){string return_string = string.Empty;SortedDictionary<string, string> sParams = new SortedDictionary<string, string>();sParams.Add("appid", refundorder.appid);sParams.Add("mch_id", refundorder.mch_id);
//                    sParams.Add("transaction_id", refundorder.transaction_id);sParams.Add("out_trade_no", refundorder.out_trade_no);sParams.Add("nonce_str", refundorder.nonce_str);sParams.Add("out_refund_no", refundorder.out_refund_no);sParams.Add("total_fee", refundorder.total_fee.ToString());sParams.Add("refund_fee", refundorder.refund_fee.ToString());sParams.Add("op_user_id", refundorder.op_user_id);refundorder.sign = getsign(sParams, key);sParams.Add("sign", refundorder.sign);//拼接成XML请求数据StringBuilder sbPay = new StringBuilder();foreach (KeyValuePair<string, string> k in sParams){if (k.Key == "attach" || k.Key == "body" || k.Key == "sign"){sbPay.Append("<" + k.Key + "><![CDATA[" + k.Value + "]]></" + k.Key + ">");}else{sbPay.Append("<" + k.Key + ">" + k.Value + "</" + k.Key + ">");}}return_string = string.Format("<xml>{0}</xml>", sbPay.ToString().TrimEnd(','));return return_string;}public string PostXmlAndCertToUrl(string url, string postData,string cert,string password){string resp = string.Empty;ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult);//调用证书System.Security.Cryptography.X509Certificates.X509Certificate2 cer = new System.Security.Cryptography.X509Certificates.X509Certificate2(cert, password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.PersistKeySet | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet);HttpWebRequest webrequest = (HttpWebRequest)HttpWebRequest.Create(url);webrequest.ClientCertificates.Add(cer);webrequest.Method = "post";webrequest.ContentType = "application/x-www-form-urlencoded";webrequest.ContentLength = postData.Length;//webrequest.ContentType = "text/xml";//byte[] data = System.Text.Encoding.UTF8.GetBytes(postData);//webrequest.ContentLength = data.Length;HttpWebResponse response = null;try{StreamWriter swRequestWriter = new StreamWriter(webrequest.GetRequestStream());swRequestWriter.Write(postData);if (swRequestWriter != null)swRequestWriter.Close();response = (HttpWebResponse)webrequest.GetResponse();using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)){resp = reader.ReadToEnd();}}catch (Exception exp){throw exp;}finally{if (response != null)response.Close();}return resp;}public string getNoncestr(){Random random = new Random();return GetMD5(random.Next(1000).ToString(), "GBK").ToLower().Replace("s", "S");}

退款功能实现

假设点击退款按钮事件

protected void Button_Click(object sender, EventArgs e)
{string appId = “”; //服务号的appIdstring paySignKey = “”; //申请的支付签名KEY;string mch_id = “”;  //申请的支付商户IDstring OrderID = "";  //支付订单号string OrderAmount = (Convert.ToInt32((float.Parse(Amount.Text) * 100))).ToString();  //订单支付金额,Amount.Text 支付金额string RefundOrderAmount = (Convert.ToInt32((float.Parse(Amount.Text) * 100))).ToString();   //退款金额(Amount.Text)这里表示全额退款string RefundOrderID = Guid.NewGuid().ToString().Replace("-", "");   //生成退款订单号//创建退款订单RefundOrder order = new RefundOrder();order.appid = appId;order.mch_id = mch_id;order.out_trade_no = OrderID;order.nonce_str = tenpay.getNoncestr();order.out_refund_no = RefundOrderID;order.total_fee = int.Parse(OrderAmount);order.refund_fee = int.Parse(RefundOrderAmount);order.op_user_id = mch_id;string cert = “d:\\apiclient_cert.p12";  //退款证书路径//私钥(在安装证书时设置)string password =""; //证书密码
//创建订单明细类,调用getRefundOrderDetail方法进行退款RefundOrderDetail orderdetail = getRefundOrderDetail(order, paySignKey, cert, password);string rv = ("退款订单号:" + RefundOrderID + "<br>");try{rv += ("退款金额:" + (double.Parse(orderdetail.total_fee) / 100).ToString() + "<br>");}catch (Exception eee){rv += ("退款金额:<br>");}rv += ("<b>交易状态:&nbsp;" + (orderdetail.result_code == "SUCCESS" ? "退款申请成功" : "退款申请失败") + "(" + orderdetail.result_code + ")" + "</b><br>");rv += ("可能的错误描述:" + orderdetail.err_code_des);
}

对帐

退款申请成功后,仅为申请状态,需要通过查询退款情况以确定是否完成,该功能可以在考生方进行实现,考生可随时查询自己的对帐情况。

以下是参考代码,该代码可实现支付与退款的查询:

protected void queryOrder(object sender, EventArgs e)
{string OrderID =”“; //订单号string paytype = ”“;  //查询类型,支付消费或退款string appId = "";   //服务号 appidstring paySignKey = "";   //支付签名keystring mch_id = "";    //支付商户号if (paytype == "消费"){try{string openid = ”“;   QueryOrder order = new QueryOrder();order.appid = appId;order.mch_id = mch_id;order.out_trade_no = OrderID;order.nonce_str = getNoncestr();OrderDetail orderdetail = getOrderDetail(order, paySignKey);string rv = ("订单号:" + OrderID + "<br>");rv += ("付款人ID比对识别:" + (openid == orderdetail.openid ? "成功" : "失败") + "<br>");rv += ("交易金额:" + (double.Parse(orderdetail.total_fee) / 100).ToString() + "<br>");rv += ("<b>交易状态:&nbsp;" + (orderdetail.trade_state == "SUCCESS" ? "成功" : "失败") + "(" + orderdetail.trade_state + ")" + "</b><br>");rv += ("支付交易时间:" + (orderdetail.time_end != "" && orderdetail.time_end.Length == 14 ? orderdetail.time_end.Substring(0, 4) + "-" + orderdetail.time_end.Substring(4, 2) + "-" + orderdetail.time_end.Substring(6, 2) + " " + orderdetail.time_end.Substring(8, 2) + ":" + orderdetail.time_end.Substring(10, 2) + ":" + orderdetail.time_end.Substring(12, 2) : "") + "<br>");}catch (Exception ex){return;}}else if (paytype == "退款"){try{RefundOrder order = new RefundOrder();order.appid = appId;order.mch_id = mch_id;order.out_trade_no = OrderID;order.nonce_str = getNoncestr();RefundOrderDetail orderdetail = getRefundQueryOrderDetail(order, paySignKey);string rv = ("<b>交易状态:&nbsp;" + (orderdetail.result_code == "SUCCESS" ? "成功" : "失败") + "(" + orderdetail.result_code + ")" + "</b><br>");rv += ("其它说明:" + orderdetail.err_code_des);}catch (Exception ex){}}
}

支付商家后台相关要点

实时交易帐单查询

登录后台后,该操作可以进行实时交易的帐单对帐功能,以备在争议的时候进行查询,基本操作如下图:

点击交易中心、交易订单、批量订单查询、查询即可下载EXCEL格式的订单。

精确交易帐单查询

登录后台后,可查询精确交易帐单,该帐单每天10:00更新前一天的数据交易,我们可以进行CSV格式的下载,操作如下图:

点击交易中心、交易帐单、打包下载即可,请注意图中圈注的提示。 

小结

以上提供的代码仅供参考,在实际的应用中,我们还可以根据业务需要编写其它功能,如下载微信官方对帐单,导入到应用系统中,与业务数据进行对帐,以排查争议数据;查询订单结果状态以更新业务争议状态信息等。

以上就是自己的一些分享,时间仓促,不妥之处还请大家批评指正!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/225523.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GitHub----使用记录

一、上传文件到仓库 1、首先新建一个github仓库 然后先记住这一句指令 2、下载git工具 https://git-scm.com/downloads 下载工具安装不用运行 3、使用git工具上传文件并推送 找到你想上传的文件的位置&#xff0c;右击git Bush here git init &#xff1a;初始化这个仓…

【项目实战】SpringBoot连接openGauss

一&#xff1a;Docker安装openGauss 1.下载openGauss 安装好Docker好以后&#xff0c;执行如下命令下载openGauss3.0镜像。docker pull enmotech/opengauss:3.0.0 2.运行openGauss 执行如下命令docker run -itd --name opengauss \ --restartalways \ --privilegedtrue \ …

OpenAi Q* (Q Star)项目入门介绍

为初学者解释 Open Ai 的 Q*(Q Star) Q* 的两个可能来源。 1)Q 可能是指 "Q-learning",这是一种用于强化学习的机器学习算法。 Q 名称的由来*:把 "Q*"想象成超级智能机器人的昵称。 Q 的意思是这个机器人非常善于做决定。 它从经验中学习,就像你从玩…

什么是数据填报?

数据填报是报表用以满足用户提出的灵活报送数据的需求&#xff0c;能快速开发各类数据采集系统的专业功能。多源填报模型&#xff0c;可实现数据的多源抽取与多源回填&#xff0c;在同一张填报表上实现数据提交至多个不同的数据表、数据库。 随着业务快速变化和扩大&#xff0c…

邮件群发:避免垃圾邮箱,提升营销效果

群发邮件为什么会进入垃圾邮箱呢&#xff1f;常见的原因有&#xff1a;邮件内容出现问题、域名和IP的信誉度不高、退订数或投诉过多等原因。所以&#xff0c;营销人员在做EDM的时候&#xff0c;应该把握方式技巧&#xff0c;才能获取良好的营销效果&#xff0c;避免邮件成为垃圾…

特征可视化,代码详解

输入 输出 代码 import os import torch import torchvision as tv import torchvision.transforms as transforms import torch.nn as nn import torch.optim as optim import argparse import skimage.data import skimage.io import skimage.transform import numpy as np…

15:00面试,15:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

C++值常用集合算法

C值常用集合算法 set_intersection #include<iostream> using namespace std; #include<vector> #include<numeric> #include<algorithm>class MyPrint { public:void operator()(int val){cout << val<<" ";} };void test() {v…

EC 404 information economics

EC 404 information economics WeChat: zh6-86

【Cmake】Cmake基础学习

CMake学习 一、基础学习 1. 利用Cmake进行单个源代码构建可执行文件 (1)基础命令 最基本的 CMake项目是由单个源代码文件构建的可执行文件。对于这样的简单项目,只需要一个包含三个命令的 CMakeLists.txt 文件。 注意: 虽然 CMake 支持大写、小写和混合大小写命令,但是…

个人投资白银收益怎么样?

个人投资白银是可以带来丰厚的收益&#xff0c;但收益的具体情况取决于多种因素。以下是一些明确的答案和举例&#xff0c;帮助投资者更好地理解个人投资白银的收益情况。 白银市场的价格波动是决定投资收益的主要因素之一&#xff0c;白银价格受全球经济形势、地缘局势风险、…

运行时错误/缺陷到底是什么缺陷

运行时错误(Run-time Error)是一种跟程序运行状态相关的缺陷。这类缺陷不能通过直接禁用相关特性来屏蔽&#xff0c;而是需要通过分析变量的数值状态来发现可能的异常。简单来说&#xff0c;这些缺陷通常只有当程序执行起来以后&#xff0c;才能逐渐暴露出的缺陷&#xff0c;一…