記一次.Net5接入支付寶SDK的小插曲

C發表於2021-07-25

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


 

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

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

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

 

相關文章