pingpp 2.0.15 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,25 +2,17 @@ module Pingpp
2
2
  class PingppObject
3
3
  include Enumerable
4
4
 
5
- attr_accessor :api_key
6
- @@permanent_attributes = Set.new([:api_key, :id])
5
+ @@permanent_attributes = Set.new([:id])
7
6
 
8
7
  # The default :id method is deprecated and isn't useful to us
9
8
  if method_defined?(:id)
10
9
  undef :id
11
10
  end
12
11
 
13
- def initialize(id=nil, api_key=nil)
14
- # parameter overloading!
15
- if id.kind_of?(Hash)
16
- @retrieve_options = id.dup
17
- @retrieve_options.delete(:id)
18
- id = id[:id]
19
- else
20
- @retrieve_options = {}
21
- end
22
-
23
- @api_key = api_key
12
+ def initialize(id=nil, opts={})
13
+ id, @retrieve_params = Util.normalize_id(id)
14
+ @opts = Util.normalize_opts(opts)
15
+ @original_values = {}
24
16
  @values = {}
25
17
  # This really belongs in APIResource, but not putting it there allows us
26
18
  # to have a unified inspect method
@@ -29,8 +21,13 @@ module Pingpp
29
21
  @values[:id] = id if id
30
22
  end
31
23
 
32
- def self.construct_from(values, api_key=nil)
33
- self.new(values[:id], api_key).refresh_from(values, api_key)
24
+ def self.construct_from(values, opts={})
25
+ values = Pingpp::Util.symbolize_names(values)
26
+ self.new(values[:id]).send(:initialize_from, values, opts)
27
+ end
28
+
29
+ def ==(other)
30
+ other.is_a?(PingppObject) && @values == other.instance_variable_get(:@values)
34
31
  end
35
32
 
36
33
  def to_s(*args)
@@ -42,30 +39,11 @@ module Pingpp
42
39
  "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(@values)
43
40
  end
44
41
 
45
- def refresh_from(values, api_key, partial=false)
46
- @api_key = api_key
47
-
48
- @previous_metadata = values[:metadata]
49
- removed = partial ? Set.new : Set.new(@values.keys - values.keys)
50
- added = Set.new(values.keys - @values.keys)
51
-
52
- instance_eval do
53
- remove_accessors(removed)
54
- add_accessors(added)
55
- end
56
- removed.each do |k|
57
- @values.delete(k)
58
- @transient_values.add(k)
59
- @unsaved_values.delete(k)
60
- end
61
- values.each do |k, v|
62
- @values[k] = Util.convert_to_pingpp_object(v, api_key)
63
- @transient_values.delete(k)
64
- @unsaved_values.delete(k)
65
- end
66
-
67
- return self
42
+ def refresh_from(values, opts, partial=false)
43
+ initialize_from(values, opts, partial)
68
44
  end
45
+ extend Gem::Deprecate
46
+ deprecate :refresh_from, "#initialize_from", 2017, 01
69
47
 
70
48
  def [](k)
71
49
  @values[k.to_sym]
@@ -103,12 +81,12 @@ module Pingpp
103
81
  end
104
82
 
105
83
  def _dump(level)
106
- Marshal.dump([@values, @api_key])
84
+ Marshal.dump([@values, @opts])
107
85
  end
108
86
 
109
87
  def self._load(args)
110
- values, api_key = Marshal.load(args)
111
- construct_from(values, api_key)
88
+ values, opts = Marshal.load(args)
89
+ construct_from(values, opts)
112
90
  end
113
91
 
114
92
  if RUBY_VERSION < '1.9.2'
@@ -117,15 +95,37 @@ module Pingpp
117
95
  end
118
96
  end
119
97
 
