2017 年 Ruby on Rails 5 微信公众号给用户发红包
发放普通红包
这次要实现的是"发放普通红包",官方文档:
https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
今天2017年11月14号,成功把发红包搞定。
我先写业务上需要发红包的理由。再写技术实现细节。
业务描述
寓住(yuzhu.me)是一个公寓点评网站。可以想成"公寓版的豆瓣"。比如住过 You+ 青年公寓,广州广纸社区 的人,就可以上去评分,写评论。
为什么发红包
真正让寓住有用的,是住过公寓的人上来填写的数据,这些数据让别人知道这个公寓的质量如何。从而决定要不要去住。
那么让这类人群上来填写是关键。发红包就是希望通过这个 "填评价发红包" 的活动。
吸引一些人上来填数据。
公众号类型:服务号
我记得是只有服务号可以发红包还是怎么滴,忘记了。总之先说明下,示例中用的是服务号。
代码实现
没有用微信相关的 gem,我不喜欢用,因为实际代码不多,不到 40 行。
第 1 步:引入一个 gem
在 Gemfile
中引入 rest-client
这个发 http 请求的 gem。
gem 'rest-client' # for HTTP, https://github.com/rest-client/rest-client
第 2 步:创建新目录和文件,并加到自动载入里。
我们的代码要写到 app/weixin/weixin.rb
里,
写到这个路径并没有什么特别的理由,你不用非得照做,我个人这样做的理由是我喜欢把微信相关的代码都放在一起。如果都塞一个 controller 里会感觉很奇怪。。
因为 app/weixin/
不是常规目录,
所以在 config/application.rb
加上一句 config.autoload_paths += Dir[Rails.root.join('app', 'weixin')]
达到载入这里面代码的目的。
第 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 这个函数甚至都不用翻页,一页搞定。)
另外2个函数我放最底下了。
我估计大家最困惑的部分是【签名】 + 【怎么使用证书】。
上面的代码都展示了。
签名:用 def get_sign
证书:可以看到代码里使用证书的时候是这样用的:"./secret/apiclient_cert.pem"
那么实际放置位置是 Ruby on Rails 项目的根目录
上图中 Yuzhu2
文件夹就是 Ruby on Rails 项目。
证书是下载自
实测
http://localhost:6001/test/red
我这里随手写了个路由,
完结
如果有问题可以给我发邮件:guokrfans#gmail.com