继上一篇 ".NET Core 微信小程序支付 --(统一下单) 后", 本文将实现统一退款功能, 能支付就应该能退款嘛, 一般涉及到钱的东西都会比较敏感, 所以在设计退款流程时一定要严谨, 不能出一点差错, 否则你将会面临自己掏腰包的可能, 下面我们来讲一讲退款的实现步骤.
目录
1, 退款应该场景及规则
2, 实现统一退款流程
3, 退款统一回调处理
4, 总结
退款应该场景及规则
当交易发生之后一段时间内, 由于买家或者卖家的原因需要退款时, 卖家可以通过退款接口将支付款退还给买家, 微信支付将在收到退款请求并且验证成功之后, 按照退款规则将支付款按原路退到买家帐号上.
规则
1, 交易时间超过一年的订单无法提交退款;
2, 微信支付退款支持单笔交易分多次退款, 多次退款需要提交原支付订单的商户订单号和设置不同的退款单号. 申请退款总金额不能超过订单金额. 一笔退款失败后重新提交, 请不要更换退款单号, 请使用原商户退款单号.
3, 请求频率限制: 150qps, 即每秒钟正常的申请退款请求次数不超过 150 次
错误或无效请求频率限制: 6qps, 即每秒钟异常或错误的退款申请请求不超过 6 次
4, 每个支付订单的部分退款次数不能超过 50 次
接口地址
接口链接: https://api.mch.weixin.qq.com/secapi/pay/refund
相关参数
官方退款文档
文档地址: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_4&index=6
实现统一退款流程
如果业务有多处退款流程, 可以将退款流程进行封装, 方便多位置调用; 如果公司有同主体下的不同小程序, 都需要退款功能, 也是可以进行封装, 针对不同的小程序进行退款.
去商户后台先下载证书, 退款时需要, 下图是商户后台下载证书的界面:
引用包:
- Senparc.Weixin.WxOpen
- Senparc.Weixin.TenPay
注册公众号, 小程序信息
- services.AddSenparcGlobalServices(Configuration)
- .AddSenparcWeixinServices(Configuration);
- IRegisterService register = RegisterService.Start(env, senparcSetting.Value).UseSenparcGlobal(false, null);
- register.UseSenparcWeixin(senparcWeixinSetting.Value, senparcSetting.Value)
- .RegisterTenpayV3(senparcWeixinSetting.Value, "appid");
统一退款代码实现
- publicbool RefundProduct(OrdOrderProduct ordOrderProduct, PayOrderMstParam payOrderMstParam, OrdOrderPayItem ordOrderPayItem, string appID, DateTime thisTime, ref string errMsg)
- {
- try
- {
- OrdOrderPayMst refPay = null;
- if (!PayOrderManager.CreatePayOrderMST(payOrderMstParam, thisTime, ref refPay, ref errMsg))
- {
- errMsg = "生成退款单出错!" + errMsg;
- return false;
- }
- var PayInfo = Senparc.Weixin.Config.SenparcWeixinSetting.Items[appID];
- string AppID = PayInfo.WxOpenAppId;
- string AppSecret = PayInfo.WxOpenAppSecret;
- string Mch_id = PayInfo.TenPayV3_MchId;// 商户号
- string Mch_key = PayInfo.TenPayV3_Key;// 商户密钥
- string notifyUrl = string.Format(PayInfo.TenPayV3_TenpayNotify, "RefundNotifyUrl");
- var timeStamp = TenPayV3Util.GetTimestamp();
- var nonceStr = TenPayV3Util.GetNoncestr();
- // 支付源单号
- string outTradeNo = ordOrderPayItem.PayNo;// 商户订单号 / 支付单号
- refPay.PayNoSource = ordOrderPayItem.PayNo;
- // 退款单号
- string outRefundNo = refPay.PayNo;// 新退款单号
- // 支付时的总金额
- int totalFee = (int)(ordOrderPayItem.PayPrice * 100);
- // 退款金额
- int refundFee = (int)(refPay.PayPrice * 100);
- string opUserId = PayInfo.TenPayV3_MchId;
- var dataInfo = new TenPayV3RefundRequestData(AppID, Mch_id, Mch_key,
- null, nonceStr, null, outTradeNo, outRefundNo, totalFee, refundFee, opUserId, null, notifyUrl: notifyUrl);
- //Logger.Info($"PayInfo={PayInfo.SerializeObject()}");
- //Logger.Info($"dataInfo={dataInfo.SerializeObject()}");
- //var basePath = AppContext.BaseDirectory;
- //var certPath = Path.Combine(basePath, "Config/apiclient_cert.p12");
- //var cert = @"D:\projects\orderapi.trydou.com\Config\apiclient_cert.p12";// 根据自己的证书位置修改
- //var password = Mch_id;// 默认为商户号, 建议修改
- // 配置好证书地址, V3 自动识别
- var result = TenPayV3.Refund(dataInfo);
- refPay.PayResult = result.SerializeObject();
- //Logger.Info("提交退款申请:" + refPay.PayResult);
- if (result.return_code.ToUpper() == "SUCCESS" && result.result_code.ToUpper() == "SUCCESS")
- {
- using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
- {
- // 业务处理
- // 提交事务
- scope.Complete();
- }
- return true;
- }
- else
- {
- errMsg = result.err_code_des;
- Logger.Error(string.Format("提交退款失败, 退款单号 ={0}, 关联订单号 ={1}, 关联产品 ={2}, 退款 result={3}",
- refPay.PayNo, refPay.RelationNo, refPay.RelationNos, refPay.PayResult));
- }
- }
- catch (Exception ex)
- {
- errMsg = ex.Message;
- Logger.Error(string.Format("提交退款异常: Message={0},StackTrace={1}", ex.Message, ex.StackTrace));
- }
- return false;
- }
注: 注意退款接口的参数, 如: 金额, 退款地址等, 确保参数正确, 一般微信会收到退款的请求指令, 微信处理成功后, 会异步回调退款的接口给服务器.
退款统一回调处理
直接上代码如下:
- /// <summary>
- /// 退款回调
- /// </summary>
- [HttpPost("RefundNotifyUrl")]
- public ActionResult RefundNotifyUrl()
- {
- ResponseResult result = new ResponseResult();
- ResponseHandler resHandler = new ResponseHandler(HttpContext);
- string return_code = resHandler.GetParameter("return_code");
- string return_msg = resHandler.GetParameter("return_msg");
- try
- {
- var mch_key = Senparc.Weixin.Config.SenparcWeixinSetting.TenPayV3_Key;
- if (return_code.ToUpper() == "SUCCESS")
- {
- //string result_code = resHandler.GetParameter("result_code");
- //string appId = resHandler.GetParameter("appid");
- //string mch_id = resHandler.GetParameter("mch_id");
- //string nonce_str = resHandler.GetParameter("nonce_str");
- string req_info = resHandler.GetParameter("req_info");
- var decodeReqInfo = TenPayV3Util.DecodeRefundReqInfo(req_info, mch_key);
- var decodeDoc = XDocument.Parse(decodeReqInfo);
- var refundNotifyXml = decodeDoc.SerializeObject();
- // 获取接口中需要用到的信息
- string out_trade_no = decodeDoc.Root.Element("out_trade_no").Value;
- string out_refund_no = decodeDoc.Root.Element("out_refund_no").Value;
- string transaction_id = decodeDoc.Root.Element("transaction_id").Value;
- string refund_id = decodeDoc.Root.Element("refund_id").Value;
- int total_fee = int.Parse(decodeDoc.Root.Element("total_fee").Value);
- int refund_fee = int.Parse(decodeDoc.Root.Element("refund_fee").Value);
- RefundNotifyParam param = new RefundNotifyParam()
- {
- PayNo = out_trade_no,// 商户订单号
- PayPrice = ((float)refund_fee.ToInt() / 100).ToDecimal(),// 退款金额
- Out_refund_no = out_refund_no,// 商户退款单号
- TransactionNo = transaction_id,// 微信订单号
- Refund_id = refund_id, // 微信退款单号
- };
- Logger.Info(string.Format("退款回调参数, return_code={0},return_msg={1},refundNotifyXml={2}", return_code, return_msg, refundNotifyXml));
- result = Service.RefundNotifyUrl(param);
- if (result.errno != 0)
- {
- // 回调处理逻辑失败
- Logger.Error(string.Format("退款回调业务处理失败: 退款单号 {0},{1}", param.Out_refund_no, result.errmsg));
- }
- else
- {
- Logger.Info(string.Format("退款回调业务处理成功, 退款单号:{0}", param.Out_refund_no));
- string xml = string.Format(@"<xml>
- <return_code><![CDATA[{0}]]></return_code>
- <return_msg><![CDATA[{1}]]></return_msg>
- </xml>", return_code, return_msg);
- return Content(xml, "text/xml");
- }
- }
- else
- {
- // 错误的订单处理
- Logger.Error(string.Format("退款回调失败, return_code={0},return_msg={1}", return_code, return_msg));
- }
- }
- catch (Exception ex)
- {
- Logger.Error(string.Format("退款回调异常: Message={0},StackTrace={1}", ex.Message, ex.StackTrace));
- }
- return Content("fail", "text/xml");
- }
注: 如果业务处理退款成功后, 请返回结果告诉微信 SUCCESS, 否则微信也会按规则不停发送退款回调给服务器, 直到次数用完为止, 具体查看上面规则文档.
总结
来源: https://www.cnblogs.com/hailang8/p/11508627.html