class WechatPayment::Client

Constants

GATEWAY_URL
GENERATE_JS_PAY_REQ_REQUIRED_FIELDS
INVOKE_REFUND_REQUIRED_FIELDS
INVOKE_UNIFIEDORDER_REQUIRED_FIELDS
ORDER_REQUIRED_FIELD
REFUND_REQUIRED_PARAMS

Public Class Methods

decrypt_refund_notify(decrypted_data) click to toggle source

解密微信退款回调信息

result = Hash.from_xml(request.body.read)

data = WechatPayment::Service.decrypt_refund_notify(result)

# File lib/wechat_payment/client.rb, line 204
def self.decrypt_refund_notify(decrypted_data)
  aes = OpenSSL::Cipher::AES.new('256-ECB')
  aes.decrypt
  aes.key = Digest::MD5.hexdigest(WechatPayment.key)
  result = aes.update(Base64.decode64(decrypted_data)) + aes.final
  Hash.from_xml(result)["root"]
end
gen_js_pay_payload(order_result, options = {}) click to toggle source

生成下单成功后返回给前端拉起微信支付的数据结构

# File lib/wechat_payment/client.rb, line 111
def self.gen_js_pay_payload(order_result, options = {})
  payment_params = {
    appId: WechatPayment.sub_appid || WechatPayment.appid,
    package: "prepay_id=#{order_result["prepay_id"]}",
    key: options.delete(:key) || WechatPayment.key,
    nonceStr: SecureRandom.hex(16),
    timeStamp: Time.now.to_i.to_s,
    signType: 'MD5'
  }

  payment_params[:paySign] = WechatPayment::Sign.generate(payment_params)

  payment_params
end
generate_js_pay_req(params, options = {}) click to toggle source
# File lib/wechat_payment/client.rb, line 127
def self.generate_js_pay_req(params, options = {})
  check_required_options(params, GENERATE_JS_PAY_REQ_REQUIRED_FIELDS)

  params = {
    appId: options.delete(:appid) || WechatPayment.appid,
    package: "prepay_id=#{params.delete(:prepayid)}",
    key: options.delete(:key) || WechatPayment.key,
    nonceStr: params.delete(:noncestr),
    timeStamp: Time.now.to_i.to_s,
    signType: 'MD5'
  }.merge(params)

  params[:paySign] = WechatPayment::Sign.generate(params)
  params
end
handle_payment_notify(notify_data) click to toggle source

处理支付回调

# File lib/wechat_payment/client.rb, line 79
def self.handle_payment_notify(notify_data)
  if !WechatPayment::Sign.verify?(notify_data)
    payment_logger.error("{msg: 签名验证失败, errors: #{notify_data}}")
    WechatPayment::ServiceResult.new(errors: notify_data, message: "回调签名验证失败")
  end

  result = WechatPayment::InvokeResult.new(notify_data)

  if result.success?
    payment_logger.info("{callback: #{notify_data}}")
    WechatPayment::ServiceResult.new(success: true, data: notify_data)
  else
    payment_logger.error("{callback: #{notify_data}}")
    WechatPayment::ServiceResult.new(errors: notify_data)
  end
end
handle_refund_notify(encrypted_notify_data) click to toggle source

处理退款回调

# File lib/wechat_payment/client.rb, line 97
def self.handle_refund_notify(encrypted_notify_data)
  notify_data = decrypt_refund_notify(encrypted_notify_data)

  result = WechatPayment::InvokeResult.new(notify_data)
  if result.success?
    refund_logger.info "{callback: #{notify_data}}"
    WechatPayment::ServiceResult.new(success: true, data: notify_data)
  else
    refund_logger.error "{callback: #{notify_data}}"
    WechatPayment::ServiceResult.new(errors: notify_data)
  end
