Featured image of post 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

使用 Hugo 构建
主题 StackJimmy 设计