首页 > 编程语言 >.net core 实现微信支付-小程序支付(服务端代码)

.net core 实现微信支付-小程序支付(服务端代码)

时间:2024-03-22 14:44:36浏览次数:33  
标签:core set string get 微信 支付 new public

前言

前段时间研究了下微信支付-小程序支付的功能。但微信支付文档中关于.net C#的语言的sdk没有,只有java go 和php版本的,当然社区也有很多已经集成好的微信支付.net core sdk,但他们的的又都太臃肿了(我只需要个微信支付即可),集成度也特别深,用起来还得先学习下他们的文档,拿来主义固然好,主要的是我希望更清晰的了解微信支付的流程和集成方式,使用他们的sdk等以后对微信支付流程有更清晰的了解后再用也不迟,这里打算自己实现了。在此整理下以防自己忘了。
接入前准备这里就不提了,参考官方文档,一步一步来就可以了。

实现思路

java和C# 基本上差不多,照着Java的sdk来就可以了。

这里涉及到几个关键配置。

  • 商户号 mchid
  • 商户注册申请和商户apiv3证书(APIv3证书私钥) private.key 调用微信api时签名使用
  • 商户apiv3证书密钥 merchant_serial_no
  • 微信平台证书的公钥 public.key 验证微信响应时验签使用
  • 小程序的 appid
  • 对应小程序的支付用户的openid

发起支付

发起支付的文档参考小程序下单
这里的小程序下单并不会直接发起支付,而是生成一个预订单号,prepay_id,当有了这个id后然后再调用小程序发起支付接口进行实际支付。

以下是代码小程序下单的C#版

点击查看代码
public class WeiXinPayOrderRequestDTO
    {
        /// <summary>
        /// 应用id
        /// </summary>
        public string appid { get; set; }

        /// <summary>
        /// 商户id
        /// </summary>
        public string mchid { get; set; }

        /// <summary>
        /// 商品描述
        /// </summary>
        public string description { get; set; }

        /// <summary>
        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一。
        /// </summary>
        public string out_trade_no { get; set; }

        /// <summary>
        /// 订单失效时间,遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,yyyy-MM-DD表示年月日,
        /// T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区
        /// (+08:00表示东八区时间,领先UTC8小时,即北京时间)。
        /// 例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒
        /// </summary>
        public string time_expire { get; set; }

        /// <summary>
        /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用,实际情况下只有支付完成状态才会返回该字段。
        /// 本系统存放的是订单id
        /// </summary>
        public string attach { get; set; }

        /// <summary>
        ///  通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。
        /// </summary>
        public string notify_url { get; set; }

        /// <summary>
        /// 是否开通发票
        /// </summary>
        public bool support_fapiao { get; private set; } = false;

        /// <summary>
        /// 订单金额
        /// </summary>
        public WeiXinPayOrderAmout amount { get; set; }

        /// <summary>
        /// 支付人
        /// </summary>
        public WeiXinPayOrderPayer payer { get; set; }


        /// <summary>
        /// 结算信息
        /// </summary>
        public WeiXinPayOrderSettleInfo settle_info { get; private set; } = new WeiXinPayOrderSettleInfo
        {
            profit_sharing = false,
        };
    }

public class WeiXinPayOrderAmout
    {
        /// <summary>
        ///总金额
        /// </summary>
        public int total { get; set; }

        /// <summary>
        /// CNY:人民币,境内商户号仅支持人民币。
        /// </summary>
        public string currency { get; private set; } = "CNY";
    }
public class WeiXinPayOrderPayer
    {
        /// <summary>
        /// 小程序对应的用户openid
        /// </summary>
        public string openid { get; set; }
    }