end
new() click to toggle source
# File lib/wechat_payment/client.rb, line 6
def initialize # (merchant = WechatPayment)
  # required_attrs = [:appid, :mch_id, :key, :app_secret, :cert_path]
  # missing_attrs = required_attrs.reject { |attr| merchant.respond_to?(attr) }
  # if missing_attrs.present?
  #   raise Exceptions::MerchantMissingAttr.new("Missing attributes: #{missing_attrs}, merchant target must respond to: appid, mch_id, key, appsecret, cert_path")
  # end
  #
  # @merchant = merchant
  # cert_path = Rails.root.join(merchant.cert_path)
  #
  # WechatPayment.appid = merchant.appid
  # WechatPayment.key = merchant.key
  # WechatPayment.mch_id = merchant.mch_id
  # WechatPayment.appsecret = merchant.app_secret
  # WechatPayment.set_apiclient_by_pkcs12(File.binread(cert_path), merchant.mch_id)
end

Private Class Methods

payment_logger() click to toggle source
# File lib/wechat_payment/client.rb, line 263
def self.payment_logger
  @payment_logger ||= WechatPayment::RLogger.make("wx_payment")
end
refund_logger() click to toggle source
# File lib/wechat_payment/client.rb, line 267
def self.refund_logger
  @refund_logger ||= WechatPayment::RLogger.make("wx_refund")
end

Public Instance Methods

check_required_options(options, names) click to toggle source
# File lib/wechat_payment/client.rb, line 217
def check_required_options(options, names)
  names.each do |name|
    warn("WechatPayment Warn: missing required option: #{name}") unless options.has_key?(name)
  end
end
invoke_refund(params, options = {}) { |result| ... } click to toggle source

out_trade_no 和 transaction_id 是二选一(必填)

# File lib/wechat_payment/client.rb, line 169
def invoke_refund(params, options = {})
  params = {
    appid: options.delete(:appid) || WechatPayment.appid,
    mch_id: options.delete(:mch_id) || WechatPayment.mch_id,
    key: options.delete(:key) || WechatPayment.key,
    nonce_str: SecureRandom.uuid.tr('-', ''),
  }.merge(params)

  params[:op_user_id] ||= params[:mch_id]

  check_required_options(params, INVOKE_REFUND_REQUIRED_FIELDS)
  warn("WechatPayment Warn: missing required option: out_trade_no or transaction_id must have one") if ([:out_trade_no, :transaction_id] & params.keys) == []

  options = {
    cert: options.delete(:apiclient_cert) || WechatPayment.apiclient_cert,
    key: options.delete(:apiclient_key) || WechatPayment.apiclient_key,
    verify_mode: OpenSSL::SSL::VERIFY_NONE
  }.merge(options)

  result = WechatPayment::InvokeResult.new(
    Hash.from_xml(
      invoke_remote("/secapi/pay/refund", make_payload(params), options)
    )
  )

  yield result if block_given?

  result
end
invoke_remote(url, payload, options = {}) click to toggle source
# File lib/wechat_payment/client.rb, line 223
def invoke_remote(url, payload, options = {})
  uri = URI("#{GATEWAY_URL}#{url}")

  req = Net::HTTP::Post.new(uri)
  req['Content-Type'] = 'application/xml'

  options = {
    use_ssl: true
  }.merge(options)

  res = Net::HTTP.start(uri.hostname, uri.port, **options) do |http|
    http.use_ssl = true
    http.request(req, payload)
  end

  res.body
end
invoke_unifiedorder(params, options = {}) { |result| ... } click to toggle source
# File lib/wechat_payment/client.rb, line 145
def invoke_unifiedorder(params, options = {})
  params = {
    appid: options.delete(:appid) || WechatPayment.appid,
    mch_id: options.delete(:mch_id) || WechatPayment.mch_id,
    key: options.delete(:key) || WechatPayment.key,
    nonce_str: SecureRandom.uuid.tr('-', '')
  }.merge(params)

  check_required_options(params, INVOKE_UNIFIEDORDER_REQUIRED_FIELDS)

  result = WechatPayment::InvokeResult.new(
    Hash.from_xml(
      invoke_remote("/pay/unifiedorder", make_payload(params), options)
    )
  )

  yield result if block_given?

  result
