Class: Wechatpay::Api::V3::Client

Inherits:
Object
  • Object
show all
Includes:
JSAPI, Trade
Defined in:
lib/wechatpay/api/v3/client.rb

Constant Summary collapse

SCHEMA =
'WECHATPAY2-SHA256-RSA2048'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from JSAPI

#js_prepay, #js_sign

Methods included from Trade

#notice

Constructor Details

#initialize(appid, mch_id, **opts) ⇒ Client

Returns a new instance of Client.



18
19
20
21
22
23
24
25
26
27
# File 'lib/wechatpay/api/v3/client.rb', line 18

def initialize(appid, mch_id, **opts)
  @appid = appid
  @mch_id = mch_id
  @rsa_key = OpenSSL::PKey::RSA.new opts[:cert] if opts[:cert]
  @serial_no = opts[:cert_no]
  @key = opts[:key]
  @site = opts[:site] || 'https://api.mch.weixin.qq.com'

  @logger = Logger.new(STDOUT)
end

Instance Attribute Details

#appidObject (readonly)

Returns the value of attribute appid.



13
14
15
# File 'lib/wechatpay/api/v3/client.rb', line 13

def appid
  @appid
end

#keyObject (readonly)

Returns the value of attribute key.



13
14
15
# File 'lib/wechatpay/api/v3/client.rb', line 13

def key
  @key
end

#loggerObject

Returns the value of attribute logger.



14
15
16
# File 'lib/wechatpay/api/v3/client.rb', line 14

def logger
  @logger
end

#mch_idObject (readonly)

Returns the value of attribute mch_id.



13
14
15
# File 'lib/wechatpay/api/v3/client.rb', line 13

def mch_id
  @mch_id
end

#rsa_keyObject (readonly)

Returns the value of attribute rsa_key.



13
14
15
# File 'lib/wechatpay/api/v3/client.rb', line 13

def rsa_key
  @rsa_key
end

#serial_noObject (readonly)

Returns the value of attribute serial_no.



13
14
15
# File 'lib/wechatpay/api/v3/client.rb', line 13

def serial_no
  @serial_no
end

#siteObject

Returns the value of attribute site.



14
15
16
# File 'lib/wechatpay/api/v3/client.rb', line 14

def site
  @site
end

Instance Method Details

#authorization_header(http_method, path, body) ⇒ Object



97
98
99
# File 'lib/wechatpay/api/v3/client.rb', line 97

def authorization_header(http_method, path, body)
  [SCHEMA, authorization_params(http_method, path, body)].join ' '
end

#authorization_params(http_method, path, body) ⇒ Object



101
102
103
104
105
106
107
108
109
# File 'lib/wechatpay/api/v3/client.rb', line 101

def authorization_params(http_method, path, body)
  timestamp = Time.now.to_i
  rnd = SecureRandom.hex
  signature = sign_with(body, http_method, path, timestamp, rnd)
  [
    "mchid=\"#{mch_id}\"", "serial_no=\"#{serial_no}\"", "nonce_str=\"#{rnd}\"",
    "timestamp=\"#{timestamp}\"", "signature=\"#{signature}\""
  ].join(',')
end

#certObject



65
66
67
# File 'lib/wechatpay/api/v3/client.rb', line 65

def cert
  Wechatpay::Api::V3::Cert.instance.load || update_certs
end

#connectionObject



29
30
31
32
33
34
35
36
# File 'lib/wechatpay/api/v3/client.rb', line 29

def connection
  Faraday.new(url: @site) do |conn|
    conn.request :retry
    conn.response :logger
    # conn.response :raise_error
    conn.adapter :net_http
  end
end

#dechipher(data, nonce, auth_data) ⇒ Object



119
120
121
122
123
124
125
126
127
128
# File 'lib/wechatpay/api/v3/client.rb', line 119

def dechipher(data, nonce, auth_data)
  chipher = OpenSSL::Cipher.new 'aes-256-gcm'
  chipher.decrypt
  chipher.key = key
  chipher.iv = nonce
  chipher.padding = 0
  chipher.auth_data = auth_data
  chipher.auth_tag = data[-16, 16]
  chipher
end

#decrypt(cipher_text, nonce, auth_data) ⇒ Object



111
112
113
114
115
116
117
# File 'lib/wechatpay/api/v3/client.rb', line 111

def decrypt(cipher_text, nonce, auth_data)
  raise :cipher_text_invalid if (cipher_text.try(:length) || 0) < 16

  data = Base64.strict_decode64(cipher_text)
  dec = dechipher(data, nonce, auth_data)
  dec.update(data[0, data.length - 16]).tap { |s| logger.debug "DEC: #{s}" } + dec.final
end

#get(path, params = nil) ⇒ Object



38
39
40
41
42
43
44
45
46
# File 'lib/wechatpay/api/v3/client.rb', line 38

def get(path, params = nil)
  resp = connection.get(path, params) do |req|
    path = req.path
    path = [req.path, Faraday::Utils.build_query(req.params)].join('?') unless params.nil? || params.empty?
    req.headers['Authorization'] = authorization_header('GET', path, nil)
    req.headers['Accept'] = 'application/json'
  end
  handle resp
end

#handle(resp) ⇒ Object



77
78
79
80
81
82
83
# File 'lib/wechatpay/api/v3/client.rb', line 77

def handle(resp)
  logger.debug { "HANDLE RESPONSE: #{resp.inspect}" }
  data = resp.body
  raise :empty_body unless data && !data.empty?

  MultiJson.load data, symbolize_keys: true
end

#post(path, data, **headers) ⇒ Object



48
49
50
51
52
53
54
55
# File 'lib/wechatpay/api/v3/client.rb', line 48

def post(path, data, **headers)
  body = data.is_a?(Hash) ? MultiJson.dump(data) : data
  resp = connection.post(path, body, headers) do |req|
    req.headers['Authorization'] = authorization_header('POST', path, body)
    req.headers['Accept'] = 'application/json'
  end
  handle resp
end

#sign_content(content) ⇒ Object



91
92
93
94
95
# File 'lib/wechatpay/api/v3/client.rb', line 91

def sign_content(content)
  digest = OpenSSL::Digest::SHA256.new
  signed = rsa_key.sign(digest, content)
  Base64.strict_encode64 signed
end

#sign_with(body, method, path, timestamp, rnd) ⇒ Object



85
86
87
88
89
# File 'lib/wechatpay/api/v3/client.rb', line 85

def sign_with(body, method, path, timestamp, rnd)
  str = [method, path, timestamp, rnd, body].join("\n") + "\n"
  logger.debug { "Sign Content: #{str.inspect}" }
  sign_content(str)
end

#update_certsObject



69
70
71
72
73
74
75
# File 'lib/wechatpay/api/v3/client.rb', line 69

def update_certs
  certs = get('/v3/certificates')[:data]
  certs.map do |raw|
    Wechatpay::Api::V3::Cert.instance.update(raw, &method(:decrypt))
  end
  Wechatpay::Api::V3::Cert.instance
end

#verify(headers, body) ⇒ Object



57
58
59
60
61
62
63
# File 'lib/wechatpay/api/v3/client.rb', line 57

def verify(headers, body)
  sha256 = OpenSSL::Digest::SHA256.new
  key = cert.certificate.public_key
  sign = Base64.strict_decode64(headers['Wechatpay-Signature'])
  data = %w[Wechatpay-Timestamp Wechatpay-Nonce].map { |k| headers[k] }
  key.verify sha256, sign, data.append(body).join("\n") + "\n"
end