98
+ def serialize_params(options = {})
99
+ update_hash = {}
100
+
101
+ @values.each do |k, v|
102
+ unsaved = @unsaved_values.include?(k)
103
+ if options[:force] || unsaved || v.is_a?(PingppObject)
104
+ update_hash[k.to_sym] =
105
+ serialize_params_value(@values[k], @original_values[k], unsaved, options[:force])
106
+ end
107
+ end
108
+
109
+ update_hash.reject! { |_, v| v == nil }
110
+
111
+ update_hash
112
+ end
113
+
120
114
  protected
121
115
 
122
116
  def metaclass
123
117
  class << self; self; end
124
118
  end
125
119
 
120
+ def protected_fields
121
+ []
122
+ end
123
+
126
124
  def remove_accessors(keys)
125
+ f = protected_fields
127
126
  metaclass.instance_eval do
128
127
  keys.each do |k|
128
+ next if f.include?(k)
129
129
  next if @@permanent_attributes.include?(k)
130
130
  k_eq = :"#{k}="
131
131
  remove_method(k) if method_defined?(k)
@@ -134,9 +134,11 @@ module Pingpp
134
134
  end
135
135
  end
136
136
 
137
- def add_accessors(keys)
137
+ def add_accessors(keys, values)
138
+ f = protected_fields
138
139
  metaclass.instance_eval do
139
140
  keys.each do |k|
141
+ next if f.include?(k)
140
142
  next if @@permanent_attributes.include?(k)
141
143
  k_eq = :"#{k}="
142
144
  define_method(k) { @values[k] }
@@ -147,9 +149,15 @@ module Pingpp
147
149
  "We interpret empty strings as nil in requests." +
148
150
  "You may set #{self}.#{k} = nil to delete the property.")
149
151
  end
150
- @values[k] = v
152
+ @values[k] = Util.convert_to_pingpp_object(v, @opts)
153
+ dirty_value!(@values[k])
151
154
  @unsaved_values.add(k)
152
155
  end
156
+
157
+ if [FalseClass, TrueClass].include?(values[k].class)
158
+ k_bool = :"#{k}?"
159
+ define_method(k_bool) { @values[k] }
160
+ end
153
161
  end
154
162
  end
155
163
  end
@@ -158,7 +166,7 @@ module Pingpp
158
166
  # TODO: only allow setting in updateable classes.
159
167
  if name.to_s.end_with?('=')
160
168
  attr = name.to_s[0...-1].to_sym
161
- add_accessors([attr])
169
+ add_accessors([attr], {})
162
170
  begin
163
171
  mth = method(name)
164
172
  rescue NameError
@@ -183,5 +191,99 @@ module Pingpp
183
191
  def respond_to_missing?(symbol, include_private = false)
184
192
  @values && @values.has_key?(symbol) || super
185
193
  end