end
make_payload(params, sign_type = WechatPayment::Sign::SIGN_TYPE_MD5) click to toggle source
# File lib/wechat_payment/client.rb, line 212
def make_payload(params, sign_type = WechatPayment::Sign::SIGN_TYPE_MD5)
  sign = WechatPayment::Sign.generate(params, sign_type)
  "<xml>#{params.except(:key).sort.map { |k, v| "<#{k}>#{v}</#{k}>" }.join}<sign>#{sign}</sign></xml>"
end
order(order_params) click to toggle source

下单

# File lib/wechat_payment/client.rb, line 25
def order(order_params)
  check_required_key!(order_params, ORDER_REQUIRED_FIELD)

  order_params.merge!(**WechatPayment.as_payment_params,
                      trade_type: :JSAPI,
                      notify_url: payment_notify_url)

  # 如果是服务商模式(根据参数中有没有 sub_appid 判断),就把 openid 换成 sub_openid
  if order_params[:sub_appid]
    order_params[:sub_openid] = order_params.delete(:openid)
  end

  order_result = invoke_unifiedorder(order_params)

  if order_result.success?

    payment_logger.info("{params: #{order_params}, result: #{order_result}}")
    # WechatPayment::ServiceResult.new(success: true, data: { order_result: order_result.with_indifferent_access, js_payload: mini_program_request_params.with_indifferent_access })
    WechatPayment::ServiceResult.new(success: true, data: order_result.with_indifferent_access)
  else
    payment_logger.error("{params: #{order_params}, result: #{order_result}}")
    WechatPayment::ServiceResult.new(success: false, errors: order_result.with_indifferent_access)
  end
end
payment_notify_url() click to toggle source

支付回调地址

# File lib/wechat_payment/client.rb, line 51
def payment_notify_url
  ENV["WECHAT_PAYMENT_NOTIFY_URL"] || "#{WechatPayment.host}/wechat_payment/callback/payment"
end
refund(refund_params) click to toggle source

退款

# File lib/wechat_payment/client.rb, line 57
def refund(refund_params)
  check_required_key!(refund_params, REFUND_REQUIRED_PARAMS)

  refund_params.merge!(**WechatPayment.as_payment_params, notify_url: refund_notify_url)

  refund_result = invoke_refund(refund_params.to_options)

  if refund_result.success?
    refund_logger.info "{params: #{refund_params}, result: #{refund_result}"
    WechatPayment::ServiceResult.new(success: true, data: refund_result)
  else
    refund_logger.error "{params: #{refund_params}, result: #{refund_result}"
    WechatPayment::ServiceResult.new(success: false, errors: refund_result)
  end
end
refund_notify_url() click to toggle source

退款回调地址

# File lib/wechat_payment/client.rb, line 74
def refund_notify_url
  ENV["WECHAT_REFUND_NOTIFY_URL"] || "#{WechatPayment.host}/wechat_payment/callback/refund"
end

Private Instance Methods

check_required_key!(data, required_keys) click to toggle source

判断 hash 是否缺少 key

# File lib/wechat_payment/client.rb, line 245
def check_required_key!(data, required_keys)
  lack_of_keys = required_keys - data.keys.map(&:to_sym)

  if lack_of_keys.present?
    raise WechatPayment::MissingKeyError.new("Parameter missing keys: #{lack_of_keys}")
  end
end
payment_logger() click to toggle source

支付日志

# File lib/wechat_payment/client.rb, line 254
def payment_logger
  WechatPayment::Client.payment_logger
end
refund_logger() click to toggle source

退款日志

# File lib/wechat_payment/client.rb, line 259
def refund_logger
  WechatPayment::Client.refund_logger
end