党费收缴管理系统(三) C# 调用jsapi实现微信支付功能技术难点解决_c#党费计算-程序员宅基地

技术标签: c#  微信  党费缴纳系统开发  开发语言  

本人不是专门的程序员,所以代码质量我自己也知道很渣,纯属为了跑通功能写的.

  1. JSAPI使用了腾讯支付的V3api, 要求证书加密解密的,之前用过PHP版本跑通过V2的,原本以为是件很简单的事情,发现自己低估了难度.
  2. 走了个弯路,对腾讯支付统一下单的流程自己误解了先后顺序,以为是首先调用腾讯的jsapi唤起微信来做支付,然后支付过程是统一下单,在这个问题上纠结了一个周末. 突然发现自己完全理解错了,是先要通过统一下单api生成prepay_id 后,再调用jsapi来唤起微信支付.
  3. 测试环境没有部署在公网,直接写hosts方式来,所以notify是收不到的,这个也很容易理解.目前自己也就跑到调用其支付后面暂时没写了,等后面再来补充.
  4. 因为使用的asp.net core mvc 模式,再调用jsapi总是报签名验证错误,但自己点断抓生成的签名和使用腾讯官方提供的签名验证工具校验是一直, 这个昨天折腾了一下午,最后发现是view视图编译后, 对页面上的paySign进行了转义,将+进行了转义,这是个大坑,而且很难发现,最后用@HTML.Raw 强制显示原文.
  5. 回顾整个撸码的过程,虽然腾讯官方没有提供v3 的sdk .net 的版本, 但一些核心关键代码是已经给出来了,比如签名过程.

这个是核心的签名过程的类,商户ID和证书序号在支付后台都可以获取

    public class HttpHandler : DelegatingHandler
    {
        private readonly string merchantId;   \\商户ID
        private readonly string serialNo;   \\证书序号
        public HttpHandler(string merchantId, string merchantSerialNo)
        {
            InnerHandler = new HttpClientHandler();
            this.merchantId = merchantId;
            this.serialNo = merchantSerialNo;
        }
        protected async override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            var auth = await BuildAuthAsync(request);
            string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";
            request.Headers.Add("Authorization", value);
            //官网的代码没有添加 User-Agent 和  Accept  导致一直报错, 但是api文档里面又提到需要.
            request.Headers.Add("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36 Edg/119.0.0.0");
            request.Headers.Add("Accept", "application/json");
            return await base.SendAsync(request, cancellationToken);
        }
        protected async Task<string> BuildAuthAsync(HttpRequestMessage request)
        {
            string method = request.Method.ToString();
            string body = "";
            if (method == "POST" || method == "PUT" || method == "PATCH")
            {
                var content = request.Content;
                body = await content.ReadAsStringAsync();
            }
            string uri = request.RequestUri.PathAndQuery;
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            string nonce = Path.GetRandomFileName();
            string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n";
            string signature = WxPayTool.Sign(message);
                //Sign(message);
            return $"mchid=\"{merchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{serialNo}\",signature=\"{signature}\"";
        }
        protected string Sign(string message)
        {
            //   私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY-----
            //        亦不包括结尾的-----END PRIVATE KEY-----
            string privateKey = @"";   //最好是用notepad去打开你腾讯支付后台获取到  apiclient_key.pem, 正式代码可以写个类去读私钥文件的内容,私钥存在指定位置.
            byte[] keyData = Convert.FromBase64String(privateKey);

            var rsa = RSA.Create();
            //适用该方法的版本https://learn.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.asymmetricalgorithm.importpkcs8privatekey?view=net-7.0
            rsa.ImportPkcs8PrivateKey(keyData, out _);
            byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
            return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
        }

控制器执行统一下单,并构造jaspi所需要的数据包

