class Qpay::Client

Public Class Methods

new(options = {}) click to toggle source
# File lib/qpay/client.rb, line 6
def initialize(options = {})
  @config = Config.new
  @config.appid = options[:appid]
  @config.mch_id = options[:mch_id]
  @config.app_key = options[:app_key]
  @config.api_key = options[:api_key]
  @config.cert_type = options[:cert_type]
  @config.cert_file = options[:cert_file]
  @config.cert_password = options[:cert_password]
  @config.adapter = options[:adapter] || :patron
end
post(url, body, format:) click to toggle source
Calls superclass method
# File lib/qpay/client.rb, line 33
def self.post(url, body, format:)
  super(url, body: body, format: format).parsed_response
end

Public Instance Methods

api() click to toggle source
# File lib/qpay/client.rb, line 22
def api
  @api ||= case config.adapter
             when :httparty
               require 'httparty'
               c = Class.new do
                 include ::HTTParty
                 base_uri 'https://qpay.qq.com/cgi-bin'
                 disable_rails_query_string_format
                 default_timeout 10
                 headers 'Content-Type' => 'application/xml'

                 def self.post(url, body, format:)
                   super(url, body: body, format: format).parsed_response
                 end
               end

               c.default_options.delete(:p12)
               c.default_options.delete(:p12_password)
               c.default_options.delete(:pem)
               c.default_options.delete(:pem_password)

               # set cert option
               if @config.cert_type && @config.cert_file && File.exist?(@config.cert_file)
                 c..public_send(@config.cert_type.to_sym, File.read(@config.cert_file), @config.cert_password)
               end
               c
             when :patron
               require 'patron'
               Class.new do
                 def self.post(url, body, format:)
                   s = Patron::Session.new(base_url: 'https://qpay.qq.com/cgi-bin',
                                           timeout: 10,
                                           headers: {'Content-Type' => 'application/xml'},
                                           force_ipv4: true)
                   body = s.post(url, body).body
                   case format
                     when :xml
                       MultiXml.parse(body)
                     when :csv
                       require 'csv'
                       CSV.parse(body)
                   end
                 end
               end
           end
end
closeorder(out_trade_no, more_params = {}) click to toggle source

以下情况需要调用关单接口:

* 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付
* 系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口

注意:订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。

# File lib/qpay/client.rb, line 100
def closeorder(out_trade_no, more_params = {})
  params = more_params.merge(out_trade_no: out_trade_no)

  api.post('/pay/qpay_close_order.cgi', request_params(params), format: :xml)
end
config() click to toggle source
# File lib/qpay/client.rb, line 18
def config
  @config.dup
end
downloadbill(bill_date, more_params = {}) click to toggle source

商户可以通过该接口下载历史交易清单。

注意:

1、在QQ钱包侧未成功下单的交易不会出现在对账单中;
2、QQ钱包在次日9点启动生成前一天的对账单,建议商户10点后再获取;
3、对账单中涉及金额的字段单位为“元”;
4、对账单接口只能下载三个月以内的对账单。
# File lib/qpay/client.rb, line 137
def downloadbill(bill_date, more_params = {})
  params = more_params.merge(bill_date: Time.parse(bill_date.to_s).strftime('%Y%m%d'))

  # csv data
  api.post("/sp_download/qpay_mch_statement_down.cgi", request_params(params), format: :csv)
end
orderquery(out_trade_no, more_params = {}) click to toggle source

该接口提供所有手Q钱包订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。

需要调用查询接口的情况:

* 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
* 调用支付接口后,返回系统错误或未知交易状态情况;
* 调用 qpay_close_order.cgi 关闭订单接口之前之前,需确认支付状态
# File lib/qpay/client.rb, line 89
def orderquery(out_trade_no, more_params = {})
  params = more_params.merge(out_trade_no: out_trade_no)

  api.post('/pay/qpay_order_query.cgi', request_params(params), format: :xml)
end
refund(out_trade_no, out_refund_no, total_fee, refund_fee, more_params = {}) click to toggle source

当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,QQ钱包将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。

注意:

1.交易时间超过一年的订单无法提交退款;
2.QQ钱包退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。一笔退款失败后重新提交,要采用原来的退款单号。总退款金额不能超过用户实际支付金额。
# File lib/qpay/client.rb, line 112
def refund(out_trade_no, out_refund_no, total_fee, refund_fee, more_params = {})
  params = more_params.merge(out_trade_no: out_trade_no,
                             out_refund_no: out_refund_no,
                             total_fee: total_fee,
                             refund_fee: refund_fee)
  params[:refund_fee_type] ||= 'CNY'
  params[:op_user_id] ||= Qpay.config.mch_id

  api.post('/pay/qpay_refund.cgi', request_params(params), format: :xml)
end
refundquery(out_trade_no, more_params = {}) click to toggle source

提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用余额支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。

# File lib/qpay/client.rb, line 124
def refundquery(out_trade_no, more_params = {})
  params = more_params.merge(out_trade_no: out_trade_no)

  api.post('/pay/qpay_refund_query.cgi', request_params(params), format: :xml)
end
unifiedorder(body, out_trade_no, total_fee, spbill_create_ip, notify_url, more_params = {}) click to toggle source

除被扫支付场景以外,商户系统先调用该接口在QQ钱包服务后台生成预支付交易单,返回正确的预支付交易回话标识后再按具体的场景生成交易串调起支付。

# File lib/qpay/client.rb, line 70
def unifiedorder(body, out_trade_no, total_fee, spbill_create_ip, notify_url, more_params = {})
  params = more_params.merge(body: body,
                             out_trade_no: out_trade_no,
                             total_fee: total_fee,
                             spbill_create_ip: spbill_create_ip,
                             notify_url: notify_url)
  params[:fee_type] ||= 'CNY'
  params[:time_start] ||= Time.now.strftime('%Y%m%d%H%M%S')
  params[:trade_type] ||= 'APP'

  api.post('/pay/qpay_unified_order.cgi', request_params(params), format: :xml)
end

Private Instance Methods

request_params(params) click to toggle source
# File lib/qpay/client.rb, line 145
def request_params(params)
  params.merge!(appid: @config.appid, mch_id: @config.mch_id, nonce_str: SecureRandom.urlsafe_base64)
  to_xml(Qpay.params_with_sign(params, @config))
end
to_xml(params) click to toggle source
# File lib/qpay/client.rb, line 150
def to_xml(params)
  xml = '<xml>'
  ((params.keys - ['sign', :sign]).sort | ['sign', :sign]).each do |k|
    xml << "<#{k}>#{params[k]}</#{k}>" if params[k]
  end
  xml << '</xml>'
end