由於業務需求,在專案裡面要接入支付寶的支付功能,於是在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了。


唉,也是怪自己太粗心了。

不過這種不判斷鍵從字典拿資料,也有點內什麼....   如果不看原始碼,我還真不會想到是配置得問題

自己以後寫程式碼,也會注意這些小細節了。