public class WeiXinPayOrderSettleInfo
    {
        /// <summary>
        /// 是否指定分账
        /// </summary>
        public bool profit_sharing { get; set; }
    }
 public async Task<WeiXinPayOrderResponseDTO> SubmitOrderAsync(WeiXinPayOrderRequestDTO request)
 {
     string nonceStr = StringUtils.RandomNonceString(10);
     long timeStamp = DateTimeHelper.NowTimeStamp();
     HttpRequestMessage httpRequest = new()
     {
         Method = HttpMethod.Post
     };
     request.appid = options.AppId;
     request.mchid = options.Mchid;
     string json = request.ToJson();
     httpRequest.Content = new StringContent(json, Encoding.UTF8, "application/json");
     httpRequest.Headers.Add("Accept", "*/*");
     httpRequest.Headers.Add("User-Agent", "dotnet/6.0");

     httpRequest.RequestUri = new Uri("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
     string orderTosignData = BuildSubmitOrderToSignData(httpRequest.Method, httpRequest.RequestUri, timeStamp, nonceStr, json);
     string signature = "";
     // 创建RSA加密服务提供程序实例
     using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
     {
         // 加载私钥
         rsa.ImportFromPem(options.ApiPrivateKeyContent);

         // 将待签名的数据转换为字节数组
         byte[] dataBytes = Encoding.UTF8.GetBytes(orderTosignData);

         // 计算SHA256 with RSA签名
         byte[] signatureBytes = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

         // 将签名结果进行Base64编码
         signature = Convert.ToBase64String(signatureBytes);

         // 输出签名值
         Console.WriteLine("签名值: " + signature);
     }
     string authrizationValue = $"mchid=\"{options.Mchid}\",serial_no=\"{options.ApiSerialNumber}\",nonce_str=\"{nonceStr}\",timestamp=\"{timeStamp}\",signature=\"{signature}\"";
     httpRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("WECHATPAY2-SHA256-RSA2048", authrizationValue);
     HttpResponseMessage response = await httpClient.SendAsync(httpRequest);
     string responseString = await response.Content.ReadAsStringAsync();
     Console.WriteLine($"Response: {responseString}");
     var orderResult = Newtonsoft.Json.JsonConvert.DeserializeObject<WeiXinPayOrderResponseDTO>(responseString);

     return orderResult;
 }

然后是调用微信支付所需要的requestPayment body

点击查看代码
public class WeiXinPayrequestPayment
{
    /// <summary>
    /// 时间戳
    /// </summary>
    public string timeStamp { get; set; }

    /// <summary>
    /// 随机字符
    /// </summary>
    public string nonceStr { get; set; }

    /// <summary>
    /// 支付的预支付号
    /// </summary>
    public string package { get; set; }

    /// <summary>
    /// 签名类型
    /// </summary>
    public string signType { get; } = "RSA";


    /// <summary>
    /// 小程序支付发起的签名
    /// </summary>
    public string paySign { get; set; }

}
public async Task<WeiXinPayrequestPayment> WeiXinPayrequestPayment(string prepayId)
{
    string nonceStr = StringUtils.RandomNonceString(16);
    string timeStamp = DateTimeHelper.NowTimeStamp().ToString();
    string package = $"prepay_id={prepayId}";
    string dataToSign = options.AppId + "\n" + timeStamp + "\n" + nonceStr + "\n" + package + "\n";
    string sign = "";
    // 创建RSA加密服务提供程序实例
    using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
    {
        // 加载私钥
        rsa.ImportFromPem(options.ApiPrivateKeyContent);

        // 将待签名的数据转换为字节数组
        byte[] dataBytes = Encoding.UTF8.GetBytes(dataToSign);

        // 计算SHA256 with RSA签名
        byte[] signatureBytes = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

        // 将签名结果进行Base64编码
        sign = Convert.ToBase64String(signatureBytes);
    }
    return new WeiXinPayrequestPayment
    {
        nonceStr = nonceStr,
        package = package,
        paySign = sign,
        timeStamp = timeStamp,
    };
}

微信异步通知的验签与解密

解密
微信异步通知的body中存在加密部分, 其中的body中ciphertext字段是密文,商户端需要使用apiv3的密钥进行解密,文档参见
如何加密解密敏感信息代码如下:

点击查看代码
        private async Task<string> WechatAesGcmDecrypt(string associatedData, string nonce, string ciphertext)
        {
            GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());

            AeadParameters aeadParameters = new AeadParameters(
                new KeyParameter(Encoding.UTF8.GetBytes(options.ApiAESKey)),// 商户APIv3 密钥
                128,
                Encoding.UTF8.GetBytes(nonce),
                Encoding.UTF8.GetBytes(associatedData));
            gcmBlockCipher.Init(false, aeadParameters);
            byte[] data = Convert.FromBase64String(ciphertext);
            byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];
            int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);
            gcmBlockCipher.DoFinal(plaintext, length);
            return Encoding.UTF8.GetString(plaintext);
        }

验签:
由于签名算法中,调用微信api后微信响应的数据(response)和微信主动请求商户地址的数据(wechat request)都是使用微信平台证书的私钥进行签名的。尤为注意,验签这里必须使用微信平台证书中的公钥public_key进行验签操作。

点击查看代码
 public async Task<bool> VerifyWeiXinSign(string nonce, long timestamp, string serial, string body, string signature)
 {            
     logger.LogInformation($"开始验证微信签名,\n nonce:{nonce}" + $"\n timestamp:{timestamp}"
         + $"\n serial:{serial}\n body:{body}\n signature:{signature}");
     try
     {
         // 将Unix时间戳转换为DateTime  
         DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp).ToLocalTime();
         // 获取当前时间  
         DateTime currentTime = DateTime.Now;
         // 计算时间差  
         TimeSpan timeDifference = currentTime.Subtract(dateTime);
         if (timeDifference.Minutes > 20)
         {
             throw new Exception("当前请求已过期");
         }
         string dataToSign = $"{timestamp}\n{nonce}\n{body}\n";
         // 待验签的数据
         byte[] dataBytes = Encoding.UTF8.GetBytes(dataToSign);

         using var reader = new StringReader(options.WechatPlatformPublicKey);
         var pemReader = new PemReader(reader);
         object obj = pemReader.ReadObject();
         RsaKeyParameters publicKeyParams = null;
         if (obj is RsaKeyParameters)
         {
             publicKeyParams = (RsaKeyParameters)obj;
         }
         else
         {
             throw new Exception("公钥内容加载失败,Invalid public key format.");
         }

         // 签名
         byte[] signatureBytes = Convert.FromBase64String(signature);
         var verifier = SignerUtilities.GetSigner("SHA256withRSA");
         verifier.Init(false, publicKeyParams);
         verifier.BlockUpdate(dataBytes, 0, dataBytes.Length);
         bool isVaild = verifier.VerifySignature(signatureBytes);
         logger.LogInformation($"微信验证结果 nonce:{nonce} isVaild:{isVaild}");
         return isVaild;
     }
     catch (Exception ex)
     {
         throw;
     }
     finally
     {
         logger.LogInformation($"结束验证微信签名");
     }
 }

