开发前提
我们这里使用的是微信的APP支付,与微信公众号支付和扫码等支付不同的是,我们需要用到微信开放平台,并非微信公众平台。
首先需要注册微信开放平台,申请一个APP应用,把应用基本信息和应用的一些开发信息填写进去,完成创建。
然后也需要有一个微信商户平台,这里用到的商户平台可以是已注册的,也可以是新注册的,只要在商户平台进行和此开放平台绑定,且开放平台站内信通过就行了;
然后分别在微信开放平台和微信商户平台进行微信支付的申请;申请通过之后便可以接入微信支付了。
进入正题
在实现微信APP支付时,后端只需要做两步操作:
1.请求微信统一下单API,将发起支付用到的参数返回给APP原生,且数据库保存预支付订单信息;
2.支付回调,微信APP支付完成后请求一个回调地址,这个回调地址在请求微信统一下单API接口传入,验证参数后,更改数据库保存的订单支付状态;
首先就是第一步:获取APP调起微信支付用的参数(统一下单API)
#获取APP调起微信支付用的参数
def get_wechat_pay_datas
send_url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
ip = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}
spbill_create_ip = ip.ip_address if ip
out_trade_no = "#{Time.now.strftime('%Y%m%d%H%M%S')}#{rand(1...999999)}"
nonce_str = Digest::MD5.hexdigest("#{Time.now.to_i}#{rand(1...1000)}")
datas = {
appid: ENV['WECHAT_PAY_APPID'], #应用ID
mch_id: ENV['WECHAT_PAY_MCH_ID'], #商户号
nonce_str: nonce_str,#随机字符串,不长于32位
body: "商品1", #订单标题
out_trade_no: out_trade_no, #商户订单号
total_fee: 1, #实际支付金额,单位为分,不能存在小数点
spbill_create_ip: spbill_create_ip, #终端IP
notify_url: ENV['WECHAT_PAY_NOTIFY_URL'], #回调地址 如http://www.123.com/abc
trade_type: "APP" #APP支付
}
datas[:sign] = get_wechat_pay_sign_str(datas) #根据以上参数生成sign
data_xml = hash_to_xml(datas) #将hash转为xml #请求参数整合完毕,请求微信统一下单API地址
result = Faraday.post(send_url, data_xml)
result = Hash.from_xml(result.body)['xml']
return "失败抛出" if result["return_code"] != 'SUCCESS' || result["result_code"] != 'SUCCESS' #统一下单API请求失败
#接下来进行二次验证
noncestr = Digest::MD5.hexdigest("#{Time.now.to_i}#{rand(1...1000)}")
next_datas = {
appid: ENV['WECHAT_PAY_APPID'], #应用ID
partnerid: ENV['WECHAT_PAY_MCH_ID'], #商户号
prepayid: result["prepay_id"], # 通过刚才微信统一下单API获得的prepay_id
package: "Sign=WXPay", #固定值
noncestr: noncestr, #随机字符串,不长于32位
timestamp: Time.now.to_i #当前时间戳
}
#获取二次签名的sign
next_datas[:sign] = get_wechat_pay_sign_str(next_datas)
next_datas.delete(:appid) #删除appid的目的是在返回给签前端调起支付参数时,不暴露出来,让前端手动拼接
next_datas.delete(:partnerid) #与appid目的一致,让前端手动拼接
next_datas #这个结果便是APP调起支付需要的参数了
#此时便可以创建数据库预支付的订单信息了,完成内部程序逻辑
end
#获取sign
def get_wechat_pay_sign_str
#以下为微信官方制定的Sign计算方式
sign_arr = []
sort_param(params).map{ |key,value|
sign_arr << "#{key}=#{value}" if value.present? #排除为空的值
}
sign_str = sign_arr.join('&') #参数以&符拼接成字符串
sign_str += "&key=#{ENV['WECHAT_PAY_MCH_KEY']}" #结尾拼接上商户平台设置的密钥key
Digest::MD5.hexdigest(sign_str).upcase #最终生成的字符串md5加密并转大写
end
#将数据hash转xml类型
def hash_to_xml params
xml = ""
params.each_with_index do |value, key|
xml += "<#{value[0].to_s}>"
end
xml += ""
xml
end
然后就是第二步:支付回调
这个地址就是第一步的 获取APP调起微信支付用的参数传入的notify_url参数,也是访问如下代码方法的链接地址;
desc '微信支付回调'
content_type :xml, "text/xml" #使方法支持xml参数数据类型
format :xml #使方法支持xml参数数据类型
post :wechat_pay_notify_url do
ActiveRecord::Base.transaction do #增加事务
$logger.info "微信支付回调"
params_data = Hash.from_xml(request.body)['xml']#接收微信回调过来的参数
if params_data["return_code"].present? && params_data["return_code"] == 'SUCCESS' #微信回调参数正确
if Redis.is_pay_order_vaild? params_data["out_trade_no"] #判断redis此订单号是否存在,且状态是否为未回调 才可以进行回调操作
$logger.info "通过redis验证"
#验签过程
if check_wechat_pay(params_data)
#验签成功
#更新此订单的redis数据
Redis.set_pay_order_invaild params_data["out_trade_no"]
$logger.info "验签成功-订单号#{params_data['out_trade_no']}"
#判断APPID是否正确/商户号是否正确/订单号是否存在/订单未支付状态/是否为微信支付/判断金额是否正确
pay_order = Order.find_by_out_trade_no(params_data["out_trade_no"]) #预支付订单信息
if params_data["appid"] == ENV['WECHAT_PAY_APPID'] && params_data["mch_id"] == ENV['WECHAT_PAY_MCH_ID'] && pay_order.present? && !pay_order.is_paid? && pay_order.pay_type == 'wechat_pay' && (pay_order.spend_rmb_money * 100).to_i == params_data["total_fee"].to_i
#回调信息通过
$logger.info "回调信息通过-订单号#{params_data['out_trade_no']}"
#开始执行内部程序逻辑处理,如修改订单状态
xxxxxxxx
#逻辑处理已完成
$logger.info "逻辑处理已完成-订单号#{params_data['out_trade_no']}"
#返回给微信处理完毕的报文
success_datas = {
return_code: "SUCCESS", #状态码
return_msg: "OK" #返回信息
}
#将hash转为xml
success_data_xml = hash_to_xml(success_datas) #hash_to_xml
return success_data_xml
else
$logger.info "回调信息未通过-订单号#{params_data['out_trade_no']}"
end
else
$logger.info "验签失败-订单号#{params_data['out_trade_no']}"
end
end
end
end
end
#微信支付回调验签
def check_wechat_pay params
sign = params["sign"] #将传入的sign保存下来
params.delete("sign") #删除sign参数,保存其余参数
signature = get_wechat_pay_sign_str(params) #通过其余参数重新计算sign,get_wechat_pay_sign_str方法和统一下单为同一个
sign == signature #将自己生成的sign和传入的sign进行对比验签
end
以上便是 ruby实现微信APP支付全流程啦!