1. 先在支付宝下载密钥生成器
https://docs.open.alipay.com/291/106097
2. 使用密钥生成器生成密钥,我们ruby自然用非java专用生成,使用RSA2,即2048
把密钥保存下,然后把公钥添加到支付宝开发配置里
3. 修改密钥名称,如rsa.pem(私钥), rsa.key(公钥),拷贝到项目下
4.参数设置
biz_content = {
out_trade_no: order[:order_sn], # out_trade_no 商户订单号
product_code: 'FAST_INSTANT_TRADE_PAY', # 销售产品码 目前仅支持FAST_INSTANT_TRADE_PAY
total_amount: order[:money], # 订单总金额,单位为元,精确到小数点后两位
subject: order[:title] #订单标题
}
param = {
app_id: @appid, # 支付宝分配给开发者的应用ID
method: 'alipay.trade.page.pay', # 接口名称alipay.trade.page.pay
format: 'JSON',
return_url: @notify_url,
charset: 'utf-8',
sign_type: 'RSA2', # 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA
timestamp: Time.zone.now.strftime("%Y-%m-%d %H:%M:%S"), # 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss"
version: '1.0', #调用的接口版本,固定为:1.0
notify_url: @notify_url,
biz_content: JSON.generate(biz_content)
}
5.参数排序并拼接
'''
参数从小到大排序
'''
def self.sort_param(data)
data = data.sort{ |a,b| a.to_s <=> b.to_s }.to_h
end
'''
hash转url参数格式化
'''
def self.to_query(param)
format = []
param.each_with_index do |value|
if value[1].present?
format << "#{value[0]}=#{value[1]}"
end
end
format.join('&')
end
6.加密
def sign_string(data)
key = File.read(File.dirname(__FILE__) + '/rsa.pem')
pkey = OpenSSL::PKey::RSA.new(Base64.decode64(key))
#signature = pkey.sign(OpenSSL::Digest::SHA256.new, data)
signature = pkey.sign('sha256', data)
#signature = pkey.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256")
signature = Base64.strict_encode64(signature)
end
7.把sign添加到参数列表
param[:sign] = sign
8.发送请求
<% if @alipay.present? %>
<div class="pay-type">支付宝支付</div>
<form name="payForm" action="<%= @alipay[:url]%>" method="post">
<% @alipay[:param].each do |key, value| %>
<input type="hidden" name="<%= key %>" value="<%= value %>">
<% end %>
<input type="submit" value="立即支付">
</form>
<% end %>
这里一定要记住, 我们的标题一般都是中文,会遇到签名不通过问题,一定要加编码
https://openapi.alipay.com/gateway.do?charset=utf-8
charset=utf-8
charset=utf-8
charset=utf-8
在这里卡了很久,csdn还是比较可靠的,很多大侠。
9.回调验证
回调验证顺序也差不多
排除sign和sign_type
param.delete('sign')
param.delete('sign_type')
url_dncode
param.each{ |key, val| param[key] = URI::decode(val) }
排序并拼接
验证签名
def check_sign_string(data, signature)
signature = Base64.strict_decode64(signature)
key = File.read(File.dirname(__FILE__) + '/rsa.key')
pkey = OpenSSL::PKey::RSA.new(Base64.decode64(key))
pkey.verify('sha256', signature, data)
end
完整代码
module Payment
class Alipay
def initialize
@send_url = 'https://openapi.alipay.com/gateway.do?charset=utf-8'.freeze
@notify_url = Rails.configuration.application['PAY_RESPOND']
@appid = 'youappid'
end
'''
支付
'''
def pay_code(order)
# code 网关返回码
# msg 网关返回码描述
# sign 签名
# trade_no 支付宝交易号
# out_trade_no 商户订单号
# seller_id 收款支付宝账号对应的支付宝唯一用户号
# total_amount 交易金额
param = pay_params(order)
res = {
url: @send_url,
param: param
}
res
end
'''
支付确认
'''
def check_pay(param)
return '' if param['app_id'] != @appid
sign = param['sign']
param.delete('sign')
param.delete('sign_type')
param.each{ |key, val| param[key] = URI::decode(val) }
# 排序
param = Tools::Sign.sort_param(param)
# 转为url
formatstr = Tools::Sign.to_query(param)
check_sign = check_sign_string(formatstr, sign)
if check_sign
res = {
out_trade_no: param['out_trade_no'],
pay_order_sn: param['trade_no'],
pay_money: param['total_amount'].to_f
}
else
res = ''
end
end
'''
签名
'''
def sign_param(param)
# 排序
param = Tools::Sign.sort_param(param)
# 转为url
formatstr = Tools::Sign.to_query(param)
# 签名
signature = sign_string(formatstr).to_s.chomp
end
'''
公共参数
'''
def pay_params(order)
# biz_content 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递
biz_content = {
out_trade_no: order[:order_sn], # out_trade_no 商户订单号
product_code: 'FAST_INSTANT_TRADE_PAY', # 销售产品码 目前仅支持FAST_INSTANT_TRADE_PAY
total_amount: order[:money], # 订单总金额,单位为元,精确到小数点后两位
subject: order[:title] #订单标题
}
param = {
app_id: @appid, # 支付宝分配给开发者的应用ID
method: 'alipay.trade.page.pay', # 接口名称alipay.trade.page.pay
format: 'JSON',
return_url: @notify_url,
charset: 'utf-8',
sign_type: 'RSA2', # 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA
timestamp: Time.zone.now.strftime("%Y-%m-%d %H:%M:%S"), # 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss"
version: '1.0', #调用的接口版本,固定为:1.0
notify_url: @notify_url,
biz_content: JSON.generate(biz_content)
}
#final_url = URI.encode(sign_param(param))
sign = sign_param(param)
param[:sign] = sign
param
end
'''
生成签名
'''
def sign_string(data)
key = File.read(File.dirname(__FILE__) + '/rsa.pem')
pkey = OpenSSL::PKey::RSA.new(Base64.decode64(key))
#signature = pkey.sign(OpenSSL::Digest::SHA256.new, data)
signature = pkey.sign('sha256', data)
#signature = pkey.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256")
signature = Base64.strict_encode64(signature)
end
'''
验证签名
'''
def check_sign_string(data, signature)
signature = Base64.strict_decode64(signature)
key = File.read(File.dirname(__FILE__) + '/rsa.key')
pkey = OpenSSL::PKey::RSA.new(Base64.decode64(key))
pkey.verify('sha256', signature, data)
end
end
end
@alipay = Payment::Alipay.new.pay_code(order)
<% if @alipay.present? %>
<div class="pay-type">支付宝支付</div>
<form name="payForm" action="<%= @alipay[:url]%>" method="post">
<% @alipay[:param].each do |key, value| %>
<input type="hidden" name="<%= key %>" value="<%= value %>">
<% end %>
<input type="submit" value="立即支付">
</form>
<% end %>
回调
param = param.to_unsafe_h
if param['trade_status'].present? && param['trade_status'] == 'TRADE_SUCCESS'
pay_res = Payment::Alipay.new.check_pay(param)
if pay_res.present?
change_with_result(pay_res)
return render plain: 'success'
end
end
return render plain: 'fail'
这里还有个问题要记住, rails5.1后,prams就不再是hash了,是一个对象
所以会报错
解决是
param = param.to_unsafe_h
我也记录了下这个问题