194
+
195
+ def update_attributes(values, opts = {}, method_options = {})
196
+ dirty = method_options.fetch(:dirty, true)
197
+ values.each do |k, v|
198
+ add_accessors([k], values) unless metaclass.method_defined?(k.to_sym)
199
+ @values[k] = Util.convert_to_pingpp_object(v, opts)
200
+ dirty_value!(@values[k]) if dirty
201
+ @unsaved_values.add(k)
202
+ end
203
+ end
204
+
205
+ def initialize_from(values, opts, partial=false)
206
+ @opts = Util.normalize_opts(opts)
207
+ @original_values = Marshal.load(Marshal.dump(values)) # deep copy
208
+
209
+ removed = partial ? Set.new : Set.new(@values.keys - values.keys)
210
+ added = Set.new(values.keys - @values.keys)
211
+
212
+ instance_eval do
213
+ remove_accessors(removed)
214
+ add_accessors(added, values)
215
+ end
216
+
217
+ removed.each do |k|
218
+ @values.delete(k)
219
+ @transient_values.add(k)
220
+ @unsaved_values.delete(k)
221
+ end
222
+
223
+ update_attributes(values, opts, :dirty => false)
224
+ values.each do |k, _|
225
+ @transient_values.delete(k)
226
+ @unsaved_values.delete(k)
227
+ end
228
+
229
+ self
230
+ end
231
+
232
+ def serialize_params_value(value, original, unsaved, force)
233
+ case true
234
+ when value == nil
235
+ ''
236
+
237
+ when value.is_a?(APIResource) && !value.save_with_parent
238
+ nil
239
+
240
+ when value.is_a?(Array)
241
+ update = value.map { |v| serialize_params_value(v, nil, true, force) }
242
+
243
+ # This prevents an array that's unchanged from being resent.
244
+ if update != serialize_params_value(original, nil, true, force)
245
+ update
246
+ else
247
+ nil
248
+ end
249
+
250
+ when value.is_a?(Hash)
251
+ Util.convert_to_pingpp_object(value, @opts).serialize_params
252
+
253
+ when value.is_a?(PingppObject)
254
+ update = value.serialize_params(:force => force)
255
+ update = empty_values(original).merge(update) if original && unsaved
256
+
257
+ update
258
+
259
+ else
260
+ value
261
+ end
262
+ end
263
+
264
+ private
265
+
266
+ def dirty_value!(value)
267
+ case value
268
+ when Array
269
+ value.map { |v| dirty_value!(v) }
270
+ when PingppObject
271
+ value.dirty!
272
+ end
273
+ end
274
+
275
+ def empty_values(obj)
276
+ values = case obj
277
+ when Hash then obj
278
+ when PingppObject then obj.instance_variable_get(:@values)
279
+ else
280
+ raise ArgumentError, "#empty_values got unexpected object type: #{obj.class.name}"
281
+ end
282
+
283
+ values.inject({}) do |update, (k, _)|
284
+ update[k] = ''
285
+ update
286
+ end
287
+ end
186
288
  end
187
289
  end
@@ -1,9 +1,13 @@
1
1
  module Pingpp
2
2
  class RedEnvelope < APIResource
3
- include Pingpp::APIOperations::List
4
- include Pingpp::APIOperations::Create
3
+ extend Pingpp::APIOperations::Create
4
+ extend Pingpp::APIOperations::List
5
5
 
6
- def self.url
6
+ def self.object_name
7
+ 'red_envelope'
8
+ end
9
+
10
+ def self.resource_url(opts={})
7
11
  '/v1/red_envelopes'
8
12
  end
9
13
 
data/lib/pingpp/refund.rb CHANGED
@@ -1,14 +1,23 @@
1
1
  module Pingpp
2
2
  class Refund < APIResource
3
- include Pingpp::APIOperations::Update
4
- include Pingpp::APIOperations::List
3
+ extend Pingpp::APIOperations::Create
4
+ extend Pingpp::APIOperations::List
5
5
 
6
- def url
7
- "#{Charge.url}/#{CGI.escape(charge)}/refunds/#{CGI.escape(id)}"
6
+ def self.retrieve(charge, id, opts={})
7
+ opts[:parents] = ['charges', charge]
8
+ super(id, opts)
8
9
  end
9
10
 
10
- def self.retrieve(id, api_key=nil)
11
- raise NotImplementedError.new("Refunds cannot be retrieved without a charge ID. Retrieve a refund using charge.refunds.retrieve('refund_id')")
11
+ def self.create(charge, params, opts={})
12
+ opts[:parents] = ['charges', charge]
13
+ super(params, opts)
12
14
  end
15
+
16
+ def self.list(charge, filters={}, opts={})
17
+ opts[:parents] = ['charges', charge]
18
+ super(filters, opts)
19
+ end
20
+
21
+ singleton_class.send(:alias_method, :all, :list)
13
22
  end
14
23
  end
@@ -1,18 +1,18 @@
1
1
  module Pingpp
2
2
  class SingletonAPIResource < APIResource
3
- def self.url
3
+ def self.resource_url(opts={})
4
4
  if self == SingletonAPIResource
5
- raise NotImplementedError.new('SingletonAPIResource is an abstract class. You should perform actions on its subclasses (Account, etc.)')
5
+ raise NotImplementedError.new('SingletonAPIResource is an abstract class.')
6
6
  end
