由於業務需求,在專案裡面要接入支付寶的支付功能,於是在github上找到了支付寶的官方sdk:https://hub.fastgit.org/alipay/alipay-easysdk
先說問題:
在按照官方例項的程式碼寫了個demo,也就簡單的一行,不愧是EasySDK,夠easy
1 AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace() 2 .PreCreate("Apple iPhone11 128G", "2234567234890", "5799.00");
該程式碼在執行時,總丟擲一個異常:
說字典不存在鍵 ”sign“ ,這異常給我看的的一愣一愣的,我當時想不通啊,這哪裡來的這個東西。
仔細檢視堆疊資訊之後發現是SDK提供的方法PerCreate裡面的問題,反手直接懟著原始碼就上了,進去一看:
1 public AlipayTradePrecreateResponse PreCreate(string subject, string outTradeNo, string totalAmount) 2 { 3 Dictionary<string, object> runtime_ = new Dictionary<string, object> 4 { 5 {"ignoreSSL", this._kernel.GetConfig("ignoreSSL")}, 6 {"httpProxy", this._kernel.GetConfig("httpProxy")}, 7 {"connectTimeout", 15000}, 8 {"readTimeout", 15000}, 9 {"retry", new Dictionary<string, int?> 10 { 11 {"maxAttempts", 0}, 12 }}, 13 }; 14 15 TeaRequest _lastRequest = null; 16 Exception _lastException = null; 17 long _now = System.DateTime.Now.Millisecond; 18 int _retryTimes = 0; 19 while (TeaCore.AllowRetry((IDictionary) runtime_["retry"], _retryTimes, _now)) 20 { 21 if (_retryTimes > 0) 22 { 23 int backoffTime = TeaCore.GetBackoffTime((IDictionary)runtime_["backoff"], _retryTimes); 24 if (backoffTime > 0) 25 { 26 TeaCore.Sleep(backoffTime); 27 } 28 } 29 _retryTimes = _retryTimes + 1; 30 try 31 { 32 TeaRequest request_ = new TeaRequest(); 33 Dictionary<string, string> systemParams = new Dictionary<string, string> 34 { 35 {"method", "alipay.trade.precreate"}, 36 {"app_id", this._kernel.GetConfig("appId")}, 37 {"timestamp", this._kernel.GetTimestamp()}, 38 {"format", "json"}, 39 {"version", "1.0"}, 40 {"alipay_sdk", this._kernel.GetSdkVersion()}, 41 {"charset", "UTF-8"}, 42 {"sign_type", this._kernel.GetConfig("signType")}, 43 {"app_cert_sn", this._kernel.GetMerchantCertSN()}, 44 {"alipay_root_cert_sn", this._kernel.GetAlipayRootCertSN()}, 45 }; 46 Dictionary<string, object> bizParams = new Dictionary<string, object> 47 { 48 {"subject", subject}, 49 {"out_trade_no", outTradeNo}, 50 {"total_amount", totalAmount}, 51 }; 52 Dictionary<string, string> textParams = new Dictionary<string, string>(){}; 53 request_.Protocol = this._kernel.GetConfig("protocol"); 54 request_.Method = "POST"; 55 request_.Pathname = "/gateway.do"; 56 request_.Headers = new Dictionary<string, string> 57 { 58 {"host", this._kernel.GetConfig("gatewayHost")}, 59 {"content-type", "application/x-www-form-urlencoded;charset=utf-8"}, 60 }; 61 request_.Query = this._kernel.SortMap(TeaConverter.merge 62 ( 63 new Dictionary<string, string>() 64 { 65 {"sign", this._kernel.Sign(systemParams, bizParams, textParams, this._kernel.GetConfig("merchantPrivateKey"))}, 66 }, 67 systemParams, 68 textParams 69 )); 70 request_.Body = TeaCore.BytesReadable(this._kernel.ToUrlEncodedRequestBody(bizParams)); 71 _lastRequest = request_; 72 TeaResponse response_ = TeaCore.DoAction(request_, runtime_); 73 74 Dictionary<string, object> respMap = this._kernel.ReadAsJson(response_, "alipay.trade.precreate"); 75 if (this._kernel.IsCertMode()) 76 { 77 if (this._kernel.Verify(respMap, this._kernel.ExtractAlipayPublicKey(this._kernel.GetAlipayCertSN(respMap)))) 78 { 79 return TeaModel.ToObject(this._kernel.ToRespModel(respMap)); 80 } 81 } 82 else 83 { 84 if (this._kernel.Verify(respMap, this._kernel.GetConfig("alipayPublicKey"))) 85 { 86 return TeaModel.ToObject(this._kernel.ToRespModel(respMap)); 87 } 88 } 89 throw new TeaException(new Dictionary<string, string> 90 { 91 {"message", "驗籤失敗,請檢查支付寶公鑰設定是否正確。"}, 92 }); 93 } 94 catch (Exception e) 95 { 96 if (TeaCore.IsRetryable(e)) 97 { 98 _lastException = e; 99 continue; 100 } 101 throw e; 102 } 103 } 104 105 throw new TeaUnretryableException(_lastRequest, _lastException); 106 }
太長了不想看,於是直接另起個demo除錯這段原始碼,發現每當走到第84行:this._kernel.Verify() 這句程式碼時就會出現異常,F12進去發現又是一個單獨的程式集
通過dnSpy反編譯看了一下這個方法的執行邏輯:
他直接從傳入的字典中,讀取了sign,可想而知當時傳過去的字典肯定是沒有這個鍵,回過頭去看這個字典的內容,是讀取的支付寶響應報文資訊的
除錯到verify執行前看了一下字典的內容
還真是,他並沒有sign這個鍵,到這裡我又楞住了,這... 這怎麼玩?
響應報文沒有這個鍵啊,咋整,總不能手動改個原始碼繼續用吧,但是他這個校驗,也是為了資料的安全性。
到這裡得到兩個資訊:
1. 請求是能夠正常收發的。
2. 丟擲異常是因為程式碼判斷了響應報文的sign,而響應報文沒有sign。
只得從其他地方下手,響應報文提示無效的AppID引數,於是我重新核對了SDK的配置資訊後再次除錯,發現了問題所在。
在官方提供的demo中的配置資訊是:
static private Config GetConfig() { return new Config() { Protocol = "https", GatewayHost = "openapi.alipay.com", SignType = "RSA2", AppId = "<-- 請填寫您的AppId,例如:2019091767145019 -->", // 為避免私鑰隨原始碼洩露,推薦從檔案中讀取私鑰字串而不是寫入原始碼中 MerchantPrivateKey = "<-- 請填寫您的應用私鑰,例如:MIIEvQIBADANB ... ... -->", MerchantCertPath = "<-- 請填寫您的應用公鑰證書檔案路徑,例如:/foo/appCertPublicKey_2019051064521003.crt -->", AlipayCertPath = "<-- 請填寫您的支付寶公鑰證書檔案路徑,例如:/foo/alipayCertPublicKey_RSA2.crt -->", AlipayRootCertPath = "<-- 請填寫您的支付寶根證書檔案路徑,例如:/foo/alipayRootCert.crt -->", // 如果採用非證書模式,則無需賦值上面的三個證書路徑,改為賦值如下的支付寶公鑰字串即可 // AlipayPublicKey = "<-- 請填寫您的支付寶公鑰,例如:MIIBIjANBg... -->" //可設定非同步通知接收服務地址(可選) NotifyUrl = "<-- 請填寫您的支付類介面非同步通知接收服務地址,例如:https://www.test.com/callback -->", //可設定AES金鑰,呼叫AES加解密相關介面時需要(可選) EncryptKey = "<-- 請填寫您的AES金鑰,例如:aa4BtZ4tspm2wnXLb1ThQA== -->" }; }
我在測試時僅僅修改了註釋掉的資訊,忽略了上面的Protocol、GatwayHot、SignType,眼睛看的太快,發現HTTPS和RSA2都沒啥問題,本能的認為GatewayHost也沒啥問題,但是在我仔細檢視支付寶沙箱環境提供的資訊之後發現
沙箱的閘道器環境是openapi.alipaydev.com
這也就解釋的通為什麼他會提示AppID引數無效了
重新執行後的程式碼得到的響應報文為:
此時便能成功獲取到sign了。
唉,也是怪自己太粗心了。
不過這種不判斷鍵從字典拿資料,也有點內什麼.... 如果不看原始碼,我還真不會想到是配置得問題
自己以後寫程式碼,也會注意這些小細節了。