前言:最近做了App的支付功能,雖然最后的代碼只有巴掌大,但是踩坑的經歷真的是異常豐富,問就是官方坑爹的版本升級和core少的可憐的參考資料(望天)。
閱讀本文章您將收獲到:1.微信和支付寶支付的保姆級后端教程
2.官方文檔解析
3.獨家的踩坑提示
4.一個本地可運行的demo
注意事項:1. 僅后端代碼部分,不涉及任何前端
2.僅APP支付
代碼demo: https://files-cdn.cnblogs.com/files/rulasann/PayUtil.zip (已重新上傳)
part1:支付寶App支付
1.官方文檔:
https://opendocs.alipay.com/apis/api_1/alipay.trade.app.pay
2.參數說明(請結合官方文檔閱讀):
有兩種參數類型,公共參數和請求參數,如果調用官方的SDK,公共參數中的sign不需要自己簽。例:
IAopClient client = new DefaultAopClient("https://openapi.alipay.com/gateway.do", "app_id", "merchant_private_key", "json", "1.0", "RSA2", "alipay_public_key", "GBK", false);
公共參數中的biz_content即為請求參數,這里可不參照官方給的示例寫,他們有封裝好的model。例:
//以下為發起請求的最簡參數
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); model.TotalAmount = "0.01"; // 訂單總金額,單位為元,精確到小數點后兩位,取值范圍[0.01,100000000] //model.Body = ""; // 商品描述 model.Subject = "交易標題"; // 商品標題/交易標題/訂單標題/訂單關鍵字等 model.OutTradeNo = ""; // 商戶訂單號,由商家自定義,需保證商家系統中唯一。僅支持數字、字母、下划線 model.ProductCode = "QUICK_MSECURITY_PAY"; // 銷售產品碼,商家和支付寶簽約的產品碼。QUICK_MSECURITY_PAY:App支付。 request.SetBizModel(model); // 將業務model載入到request
關於notify_url,非必填,如果需要,可以這樣寫:
request.SetNotifyUrl(notify_url)
3.完整請求示例,需Nuget引用AlipaySDKNet.Standard,其次建議用一個實體類封裝配置信息:

1 public class AliPayBasicItem 2 { 3 /// <summary> 4 /// 開發者的應用ID , 必填 5 /// </summary> 6 public static string app_id = ""; 7 8 /// <summary> 9 /// 請求使用的編碼格式 10 /// </summary> 11 public static string charset = "utf-8"; 12 13 /// <summary> 14 /// 僅支持"JSON",非必填 15 /// </summary> 16 public static string format = "json"; 17 18 /// <summary> 19 /// 簽名算法 20 /// </summary> 21 public static string sign_type = "RSA2"; 22 23 /// <summary> 24 /// 調用的接口版本 25 /// </summary> 26 public static string version = "1.0"; 27 28 /// <summary> 29 /// 支付寶請求url 30 /// </summary> 31 public static string url = "https://openapi.alipay.com/gateway.do"; 32 33 /// <summary> 34 /// 商戶私鑰 (必填) 35 /// </summary> 36 public static string merchant_private_key = ""; 37 38 /// <summary> 39 /// 支付寶公鑰 (必填) 40 /// </summary> 41 public static string alipay_public_key = ""; 42 43 /// <summary> 44 /// 支付完成后的通知地址 非必填 45 /// </summary> 46 public static string pay_notify_url = ""; 47 48 /// <summary> 49 /// 幣種 50 /// </summary> 51 public static string currency = "CNY"; 52 53 /// <summary> 54 /// 退款完成后的通知地址 非必填 55 /// </summary> 56 //public static string refund_notify_url = ""; 57 58 /// <summary> 59 /// 應用名稱 60 /// </summary> 61 public static string app_name = "mc"; 62 63 /// <summary> 64 /// 簽約號 (必填) 65 /// </summary> 66 public static string pid = ""; 67 }

1 public string AppPay() 2 { 3 IAopClient client = new DefaultAopClient(AliPayBasicItem.url, AliPayBasicItem.app_id, AliPayBasicItem.merchant_private_key, AliPayBasicItem.format, AliPayBasicItem.version, AliPayBasicItem.sign_type, AliPayBasicItem.alipay_public_key, AliPayBasicItem.charset, false); 4 AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest(); 5 6 AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); 7 model.TotalAmount = "0.01"; // 訂單總金額,單位為元,精確到小數點后兩位,取值范圍[0.01,100000000] 8 //model.Body = ""; // 商品描述 9 model.Subject = "交易標題"; // 商品標題/交易標題/訂單標題/訂單關鍵字等 10 model.OutTradeNo = ""; // 商戶訂單號,由商家自定義,需保證商家系統中唯一。僅支持數字、字母、下划線 11 model.ProductCode = "QUICK_MSECURITY_PAY"; // 銷售產品碼,商家和支付寶簽約的產品碼。QUICK_MSECURITY_PAY:App支付。 12 request.SetBizModel(model); // 將業務model載入到request 13 //request.SetNotifyUrl(AliPayBasicItem.pay_notify_url); 14 15 AlipayTradeAppPayResponse response = client.SdkExecute(request); 16 var info = response.Body; 17 18 return info // 將這里的info直接返給前端 19 }
4.響應:
有坑注意:官方給的響應示例是個json,實際上我們拿到的(即上面示例中的info)並不長這樣,如下圖所示,是加密過的,看不懂沒關系,不需要處理,直接返給前端就好,由前端拿着那長串去喚起支付,至此支付過程結束。
5.支付結果處理:
① 如果notify_url 賦了值,支付寶會將支付結果post給這個接口,處理一下接收數據
② 可主動調用查詢接口去獲取支付結果 https://opendocs.alipay.com/apis/api_1/alipay.trade.query ,寫法跟支付類似,封裝的model為AlipayTradeQueryModel
part2:微信App支付
1.官方文檔:
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
有坑注意:微信支付接口已經升級到v3版本,網上找的示例可能還是老版本,區別是,老版本為MD5加密,參數格式為XML,新版本為SHA2加密,參數格式為json
2.參數說明:
官方給的請求參數如下圖,沒什么好說的,就一個json:
*金額total是int類型;
*notify_url與支付寶不同,微信是必填,官方建議是https且無端口號,實際上http加有端口號也是可以的(不建議哈);
3.簽名:
需手動對接口參數簽名,簽名過程參考:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml ,以下以微信支付為例:
①拼接待簽名字符串:請求方法\n+URL\n+時間戳\n+隨機字符串\n+請求參數\n
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); string time_str = Convert.ToInt64(ts.TotalSeconds).ToString(); string sign_url = "/v3/pay/transactions/app"; string nonce_str = Guid.NewGuid().ToString().Replace("-", ""); string body_str = ""; // 上面的參數 string to_sign = "POST" + "\n" + sign_url + "\n" + time_str + "\n" + nonce_str + "\n" + body_str + "\n";
②使用商戶私鑰對待簽名串進行SHA256 with RSA簽名,並對簽名結果進行Base64編碼得到簽名值
byte[] keyData = Convert.FromBase64String(key); using (CngKey cngKey = CngKey.Import(keyData, CngKeyBlobFormat.Pkcs8PrivateBlob)) using (RSACng rsa = new RSACng(cngKey)) { byte[] data = System.Text.Encoding.UTF8.GetBytes(to_sign); string sign = Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)); }
③設置請求頭:Authorization: 認證類型 簽名信息
認證類型為:WECHATPAY2-SHA256-RSA2048
簽名信息為:商戶號mchid+商戶API證書serial_no+請求隨機串nonce_str+時間戳timestamp+簽名值sign
string value = $"WECHATPAY2-SHA256-RSA2048 " + "mchid=\"" + mch_id + "\",serial_no=\"" + serial_no + "\",nonce_str=\"" + nonce_str + "\",timestamp=\"" + time_str + "\",signature=\"" + sign + "\""; request.Headers.Add("Authorization", value);
有坑注意:請求頭還需有以下設置,不然會報400
request.ContentType = "application/json;charset=utf-8"; request.Headers.Add("Accept", "application/json"); request.Headers.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
4.返回值處理:
請參考https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml;
以上是前端調起支付要用到的字段,由后端通過返回值拼接處理而成
5.完整代碼示例:
①要用到的實體類

