2017 年 Ruby on Rails 5 微信公众号给用户发红包

发放普通红包

2017 年 Ruby on Rails 5 微信公众号给用户发红包

这次要实现的是"发放普通红包",官方文档:

https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3

13

今天2017年11月14号,成功把发红包搞定。
我先写业务上需要发红包的理由。再写技术实现细节。

业务描述

寓住(yuzhu.me)是一个公寓点评网站。可以想成"公寓版的豆瓣"。比如住过 You+ 青年公寓,广州广纸社区 的人,就可以上去评分,写评论。

为什么发红包

真正让寓住有用的,是住过公寓的人上来填写的数据,这些数据让别人知道这个公寓的质量如何。从而决定要不要去住。

那么让这类人群上来填写是关键。发红包就是希望通过这个 "填评价发红包" 的活动。
吸引一些人上来填数据。

公众号类型:服务号

414-1

我记得是只有服务号可以发红包还是怎么滴,忘记了。总之先说明下,示例中用的是服务号。

代码实现

没有用微信相关的 gem,我不喜欢用,因为实际代码不多,不到 40 行。

第 1 步:引入一个 gem
Gemfile 中引入 rest-client 这个发 http 请求的 gem。

gem 'rest-client' # for HTTP, https://github.com/rest-client/rest-client

gemfile

第 2 步:创建新目录和文件,并加到自动载入里。
我们的代码要写到 app/weixin/weixin.rb 里,
写到这个路径并没有什么特别的理由,你不用非得照做,我个人这样做的理由是我喜欢把微信相关的代码都放在一起。如果都塞一个 controller 里会感觉很奇怪。。

因为 app/weixin/ 不是常规目录,
所以在 config/application.rb 加上一句 config.autoload_paths += Dir[Rails.root.join('app', 'weixin')] 达到载入这里面代码的目的。
444

第 3 步:代码

  def send_red_pocket()
      total_fee_in_cent = 100
      # 单位是分,100分就是1元,之所以是1元,是因为我试过1。然后提示说最少1元。
      reward = Reward.create(receive_user_id: 20, amount_in_cents: 1)    # 这个 Reward Model 你不用照做,我做这个就是为了生成订单号而已。

      mch_billno = "#{reward.id}z101zg#{Time.now.to_i}"
      # 生成我们这边的商户订单号并且保存下来,纯粹就是为了唯一性而已。

      reward.mch_billno = mch_billno
      reward.save!

      open_id = 'ohqbNwCZKH70y3QOBvMWzlX4wDoo'
      # 谁接受红包

      # 下面的参数不解释了,因为都是在文档里有说明的。
      params = {
        nonce_str: SecureRandom.uuid.tr('-', ''),
        mch_billno: mch_billno,
        mch_id: 1426639999,
        wxappid: 'wx9536995d45129999',
        send_name: '寓住',
        re_openid: open_id,
        total_amount: total_fee_in_cent,
        total_num: 1,
        wishing: '红包祝福语',
        client_ip: '113.109.213.119',
        act_name: '猜灯谜抢红包活动',
        remark: '备注',
        key: '12b4e65fc31eeb96e5c4fa4cdadj9999',
      }

      url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack"
      r = RestClient::Request.execute(
        {
          method: :post,
          url: url,
          payload: make_payload(params),
          headers: { content_type: 'application/xml' },
          :ssl_client_cert  =>  OpenSSL::X509::Certificate.new(File.read("./secret/apiclient_cert.pem")),
          :ssl_client_key   =>  OpenSSL::PKey::RSA.new(File.read("./secret/apiclient_key.pem"), "1426631702"),
          :ssl_ca_file      =>  "./secret/rootca.pem",
          :verify_ssl       =>  OpenSSL::SSL::VERIFY_PEER
        }
      )
      return Hash.from_xml(r)
  end
  
  
  
  
    # 签名
  def get_sign(params)
    key = params.delete(:key)
    query = params.sort.map do |k, v|
      "#{k}=#{v}" if v.to_s != ''
    end.compact.join('&')
    Digest::MD5.hexdigest("#{query}&key=#{key}").upcase
  end

  # 做 xml 参数
  def make_payload(params)
    sign = get_sign(params)
    params.delete(:key) if params[:key]
    "<xml>#{params.map { |k, v| "<#{k}>#{v}</#{k}>" }.join}<sign>#{sign}</sign></xml>"
  end

截图:(可以看到 send_red_pocket 这个函数甚至都不用翻页,一页搞定。)
14
另外2个函数我放最底下了。
15-1

我估计大家最困惑的部分是【签名】 + 【怎么使用证书】。
上面的代码都展示了。

签名:用 def get_sign

证书:可以看到代码里使用证书的时候是这样用的:"./secret/apiclient_cert.pem"
那么实际放置位置是 Ruby on Rails 项目的根目录rails
上图中 Yuzhu2 文件夹就是 Ruby on Rails 项目。

证书是下载自
cert

实测

http://localhost:6001/test/red 我这里随手写了个路由,
16

第一步 第一步 第一步 第一步

完结

如果有问题可以给我发邮件:guokrfans#gmail.com