记一次.Net5接入支付宝SDK的小插曲
由于业务需求,在项目里面要接入支付宝的支付功能,于是在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了。
唉,也是怪自己太粗心了。
不过这种不判断键从字典拿数据,也有点内什么…. 如果不看源码,我还真不会想到是配置得问题
自己以后写代码,也会注意这些小细节了。