开源项目 [微信开发系列] 使用 JS API 实现微信支付功能

ruby_sky · 2015年06月23日 · 最后由 ccckh 回复于 2021年04月08日 · 13432 次阅读
本帖已被管理员设置为精华贴

由于轮子 https://github.com/jasl/wx_pay.git (感谢作者 @jasl) 使用比较良好,weixin_authorize(https://github.com/lanrion/weixin_authorize)目前暂不提供支付接口集成。

下面是使用 JSAPI 实现支付的使用说明:

安装

Gemfile:

gem 'wx_pay', git: 'https://github.com/jasl/wx_pay.git'

your-app/config/initializers/wx_pay.rb:

# required
# WxPay.appid = Rails.application.secrets.app_id
WxPay.appid = "wxf5xxx"
WxPay.key = "xxxx"
WxPay.mch_id = "123123"
# optional - configurations for RestClient timeout, etc.
WxPay.extra_rest_client_options = {timeout: 2, open_timeout: 3}
# params = {
#   body: '测试商品',
#   out_trade_no: 'test003',
#   total_fee: 1,
#   spbill_create_ip: '127.0.0.1',
#   notify_url: ERB::Util.url_encode('http://making.dev/notify'),
#   trade_type: 'JSAPI',
#   nonce_str: "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
#   openid: "oah0RuOMkP2Kurp48bweSoaDqoEM"
# }

生成预支付 ID

order.rb:

# 只有当微信支付时使用到,订单一旦确认(confirm),即进行获取
# {remote_ip: request.ip}
def set_prepay_id(options={})
  return true if Rails.env.development?
  return true if is_prepay_id_valid?
  # 重新更新支付流水号
  self.pay_serial_number = "#{number}-#{Time.current.to_i}"
  unifiedorder = {
    body: "#{SITE_NAME}-#{number}",
    out_trade_no: pay_serial_number,
    total_fee: (total_price * 100).to_i, # 需要转换为分
    spbill_create_ip: options[:remote_ip] || '127.0.0.1',
    notify_url: wx_notify_url,
    trade_type: "JSAPI",
    nonce_str: SecureRandom.hex,
    openid: user.openid
   }
   Rails.logger.debug("unifiedorder_params: #{unifiedorder}")
   res = WxPay::Service.invoke_unifiedorder(unifiedorder)
   if res.success?
    self.prepay_id = res["prepay_id"]
    self.pre_pay_id_expired_at = Time.current + 2.hours
    Rails.logger.debug("set prepay_id: #{self.prepay_id}")
    self.save(validate: false)
   else
    Rails.logger.debug("set prepay_id fail: #{res}")
    false
   end
end

# {remote_ip: request.ip}
def get_prepay_id(options={})
  set_prepay_id(options) if !is_prepay_id_valid?
  prepay_id
end

def wx_notify_url
  "#{Rails.application.secrets.host_url}pay_notify/weixin_notify"
end

# 总价
def total_price
  self.item_total.to_f - self.promo_total.to_f
end

# 判断是否有效
def is_prepay_id_valid?
  prepay_id.present? && Time.current.to_i <= pre_pay_id_expired_at.to_i
end

调起支付

orders_controller.rb:

def show
  @cash_voucher = @order.cash_voucher
  @pay_p = {
    appId: Rails.application.secrets.app_id,
    timeStamp: Time.now.to_i.to_s,
    nonceStr: SecureRandom.hex,
    package: "prepay_id=#{@order.get_prepay_id(remote_ip: request.ip)}",
    signType: "MD5"
  }
  @pay_sign = WxPay::Sign.generate(@pay_p)
end

views/orders/show.html.erb: 此处需要使用到 JS SDK 的 API,请查看: https://github.com/lanrion/weixin_authorize/wiki/js-sdk 用户进入这个页面时,如果预支付 ID 无效则重新进行获取,点击“立即支付”时,会发起支付。

.....
.....
<% if !@order.paid? && !@order.cancel? %>
  <a href="javascript:;" class="to_pay btn btn-small">立即支付</a>
