集成支付宝快捷支付

Published on:

集成支付宝快捷支付小记。


客户端

客户端集成移动端支付宝快捷支付,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

相关阅读:

集成微信支付

Comments

comments powered by Disqus