开发前提
我们这里使用的是微信的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支付全流程啦!