public  IActionResult  pay() {
    string prepay_id = "";
    OrderData orderData = new OrderData();    
    orderData.appid = "";    //企业微信ID
    orderData.mchid = "";   //商户ID
    orderData.description = "";
    orderData.out_trade_no = "DF-" + DateTime.Now.ToString("yyyyMMddHHmmssMM");
    orderData.notify_url = "";
    orderData.attach = "";
    orderData.amount = new OrderData.WxPayAmountModel
    {
        total = 1,
        currency = "CNY"
    };
    orderData.payer = new OrderData.Payer
    {
        openid = "oKpVN05g3KNRBTWGbPOnzw8Z64Mg"
    };
    string url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
    HttpClient client = new HttpClient(new HttpHandler(" #商户ID# ", " #商户证书序号#"));
    var bodyJson  = new   StringContent(JsonConvert.SerializeObject(orderData).ToString(), Encoding.UTF8, "application/json");
    var response = client.PostAsync(url, bodyJson);
    var rep = response.Result;//在这里会等待task返回
    var respStr = rep.Content.ReadAsStringAsync().Result;
     JObject jo = (JObject)JsonConvert.DeserializeObject(respStr);
    prepay_id = jo["prepay_id"].ToString();
    WxPrePayData prePayData = new WxPrePayData();
    prePayData.PrePayBillId = prepay_id;
    prePayData.NonceStr = Path.GetRandomFileName();

    prePayData.TimeStamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
    prePayData.PrePayAmount = 1;
    prePayData.AppId = "#企业微信ID#";
    prePayData.Package = "prepay_id=" + prePayData.PrePayBillId;   //这个prepay_id 是统一下单接口返回来的,这里我也折腾了好就才搞明白
    string message = prePayData.AppId + "\n" + prePayData.TimeStamp + "\n" + prePayData.NonceStr + "\n" + prePayData.Package + "\n";
    prePayData.PaySign = WxPayTool.Sign(message);

    ViewData["data"] = prePayData;

      return  View();
}
public class OrderData
{
    public string appid {  get; set; }
    public string mchid { get; set; }
    public string description { get; set; }

    public string out_trade_no { get; set; }

    public string notify_url { get; set; }

    public string attach { set; get; }

    public WxPayAmountModel amount { set; get; }
    public Payer payer { set; get; }    

    public class WxPayAmountModel
    {
        /// <summary>
        /// 订单总金额,单位为分。
        /// </summary>
        public int total { set; get; }

        /// <summary>
        /// 货币类型,CNY:人民币,境内商户号仅支持人民币。
        /// </summary>
        public string currency { set; get; } = "CNY";
    }

    public class Payer { 
    
        public string openid { set; get; }
    }
}

public class WxPrePayData
{
     public string  AppId { get; set; }
    public string TimeStamp { get; set; }
    public string NonceStr { get; set; }
    public string Package { get; set; }
    public string PaySign { get; set;}
    public static string signType = "RSA";

}

支付前端核心js代码直接用的官网上的实例, 我这里是点击id=“pay” 的按钮调用jsapi支付,加载页面前已经完成了统一下单过程.

    $("#pay").click(function () {

        function onBridgeReady() {
            WeixinJSBridge.invoke('getBrandWCPayRequest', {
                "appId": "@data.AppId",     //公众号ID,由商户传入
                "timeStamp": "@data.TimeStamp",     //时间戳,自1970年以来的秒数
                "nonceStr": "@data.NonceStr",      //随机串
                "package": "@data.Package",
                "signType": "RSA",     //微信签名方式:
                "paySign": "@Html.Raw(data.PaySign)"  //微信签名
            },
                function (res) {
                    alert(res.err_msg);
                    if (res.err_msg == "get_brand_wcpay_request:ok") {
                        // 使用以上方式判断前端返回,微信团队郑重提示:
                        //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
                    }
                });
        }
        if (typeof WeixinJSBridge == "undefined") {
            if (document.addEventListener) {
                document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
            } else if (document.attachEvent) {
                document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
                document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
            }
        } else {
            onBridgeReady();
        }


    })

另外就是控制器需要和你在支付后台设置的jsapi的目录保持一致的.

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/waxyy002/article/details/136497248

智能推荐

八进制转二进制_8进制46.5转换为二进制-程序员宅基地

文章浏览阅读7.4w次,点赞13次,收藏25次。位(bit) 一位二进制数,又称比特字节(byte) 1B = 8b 内存存储的最小单元字长:同一时间内,计算机能处理的二进制位数字长决定了计算机的运算精度,字长越长,计算机的运算精度就越高。因此,高性能的计算机,其字长较长,而性能较差的计算机,其字长相对要短一些。    其次,字长决定了指令直接寻址的能力。一般机器的字长都是字节的1、2、4、8倍。微机的字长为8位、16_8进制46.5转换为二进制

Ruoyi-Cloud 踩坑的BUG_若依 no message available-程序员宅基地

文章浏览阅读5.3k次,点赞6次,收藏15次。Ruoyi-Cloud报错统计端口工具项目测试ruoyi官网常见问题端口Web server failed to start. Port 端口 was already in use.( Web服务器无法启动。端口8080已在使用中。)解决:打开cmd: netstat -ano | findstr 8080TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 9824TCP [::]:8080 _若依 no message available

短视频抖音seo矩阵系统源码:技术开发与实践(三)-程序员宅基地

