记一次.Net5接入支付宝SDK的小插曲

由于业务需求,在项目里面要接入支付宝的支付功能,于是在github上找到了支付宝的官方sdk://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 = "<-- 请填写您的支付类接口异步通知接收服务地址,例如://www.test.com/callback -->",

                //可设置AES密钥,调用AES加解密相关接口时需要(可选)
                EncryptKey = "<-- 请填写您的AES密钥,例如:aa4BtZ4tspm2wnXLb1ThQA== -->"
            };
        }

我在测试时仅仅修改了注释掉的信息,忽略了上面的Protocol、GatwayHot、SignType,眼睛看的太快,发现HTTPS和RSA2都没啥问题,本能的认为GatewayHost也没啥问题,但是在我仔细查看支付宝沙箱环境提供的信息之后发现

沙箱的网关环境是openapi.alipaydev.com

这也就解释的通为什么他会提示AppID参数无效了

重新运行后的代码得到的响应报文为:

 

此时便能成功获取到sign了。


 

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

不过这种不判断键从字典拿数据,也有点内什么….   如果不看源码,我还真不会想到是配置得问题

自己以后写代码,也会注意这些小细节了。