源碼: 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 ~!