文章浏览阅读972次。(6)评论表(comment): 存储用户评论信息,如评论ID、用户ID、视频ID、评论内容、评论时间等。(1)用户表(user):存储用户信息,如用户ID、用户名、密码、性别、年龄、地区、注册时间等。

Postman接口测试——我看过最详细+全面的文章教程了【转载】-程序员宅基地

文章浏览阅读2.9w次,点赞59次,收藏488次。Postman接口测试——我看过最详细+全面的文章教程了【转载】_postman接口测试

Thymeleaf基础 遍历List、Map、List「map」、Map「List」_thymeleaf遍历list<map>-程序员宅基地

文章浏览阅读6.6w次,点赞21次,收藏35次。@RequestMapping("/hello") public String hello(Model map){ // 将要遍历的map Map user= new HashMap(); user.put("name", "姓名"); user.put("age", "年龄"); user.put("sex", "性别"); user.put("birth..._thymeleaf遍历list

java框架开发面试题,Java面试心得必备技能储备详解-程序员宅基地

文章浏览阅读817次,点赞20次,收藏18次。作为一名即将求职的程序员,面对一个可能跟近些年非常不同的 2019 年,你的就业机会和风口会出现在哪里?在这种新环境下,工作应该选择大厂还是小公司?已有几年工作经验的老兵,又应该如何保持和提升自身竞争力,转被动为主动?就目前大环境来看,跳槽成功的难度比往年高很多。一个明显的感受:今年的面试,无论一面还是二面,都很考验Java程序员的技术功底。最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“Java经典面试问题(含答案解析).pdf和一份网上搜集的“Java程序员面试笔试真题库.pdf。

随便推点

云计算虚拟化技术与开发-------虚拟化技术应用第五章内容(纯软件/半虚拟化/直接分配三种I/O虚拟化方案的对比、virtio实现I/O半虚拟化的原理、气球技术的作用和原理、V2V在线迁移的特点)_迁移virt-v2v -ic qemu+ssh://192.168.2.87/system -o l-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏2次。纯软件/半虚拟化/直接分配三种I/O虚拟化方案的对比、virtio实现I/O半虚拟化的原理、气球技术的作用和原理、V2V在线迁移的特点、作用及KVM中的运行步骤、KVM虚拟化的安全技术架构、QEMU monitor的基本使用_迁移virt-v2v -ic qemu+ssh://192.168.2.87/system -o local -os /root/dcaaslab/

ssm/php/node/python青少年编程课程教学评价-程序员宅基地

文章浏览阅读698次,点赞20次,收藏25次。随着编程教育的普及,如何确保教学质量,如何让青少年在学习编程的过程中获得最佳的学习体验和知识掌握,是教育者和家长共同关心的问题。教学评价“1287e”的开发和应用,不仅能够帮助教师了解教学过程中的优势和不足,还能够为学生提供个性化的学习反馈,从而促进学生的个性化发展。从长远来看,青少年编程课程的教学评价不仅对提升当前的教学效果具有重要意义,对于培养未来社会所需的创新人才也具有深远的影响。探讨和实施有效的教学评价机制,对于推动青少年编程教育的发展,乃至整个教育体系的创新都具有重要意义。

2.3 在 DOM 元素中插入 html 代码_js 操作dom 插入html-程序员宅基地

文章浏览阅读824次。<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>插入内容为HTML代码</title> <script src="../js/vue.js"></script></head><body> <div id="app"> <h2&g._js 操作dom 插入html

RouterOS与爱快软路由有哪些区别?_routeros和爱快哪个强大-程序员宅基地

文章浏览阅读6.6k次,点赞37次,收藏24次。选择合适的软路由应基于实际需求和使用场景,对于普通用户和专业用户来说,不同的软路由可能有不同的优势和适用性。最终选择应当考虑网络功能、易用性、稳定性以及所需的特定功能需求。_routeros和爱快哪个强大

解决“STSong-Light‘ with ‘UniGB-UCS2-H‘ is not recognized“问题_font 'stsong-light' with 'unigb-ucs2-h' is not rec-程序员宅基地

文章浏览阅读6.2w次,点赞8次,收藏20次。iText5.x版本以上中的font和encoding文件都是从String RESOURCE_PATH = “com/itextpdf/text/pdf/fonts/”加载的,而itextasian1.5.x.jar的包名是com.lowagie.text.pdf.fonts, 包名不一致,导致路径错误。解决方法如下:   1.将itextasian1.5.x.ja解压,找到里面itextasi......_font 'stsong-light' with 'unigb-ucs2-h' is not recognized.

推荐文章

热门文章

相关标签