重中之重

  1. 第三方(商户)调用微信api的签名中使用的密钥 都是商户apiv3证书的私钥进行签名的也就是private_key。
  2. 在微信通知到第三方的通知url上或者调用完微信接口后的Response,必须使用微信平台证书的公钥进行验签的,也就是publick_key。
  3. 由于微信调整,微信平台证书可能会定期更换,这里需要定期更新微信平台证书的公钥。具体参考平台证书更换指引由于我只处于研究阶段,所以这里的微信平台证书自动更换就没往下研究。

参考链接
微信支付文档-接入前准备-小程序支付
微信支付文档-签名
微信支付文档-关键概念

标签:core,set,string,get,微信,支付,new,public
From: https://www.cnblogs.com/dyhuang/p/18086603

相关文章

  • java毕业设计二手物品交易微信小程序[附源码]
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:在数字化时代的浪潮下,微信小程序凭借其无需下载安装、即用即走的便捷特性迅速普及,成为互联网应用的新宠。伴随着社会消费观念的变迁和绿色环保意识的提升......
  • springboot/java/php/node/python微信小程序的宠物管理系统【计算机毕设】
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:随着社会的发展和人们生活水平的提高,宠物已经成为越来越多家庭的重要成员。伴随着宠物数量的增加,宠物管理问题也日益凸显,如宠物的健康监护、日常饮食管理......
  • 毕业设计3283基于微信的选修课考勤签到小程序的设计与实现【源代码+文档+调试+讲解视
    摘要本文旨在设计一个基于微信的选修课考勤签到小程序,实现服务器端、教师模块和学生模块的功能需求。通过详细的功能需求分析、数据库设计、界面设计以及测试和优化,本文将为该小程序的开发提供全面的指导。开发技术微信小程序;JSP技术;JAVA语言;MYSQL数据库微信小程序微信......
  • 微信公众号开发 - 扫描带参数二维码事件支持EventKey字符串传参
    $access_token=$this->access_token();//获取access_token$json_url='https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token='.$access_token;$scene_id="A123B";$curl_data='{"action_name&......
  • 7*24h新闻自动发送至微信(requests+pywinauto)
    7*24h新闻自动发送至微信(requests+pywinauto)1.爬取新闻列表2.发送至微信指定联系人1.爬取新闻列表目标地址:https://kuaixun.eastmoney.com/获取新闻列表importtime,requests,jsonimportpandasaspdsession=requests.session()headers={'User-Agent'......
  • 超简单的.net Core上传文件到七牛云保存
    经过我一天的努力,在网上查找内容,外加看官网配置,再加请教前辈,终于总结出一个简单的方法来实现这个功能1、需要你注册七牛的账号,提交实名认证2、登录七牛云平台->对象存储->新建空间我用的是这个NuGet包,每个人都有每个人的方法,可以借鉴我的开发环境:操作系统:Windows10家庭中......
  • CorelDRAW2024专业的平面设计软件,专注于矢量图形编辑与排版
    CorelDRAW2024是一款专业的平面设计软件,专注于矢量图形编辑与排版。它可以提供无缝的图形、版面、插图、照片编辑、摹图、网络图像、印刷项目、美术作品、排版等设计体验,并可应用于商标设计、标志制作、模型绘制、插图描画、排版及分色输出等领域。CDR2024绿色版下载链接:ht......
  • 微信小程序开发第三章作业
    一、页面布局wxml代码:<viewclass="content"><viewclass="layout-top"><viewclass="screen">168</view></view><viewclass="layout-bottom"><viewclass="btnGroup"><......
  • 微信小程序(自定义tabBar)
     https://developers.weixin.qq.com/miniprogram/dev/framework/ability/custom-tabbar.html    重新编译后,底部的tabBar就显示的是自定义tabBar文件夹下面的index.wxml的内容了然后我们可以使用vant的组件定义我们的tabbar ......
  • 毕业设计3257 基于微信小程序的校园招聘信息管理系统的设计与实现【源代码+文档+调试+
    摘要本文介绍了一个基于微信小程序的校园招聘信息管理系统的设计与实现。该系统旨在为学生和企业提供一个便捷、高效的招聘信息交流平台。服务器端负责数据管理,包括用户、简历、岗位、应聘信息和论坛帖子的管理;学生和企业客户端则提供注册登录、信息浏览、搜索、发布、管理......