源码: 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; } }
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); }
主要用于扫码,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(); }
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; } }
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) + "}"; //} }
公共参数类里包含两个方法,设置不同支付方式及设置支付主体
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; } }
支付主体里也有一个方法设置销售产品码,用于设置商家和支付宝签约的产品码
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; } } }
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); }
实现接口类(敲黑板,画重点)
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 }; } } }
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 ~!