cash_app_pay 0.0.1
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/cash_app_pay/api_operations/create.rb +16 -0
- data/lib/cash_app_pay/api_operations/delete.rb +27 -0
- data/lib/cash_app_pay/api_operations/list.rb +17 -0
- data/lib/cash_app_pay/api_operations/request.rb +45 -0
- data/lib/cash_app_pay/api_operations/retrieve.rb +24 -0
- data/lib/cash_app_pay/api_operations/save.rb +16 -0
- data/lib/cash_app_pay/api_operations/update.rb +31 -0
- data/lib/cash_app_pay/api_operations/upsert.rb +22 -0
- data/lib/cash_app_pay/api_resource.rb +58 -0
- data/lib/cash_app_pay/cash_app_pay_client.rb +117 -0
- data/lib/cash_app_pay/cash_app_pay_configuration.rb +19 -0
- data/lib/cash_app_pay/cash_app_pay_object.rb +77 -0
- data/lib/cash_app_pay/cash_app_pay_response.rb +17 -0
- data/lib/cash_app_pay/connection_manager.rb +79 -0
- data/lib/cash_app_pay/endpoint.rb +8 -0
- data/lib/cash_app_pay/error_object.rb +6 -0
- data/lib/cash_app_pay/errors.rb +45 -0
- data/lib/cash_app_pay/helpers/symbolize.rb +31 -0
- data/lib/cash_app_pay/list_object.rb +64 -0
- data/lib/cash_app_pay/persistent_http_client.rb +48 -0
- data/lib/cash_app_pay/resources/api_key.rb +19 -0
- data/lib/cash_app_pay/resources/brand.rb +20 -0
- data/lib/cash_app_pay/resources/customer.rb +56 -0
- data/lib/cash_app_pay/resources/customer_request.rb +18 -0
- data/lib/cash_app_pay/resources/dispute.rb +142 -0
- data/lib/cash_app_pay/resources/dispute_evidence.rb +9 -0
- data/lib/cash_app_pay/resources/fee_plan.rb +16 -0
- data/lib/cash_app_pay/resources/grant.rb +13 -0
- data/lib/cash_app_pay/resources/merchant.rb +20 -0
- data/lib/cash_app_pay/resources/payment.rb +63 -0
- data/lib/cash_app_pay/resources/refund.rb +62 -0
- data/lib/cash_app_pay/resources/webhook.rb +20 -0
- data/lib/cash_app_pay/resources/webhook_event.rb +15 -0
- data/lib/cash_app_pay/version.rb +5 -0
- data/lib/cash_app_pay.rb +69 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 56ca23a3952f68fcf4c16941a887ee4a66016774074405b6cadcc5ce63cc8168
|
4
|
+
data.tar.gz: 57edbf0f525f96d03da5e838b31192472bd9dc643b49cc6514dd5e227e93329e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8cb314461eea62e42789ffc4a0de5ddd88d1c6b45c64ec30575e0f9ff36e996852cb2d3717fa46de7de492928cfc980ce26ee6edbc60aded6d7c4101816d1b16
|
7
|
+
data.tar.gz: 0755fbff98ba362c1756e23420701f7c2a186dec0fb210bb259db713267b86303ed7e5ca578535236b1ed6e95a07af713f511a5f20dc06ed1ad649a36ac5e2bf
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
module APIOperations
|
5
|
+
module Create
|
6
|
+
def create(params = {}, opts = {})
|
7
|
+
request_cash_app_pay_object(
|
8
|
+
method: :post,
|
9
|
+
path: resource_url,
|
10
|
+
params: params,
|
11
|
+
opts: opts
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
module APIOperations
|
5
|
+
module Delete
|
6
|
+
def delete(opts = {})
|
7
|
+
delete_cash_app_pay_object(
|
8
|
+
path: resource_url,
|
9
|
+
opts: opts
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend(ClassMethods)
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def delete(resource, _params = {}, opts = {})
|
19
|
+
delete_cash_app_pay_object(
|
20
|
+
path: "#{resource_url}/#{CGI.escape(resource)}",
|
21
|
+
opts: opts
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
module APIOperations
|
5
|
+
module List
|
6
|
+
def list(filters = {}, opts = {})
|
7
|
+
response, opts = execute_resource_request(
|
8
|
+
method: :get,
|
9
|
+
url: resource_url,
|
10
|
+
url_params: filters,
|
11
|
+
opts: opts
|
12
|
+
)
|
13
|
+
ListObject.initialize_from_response(self, response, opts, filters)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
module APIOperations
|
5
|
+
module Request
|
6
|
+
def request_cash_app_pay_object(method:, path:, params:, opts: {})
|
7
|
+
body = self.class.encode_body(params) unless params.nil?
|
8
|
+
response, opts = self.class.execute_resource_request(method: method, url: path, body_params: body, opts: opts)
|
9
|
+
initialize_from(response.data, opts)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def delete_cash_app_pay_object(path:, opts: {})
|
19
|
+
response, = execute_resource_request(method: :delete, url: path, opts: opts)
|
20
|
+
response.http_status == 200
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def execute_resource_request(method:, url:, url_params: nil, body_params: nil, opts: {})
|
25
|
+
response = CashAppPay::CashAppPayClient.execute_request(method_name: method, path: url,
|
26
|
+
url_params: url_params, body_params: body_params, opts: opts)
|
27
|
+
[response, opts]
|
28
|
+
end
|
29
|
+
|
30
|
+
def request_cash_app_pay_object(method:, path:, params:, opts: {})
|
31
|
+
body = encode_body(params) unless params.nil?
|
32
|
+
response, opts = execute_resource_request(method: method, url: path, body_params: body, opts: opts)
|
33
|
+
initialize_from_net_response(response, opts)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def delete_cash_app_pay_object(path:, opts: {})
|
39
|
+
response, = execute_resource_request(method: :delete, url: path, opts: opts)
|
40
|
+
response.http_status == 200
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
module APIOperations
|
5
|
+
module Retrieve
|
6
|
+
def refresh
|
7
|
+
response, opts = self.class.execute_resource_request(method: :get, url: resource_url, opts: @opts)
|
8
|
+
initialize_from(response.data, opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def retrieve(id, opts = {})
|
17
|
+
instance = new({ id: id }, opts)
|
18
|
+
instance.refresh
|
19
|
+
instance
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
module APIOperations
|
5
|
+
module Save
|
6
|
+
def save(opts = {})
|
7
|
+
request_cash_app_pay_object(
|
8
|
+
method: :post,
|
9
|
+
path: self.class.resource_url,
|
10
|
+
params: @values,
|
11
|
+
opts: opts
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
module APIOperations
|
5
|
+
module Update
|
6
|
+
def update(params = {}, opts = {})
|
7
|
+
request_cash_app_pay_object(
|
8
|
+
method: :patch,
|
9
|
+
path: resource_url,
|
10
|
+
params: params,
|
11
|
+
opts: opts
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def update(resource, params = {}, opts = {})
|
21
|
+
request_cash_app_pay_object(
|
22
|
+
method: :patch,
|
23
|
+
path: "#{resource_url}/#{CGI.escape(resource)}",
|
24
|
+
params: params,
|
25
|
+
opts: opts
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
module APIOperations
|
5
|
+
module Upsert
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def upsert(resource, params = {}, opts = {})
|
12
|
+
request_cash_app_pay_object(
|
13
|
+
method: :put,
|
14
|
+
path: "#{resource_url}/#{CGI.escape(resource)}",
|
15
|
+
params: params,
|
16
|
+
opts: opts
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
class APIResource < CashAppPayObject
|
5
|
+
include CashAppPay::APIOperations::Request
|
6
|
+
|
7
|
+
attr_reader :opts
|
8
|
+
|
9
|
+
def initialize(values = {}, opts = {})
|
10
|
+
super(values)
|
11
|
+
@opts = opts
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.resource_url
|
15
|
+
raise NotImplementedError, 'API Resource is an abstract class'
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.initialize_from_net_response(response, opts)
|
21
|
+
instance = new
|
22
|
+
instance.send(:initialize_from, response.data, opts)
|
23
|
+
instance
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize_from(values, opts)
|
27
|
+
@values = values.fetch(self.class.object_name, {})
|
28
|
+
@opts = opts
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def resource_url
|
33
|
+
unless (id = self.id)
|
34
|
+
raise InvalidRequestError.new(
|
35
|
+
"Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}",
|
36
|
+
'id'
|
37
|
+
)
|
38
|
+
end
|
39
|
+
"#{self.class.resource_url}/#{CGI.escape(id)}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Encode the params for the body of the request.
|
43
|
+
# If the params contains `idempotency_key` then put this at the root
|
44
|
+
# e.g. { request: { id: 1 }, idempotency_key: 'key' }
|
45
|
+
# params: Hash of params
|
46
|
+
def self.encode_body(params)
|
47
|
+
idempotency_key = params.delete(:idempotency_key) || params.delete('idempotency_key')
|
48
|
+
body = if params.empty? && !idempotency_key.nil?
|
49
|
+
{ idempotency_key: idempotency_key }
|
50
|
+
else
|
51
|
+
named_body_params = Hash[object_name, params]
|
52
|
+
named_body_params[:idempotency_key] = idempotency_key unless idempotency_key.nil?
|
53
|
+
named_body_params
|
54
|
+
end
|
55
|
+
body.to_json
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cash_app_pay/version'
|
4
|
+
|
5
|
+
module CashAppPay
|
6
|
+
class CashAppPayClient
|
7
|
+
def self.execute_request(method_name:, path:, url_params:, body_params: nil, opts: {})
|
8
|
+
base_uri = opts[:api_base] || CashAppPay.api_base
|
9
|
+
client_id = opts[:client_id] || CashAppPay.client_id
|
10
|
+
|
11
|
+
check_client_id!(client_id)
|
12
|
+
|
13
|
+
method = method_name.to_s.upcase
|
14
|
+
url = URI::HTTPS.build(host: base_uri, path: path)
|
15
|
+
url.query = URI.encode_www_form(url_params) if !url_params.nil? && !url_params.empty?
|
16
|
+
http = CashAppPay::PersistentHttpClient.get(url)
|
17
|
+
|
18
|
+
headers = if path.start_with?(NETWORK_API_PATH_PREFIX) || path.start_with?(MANAGE_API_PATH_PREFIX)
|
19
|
+
network_api_headers(client_id, opts)
|
20
|
+
elsif path.start_with?(CUSTOMER_REQUEST_API_PATH_PREFIX)
|
21
|
+
customer_request_api_headers(client_id)
|
22
|
+
else
|
23
|
+
raise InvalidRequestError.new('path', '')
|
24
|
+
end
|
25
|
+
|
26
|
+
request = Net::HTTPGenericRequest.new(method, !body_params.nil?, true, url, headers)
|
27
|
+
request.body = body_params unless body_params.nil?
|
28
|
+
|
29
|
+
response = http.request(request)
|
30
|
+
|
31
|
+
begin
|
32
|
+
resp = CashAppPayResponse.from_net_http(response)
|
33
|
+
handle_error_response(resp.error_data) if resp.error_data
|
34
|
+
rescue JSON::ParserError
|
35
|
+
raise general_api_error(response.code.to_i, response.body)
|
36
|
+
end
|
37
|
+
|
38
|
+
resp
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.handle_error_response(response_errors)
|
42
|
+
return unless (error_response = response_errors.first)
|
43
|
+
|
44
|
+
raise APIResponseError, error_response
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.check_client_id!(client_id)
|
48
|
+
return if client_id
|
49
|
+
|
50
|
+
raise AuthenticationError, 'No Client ID provided. Set your API key using CashAppPay.client_id = <CLIENT-ID>'
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.check_api_base!(api_base)
|
54
|
+
return if api_base
|
55
|
+
|
56
|
+
raise AuthenticationError, 'No API base provided. Set your API key using CashAppPay.api_base = <API-BASE>'
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.check_region!(region)
|
60
|
+
raise AuthenticationError, 'No Region provided. Set your API key using CashAppPay.region = <REGION>' unless region
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.check_signature!(signature)
|
64
|
+
return if signature
|
65
|
+
|
66
|
+
raise AuthenticationError, 'No Signature provided. Set your API key using CashAppPay.signature = <SIGNATURE>'
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.check_api_key!(api_key)
|
70
|
+
return if api_key
|
71
|
+
|
72
|
+
raise AuthenticationError, 'No Region provided. Set your API key using CashAppPay.api_key = <API-KEY>'
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.general_api_error(status, body)
|
76
|
+
APIError.new("Invalid response object from API: #{body.inspect} (HTTP response code was #{status})",
|
77
|
+
http_status: status, http_body: body)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.user_agent
|
81
|
+
"cash-app-pay-ruby/v#{CashAppPay::VERSION} RubyBindings (#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})) RUBY_PLATFORM #{defined?(RUBY_ENGINE) ? "(#{RUBY_ENGINE})" : ''}"
|
82
|
+
end
|
83
|
+
|
84
|
+
CUSTOMER_REQUEST_API_PATH_PREFIX = '/customer-request/'
|
85
|
+
NETWORK_API_PATH_PREFIX = '/network/'
|
86
|
+
MANAGE_API_PATH_PREFIX = '/management/'
|
87
|
+
|
88
|
+
def self.customer_request_api_headers(client_id)
|
89
|
+
{
|
90
|
+
"Authorization": ['Client', client_id].join(' '),
|
91
|
+
"Accept": 'application/json',
|
92
|
+
"Content-Type": 'application/json',
|
93
|
+
"User-Agent": user_agent
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.network_api_headers(client_id, opts)
|
98
|
+
api_key = opts[:api_key] || CashAppPay.api_key
|
99
|
+
signature = opts[:signature] || CashAppPay.signature
|
100
|
+
region = opts[:region] || CashAppPay.region
|
101
|
+
|
102
|
+
check_api_key!(api_key)
|
103
|
+
check_signature!(signature)
|
104
|
+
check_region!(region)
|
105
|
+
|
106
|
+
authorization = ['Client', client_id, api_key].join(' ')
|
107
|
+
{
|
108
|
+
"Authorization": authorization,
|
109
|
+
"X-Region": region,
|
110
|
+
"X-Signature": signature,
|
111
|
+
"Accept": 'application/json',
|
112
|
+
"Content-Type": 'application/json',
|
113
|
+
"User-Agent": user_agent
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cash_app_pay/endpoint'
|
4
|
+
|
5
|
+
module CashAppPay
|
6
|
+
class CashAppPayConfiguration
|
7
|
+
attr_accessor :client_id, :api_base, :region, :signature, :api_key
|
8
|
+
|
9
|
+
def self.setup
|
10
|
+
new.tap do |instance|
|
11
|
+
yield(instance) if block_given?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@api_base = Endpoint::PRODUCTION
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
class CashAppPayObject
|
5
|
+
using CashAppPay::Helpers::Symbolize
|
6
|
+
|
7
|
+
attr_accessor :values
|
8
|
+
|
9
|
+
def initialize(values)
|
10
|
+
@values = values.deep_symbolize_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h(*_args)
|
14
|
+
@values
|
15
|
+
end
|
16
|
+
|
17
|
+
alias to_hash to_h
|
18
|
+
alias as_json to_h
|
19
|
+
|
20
|
+
def to_json(*_args)
|
21
|
+
@values.to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s(*_args)
|
25
|
+
JSON.pretty_generate(to_h)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_str
|
29
|
+
self['id'].to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
id_string = !id.nil? ? " id=#{id}" : ''
|
34
|
+
"#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " +
|
35
|
+
JSON.pretty_generate(@values)
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](name)
|
39
|
+
data = @values[typed_key(name)]
|
40
|
+
if data.is_a?(Hash)
|
41
|
+
CashAppPay::CashAppPayObject.new(data)
|
42
|
+
elsif data.is_a?(Array)
|
43
|
+
data.map do |item|
|
44
|
+
item.is_a?(Hash) ? CashAppPay::CashAppPayObject.new(item) : item
|
45
|
+
end
|
46
|
+
else
|
47
|
+
data
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def ==(other)
|
52
|
+
if other.is_a?(CashAppPay::CashAppPayObject)
|
53
|
+
@values == other.values
|
54
|
+
else
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def []=(name, value)
|
60
|
+
@values[typed_key(name)] = value
|
61
|
+
end
|
62
|
+
|
63
|
+
def method_missing(name, *_args)
|
64
|
+
if name.to_s.end_with?('=')
|
65
|
+
attr = name.to_s[0...-1].to_sym
|
66
|
+
val = _args.first
|
67
|
+
self[attr] = val
|
68
|
+
else
|
69
|
+
self[name]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def typed_key(key)
|
74
|
+
key.to_sym
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
class CashAppPayResponse
|
5
|
+
attr_accessor :http_headers, :http_status, :data, :error_data, :http_body
|
6
|
+
|
7
|
+
def self.from_net_http(http_resp)
|
8
|
+
resp = CashAppPayResponse.new
|
9
|
+
resp.http_headers = http_resp.each_header.to_h
|
10
|
+
resp.http_status = http_resp.code.to_i
|
11
|
+
resp.http_body = http_resp.body
|
12
|
+
resp.data = JSON.parse(http_resp.read_body, symbolize_names: true) unless resp.http_body.empty?
|
13
|
+
resp.error_data = resp&.data&.fetch(:errors, nil)
|
14
|
+
resp
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# connection manager represents
|
4
|
+
# a cache of all keep-alive connections
|
5
|
+
# in a current thread
|
6
|
+
module CashAppPay
|
7
|
+
class ConnectionManager
|
8
|
+
DEFAULT_OPTIONS = { read_timeout: 80, open_timeout: 30 }.freeze
|
9
|
+
# if a client wasn't used within this time range
|
10
|
+
# it gets removed from the cache and the connection closed.
|
11
|
+
# This helps to make sure there are no memory leaks.
|
12
|
+
STALE_AFTER = 60 * 5 # 5.minutes
|
13
|
+
|
14
|
+
# Seconds to reuse the connection of the previous request. If the idle time is less than this Keep-Alive Timeout, Net::HTTP reuses the TCP/IP socket used by the previous communication. Source: Ruby docs
|
15
|
+
KEEP_ALIVE_TIMEOUT = 30 # seconds
|
16
|
+
|
17
|
+
# KEEP_ALIVE_TIMEOUT vs STALE_AFTER
|
18
|
+
# STALE_AFTER - how long an Net::HTTP client object is cached in ruby
|
19
|
+
# KEEP_ALIVE_TIMEOUT - how long that client keeps TCP/IP socket open.
|
20
|
+
|
21
|
+
attr_accessor :clients_store, :last_used
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
self.clients_store = {}
|
25
|
+
self.last_used = Time.now
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_client(uri, options)
|
29
|
+
mutex.synchronize do
|
30
|
+
# refresh the last time a client was used,
|
31
|
+
# this prevents the client from becoming stale
|
32
|
+
self.last_used = Time.now
|
33
|
+
|
34
|
+
# we use params as a cache key for clients.
|
35
|
+
# 2 connections to the same host but with different
|
36
|
+
# options are going to use different HTTP clients
|
37
|
+
params = [uri.host, uri.port, options]
|
38
|
+
client = clients_store[params]
|
39
|
+
|
40
|
+
return client if client
|
41
|
+
|
42
|
+
client = Net::HTTP.new(uri.host, uri.port)
|
43
|
+
client.keep_alive_timeout = KEEP_ALIVE_TIMEOUT
|
44
|
+
|
45
|
+
# set SSL to true if a scheme is https
|
46
|
+
client.use_ssl = uri.scheme == 'https'
|
47
|
+
|
48
|
+
# dynamically set Net::HTTP options
|
49
|
+
DEFAULT_OPTIONS.merge(options).each_pair do |key, value|
|
50
|
+
client.public_send("#{key}=", value)
|
51
|
+
end
|
52
|
+
|
53
|
+
# open connection
|
54
|
+
client.start
|
55
|
+
|
56
|
+
# cache the client
|
57
|
+
clients_store[params] = client
|
58
|
+
|
59
|
+
client
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# close connections for each client
|
64
|
+
def close_connections!
|
65
|
+
mutex.synchronize do
|
66
|
+
clients_store.each_value(&:finish)
|
67
|
+
self.clients_store = {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def stale?
|
72
|
+
Time.now - last_used > STALE_AFTER
|
73
|
+
end
|
74
|
+
|
75
|
+
def mutex
|
76
|
+
@mutex ||= Mutex.new
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
# CashAppPayError is the base error from which all other more specific errors derive.
|
5
|
+
class CashAppPayError < StandardError
|
6
|
+
attr_reader :message
|
7
|
+
|
8
|
+
def initialize(message)
|
9
|
+
@message = message
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# InvalidRequestError is raised when a request is initiated with invalid
|
14
|
+
# parameters.
|
15
|
+
class InvalidRequestError < CashAppPayError
|
16
|
+
attr_accessor :param
|
17
|
+
|
18
|
+
def initialize(message, param)
|
19
|
+
super(message)
|
20
|
+
@param = param
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# AuthenticationError is raised when invalid credentials are used to connect
|
25
|
+
# to Cash App servers.
|
26
|
+
class AuthenticationError < CashAppPayError
|
27
|
+
end
|
28
|
+
|
29
|
+
class APIError < CashAppPayError
|
30
|
+
attr_reader :http_status, :http_body
|
31
|
+
|
32
|
+
def initialize(message, http_status:, http_body:)
|
33
|
+
super(message)
|
34
|
+
@http_status = http_status
|
35
|
+
@http_body = http_body
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class APIResponseError < CashAppPayError
|
40
|
+
def initialize(response)
|
41
|
+
error_object = ErrorObject.new(response)
|
42
|
+
super(error_object.to_s)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CashAppPay
|
4
|
+
module Helpers
|
5
|
+
module Symbolize
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def symbolize_recursive(hash)
|
9
|
+
{}.tap do |h|
|
10
|
+
hash.each { |key, value| h[key.to_sym] = transform(value) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def transform(thing)
|
17
|
+
case thing
|
18
|
+
when Hash then symbolize_recursive(thing)
|
19
|
+
when Array then thing.map { |v| transform(v) }
|
20
|
+
else; thing
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
refine Hash do
|
25
|
+
def deep_symbolize_keys
|
26
|
+
Symbolize.symbolize_recursive(self)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|