7
7
  "/v1/#{CGI.escape(class_name.downcase)}"
8
8
  end
9
9
 
10
- def url
11
- self.class.url
10
+ def resource_url(opts={})
11
+ self.class.resource_url(opts)
12
12
  end
13
13
 
14
- def self.retrieve(api_key=nil)
15
- instance = self.new(nil, api_key)
14
+ def self.retrieve(opts={})
15
+ instance = self.new(nil, Util.normalize_opts(opts))
16
16
  instance.refresh
17
17
  instance
18
18
  end
@@ -1,12 +1,16 @@
1
1
  module Pingpp
2
2
  class Transfer < APIResource
3
- include Pingpp::APIOperations::List
4
- include Pingpp::APIOperations::Create
3
+ extend Pingpp::APIOperations::Create
4
+ extend Pingpp::APIOperations::List
5
5
  include Pingpp::APIOperations::Update
6
6
 
7
- def self.url
7
+ def self.resource_url(opts={})
8
8
  '/v1/transfers'
9
9
  end
10
10
 
11
+ def self.cancel(id, opts={})
12
+ update(id, {:status => 'canceled'}, opts)
13
+ end
14
+
11
15
  end
12
16
  end
data/lib/pingpp/util.rb CHANGED
@@ -22,17 +22,19 @@ module Pingpp
22
22
  'refund' => Refund,
23
23
  'red_envelope' => RedEnvelope,
24
24
  'transfer' => Transfer,
25
- 'event' => Event
25
+ 'batch_refund' => BatchRefund,
26
+ 'batch_transfer' => BatchTransfer,
27
+ 'customs' => Customs
26
28
  }
27
29
  end
28
30
 
29
- def self.convert_to_pingpp_object(resp, api_key)
31
+ def self.convert_to_pingpp_object(resp, opts)
30
32
  case resp
31
33
  when Array
32
- resp.map { |i| convert_to_pingpp_object(i, api_key) }
34
+ resp.map { |i| convert_to_pingpp_object(i, opts) }
33
35
  when Hash
34
36
  # Try converting to a known object class. If none available, fall back to generic PingppObject
35
- object_classes.fetch(resp[:object], PingppObject).construct_from(resp, api_key)
37
+ object_classes.fetch(resp[:object], PingppObject).construct_from(resp, opts)
36
38
  else
37
39
  resp
38
40
  end
@@ -68,13 +70,13 @@ module Pingpp
68
70
  end
69
71
 
70
72
  def self.url_encode(key)
71
- URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
73
+ CGI.escape(key.to_s).gsub('%5B', '[').gsub('%5D', ']')
72
74
  end
73
75
 
74
76
  def self.flatten_params(params, parent_key=nil)
75
77
  result = []
76
78
  params.each do |key, value|
77
- calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
79
+ calculated_key = parent_key ? "#{parent_key}[#{key}]" : "#{key}"
78
80
  if value.is_a?(Hash)
79
81
  result += flatten_params(value, calculated_key)
80
82
  elsif value.is_a?(Array)
@@ -127,5 +129,49 @@ module Pingpp
127
129
 
128
130
  return new_headers
129
131
  end
132
+
133
+ def self.encode_parameters(params)
134
+ Util.flatten_params(params).
135
+ map { |k,v| "#{url_encode(k)}=#{url_encode(v)}" }.join('&')
136
+ end
137
+
138
+ def self.normalize_id(id)
139
+ if id.kind_of?(Hash) # overloaded id
140
+ params_hash = id.dup
141
+ id = params_hash.delete(:id)
142
+ else
143
+ params_hash = {}
144
+ end
145
+ [id, params_hash]
146
+ end
147
+
148
+ def self.normalize_opts(opts)
149
+ case opts
150
+ when String
151
+ {:api_key => opts}
152
+ when Hash
153
+ check_api_key!(opts.fetch(:api_key)) if opts.has_key?(:api_key)
154
+ check_app!(opts.fetch(:app)) if opts.has_key?(:app)
155
+ check_user!(opts.fetch(:user)) if opts.has_key?(:user)
156
+ opts.clone
157
+ else
158
+ raise TypeError.new('normalize_opts expects a string or a hash')
159
+ end
160
+ end
161
+
162
+ def self.check_api_key!(key)
163
+ raise TypeError.new("api_key must be a string") unless key.is_a?(String)
164
+ key
165
+ end
166
+
167
+ def self.check_app!(app)
168
+ raise TypeError.new("app must be a string") unless app.is_a?(String)
169
+ app
170
+ end
171
+
172
+ def self.check_user!(user)
173
+ raise TypeError.new("user must be a string") unless user.is_a?(String)
174
+ user
175
+ end
130
176
  end
131
177
  end
@@ -1,3 +1,3 @@
1
1
  module Pingpp
2
- VERSION = '2.0.15'
2
+ VERSION = '2.1.0'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  module Pingpp
2
2
  module Webhook
3
- def self.verify?(request)
4
- if !Pingpp.pub_key
3
+ def self.verify?(request, pub_key=Pingpp.pub_key)
4
+ if !pub_key
5
5
  return false
6
6
  end
7
7
 
@@ -27,7 +27,7 @@ module Pingpp
27
27
  return false if !formated_headers.has_key?(:x_pingplusplus_signature)
28
28
 
29
29
  signature = formated_headers[:x_pingplusplus_signature]
30
- rsa_public_key = OpenSSL::PKey.read(Pingpp.pub_key)
30
+ rsa_public_key = OpenSSL::PKey.read(pub_key)
31
31
  return rsa_public_key.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), raw_data)
32
32
  end
33
33
  end
@@ -9,7 +9,7 @@ module Pingpp
9
9
  'response_type' => 'code',
10
10
  'scope' => more_info ? 'snsapi_userinfo' : 'snsapi_base'
11
11
  }
12
- query_str = Pingpp.uri_encode(query_parts)
12
+ query_str = Util.encode_parameters(query_parts)
13
13
  'https://open.weixin.qq.com/connect/oauth2/authorize?' + query_str + '#wechat_redirect'
14
14
  end
15
15
 
@@ -30,7 +30,7 @@ module Pingpp
30
30
  'code' => code,
31
31
  'grant_type' => 'authorization_code'
32
32
  }
33
- query_str = Pingpp.uri_encode(query_parts)
33
+ query_str = Util.encode_parameters(query_parts)
34
34
  'https://api.weixin.qq.com/sns/oauth2/access_token?' + query_str
35
35
  end
36
36
 
@@ -54,7 +54,7 @@ module Pingpp
54
54
  'secret' => app_secret,
55
55
  'grant_type' => 'client_credential'
56
56
  }
57
- query_str = Pingpp.uri_encode(query_parts)
57
+ query_str = Util.encode_parameters(query_parts)
58
58
  access_token_url = 'https://api.weixin.qq.com/cgi-bin/token?' + query_str
59
59
  resp = get_request(access_token_url)
60
60
  if !resp['errcode'].nil? then
@@ -64,9 +64,11 @@ module Pingpp
64
64
  'access_token' => resp['access_token'],
65
65
  'type' => 'jsapi'
66
66
  }
67
- query_str = Pingpp.uri_encode(query_parts)
67
+ query_str = Util.encode_parameters(query_parts)
68
68
  jsapi_ticket_url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?' + query_str
69
69
  jsapi_ticket = get_request(jsapi_ticket_url)
70
+
71
+ return jsapi_ticket
70
72
  end
71
73
 
72
74
  def self.get_signature(charge, jsapi_ticket, url)
@@ -81,6 +83,8 @@ module Pingpp
81
83
  'url=' + url.split('#')[0]
82
84
  ]
83
85
  signature = Digest::SHA1.hexdigest(array_to_sign.join('&'))
86
+
87
+ return signature
84
88
  end
85
89
  end
86
90
  end