Published on:
April 5, 2014
集成支付宝快捷支付小记。
客户端
客户端集成移动端支付宝快捷支付,iOS和Android端很方便,使用支付宝提供的sdk就可以了,要注意的地方就是:
一定要用支付宝提供的工具,生成商户自己的公钥和密钥,密钥要经过pkcs8编码。这些都可以使用支付宝提供的工具来完成。
要把商户的公钥提交到支付宝。
基本上看着文档就可以很快完成集成测试。
当用户手机没有安装支付宝App,则会弹出安装支付宝App的弹窗,点确定就去下载支付宝App,点取消就弹出webview打开网页版支付宝结算。
服务端
服务端是用Ruby。
因为所有的支付请求都由客户端完成了,那么服务端只剩下了实现支付宝异步通知接口的任务。
异步通知接口有两步工作:RSA签名验证、Notify ID验证是否支付宝请求。
RSA签名验证
目前快捷支付的签名类型,只支持RSA, 所以服务端接收异步通知的接口Notify就只能用RSA来验证签名。支付RSA验证的逻辑是这样的(文档里也有描述,理解这个逻辑有助于顺利完成验证):
商户使用支付宝提供的工具生成RSA公钥私钥,并把商户公钥提交给支付宝。 这一步意味着双方互换公钥。
支付宝发给商户的请求是用支付宝的私钥加密的,所以必须用支付宝的公钥解密,而支付宝的公钥在文档里已经提供。
商户发往支付宝的请求,必须是商户自己的私钥加密,而支付宝那边用商户的公钥解密。就是移动客户端sdk完成的工作。所以移动客户端那边一定是用商户自己的私钥,并且是经过pkcs8编码的。
理解了RSA的加密解密逻辑,我们就了解了,服务端后台要验证来自于支付宝的异步通知POST请求,必须用支付宝的公钥了。
复制文档里支付宝的RSA公钥, 换行的部分用\n代替,如下:
-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRA\nFljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQE\nB/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5Ksi\nNG9zpgmLCUYuLkxpLQIDAQAB\n-----END PUBLIC KEY-----
然后,我们使用OpenSSL::PKey::RSA类:
rsa = OpenSSL::PKey::RSA.new(alipay_pub_key)
alipay_pub_key是上面的支付宝公钥。
一定要上面这种格式,或者你使用工具生成符合pem格式的pem文件,否则会报错。
然后我们就可以使用rsa的verify方法验证签名了:
rsa.verify('sha1', Base64.decode64(sign), rsa_string.force_encoding("utf-8"))
注意,这里的sign,是支付宝请求里的签名参数, 这里的sign一定要经过base64解码。rsa_string是根据支付宝文档生成的待签名字符串。
有的人可能不理解'sha1'。 这里sha1是签名算法,RSA是加密手段,加密了签名数据。 通过支付宝提供的java和php demo代码了解,支付宝使用的是sha1算法,所以这里需要用sha1算法来参与认证签名。用一句伪代码表示如下:
RSA_Private_key(sha1(rsa_string))
先通过RSA公钥解开签名数据,得到一个sha1加密的验证数据: A
同样用sha1加密我们的待签名字符串: B
比较两个值 A和B是否相等
这样,你就可以完成RSA验证了。
Notify ID验证
这个很简单, 带上支付宝文档里提供的参数,给支付验证接口发个请求就可以了。
后记
Ruby推荐使用ActiveMerchant,我把上面的验证过程写了个ActiveMerchant补丁,用起来很方便,毕竟ActiveMerchant帮你做了很多工作,这里就不分享了,大家可以自己去写。
最后,需要注意的地方:
你的异步通知接口必须返回text/plain 格式的success字符,否则支付宝会不断的给你发请求,24小时8次
支付宝在完成支付之后,支付状态首先是「等待付款」,这时候会发一次异步请求, 然后等一段时间之后,支付交易成功之后,又会发一次异步通知。 那么这两次通知,你必须在异步通知接口中都进行判断,并返回success。
UPDATE 2014.04.30
今天把支付宝支付客户端RSA签名验证的过程,移到了服务端,这样做是为了更加安全,RSA的密钥就不必保存到客户端了。
增加一个RSA签名方法:
def rsa_sign(rsa_string)
pri = OpenSSL::PKey::RSA.new(alipay.private_key)
sign = pri.sign('sha1', rsa_string.force_encoding("utf-8"))
signature = CGI.escape(Base64.encode64(sign))
return signature
end
以及一个生成待签名字串的方法
def create_sign_string(sign_params)
sign_keys = sign_params.keys
origin_string = sign_keys.inject(''){|s, key|
if key.to_s == 'notify_url'
s += %Q{#{key}="#{URI.encode(sign_params[key], Regexp.new(/:\/\//))}"&}
else
s += %Q{#{key}="#{sign_params[key]}"&}
end
}
return origin_string
end
相关阅读: