paypro 1.0.0 → 2.0.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +54 -0
  3. data/.gitignore +8 -47
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +37 -15
  6. data/Gemfile +12 -2
  7. data/LICENSE +1 -1
  8. data/README.md +65 -23
  9. data/Rakefile +5 -1
  10. data/bin/console +8 -0
  11. data/bin/setup +8 -0
  12. data/lib/data/{ca-bundle.crt → cacert.pem} +617 -298
  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/{paypro → pay_pro}/version.rb +1 -1
  55. data/lib/pay_pro.rb +77 -0
  56. data/paypro.gemspec +18 -11
  57. metadata +67 -48
  58. data/.circleci/config.yml +0 -74
  59. data/VERSION +0 -1
  60. data/examples/create_payment.rb +0 -7
  61. data/lib/paypro/client.rb +0 -68
  62. data/lib/paypro/errors.rb +0 -7
  63. data/lib/paypro.rb +0 -16
  64. data/spec/paypro/client_spec.rb +0 -114
  65. data/spec/paypro_spec.rb +0 -13
  66. data/spec/spec_helper.rb +0 -7
@@ -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