1。设置参数
param = {
appid: @appid, # 微信支付分配的公众账号ID
mch_id: @mch_id, #微信支付分配的商户号
nonce_str: Tools::GetRandString.rand_string(30), #随机字符串
sign_type: 'HMAC-SHA256',
body: order[:title], # 商品简单描述
out_trade_no: order[:order_sn], # 商户系统内部订单号,要求32个字符内
total_fee: (order[:money].to_f * 100).to_i, # 订单总金额,单位为分
spbill_create_ip: '120.78.161.126', # 支持IPV4和IPV6两种格式的IP地址
notify_url: @notify_url, # 异步接收微信支付结果通知的回调地址
trade_type: 'NATIVE' # JSAPI, NATIVE, MWEB
}
2.排序并拼接参数
'''
参数从小到大排序
'''
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
3.把key加入到参数
formatstr << "&key=#{@appkey}"
4.签名
'''
生成签名
'''
def sign_string(data)
#Digest::MD5.hexdigest(data).upcase
OpenSSL::HMAC.hexdigest("SHA256", @appkey, data).upcase
end
5.把签名加入参数
param[:sign] = sign_param(param)
6.把参数转为xml
'''
hash转xml
'''
def self.to_wx_xml(hash)
xml = "<xml>"
hash.each_with_index do |value, key|
xml += "<#{value[0].to_s}><![CDATA[#{value[1].to_s}]]></#{value[0].to_s}>"
end
xml += "</xml>"
end
7.获取二维码地址
result = Faraday.post(@send_url, xml_param)
result = Hash.from_xml(result.body)['xml']
if result["code_url"].blank?
''
else
result["code_url"]
end
8.把二维码地址转为二维码
require 'rqrcode_png'
module Tools
class Qrcode
def self.url_to_base64(url, options = {})
if options.blank?
options = {
w: 200,
h: 200
}
end
qrcode = RQRCode::QRCode.new(url, :size => 8, :level => :h )
str = Base64.encode64(qrcode.to_img.resize(options[:w], options[:h]).to_s)
str = "data:image/png;base64,#{str}"
end
end
end
9.使用
code_url = Payment::Wxpay.new.pay_code(order)
if code_url.blank?
flash[:status] = '交易失败'
return redirect_to customer_pay_records_path
end
@src = Tools::Qrcode.url_to_base64(code_url)
10.回调
sign排除
param.delete('sign')
签名并验证
signature = sign_param(param)
#验证签名
if sign == signature
res = {
out_trade_no: param['out_trade_no'],
pay_order_sn: param['transaction_id'],
pay_money: (param['total_fee'].to_i / 1000).to_f
}
else
res = ''
end
回调使用
result = Hash.from_xml(request.body)['xml']
if result['result_code'].present? && result['result_code'] == 'SUCCESS'
pay_res = Payment::Wxpay.new.check_pay(result)
if pay_res.present?
change_with_result(pay_res)
xml = "<xml>"
xml += " <return_code><![CDATA[SUCCESS]]></return_code>"
xml += "<return_msg><![CDATA[OK]]></return_msg>"
xml += "</xml>"
return render xml: xml
end
end
完整代码
require 'digest/md5'
module Payment
class Wxpay
def initialize
@send_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'.freeze
@notify_url = Rails.configuration.application['PAY_RESPOND']
@appid = '绑定的公众号appid'
@appkey = 'api 密钥'
@mch_id = 'mchd'
end
'''
支付
'''
def pay_code(order)
# return_code 返回状态码
# return_msg 返回信息
# appid 公众账号ID
# mch_id 商户号
# nonce_str 随机字符串
# sign 签名
# result_code 业务结果 SUCCESS/FAIL
# code_url 二维码链接 trade_type=NATIVE时有返回,此url用于生成支付二维码
param = pay_params(order)
xml_param = Tools::Sign.to_wx_xml(param)
result = Faraday.post(@send_url, xml_param)
result = Hash.from_xml(result.body)['xml']
if result["code_url"].blank?
''
else
result["code_url"]
end
end
'''
支付验签
'''
def check_pay(param)
return '' if (param['appid'] != @appid) || (param['mch_id'] != @mch_id)
sign = param['sign']
param.delete('sign')
signature = sign_param(param)
#验证签名
if sign == signature
res = {
out_trade_no: param['out_trade_no'],
pay_order_sn: param['transaction_id'],
pay_money: (param['total_fee'].to_i / 1000).to_f
}
else
res = ''
end
end
'''
签名
'''
def sign_param(param)
param = Tools::Sign.sort_param(param)
formatstr = Tools::Sign.to_query(param)
formatstr << "&key=#{@appkey}"
signature = sign_string(formatstr)
end
'''
参数
'''
def pay_params(order)
param = {
appid: @appid, # 微信支付分配的公众账号ID
mch_id: @mch_id, #微信支付分配的商户号
nonce_str: Tools::GetRandString.rand_string(30), #随机字符串
sign_type: 'HMAC-SHA256',
body: order[:title], # 商品简单描述
out_trade_no: order[:order_sn], # 商户系统内部订单号,要求32个字符内
total_fee: (order[:money].to_f * 100).to_i, # 订单总金额,单位为分
spbill_create_ip: '120.78.161.126', # 支持IPV4和IPV6两种格式的IP地址
notify_url: @notify_url, # 异步接收微信支付结果通知的回调地址
trade_type: 'NATIVE' # JSAPI, NATIVE, MWEB
}
#sign 通过签名算法计算得出的签名值
param[:sign] = sign_param(param)
param
end
'''
生成签名
'''
def sign_string(data)
#Digest::MD5.hexdigest(data).upcase
OpenSSL::HMAC.hexdigest("SHA256", @appkey, data).upcase
end
end
end
这里也遇到过签名不正确,奇怪的是用微信的签名验证工具就可以匹配成功,但就算签名不通过,好在这个问题比较简单,网上也有解决方法,就是重置下appi密钥
code_url = Payment::Wxpay.new.pay_code(order)
if code_url.blank?
flash[:status] = '交易失败'
return redirect_to customer_pay_records_path
end
@src = Tools::Qrcode.url_to_base64(code_url)
回调
result = Hash.from_xml(request.body)['xml']
if result['result_code'].present? && result['result_code'] == 'SUCCESS'
pay_res = Payment::Wxpay.new.check_pay(result)
if pay_res.present?
change_with_result(pay_res)
xml = "<xml>"
xml += " <return_code><![CDATA[SUCCESS]]></return_code>"
xml += "<return_msg><![CDATA[OK]]></return_msg>"
xml += "</xml>"
return render xml: xml
end
end