pingpp 2.0.15 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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