1 public class WxPayBasicItem 2 { 3 /// <summary> 4 /// 小程序ID 5 /// </summary> 6 public static string appid = ""; 7 8 /// <summary> 9 /// 商戶號 10 /// </summary> 11 public static string mch_id = ""; 12 13 /// <summary> 14 /// 商戶證書序列號 15 /// </summary> 16 public static string serial_no = ""; 17 18 /// <summary> 19 /// 商戶私鑰 20 /// </summary> 21 public static string private_key = ""; 22 23 /// <summary> 24 /// 小程序支付請求url 25 /// </summary> 26 //public static string order_url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; 27 28 /// <summary> 29 /// app支付請求url 30 /// </summary> 31 public static string app_url = "https://api.mch.weixin.qq.com/v3/pay/transactions/app"; 32 33 /// <summary> 34 /// 預支付完成后的通知地址 35 /// </summary> 36 public static string order_notify_url = "https://weixin.qq.com/"; 37 38 /// <summary> 39 /// 交易類型 小程序取值"JSAPI" 40 /// </summary> 41 //public static string trade_type = "JSAPI"; 42 43 /// <summary> 44 /// 查詢訂單請求url 45 /// </summary> 46 public static string query_url = "https://api.mch.weixin.qq.com/v3/pay/transactions/id"; 47 48 /// <summary> 49 /// 應用密鑰 50 /// </summary> 51 //public static string secret = ""; 52 53 /// <summary> 54 /// 授權登錄url 55 /// </summary> 56 public static string auth_login_url = "https://api.weixin.qq.com/sns/oauth2/access_token"; 57 58 /// <summary> 59 /// 微信獲取用戶信息url 60 /// </summary> 61 public static string wx_user_url = "https://api.weixin.qq.com/sns/userinfo"; 62 } 63 64 /// <summary> 65 /// App預支付參數 66 /// </summary> 67 public class AppPaymentData 68 { 69 /// <summary> 70 /// 小程序ID 71 /// </summary> 72 //[Required] 73 public string appid { get; set; } 74 75 /// <summary> 76 /// 商戶號 77 /// </summary> 78 public string mchid { get; set; } 79 80 /// <summary> 81 /// 商品描述 商品簡單描述,不超過128字節 82 /// </summary> 83 public string description { get; set; } 84 85 /// <summary> 86 /// 商戶訂單號 87 /// </summary> 88 public string out_trade_no { get; set; } 89 90 /// <summary> 91 /// 交易結束時間 yyyyMMddHHmmss,非必填 92 /// </summary> 93 //public string time_expire { get; set; } 94 95 /// <summary> 96 /// 附加數據 非必填 97 /// </summary> 98 //public string attach { get; set; } 99 100 /// <summary> 101 /// 異步通知地址 通知url必須為外網可訪問的url,不能攜帶參數。 102 /// </summary> 103 public string notify_url { get; set; } 104 105 /// <summary> 106 /// 訂單優惠標記 107 /// </summary> 108 //public string goods_tag { get; set; } 109 110 /// <summary> 111 /// 訂單金額信息 112 /// </summary> 113 public PaymentDataAmount amount { get; set; } 114 115 /// <summary> 116 /// 場景信息 117 /// </summary> 118 //public PaymentDataScene scene_info { get; set; } 119 120 /// <summary> 121 /// 結算信息 122 /// </summary> 123 //public PaymentDataSettle settle_info { get; set; } 124 } 125 126 /// <summary> 127 /// 訂單金額 128 /// </summary> 129 public class PaymentDataAmount 130 { 131 /// <summary> 132 /// 總金額 訂單總金額,單位為分 133 /// </summary> 134 public int total { get; set; } 135 136 /// <summary> 137 /// 貨幣類型 非必填,默認"CNY" 138 /// </summary> 139 public string currency { get; set; } 140 } 141 142 /// <summary> 143 /// App調起支付參數 144 /// </summary> 145 public class AppPayBackItem 146 { 147 /// <summary> 148 /// 應用id 149 /// </summary> 150 public string appid { get; set; } 151 152 /// <summary> 153 /// 商戶號 154 /// </summary> 155 public string partnerid { get; set; } 156 157 /// <summary> 158 /// 預支付交易會話ID 159 /// </summary> 160 public string prepayid { get; set; } 161 162 /// <summary> 163 /// 訂單詳情擴展字符串 164 /// </summary> 165 public string package { get; set; } 166 167 /// <summary> 168 /// 隨機字符串 169 /// </summary> 170 public string noncestr { get; set; } 171 172 /// <summary> 173 /// 時間戳 174 /// </summary> 175 public string timestamp { get; set; } 176 177 /// <summary> 178 /// 簽名 179 /// </summary> 180 public string sign { get; set; } 181 182 }
②工具類

1 public static class WxPayMethod 2 { 3 /// <summary> 4 /// 微信app下單 5 /// </summary> 6 public static string PayTest(AppPaymentData item) 7 { 8 var param = JsonConvert.SerializeObject(item); 9 10 string sign_url = WxPayBasicItem.app_url.Split(".com")[1].ToString(); 11 string time_str = GetTimeStamp(); 12 string nonce_str = GetNonceStr(); 13 string body_str = param; 14 15 string to_sign_info = "POST" + "\n" + sign_url + "\n" + time_str + "\n" + nonce_str + "\n" + body_str + "\n"; 16 string sign_info = RsaSign(to_sign_info, WxPayBasicItem.private_key); 17 18 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(WxPayBasicItem.app_url); 19 request.Method = "POST"; 20 string value = $"WECHATPAY2-SHA256-RSA2048 " + "mchid=\"" + WxPayBasicItem.mch_id + "\",serial_no=\"" + WxPayBasicItem.serial_no + "\",nonce_str=\"" + nonce_str + "\",timestamp=\"" + time_str + "\",signature=\"" + sign_info + "\""; 21 request.Headers.Add("Authorization", value); 22 request.ContentType = "application/json;charset=utf-8"; 23 request.Headers.Add("Accept", "application/json"); 24 request.Headers.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"); 25 26 byte[] bs = Encoding.UTF8.GetBytes(param); 27 request.ContentLength = bs.Length; 28 if (bs.Length > 0) 29 { 30 using (Stream reqStream = request.GetRequestStream()) 31 { 32 reqStream.Write(bs, 0, bs.Length); 33 reqStream.Close(); 34 } 35 } 36 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 37 if (response.StatusCode == HttpStatusCode.OK) 38 { 39 using (Stream mystream = response.GetResponseStream()) 40 { 41 using (StreamReader reader = new StreamReader(mystream)) 42 { 43 var info = reader.ReadToEnd(); 44 return info; 45 } 46 } 47 } 48 else 49 { 50 return ""; 51 } 52 } 53 54 /// <summary> 55 /// Rsa簽名 56 /// </summary> 57 /// <param name="str"></param> 58 /// <param name="key"></param> 59 /// <returns></returns> 60 public static string RsaSign(string str, string key) 61 { 62 var result = string.Empty; 63 64 byte[] keyData = Convert.FromBase64String(key); 65 using (CngKey cngKey = CngKey.Import(keyData, CngKeyBlobFormat.Pkcs8PrivateBlob)) 66 using (RSACng rsa = new RSACng(cngKey)) 67 { 68 byte[] data = System.Text.Encoding.UTF8.GetBytes(str); 69 result = Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)); 70 } 71 72 return result; 73 } 74 75 /// <summary> 76 /// 生成隨機字符串 77 /// </summary> 78 /// <returns></returns> 79 public static string GetNonceStr() 80 { 81 return Guid.NewGuid().ToString().Replace("-", ""); 82 } 83 84 /// <summary> 85 /// 生成時間戳,標准北京時間,時區為東八區,自1970年1月1日 0點0分0秒以來的秒數 86 /// </summary> 87 /// <returns></returns> 88 public static string GetTimeStamp() 89 { 90 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 91 return Convert.ToInt64(ts.TotalSeconds).ToString(); 92 } 93 }
③主方法

public AppPayBackItem AppOrder() { var pay_item = new AppPaymentData(); pay_item.amount = new PaymentDataAmount(); string url = WxPayBasicItem.app_url; pay_item.appid = WxPayBasicItem.appid; pay_item.mchid = WxPayBasicItem.mch_id; pay_item.description = ""; //交易描述 pay_item.out_trade_no = ""; //商戶訂單號,商戶系統中唯一 pay_item.notify_url = WxPayBasicItem.order_notify_url; pay_item.amount.total = 1; //這里是int類型,單位為分 pay_item.amount.currency = "CNY"; string response = WxPayMethod.PayTest(pay_item); if (!string.IsNullOrEmpty(response)) { //處理返回結果 string prepayid = ((JObject)JsonConvert.DeserializeObject(response))["prepay_id"].ToString(); var info = new AppPayBackItem(); info.appid = WxPayBasicItem.appid; info.partnerid = WxPayBasicItem.mch_id; info.prepayid = prepayid; info.package = "Sign=WXPay"; // 照着填 info.noncestr = WxPayMethod.GetNonceStr(); info.timestamp = WxPayMethod.GetTimeStamp(); string to_sign = info.appid + "\n" + info.timestamp + "\n" + info.noncestr + "\n" + info.prepayid + "\n"; info.sign = WxPayMethod.RsaSign(to_sign, WxPayBasicItem.private_key); return info; //把這個返回給前端就行 } else { // 失敗 } }
6.支付結果處理:與支付寶類似
7.輔助工具建議:
①微信官方的加簽驗簽工具 (如果是自己寫簽名方法的,建議用這個測一下)
②http請求工具:Advanced-REST-client(網上一搜一大把教程,這個誠心推薦!不然微信支付能測到你懷疑人生!當然其它模擬請求工具也是可以的)