pingpp 1.0.2
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.
- checksums.yaml +7 -0
- data/lib/data/ca-certificates.crt +5165 -0
- data/lib/pingpp/api_operations/create.rb +16 -0
- data/lib/pingpp/api_operations/delete.rb +11 -0
- data/lib/pingpp/api_operations/list.rb +16 -0
- data/lib/pingpp/api_operations/update.rb +57 -0
- data/lib/pingpp/api_resource.rb +33 -0
- data/lib/pingpp/certificate_blacklist.rb +53 -0
- data/lib/pingpp/channel.rb +9 -0
- data/lib/pingpp/charge.rb +19 -0
- data/lib/pingpp/errors/api_connection_error.rb +4 -0
- data/lib/pingpp/errors/api_error.rb +4 -0
- data/lib/pingpp/errors/authentication_error.rb +4 -0
- data/lib/pingpp/errors/invalid_request_error.rb +10 -0
- data/lib/pingpp/errors/pingpp_error.rb +20 -0
- data/lib/pingpp/list_object.rb +35 -0
- data/lib/pingpp/pingpp_object.rb +187 -0
- data/lib/pingpp/refund.rb +14 -0
- data/lib/pingpp/singleton_api_resource.rb +20 -0
- data/lib/pingpp/util.rb +99 -0
- data/lib/pingpp/version.rb +3 -0
- data/lib/pingpp.rb +274 -0
- metadata +182 -0
data/lib/pingpp.rb
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
# Pingpp Ruby bindings
|
2
|
+
# API spec at https://pingplusplus.com/document/api
|
3
|
+
require 'cgi'
|
4
|
+
require 'set'
|
5
|
+
require 'openssl'
|
6
|
+
require 'rest_client'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
# Version
|
10
|
+
require 'pingpp/version'
|
11
|
+
|
12
|
+
# Channel constants
|
13
|
+
require 'pingpp/channel'
|
14
|
+
|
15
|
+
# API operations
|
16
|
+
require 'pingpp/api_operations/create'
|
17
|
+
require 'pingpp/api_operations/update'
|
18
|
+
require 'pingpp/api_operations/delete'
|
19
|
+
require 'pingpp/api_operations/list'
|
20
|
+
|
21
|
+
# Resources
|
22
|
+
require 'pingpp/util'
|
23
|
+
require 'pingpp/pingpp_object'
|
24
|
+
require 'pingpp/api_resource'
|
25
|
+
require 'pingpp/singleton_api_resource'
|
26
|
+
require 'pingpp/list_object'
|
27
|
+
require 'pingpp/certificate_blacklist'
|
28
|
+
require 'pingpp/charge'
|
29
|
+
require 'pingpp/refund'
|
30
|
+
|
31
|
+
# Errors
|
32
|
+
require 'pingpp/errors/pingpp_error'
|
33
|
+
require 'pingpp/errors/api_error'
|
34
|
+
require 'pingpp/errors/api_connection_error'
|
35
|
+
require 'pingpp/errors/invalid_request_error'
|
36
|
+
require 'pingpp/errors/authentication_error'
|
37
|
+
|
38
|
+
module Pingpp
|
39
|
+
DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/data/ca-certificates.crt'
|
40
|
+
@api_base = 'https://api.pingplusplus.com'
|
41
|
+
|
42
|
+
@api_version = '2014-10-10'
|
43
|
+
|
44
|
+
@ssl_bundle_path = DEFAULT_CA_BUNDLE_PATH
|
45
|
+
@verify_ssl_certs = true
|
46
|
+
@CERTIFICATE_VERIFIED = false
|
47
|
+
|
48
|
+
|
49
|
+
class << self
|
50
|
+
attr_accessor :api_key, :api_base, :verify_ssl_certs, :api_version
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.api_url(url='')
|
54
|
+
@api_base + url
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.request(method, url, api_key, params={}, headers={})
|
58
|
+
unless api_key ||= @api_key
|
59
|
+
raise AuthenticationError.new('No API key provided. ' +
|
60
|
+
'Set your API key using "Pingpp.api_key = <API-KEY>". ' +
|
61
|
+
'You can generate API keys from the Pingpp web interface. ' +
|
62
|
+
'See https://pingplusplus.com/document/api for details, or email support@pingplusplus.com ' +
|
63
|
+
'if you have any questions.')
|
64
|
+
end
|
65
|
+
|
66
|
+
if api_key =~ /\s/
|
67
|
+
raise AuthenticationError.new('Your API key is invalid, as it contains ' +
|
68
|
+
'whitespace. (HINT: You can double-check your API key from the ' +
|
69
|
+
'Pingpp web interface. See https://pingplusplus.com/document/api for details, or ' +
|
70
|
+
'email support@pingplusplus.com if you have any questions.)')
|
71
|
+
end
|
72
|
+
|
73
|
+
request_opts = { :verify_ssl => false, :ssl_version => 'TLSv1' }
|
74
|
+
|
75
|
+
if ssl_preflight_passed?
|
76
|
+
request_opts.update(:verify_ssl => OpenSSL::SSL::VERIFY_PEER,
|
77
|
+
:ssl_ca_file => @ssl_bundle_path)
|
78
|
+
end
|
79
|
+
|
80
|
+
if @verify_ssl_certs and !@CERTIFICATE_VERIFIED
|
81
|
+
@CERTIFICATE_VERIFIED = CertificateBlacklist.check_ssl_cert(@api_base, @ssl_bundle_path)
|
82
|
+
end
|
83
|
+
|
84
|
+
params = Util.objects_to_ids(params)
|
85
|
+
url = api_url(url)
|
86
|
+
|
87
|
+
case method.to_s.downcase.to_sym
|
88
|
+
when :get, :head, :delete
|
89
|
+
# Make params into GET parameters
|
90
|
+
url += "#{URI.parse(url).query ? '&' : '?'}#{uri_encode(params)}" if params && params.any?
|
91
|
+
payload = nil
|
92
|
+
else
|
93
|
+
payload = uri_encode(params)
|
94
|
+
end
|
95
|
+
|
96
|
+
request_opts.update(:headers => request_headers(api_key).update(headers),
|
97
|
+
:method => method, :open_timeout => 30,
|
98
|
+
:payload => payload, :url => url, :timeout => 80)
|
99
|
+
|
100
|
+
begin
|
101
|
+
response = execute_request(request_opts)
|
102
|
+
rescue SocketError => e
|
103
|
+
handle_restclient_error(e)
|
104
|
+
rescue NoMethodError => e
|
105
|
+
# Work around RestClient bug
|
106
|
+
if e.message =~ /\WRequestFailed\W/
|
107
|
+
e = APIConnectionError.new('Unexpected HTTP response code')
|
108
|
+
handle_restclient_error(e)
|
109
|
+
else
|
110
|
+
raise
|
111
|
+
end
|
112
|
+
rescue RestClient::ExceptionWithResponse => e
|
113
|
+
if rcode = e.http_code and rbody = e.http_body
|
114
|
+
handle_api_error(rcode, rbody)
|
115
|
+
else
|
116
|
+
handle_restclient_error(e)
|
117
|
+
end
|
118
|
+
rescue RestClient::Exception, Errno::ECONNREFUSED => e
|
119
|
+
handle_restclient_error(e)
|
120
|
+
end
|
121
|
+
|
122
|
+
[parse(response), api_key]
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def self.ssl_preflight_passed?
|
128
|
+
if !verify_ssl_certs && !@no_verify
|
129
|
+
$stderr.puts "WARNING: Running without SSL cert verification. " +
|
130
|
+
"Execute 'Pingpp.verify_ssl_certs = true' to enable verification."
|
131
|
+
|
132
|
+
@no_verify = true
|
133
|
+
|
134
|
+
elsif !Util.file_readable(@ssl_bundle_path) && !@no_bundle
|
135
|
+
$stderr.puts "WARNING: Running without SSL cert verification " +
|
136
|
+
"because #{@ssl_bundle_path} isn't readable"
|
137
|
+
|
138
|
+
@no_bundle = true
|
139
|
+
end
|
140
|
+
|
141
|
+
!(@no_verify || @no_bundle)
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.user_agent
|
145
|
+
@uname ||= get_uname
|
146
|
+
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
|
147
|
+
|
148
|
+
{
|
149
|
+
:bindings_version => Pingpp::VERSION,
|
150
|
+
:lang => 'ruby',
|
151
|
+
:lang_version => lang_version,
|
152
|
+
:platform => RUBY_PLATFORM,
|
153
|
+
:publisher => 'pingpp',
|
154
|
+
:uname => @uname
|
155
|
+
}
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.get_uname
|
160
|
+
`uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
|
161
|
+
rescue Errno::ENOMEM => ex # couldn't create subprocess
|
162
|
+
"uname lookup failed"
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.uri_encode(params)
|
166
|
+
Util.flatten_params(params).
|
167
|
+
map { |k,v| "#{k}=#{Util.url_encode(v)}" }.join('&')
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.request_headers(api_key)
|
171
|
+
headers = {
|
172
|
+
:user_agent => "Pingpp/v1 RubyBindings/#{Pingpp::VERSION}",
|
173
|
+
:authorization => "Bearer #{api_key}",
|
174
|
+
:content_type => 'application/x-www-form-urlencoded'
|
175
|
+
}
|
176
|
+
|
177
|
+
headers[:pingplusplus_version] = api_version if api_version
|
178
|
+
|
179
|
+
begin
|
180
|
+
headers.update(:x_pingpp_client_user_agent => JSON.generate(user_agent))
|
181
|
+
rescue => e
|
182
|
+
headers.update(:x_pingpp_client_raw_user_agent => user_agent.inspect,
|
183
|
+
:error => "#{e} (#{e.class})")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.execute_request(opts)
|
188
|
+
RestClient::Request.execute(opts)
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.parse(response)
|
192
|
+
begin
|
193
|
+
# Would use :symbolize_names => true, but apparently there is
|
194
|
+
# some library out there that makes symbolize_names not work.
|
195
|
+
response = JSON.parse(response.body)
|
196
|
+
rescue JSON::ParserError
|
197
|
+
raise general_api_error(response.code, response.body)
|
198
|
+
end
|
199
|
+
|
200
|
+
Util.symbolize_names(response)
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.general_api_error(rcode, rbody)
|
204
|
+
APIError.new("Invalid response object from API: #{rbody.inspect} " +
|
205
|
+
"(HTTP response code was #{rcode})", rcode, rbody)
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.handle_api_error(rcode, rbody)
|
209
|
+
begin
|
210
|
+
error_obj = JSON.parse(rbody)
|
211
|
+
error_obj = Util.symbolize_names(error_obj)
|
212
|
+
error = error_obj[:error] or raise PingppError.new # escape from parsing
|
213
|
+
|
214
|
+
rescue JSON::ParserError, PingppError
|
215
|
+
raise general_api_error(rcode, rbody)
|
216
|
+
end
|
217
|
+
|
218
|
+
case rcode
|
219
|
+
when 400, 404
|
220
|
+
raise invalid_request_error error, rcode, rbody, error_obj
|
221
|
+
when 401
|
222
|
+
raise authentication_error error, rcode, rbody, error_obj
|
223
|
+
else
|
224
|
+
raise api_error error, rcode, rbody, error_obj
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
def self.invalid_request_error(error, rcode, rbody, error_obj)
|
230
|
+
InvalidRequestError.new(error[:message], error[:param], rcode,
|
231
|
+
rbody, error_obj)
|
232
|
+
end
|
233
|
+
|
234
|
+
def self.authentication_error(error, rcode, rbody, error_obj)
|
235
|
+
AuthenticationError.new(error[:message], rcode, rbody, error_obj)
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.api_error(error, rcode, rbody, error_obj)
|
239
|
+
APIError.new(error[:message], rcode, rbody, error_obj)
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.handle_restclient_error(e)
|
243
|
+
connection_message = "Please check your internet connection and try again. " \
|
244
|
+
"If this problem persists, you should check Pingpp's service status at " \
|
245
|
+
"https://pingplusplus.com, or let us know at support@pingplusplus.com."
|
246
|
+
|
247
|
+
case e
|
248
|
+
when RestClient::RequestTimeout
|
249
|
+
message = "Could not connect to Pingpp (#{@api_base}). #{connection_message}"
|
250
|
+
|
251
|
+
when RestClient::ServerBrokeConnection
|
252
|
+
message = "The connection to the server (#{@api_base}) broke before the " \
|
253
|
+
"request completed. #{connection_message}"
|
254
|
+
|
255
|
+
when RestClient::SSLCertificateNotVerified
|
256
|
+
message = "Could not verify Pingpp's SSL certificate. " \
|
257
|
+
"Please make sure that your network is not intercepting certificates. " \
|
258
|
+
"(Try going to (#{@api_base}) in your browser.) " \
|
259
|
+
"If this problem persists, let us know at support@pingplusplus.com."
|
260
|
+
|
261
|
+
when SocketError
|
262
|
+
message = "Unexpected error communicating when trying to connect to Pingpp. " \
|
263
|
+
"You may be seeing this message because your DNS is not working. " \
|
264
|
+
"To check, try running 'host pingplusplus.com' from the command line."
|
265
|
+
|
266
|
+
else
|
267
|
+
message = "Unexpected error communicating with Pingpp. " \
|
268
|
+
"If this problem persists, let us know at support@pingplusplus.com."
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
raise APIConnectionError.new(message + "\n\n(Network error: #{e.message})")
|
273
|
+
end
|
274
|
+
end
|
metadata
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pingpp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Xufeng Weng
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rest-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mime-types
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.25'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '3.0'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.25'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: json
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.8'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.8.1
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '1.8'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 1.8.1
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: mocha
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 0.13.2
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 0.13.2
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: shoulda
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '3.4'
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 3.4.0
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '3.4'
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 3.4.0
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: test-unit
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
type: :development
|
109
|
+
prerelease: false
|
110
|
+
version_requirements: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: rake
|
117
|
+
requirement: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
description: PingPlusPlus is the easiest way to accept payments online. See https://pingplusplus.com
|
130
|
+
for details.
|
131
|
+
email:
|
132
|
+
- xufeng.weng@pingplusplus.com
|
133
|
+
executables: []
|
134
|
+
extensions: []
|
135
|
+
extra_rdoc_files: []
|
136
|
+
files:
|
137
|
+
- lib/data/ca-certificates.crt
|
138
|
+
- lib/pingpp.rb
|
139
|
+
- lib/pingpp/api_operations/create.rb
|
140
|
+
- lib/pingpp/api_operations/delete.rb
|
141
|
+
- lib/pingpp/api_operations/list.rb
|
142
|
+
- lib/pingpp/api_operations/update.rb
|
143
|
+
- lib/pingpp/api_resource.rb
|
144
|
+
- lib/pingpp/certificate_blacklist.rb
|
145
|
+
- lib/pingpp/channel.rb
|
146
|
+
- lib/pingpp/charge.rb
|
147
|
+
- lib/pingpp/errors/api_connection_error.rb
|
148
|
+
- lib/pingpp/errors/api_error.rb
|
149
|
+
- lib/pingpp/errors/authentication_error.rb
|
150
|
+
- lib/pingpp/errors/invalid_request_error.rb
|
151
|
+
- lib/pingpp/errors/pingpp_error.rb
|
152
|
+
- lib/pingpp/list_object.rb
|
153
|
+
- lib/pingpp/pingpp_object.rb
|
154
|
+
- lib/pingpp/refund.rb
|
155
|
+
- lib/pingpp/singleton_api_resource.rb
|
156
|
+
- lib/pingpp/util.rb
|
157
|
+
- lib/pingpp/version.rb
|
158
|
+
homepage: https://pingplusplus.com/document/api
|
159
|
+
licenses:
|
160
|
+
- MIT
|
161
|
+
metadata: {}
|
162
|
+
post_install_message:
|
163
|
+
rdoc_options: []
|
164
|
+
require_paths:
|
165
|
+
- lib
|
166
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
167
|
+
requirements:
|
168
|
+
- - ">="
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '0'
|
171
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
requirements: []
|
177
|
+
rubyforge_project:
|
178
|
+
rubygems_version: 2.4.2
|
179
|
+
signing_key:
|
180
|
+
specification_version: 4
|
181
|
+
summary: Ruby bindings for the PingPlusPlus API
|
182
|
+
test_files: []
|