<% end %>
<%= content_for :javascript do %>
  <%= render "layouts/weixin_js" if !Rails.env.development? %>
  <script type="text/javascript">
    var order_id = "<%= @order.id %>";
    $(".to_pay").click(function(){
      wx.chooseWXPay({
        "timestamp": "<%= @pay_p[:timeStamp] %>",
        "nonceStr": "<%= @pay_p[:nonceStr] %>",
        "package": "<%= @pay_p[:package] %>",
        "signType": "<%= @pay_p[:signType] %>",
        "paySign": "<%= @pay_sign %>", // 支付签名
        success: function (res) {
          window.location.href = "/orders/" + order_id + "/pay_success";
        }
      });
    });
  </script>
<% end %>

接收支付回调与警报

routes.rb:

resource :pay_notify, only: [] do
  collection do
    post :weixin_notify
    post :weixin_exception_notify
  end
end

controllers:

class PayNotifiesController < ActionController::Base
  # 支付异步通知
  def weixin_notify
    result = Hash.from_xml(request.body.read)["xml"]
     Rails.logger.debug("weixin notify: #{result}")
    if WxPay::Sign.verify?(result)
      pay_serial_number = result["out_trade_no"]
      order = Order.find_by(pay_serial_number: pay_serial_number)
      order.paid_amount = result["total_fee"]
      order.state = "paid"
      order.pay_logs.new(
        pay_type: "weixin",
        trade_type: result[:trade_type],
        log: result
      )
      order.save(validate: false)
      # 支付成功后,减库存
      Rails.logger.debug("支付成功后,减库存")
      render xml: {
        return_code: "SUCCESS"
      }.to_xml(root: 'xml', dasherize: false)
    else
      render xml: {
        return_code: "SUCCESS",
        return_msg: "签名失败"
      }.to_xml(root: 'xml', dasherize: false)
    end
  end

  # 接收 报警
  # http://www.cnblogs.com/txw1958/p/weixin-payment-solution.html
  def weixin_exception_notify
    result = Hash.from_xml(request.body.read)["xml"]
    ServiceNotify.create(service_type: "weixin", content: result)
  end
end

如有问题,可以往: http://weixin-dev.com/topics 提问,又或者前往 https://github.com/lanrion/weixin_authorize/ 提问。

原文:https://github.com/lanrion/weixin_authorize/wiki/js-api-wx-pay

感谢 @rei 的工作,为 xxPay 的设计提供了原则性指导...

wx.config 写在哪里的

:plus1: good job!

WxPay.extra_rest_client_options = {timeout: 2, open_timeout: 3},这里 timeout 支付时间限制的时间单位是什么?

使用这两个 gem 解决了微信开发问题。只是接受警报,异步的情况没实现

#8 楼 @yumu01 友情提示,价格是以分为单位

#9 楼 @pathbox 查看"接收支付回调与警报",价格单位,代码上有解释,仔细看。

#8 楼 @yumu01 是秒为单位,但我不建议设置这么短,KnewOne 的实践经验上看,微信的服务偶尔会有抖动

@jasl , ok ,谢谢,我回头测试下

非常感谢你的轮子

只能用于手机支付不能用于桌面支付吗?

wx_pay 支持不支持支付到多个微信账户中?

支持不支持动态配置 WxPay.appid,WxPay.key,WxPay.mch_id

cool~退款有考虑吗

#20 楼 @windlx 后续会跟上。

#19 楼 @joker_m 多账户支持,后期会集成入 https://github.com/lanrion/weixin_authorize 这里。

感谢分享

请问 openid 是怎么获取的?

#25 楼 @ruby_sky 谢谢,已经搞明白了

playmonkey [微信开发] 开发环境搭建 提及了此话题。 07月08日 13:32
ruby_sky [微信开发系列] 手把手教你使用微信 JS SDK API 提及了此话题。 09月01日 17:46

https://h5zhifu.com 而且支持小程序、手机 H5 端,对个人开发者比较方便

ruby_sky 关闭了讨论。 04月08日 17:33
需要 登录 后方可回复, 如果你还没有账号请 注册新账号