• .net core 支付宝,微信支付 一


    源码: https://github.com/aspros-luo/Qwerty.Payment/tree/develop

     支付宝支付:参考支付宝sdk及文档,https://docs.open.alipay.com/194

    前言:

    目前实现支付宝Native支付,手机网站支付,App支付,支付回调,退款申请,退款查询

    Native支付及手机支付是由前端加基础数据传入后端,后端加签拼装成html以二维码或form表单呈现

    APP支付由后端加签,返回加签结果给app,app直接调用sdk完成支付

    1:设置支付需要的config信息,考虑到会有不同appId,所以需要设置appId,私钥和公钥

    public static class AliPayConfig
        {
            public static void Init(string appId, string privateKey, string aliPublicKey, string returnUrl, string notifyUrl)
            {
                AppId = appId;
                PrivateKey = privateKey;
                AliPublicKey = aliPublicKey;
                ReturnUrl = string.IsNullOrWhiteSpace(returnUrl) ? notifyUrl : returnUrl;
                NotifyUrl = notifyUrl;
            }
    
            public static string AppId { get; private set; }
            //public static string Gateway { get; private set; } = "https://openapi.alipay.com/gateway.do";
            internal static string Gateway { get; private set; } = "https://openapi.alipaydev.com/gateway.do";
            public static string PrivateKey { get; private set; }
            public static string AliPublicKey { get; private set; }
            public static string ReturnUrl { get; private set; }
            public static string NotifyUrl { get; private set; }
        }
    View Code

    2:通用方法,组装数据,排序,加签,验签等

    2.1:拼装数据类

    主要用于字典排序拼装

    public static string BuildParamStr(Dictionary<string, string> param)
            {
                if (param == null || param.Count == 0)
                {
                    return "";
                }
                var ascDic = param.OrderBy(o => o.Key).ToDictionary(o => o.Key, p => p.Value);
                var sb = new StringBuilder();
                foreach (var item in ascDic)
                {
                    if (!string.IsNullOrEmpty(item.Value))
                    {
                        sb.Append(item.Key).Append("=").Append(item.Value).Append("&");
                    }
                }
                return sb.ToString().Substring(0, sb.ToString().Length - 1);
            }
    View Code

    主要用于扫码,jsapi手机网站支付拼装成一个form表单,(支付宝sdk中也有)

    public static string BuildHtmlRequest(IDictionary<string, string> sParaTemp, string strMethod, string strButtonValue)
            {
                //待请求参数数组
                var dicPara = sParaTemp;
                var sbHtml = new StringBuilder();
                //sbHtml.Append("<head><meta http-equiv="Content-Type" content="text/html" charset= "" + charset + "" /></head>");
                sbHtml.Append("<form id='alipaysubmit' name='alipaysubmit' action='"+AliPayConfig.Gateway+"?charset=utf-8' method='" + strMethod + "'>");
                foreach (var temp in dicPara)
                {
                    sbHtml.Append("<input  name='" + temp.Key + "' value='" + temp.Value + "'/>");
                }
                //submit按钮控件请不要含有name属性
                sbHtml.Append("<input type='submit' value='" + strButtonValue + "' style='display:none;'></form>");
                // sbHtml.Append("<input type='submit' value='" + strButtonValue + "'></form></div>");
                //表单实现自动提交
                sbHtml.Append("<script>document.forms['alipaysubmit'].submit();</script>");
                return sbHtml.ToString();
            }
    View Code

    2.2:签名类,RSA256签名,支付宝推荐rsa256签名方式,私钥是java格式,这里引用一个nuget包,转换成dotnet格式私钥

    Org.BouncyCastle

    /// <summary>
        /// Rsa 工具类
        /// </summary>
        internal static class GenerateRsaAssist
        {
            /// <summary>
            /// 加签
            /// </summary>
            /// <returns></returns>
            public static string RasSign(string content, string privateKey, SignType signType)
            {
                var singerType = "";
                if (signType == SignType.Rsa2)
                {
                    singerType = "SHA256WithRSA";
                }
                if (signType == SignType.Rsa)
                {
                    singerType = "SHA1withRSA";
                }
                var signer = SignerUtilities.GetSigner(singerType);
                var privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
                signer.Init(true, privateKeyParam);
                var plainBytes = Encoding.UTF8.GetBytes(content);
                signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
                var signBytes = signer.GenerateSignature();
                return Convert.ToBase64String(signBytes);
            }
            /// <summary>
            /// 验签
            /// </summary>
            /// <returns></returns>
            public static bool VerifySign(string content, string publicKey, string signData, SignType signType)
            {
                var singerType = "";
                if (signType == SignType.Rsa2)
                {
                    singerType = "SHA256WithRSA";
                }
                if (signType == SignType.Rsa)
                {
                    singerType = "SHA1withRSA";
                }
                var signer = SignerUtilities.GetSigner(singerType);
                var publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
                signer.Init(false, publicKeyParam);
                var signBytes = Convert.FromBase64String(signData);
                var plainBytes = Encoding.UTF8.GetBytes(content);
                signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
                var ret = signer.VerifySignature(signBytes);
                return ret;
            }
        }
    View Code

    3:基础类

    通用方法基本完成后剩下的只要基础类来组装数据了

    3.1:添加公共请求参数类

    internal class AliPayCommonModel
        {
            public string app_id { get; private set; } = AliPayConfig.AppId;
            public string method { get; private set; }
            public string format { get; private set; } = "JSON";
            public string return_url { get; private set; } = AliPayConfig.ReturnUrl;
            public string charset { get; private set; } = "utf-8";
            public string sign_type { get; private set; } = "RSA2";
            public string timestamp { get; private set; } = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}";
            public string version { get; private set; } = "1.0";
            public string notify_url { get;private set; }= AliPayConfig.NotifyUrl;
            public string biz_content { get; private set; }
            /// <summary>
            /// 设置支付方式
            /// </summary>
            /// <param name="payMethod"></param>
            internal void SetMethod(string payMethod)
            {
                method = payMethod;
            }
            /// <summary>
            /// 设置支付主题内容
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="pay"></param>
            internal void SetBizContent<T>(T pay)
            {
                var str = pay.GetType().GetProperties().OrderBy(o=>o.Name).Aggregate("", (current, item) => current + $""{item.Name}":"{item.GetValue(pay)}",");
                biz_content ="{"+ str.Substring(0,str.Length-1)+"}";
            }
    
            //public void SetBizContent(AliRefundModel refund)
            //{
            //    var str = refund.GetType().GetProperties().OrderBy(o => o.Name).Aggregate("", (current, item) => current + $""{item.Name}":"{item.GetValue(refund)}",");
            //    biz_content = "{" + str.Substring(0, str.Length - 1) + "}";
            //}
    
            //public void SetBizContent(AliRefundQueryModel refundQuery)
            //{
            //    var str = refundQuery.GetType().GetProperties().OrderBy(o => o.Name).Aggregate("", (current, item) => current + $""{item.Name}":"{item.GetValue(refundQuery)}",");
            //    biz_content = "{" + str.Substring(0, str.Length - 1) + "}";
            //}
    
        }
    View Code

    公共参数类里包含两个方法,设置不同支付方式及设置支付主体

    3.2:添加支付主体类

        public class AliPayModel
        {
            /// <summary>
            /// 商户交易订单号
            /// </summary>
            public string out_trade_no { get; set; }
            /// <summary>
            /// 支付类型
            /// </summary>
            public string product_code { get; private set; } = "FAST_INSTANT_TRADE_PAY";
            /// <summary>
            /// 支付金额
            /// </summary>
            public string total_amount { get; set; }
            /// <summary>
            /// 标题
            /// </summary>
            public string subject { get; set; }
            /// <summary>
            /// 有效时间
            /// </summary>
            public string timeout_express { get; set; } = "30m";
            /// <summary>
            /// 设置支付方式
            /// </summary>
            /// <param name="code"></param>
            internal void SetProductCode(string code)
            {
                product_code = code;
            }
        }
    View Code

    支付主体里也有一个方法设置销售产品码,用于设置商家和支付宝签约的产品码

    4:添加网络请求类,没什么特别的,将键值数据传入支付宝请求网关,主要用于退款申请,及退款查询,native支付及手机网站支付都只是在后端加签sign后拼装成html输出的

        internal class HttpUtil
        {
            //private static readonly string _defaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";
    
            //private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
            //{
            //    return true; //总是接受     
            //}
    
            internal static async Task<HttpResponseMessage> CreatePostHttpResponse(string url, IDictionary<string, string> parameters)
            {
                var listKeyValues = parameters.Keys.Select(key => new KeyValuePair<string, string>(key, parameters[key])).ToList();
                using (var client = new HttpClient())
                {
                    var httpContent = new FormUrlEncodedContent(listKeyValues);
                    var response = await client.PostAsync(url, httpContent);
                    return response;
                }
            }
        }
    View Code

    5:接口,及实现类

    定义支付方式接口,目前涵盖native,手机网站,app支付,退款申请,退款查询,当面付功能暂未实现

    public interface IAliPayService
        {
            /// <summary>
            /// page支付,支付信息组成页面表单数据,用于pc支付
            /// </summary>
            /// <returns></returns>
            AliPayRequest NativePay(AliPayModel payModel);
            /// <summary>
            /// app支付,app端发送加签字段,返回签名数据
            /// </summary>
            /// <returns></returns>
            AliPayRequest AppPay(string preSign);
            /// <summary>
            /// jsapi支付,用于网页支付
            /// </summary>
            /// <returns></returns>
            AliPayRequest JsApiPay(AliPayModel payModel);
            /// <summary>
            /// 退款申请接口,用户发起退款申请
            /// </summary>
            /// <returns></returns>
            Task<AliRefundResponse> AliRefund(AliRefundModel refundModel);
            /// <summary>
            /// 退款查询接口,用于确认退款是否成功
            /// </summary>
            /// <returns></returns>
            Task<AliRefundQueryResponse> AliRefundQuery(AliRefundQueryModel refundQueryModel);
            /// <summary>
            /// 支付回掉接口
            /// </summary>
            /// <returns></returns>
            AliNotifyRequest AliNotify(Stream aliReturnData);
        }
    View Code

    实现接口类(敲黑板,画重点)

       public class AliPayService : IAliPayService
        {
            public AliPayRequest NativePay(AliPayModel payModel)
            {
                payModel.SetProductCode("FAST_INSTANT_TRADE_PAY");
    
                var common = new AliPayCommonModel();
                common.SetMethod("alipay.trade.page.pay");
                common.SetBizContent(payModel);
    
                var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
                var str = BuildData.BuildParamStr(parameters);
    
                var sign = GenerateRsaAssist.RasSign(str, AliPayConfig.PrivateKey, SignType.Rsa2);
                parameters.Add("sign", sign);
    
                try
                {
                    var from = BuildData.BuildHtmlRequest(parameters, "post", "post");
                    return new AliPayRequest { IsSuccess = true, PreSign = str, Sign = sign, Result = from };
                }
                catch (Exception e)
                {
                    return new AliPayRequest { IsSuccess = false, PreSign = str, Sign = sign, Result = e.Message };
                }
            }
    
            public AliPayRequest AppPay(string preSign)
            {
                try
                {
                    //payModel.SetProductCode("QUICK_MSECURITY_PAY");
                    //var common = new AliPayCommonModel();
                    //common.SetMethod("alipay.trade.app.pay");
                    //common.SetBizContent(payModel);
                    //var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
                    //var str = BuildData.BuildParamStr(parameters);
                    var sign = GenerateRsaAssist.RasSign(preSign, AliPayConfig.PrivateKey, SignType.Rsa2);
                    //return UrlEncoder.Default.Encode(str)+$"&sign={sign}";
                    sign = UrlEncoder.Default.Encode(sign);
                    return new AliPayRequest { IsSuccess = true, PreSign = preSign, Sign = sign, Result = sign };
                }
                catch (Exception e)
                {
                    return new AliPayRequest { IsSuccess = false, PreSign = preSign, Sign = "", Result = e.Message };
                }
    
            }
    
            public AliPayRequest JsApiPay(AliPayModel payModel)
            {
                payModel.SetProductCode("QUICK_WAP_WAY");
    
                var common = new AliPayCommonModel();
                common.SetMethod("alipay.trade.wap.pay");
                common.SetBizContent(payModel);
    
                var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
                var str = BuildData.BuildParamStr(parameters);
    
                var sign = GenerateRsaAssist.RasSign(str, AliPayConfig.PrivateKey, SignType.Rsa2);
                parameters.Add("sign", sign);
    
                try
                {
                    var from = BuildData.BuildHtmlRequest(parameters, "post", "post");
                    return new AliPayRequest { IsSuccess = true, PreSign = str, Sign = sign, Result = from };
                }
                catch (Exception e)
                {
                    return new AliPayRequest { IsSuccess = false, PreSign = str, Sign = sign, Result = e.Message };
                }
            }
    
            public async Task<AliRefundResponse> AliRefund(AliRefundModel refundModel)
            {
                var common = new AliPayCommonModel();
                common.SetMethod("alipay.trade.refund");
                common.SetBizContent(refundModel);
    
                var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
                var str = BuildData.BuildParamStr(parameters);
    
                var sign = GenerateRsaAssist.RasSign(str, AliPayConfig.PrivateKey, SignType.Rsa2);
                parameters.Add("sign", sign);
    
                var response = await HttpUtil.CreatePostHttpResponse(AliPayConfig.Gateway, parameters);
                var result = await response.Content.ReadAsStringAsync();
    
                var jsonResult = JsonConvert.DeserializeObject<AliRefundResponse>(result);
                return jsonResult;
            }
    
            public async Task<AliRefundQueryResponse> AliRefundQuery(AliRefundQueryModel refundQueryModel)
            {
                var common = new AliPayCommonModel();
                common.SetMethod("alipay.trade.fastpay.refund.query");
                common.SetBizContent(refundQueryModel);
                var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
                var str = BuildData.BuildParamStr(parameters);
                var sign = GenerateRsaAssist.RasSign(str, AliPayConfig.PrivateKey, SignType.Rsa2);
                parameters.Add("sign", sign);
                var response = await HttpUtil.CreatePostHttpResponse(AliPayConfig.Gateway, parameters);
                var result = await response.Content.ReadAsStringAsync();
                var jsonResult = JsonConvert.DeserializeObject<AliRefundQueryResponse>(result);
                return jsonResult;
            }
    
            public AliNotifyRequest AliNotify(Stream aliReturnData)
            {
                try
                {
                    //获取回调参数
                    var s = aliReturnData;
                    int count;
                    var buffer = new byte[1024];
                    var builder = new StringBuilder();
                    while ((count = s.Read(buffer, 0, 1024)) > 0)
                    {
                        builder.Append(Encoding.UTF8.GetString(buffer, 0, count));
                    }
                    s.Flush();
                    s.Dispose();
                    //request 接收的字符串含有urlencode,这里需要decode一下
                    var alipayReturnData = builder.ToString().Split('&').ToDictionary(a => a.Split('=')[0], a => System.Net.WebUtility.UrlDecode(a.Split('=')[1]));
                    //获取sign
                    var sign = alipayReturnData["sign"];
                    //去除sign及signtype
                    alipayReturnData.Remove("sign");
                    alipayReturnData.Remove("sign_type");
                    //获取支付宝订单号及商户交易订单号
                    var tradeNo = alipayReturnData["trade_no"];
                    var tradeIds = alipayReturnData["out_trade_no"];
    
                    var dic = alipayReturnData.ToDictionary(d => d.Key, d => d.Value);
    
                    var preSign = BuildData.BuildParamStr(dic);
                    //验签
                    var result = GenerateRsaAssist.VerifySign(preSign, AliPayConfig.AliPublicKey, sign, SignType.Rsa2);
    
                    return result
                        ?
                        new AliNotifyRequest { IsVerify = true, PayNo = tradeNo, TradeIds = tradeIds, PayTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), Sign = sign, Content = preSign }
                        :
                        new AliNotifyRequest { IsVerify = false, PayNo = tradeNo, TradeIds = "", PayTime = "", Sign = sign, Content = preSign };
                }
                catch (Exception e)
                {
                    return new AliNotifyRequest { IsVerify = false, PayNo = "", TradeIds = "", PayTime = "", Sign = "", Content = e.Message };
                }
            }
        }
    View Code

    a.接口参数主体为支付主体类,方法实现前,主体设置销售产品码

    native支付:FAST_INSTANT_TRADE_PAY

    手机网站支付:QUICK_WAP_WAY(旧版手机网站支付未涵盖)

    b.设置公共参数,设置接口名

    native支付接口名:alipay.trade.page.pay

    手机网站支付接口名:alipay.trade.wap.pay

    c.app支付比较特殊,需要Ios或Android将数据拼接完成后传入后端加签返回给app,再由app支付sdk调起

    需要注意的一个问题是, var sign = GenerateRsaAssist.RasSign(preSign, AliPayConfig.PrivateKey, SignType.Rsa2);

    加签得到的sign需要urlencode一下再返回,不然app调起会出错

     sign = UrlEncoder.Default.Encode(sign);

    退款神器,及退款查询也需要添加主体类,这里就不再赘述

    下一篇应该补上微信支付

    支付宝支付如果有遗漏内容,请告知

    如果发现任何问题也烦请指正。

    long may the sunshine ~!

  • 相关阅读:
    Epplus导出excel
    访问GitHub需要修改hosts
    如何将你的.Net Core程序部署成为服务
    生成雪花Id类
    文件操作帮助类
    工作流-WikeFlow
    《C语言进阶剖析》课程目录
    《C++深度解析》课程目录
    USB URB的status及其代表的意义
    数据结构优秀博文整理
  • 原文地址:https://www.cnblogs.com/AsprosL/p/7661480.html
Copyright © 2020-2023  润新知