paypro 0.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/build.yml +54 -0
  3. data/.gitignore +8 -44
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +36 -17
  6. data/Gemfile +14 -2
  7. data/LICENSE +1 -1
  8. data/README.md +77 -15
  9. data/Rakefile +7 -1
  10. data/bin/console +8 -0
  11. data/bin/setup +8 -0
  12. data/lib/data/{ca-bundle.crt → cacert.pem} +1724 -2013
  13. data/lib/pay_pro/api_client.rb +131 -0
  14. data/lib/pay_pro/client.rb +67 -0
  15. data/lib/pay_pro/config.rb +30 -0
  16. data/lib/pay_pro/endpoint.rb +19 -0
  17. data/lib/pay_pro/endpoints/chargebacks.rb +14 -0
  18. data/lib/pay_pro/endpoints/customers.rb +15 -0
  19. data/lib/pay_pro/endpoints/events.rb +14 -0
  20. data/lib/pay_pro/endpoints/installment_plan_periods.rb +13 -0
  21. data/lib/pay_pro/endpoints/installment_plans.rb +15 -0
  22. data/lib/pay_pro/endpoints/mandates.rb +15 -0
  23. data/lib/pay_pro/endpoints/pay_methods.rb +13 -0
  24. data/lib/pay_pro/endpoints/payments.rb +15 -0
  25. data/lib/pay_pro/endpoints/refunds.rb +14 -0
  26. data/lib/pay_pro/endpoints/subscription_periods.rb +13 -0
  27. data/lib/pay_pro/endpoints/subscriptions.rb +15 -0
  28. data/lib/pay_pro/endpoints/webhooks.rb +15 -0
  29. data/lib/pay_pro/entities/chargeback.rb +5 -0
  30. data/lib/pay_pro/entities/customer.rb +10 -0
  31. data/lib/pay_pro/entities/entity.rb +41 -0
  32. data/lib/pay_pro/entities/event.rb +5 -0
  33. data/lib/pay_pro/entities/installment_plan.rb +29 -0
  34. data/lib/pay_pro/entities/installment_plan_period.rb +5 -0
  35. data/lib/pay_pro/entities/list.rb +65 -0
  36. data/lib/pay_pro/entities/mandate.rb +5 -0
  37. data/lib/pay_pro/entities/pay_method.rb +6 -0
  38. data/lib/pay_pro/entities/payment.rb +23 -0
  39. data/lib/pay_pro/entities/refund.rb +11 -0
  40. data/lib/pay_pro/entities/resource.rb +13 -0
  41. data/lib/pay_pro/entities/subscription.rb +38 -0
  42. data/lib/pay_pro/entities/subscription_period.rb +5 -0
  43. data/lib/pay_pro/entities/webhook.rb +30 -0
  44. data/lib/pay_pro/errors.rb +36 -0
  45. data/lib/pay_pro/operations/creatable.rb +11 -0
  46. data/lib/pay_pro/operations/deletable.rb +11 -0
  47. data/lib/pay_pro/operations/getable.rb +11 -0
  48. data/lib/pay_pro/operations/listable.rb +11 -0
  49. data/lib/pay_pro/operations/requestable.rb +12 -0
  50. data/lib/pay_pro/operations/updatable.rb +11 -0
  51. data/lib/pay_pro/response.rb +21 -0
  52. data/lib/pay_pro/signature.rb +59 -0
  53. data/lib/pay_pro/util.rb +48 -0
  54. data/lib/pay_pro/version.rb +5 -0
  55. data/lib/pay_pro.rb +77 -0
  56. data/paypro.gemspec +20 -11
  57. metadata +69 -45
  58. data/.circleci/config.yml +0 -54
  59. data/VERSION +0 -1
  60. data/examples/create_payment.rb +0 -5
  61. data/lib/paypro/client.rb +0 -66
  62. data/lib/paypro/errors.rb +0 -4
  63. data/lib/paypro/version.rb +0 -3
  64. data/lib/paypro.rb +0 -14
  65. data/spec/paypro/client_spec.rb +0 -112
  66. data/spec/paypro_spec.rb +0 -11
  67. data/spec/spec_helper.rb +0 -5
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class ApiClient
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
9
+ # rubocop:disable Metrics/ParameterLists
10
+ def request(method:, uri:, params: {}, headers: {}, body: nil, options: {})
11
+ options = merge_options(options)
12
+
13
+ check_api_key!(options)
14
+
15
+ response = connection(options).public_send(method, uri) do |req|
16
+ req.params = params
17
+ req.headers = headers.merge(request_headers(options[:api_key]))
18
+ req.body = body
19
+ end
20
+
21
+ default_params = {
22
+ http_status: response.status,
23
+ http_body: response.body,
24
+ http_headers: response.headers
25
+ }
26
+
27
+ if response.status >= 400
28
+ handle_error_response(response, **default_params)
29
+ else
30
+ handle_response(response, **default_params)
31
+ end
32
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError
33
+ raise PayPro::ConnectionError.new(
34
+ message: 'Failed to make a connection to the PayPro API. ' \
35
+ 'This could indicate a DNS issue or because you have no internet connection.'
36
+ )
37
+ rescue Faraday::SSLError
38
+ raise PayPro::ConnectionError.new(
39
+ message: 'Failed to create a secure connection with the PayPro API. ' \
40
+ 'Please check your OpenSSL version supports TLS 1.2+.'
41
+ )
42
+ rescue Faraday::Error => e
43
+ raise PayPro::Error.new(message: "Failed to connect to the PayPro API. Message: #{e.message}")
44
+ end
45
+ # rubocop:enable Metrics/ParameterLists
46
+
47
+ private
48
+
49
+ def check_api_key!(options)
50
+ return unless options[:api_key].nil?
51
+
52
+ raise AuthenticationError.new(
53
+ message: 'API key not set. ' \
54
+ 'Make sure to set the API key with "PayPro.api_key = <API_KEY>". ' \
55
+ 'You can find your API key in the PayPro dashboard at "https://app.paypro.nl/developers/api-keys".'
56
+ )
57
+ end
58
+
59
+ def handle_response(response, **default_params)
60
+ PayPro::Response.from_response(response)
61
+ rescue JSON::ParserError
62
+ raise PayPro::Error.new(
63
+ message: 'Invalid response from API. ' \
64
+ 'The JSON returned in the body is not valid.',
65
+ **default_params
66
+ )
67
+ end
68
+
69
+ def handle_error_response(response, **default_params)
70
+ pay_pro_response = PayPro::Response.from_response(response)
71
+
72
+ case pay_pro_response.status
73
+ when 401
74
+ raise AuthenticationError.new(
75
+ message: 'Invalid API key supplied. ' \
76
+ 'Make sure to set a correct API key without any whitespace around it. ' \
77
+ 'You can find your API key in the PayPro dashboard at "https://app.paypro.nl/developers/api-keys".',
78
+ **default_params
79
+ )
80
+ when 404
81
+ raise ResourceNotFoundError.new(message: 'Resource not found', **default_params)
82
+ when 422
83
+ raise ValidationError.new(
84
+ param: pay_pro_response.data['error']['param'],
85
+ message: pay_pro_response.data['error']['message'],
86
+ code: pay_pro_response.data['error']['type'],
87
+ **default_params
88
+ )
89
+ end
90
+ rescue JSON::ParserError
91
+ raise PayPro::Error.new(
92
+ message: 'Invalid response from API. ' \
93
+ 'The JSON returned in the body is not valid.',
94
+ **default_params
95
+ )
96
+ end
97
+
98
+ def request_headers(api_key)
99
+ {
100
+ 'Content-Type' => 'application/json',
101
+ 'Authorization' => "Bearer #{api_key}",
102
+ 'User-Agent' => user_agent
103
+ }
104
+ end
105
+
106
+ def user_agent
107
+ @user_agent ||= "PayPro #{PayPro::VERSION} / Ruby #{RUBY_VERSION} / OpenSSL #{OpenSSL::VERSION}"
108
+ end
109
+
110
+ def connection(options)
111
+ Faraday.new(
112
+ request: {
113
+ open_timeout: @config.timeout,
114
+ timeout: @config.timeout
115
+ },
116
+ ssl: {
117
+ ca_path: @config.ca_bundle_path,
118
+ verify: @config.verify_ssl
119
+ },
120
+ url: options[:api_url]
121
+ )
122
+ end
123
+
124
+ def merge_options(options)
125
+ {
126
+ api_key: options[:api_key] || @config.api_key,
127
+ api_url: options[:api_url] || @config.api_url
128
+ }
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Client
5
+ attr_accessor :config
6
+
7
+ attr_reader :chargebacks,
8
+ :customers,
9
+ :events,
10
+ :installment_plan_periods,
11
+ :installment_plans,
12
+ :mandates,
13
+ :pay_methods,
14
+ :payments,
15
+ :refunds,
16
+ :subscription_periods,
17
+ :subscriptions,
18
+ :webhooks
19
+
20
+ def initialize(config = {})
21
+ @config = case config
22
+ when Hash
23
+ PayPro.config.merge(config)
24
+ when String
25
+ PayPro.config.merge(api_key: config)
26
+ when PayPro::Config
27
+ config
28
+ else
29
+ raise ConfigurationError.new(message: "Invalid argument: #{config}")
30
+ end
31
+
32
+ check_api_key!
33
+ setup_endpoints
34
+ end
35
+
36
+ private
37
+
38
+ def check_api_key!
39
+ return unless @config.api_key.nil? || @config.api_key == ''
40
+
41
+ raise ConfigurationError.new(
42
+ message: 'API key not set or given. ' \
43
+ 'Make sure to pass an API key or set a default with "PayPro.api_key=". ' \
44
+ 'You can find your API key in the PayPro dashboard at "https://app.paypro.nl/developers/api-keys".'
45
+ )
46
+ end
47
+
48
+ def setup_endpoints
49
+ @chargebacks = Endpoints::Chargebacks.new(api_client: api_client)
50
+ @customers = Endpoints::Customers.new(api_client: api_client)
51
+ @events = Endpoints::Events.new(api_client: api_client)
52
+ @installment_plan_periods = Endpoints::InstallmentPlanPeriods.new(api_client: api_client)
53
+ @installment_plans = Endpoints::InstallmentPlans.new(api_client: api_client)
54
+ @mandates = Endpoints::Mandates.new(api_client: api_client)
55
+ @pay_methods = Endpoints::PayMethods.new(api_client: api_client)
56
+ @payments = Endpoints::Payments.new(api_client: api_client)
57
+ @refunds = Endpoints::Refunds.new(api_client: api_client)
58
+ @subscription_periods = Endpoints::SubscriptionPeriods.new(api_client: api_client)
59
+ @subscriptions = Endpoints::Subscriptions.new(api_client: api_client)
60
+ @webhooks = Endpoints::Webhooks.new(api_client: api_client)
61
+ end
62
+
63
+ def api_client
64
+ @api_client ||= ApiClient.new(@config)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ # Config saves the configuration options
5
+ class Config
6
+ DEFAULT_CA_BUNDLE_PATH = File.join(__dir__, 'data/cacert.pem')
7
+ DEFAULT_TIMEOUT = 30
8
+ DEFAULT_VERIFY_SSL = true
9
+
10
+ ATTRIBUTES = %i[api_key api_url ca_bundle_path timeout verify_ssl].freeze
11
+
12
+ attr_accessor(*ATTRIBUTES)
13
+
14
+ def initialize
15
+ @api_key = nil
16
+ @api_url = API_URL
17
+ @ca_bundle_path = DEFAULT_CA_BUNDLE_PATH
18
+ @timeout = DEFAULT_TIMEOUT
19
+ @verify_ssl = DEFAULT_VERIFY_SSL
20
+ end
21
+
22
+ def merge(hash)
23
+ dup.tap do |instance|
24
+ hash.slice(*ATTRIBUTES).each do |key, value|
25
+ instance.public_send("#{key}=", value)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Endpoint
5
+ include PayPro::Operations::Requestable
6
+
7
+ attr_reader :api_client
8
+
9
+ def initialize(api_client:)
10
+ @api_client = api_client
11
+ end
12
+
13
+ private
14
+
15
+ def resource_url
16
+ "/#{resource_path}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class Chargebacks < Endpoint
6
+ include PayPro::Operations::Getable
7
+ include PayPro::Operations::Listable
8
+
9
+ def resource_path
10
+ 'chargebacks'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class Customers < Endpoint
6
+ include PayPro::Operations::Creatable
7
+ include PayPro::Operations::Getable
8
+ include PayPro::Operations::Listable
9
+
10
+ def resource_path
11
+ 'customers'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class Events < Endpoint
6
+ include PayPro::Operations::Getable
7
+ include PayPro::Operations::Listable
8
+
9
+ def resource_path
10
+ 'events'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class InstallmentPlanPeriods < Endpoint
6
+ include PayPro::Operations::Getable
7
+
8
+ def resource_path
9
+ 'installment_plan_periods'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class InstallmentPlans < Endpoint
6
+ include PayPro::Operations::Creatable
7
+ include PayPro::Operations::Getable
8
+ include PayPro::Operations::Listable
9
+
10
+ def resource_path
11
+ 'installment_plans'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class Mandates < Endpoint
6
+ include PayPro::Operations::Creatable
7
+ include PayPro::Operations::Getable
8
+ include PayPro::Operations::Listable
9
+
10
+ def resource_path
11
+ 'mandates'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class PayMethods < Endpoint
6
+ include PayPro::Operations::Listable
7
+
8
+ def resource_path
9
+ 'pay_methods'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class Payments < Endpoint
6
+ include PayPro::Operations::Creatable
7
+ include PayPro::Operations::Getable
8
+ include PayPro::Operations::Listable
9
+
10
+ def resource_path
11
+ 'payments'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class Refunds < Endpoint
6
+ include PayPro::Operations::Getable
7
+ include PayPro::Operations::Listable
8
+
9
+ def resource_path
10
+ 'refunds'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class SubscriptionPeriods < Endpoint
6
+ include PayPro::Operations::Getable
7
+
8
+ def resource_path
9
+ 'subscription_periods'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class Subscriptions < Endpoint
6
+ include PayPro::Operations::Creatable
7
+ include PayPro::Operations::Getable
8
+ include PayPro::Operations::Listable
9
+
10
+ def resource_path
11
+ 'subscriptions'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ module Endpoints
5
+ class Webhooks < Endpoint
6
+ include PayPro::Operations::Creatable
7
+ include PayPro::Operations::Getable
8
+ include PayPro::Operations::Listable
9
+
10
+ def resource_path
11
+ 'webhooks'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Chargeback < Entity; end
5
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Customer < Resource
5
+ include PayPro::Operations::Deletable
6
+ include PayPro::Operations::Updatable
7
+
8
+ RESOURCE_PATH = 'customers'
9
+ end
10
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Entity
5
+ attr_reader :api_client
6
+
7
+ def self.create_from_data(data, api_client:)
8
+ attributes = Util.normalize_api_attributes(data)
9
+ new(attributes: attributes, api_client: api_client)
10
+ end
11
+
12
+ private_class_method :new
13
+
14
+ def initialize(api_client:, attributes: {})
15
+ @api_client = api_client
16
+ @attributes = attributes
17
+
18
+ generate_accessors(attributes)
19
+ end
20
+
21
+ def inspect
22
+ "#<#{self.class}> #{JSON.pretty_generate(@attributes)}"
23
+ end
24
+
25
+ private
26
+
27
+ def generate_accessors(attributes)
28
+ attributes.each do |name, value|
29
+ self.class.instance_eval do
30
+ define_method(name) { instance_variable_get("@#{name}") }
31
+
32
+ define_method("#{name}=") do |v|
33
+ instance_variable_set("@#{name}", v)
34
+ end
35
+ end
36
+
37
+ send("#{name}=", Util.to_entity(value, api_client: api_client))
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Event < Entity; end
5
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class InstallmentPlan < Resource
5
+ include PayPro::Operations::Updatable
6
+
7
+ RESOURCE_PATH = 'installment_plans'
8
+
9
+ def cancel(**options)
10
+ api_request(method: 'delete', uri: resource_url, options: options)
11
+ end
12
+
13
+ def pause(**options)
14
+ api_request(method: 'post', uri: "#{resource_url}/pause", options: options)
15
+ end
16
+
17
+ def resume(**options)
18
+ api_request(method: 'post', uri: "#{resource_url}/resume", options: options)
19
+ end
20
+
21
+ def installment_plan_periods(**options)
22
+ api_request(
23
+ method: 'get',
24
+ uri: "#{resource_url}/installment_plan_periods",
25
+ options: options
26
+ )
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class InstallmentPlanPeriod < Entity; end
5
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class List < Entity
5
+ include Enumerable
6
+
7
+ attr_accessor :filters
8
+
9
+ def [](key)
10
+ data[key]
11
+ end
12
+
13
+ def each(&block)
14
+ data.each(&block)
15
+ end
16
+
17
+ def next(params: {})
18
+ return List.create_from_data({ data: [] }, api_client: api_client) if next_link.nil?
19
+
20
+ params = filters.merge(params).merge(cursor: next_id)
21
+
22
+ response = api_client.request(method: 'get', uri: next_uri.path, params: params)
23
+ Util.to_entity(response.data, api_client: api_client)
24
+ end
25
+
26
+ def previous(params: {})
27
+ return List.create_from_data({ data: [] }, api_client: api_client) if previous_link.nil?
28
+
29
+ params = filters.merge(params).merge(cursor: previous_id)
30
+
31
+ response = api_client.request(method: 'get', uri: previous_uri.path, params: params)
32
+ Util.to_entity(response.data, api_client: api_client)
33
+ end
34
+
35
+ def empty?
36
+ data.empty?
37
+ end
38
+
39
+ private
40
+
41
+ def next_id
42
+ CGI.parse(next_uri.query)['cursor'].first
43
+ end
44
+
45
+ def next_uri
46
+ @next_uri ||= URI.parse(next_link)
47
+ end
48
+
49
+ def next_link
50
+ links['next']
51
+ end
52
+
53
+ def previous_id
54
+ CGI.parse(previous_uri.query)['cursor'].first
55
+ end
56
+
57
+ def previous_uri
58
+ @previous_uri ||= URI.parse(previous_link)
59
+ end
60
+
61
+ def previous_link
62
+ @previous_link ||= links['prev']
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Mandate < Entity; end
5
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class PayMethod < Entity
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Payment < Resource
5
+ RESOURCE_PATH = 'payments'
6
+
7
+ def cancel(**options)
8
+ api_request(method: 'delete', uri: resource_url, options: options)
9
+ end
10
+
11
+ def refund(body = {}, **options)
12
+ api_request(method: 'post', uri: "#{resource_url}/refunds", body: body.to_json, options: options)
13
+ end
14
+
15
+ def refunds(params = {}, **options)
16
+ api_request(method: 'get', uri: "#{resource_url}/refunds", params: params, options: options)
17
+ end
18
+
19
+ def chargebacks(params = {}, **options)
20
+ api_request(method: 'get', uri: "#{resource_url}/chargebacks", params: params, options: options)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Refund < Resource
5
+ RESOURCE_PATH = 'refunds'
6
+
7
+ def cancel(**options)
8
+ api_request(method: 'delete', uri: resource_url, options: options)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PayPro
4
+ class Resource < Entity
5
+ include PayPro::Operations::Requestable
6
+
7
+ private
8
+
9
+ def resource_url
10
+ "/#{self.class::RESOURCE_PATH}/#{CGI.escape(id)}"
11
+ end
12
+ end
13
+ end