bambora-client 0.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.
- checksums.yaml +7 -0
- data/.github/workflows/linter.yml +13 -0
- data/.github/workflows/ruby.yml +34 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +108 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +93 -0
- data/LICENSE.txt +21 -0
- data/README.md +198 -0
- data/Rakefile +8 -0
- data/bambora-client.gemspec +55 -0
- data/bin/console +12 -0
- data/bin/setup +8 -0
- data/lib/bambora/adapters/json_response.rb +7 -0
- data/lib/bambora/adapters/multipart_mixed_request.rb +23 -0
- data/lib/bambora/adapters/query_string_response.rb +20 -0
- data/lib/bambora/adapters/response.rb +44 -0
- data/lib/bambora/bank/adapters/payment_profile_response.rb +36 -0
- data/lib/bambora/bank/batch_report_messages.rb +81 -0
- data/lib/bambora/bank/batch_report_resource.rb +79 -0
- data/lib/bambora/bank/builders/payment_profile_params.rb +39 -0
- data/lib/bambora/bank/payment_profile_resource.rb +81 -0
- data/lib/bambora/builders/batch_payment_csv.rb +41 -0
- data/lib/bambora/builders/headers.rb +43 -0
- data/lib/bambora/builders/www_form_parameters.rb +33 -0
- data/lib/bambora/builders/xml_request_body.rb +19 -0
- data/lib/bambora/client.rb +215 -0
- data/lib/bambora/client/version.rb +7 -0
- data/lib/bambora/factories/response_adapter_factory.rb +21 -0
- data/lib/bambora/rest/batch_payment_file_upload_client.rb +58 -0
- data/lib/bambora/rest/client.rb +63 -0
- data/lib/bambora/rest/json_client.rb +109 -0
- data/lib/bambora/rest/www_form_client.rb +38 -0
- data/lib/bambora/rest/xml_client.rb +35 -0
- data/lib/bambora/v1/batch_payment_resource.rb +52 -0
- data/lib/bambora/v1/payment_resource.rb +82 -0
- data/lib/bambora/v1/profile_resource.rb +85 -0
- metadata +256 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bambora
|
4
|
+
module Builders
|
5
|
+
class BatchPaymentCSV
|
6
|
+
class << self
|
7
|
+
##
|
8
|
+
# Return a CSV with one transaction per row.
|
9
|
+
#
|
10
|
+
# @see https://dev.na.bambora.com/docs/references/batch_payment/
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# Bambora::Builders::BatchPaymentCSV.build([{
|
14
|
+
# super_type: 'E',
|
15
|
+
# transaction_type: 'D',
|
16
|
+
# institution_number: 12345,
|
17
|
+
# transit_number: 123,
|
18
|
+
# account_number: 1223456789,
|
19
|
+
# amount: 10000
|
20
|
+
# reference_nubmer: 1234,
|
21
|
+
# recipient_name: 'Hup Podling',
|
22
|
+
# customer_code: '02355E2e58Bf488EAB4EaFAD7083dB6A',
|
23
|
+
# dynamic_description: 'The Skeksis',
|
24
|
+
# }])
|
25
|
+
#
|
26
|
+
# # => "E,D,12345,123,123456789,10000,1234,Hup Podling,02355E2e58Bf488EAB4EaFAD7083dB6A,The Skeksis\n"
|
27
|
+
#
|
28
|
+
# @param transactions [Array] an array of transaciton hashes.
|
29
|
+
#
|
30
|
+
# @return [String], a CSV as a string
|
31
|
+
def build(transactions)
|
32
|
+
CSV.generate(row_sep: "\r\n") do |csv|
|
33
|
+
transactions.each do |transaction|
|
34
|
+
csv << transaction.values
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bambora
|
4
|
+
module Builders
|
5
|
+
##
|
6
|
+
# Builds Headers for HTTP requests.
|
7
|
+
class Headers
|
8
|
+
attr_reader :api_key, :merchant_id, :sub_merchant_id, :content_type
|
9
|
+
|
10
|
+
##
|
11
|
+
# Initialize a new Headers object.
|
12
|
+
#
|
13
|
+
# @param options[:api_key] [String]
|
14
|
+
# @param options[:merchant_id] [String]
|
15
|
+
# @param options[:sub_merchant_id] [String] optional.
|
16
|
+
# @param options[:content_type] [String] optional.
|
17
|
+
def initialize(options = {})
|
18
|
+
options.each do |key, value|
|
19
|
+
instance_variable_set("@#{key}", value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Builds a header object.
|
25
|
+
#
|
26
|
+
# @return [Hash]
|
27
|
+
def build
|
28
|
+
headers = {
|
29
|
+
'Authorization' => "Passcode #{passcode}",
|
30
|
+
}
|
31
|
+
headers['Content-Type'] = content_type if content_type
|
32
|
+
headers['Sub-Merchant-Id'] = sub_merchant_id if sub_merchant_id
|
33
|
+
headers
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def passcode
|
39
|
+
Base64.encode64("#{merchant_id}:#{api_key}").delete("\n")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bambora
|
4
|
+
module Builders
|
5
|
+
##
|
6
|
+
# Builds WWW URL Encoded request parameters from a Hash
|
7
|
+
class WWWFormParameters
|
8
|
+
attr_reader :body
|
9
|
+
|
10
|
+
##
|
11
|
+
# Initiallze a new WWWFormParameter
|
12
|
+
#
|
13
|
+
# @params body [Hash]
|
14
|
+
def initialize(body:)
|
15
|
+
@body = body
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Convert a hash to url-encoded query parameters.
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
def to_s
|
23
|
+
URI.encode_www_form(sanitized_body)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def sanitized_body
|
29
|
+
body.reject { |_, val| val.to_s.empty? }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bambora
|
4
|
+
module Builders
|
5
|
+
##
|
6
|
+
# Builds an XML request body from a Hash
|
7
|
+
class XMLRequestBody
|
8
|
+
attr_reader :body
|
9
|
+
|
10
|
+
def initialize(body:)
|
11
|
+
@body = body
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"<?xml version='1.0' encoding='utf-8'?>#{Gyoku.xml(request: body)}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Standard Libraries
|
4
|
+
require 'base64'
|
5
|
+
require 'cgi'
|
6
|
+
require 'csv'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
# Gems
|
10
|
+
require 'faraday' # HTTP Wraper
|
11
|
+
require 'gyoku' # XML Builder
|
12
|
+
require 'multiparty' # Multipart/mixed requests
|
13
|
+
|
14
|
+
require 'bambora/client/version'
|
15
|
+
|
16
|
+
# Adapters
|
17
|
+
require 'bambora/adapters/response'
|
18
|
+
require 'bambora/adapters/json_response'
|
19
|
+
require 'bambora/adapters/multipart_mixed_request'
|
20
|
+
require 'bambora/adapters/query_string_response'
|
21
|
+
require 'bambora/bank/adapters/payment_profile_response'
|
22
|
+
|
23
|
+
# Builders
|
24
|
+
require 'bambora/builders/headers'
|
25
|
+
require 'bambora/builders/xml_request_body'
|
26
|
+
require 'bambora/builders/www_form_parameters'
|
27
|
+
require 'bambora/builders/batch_payment_csv'
|
28
|
+
require 'bambora/bank/builders/payment_profile_params'
|
29
|
+
|
30
|
+
# Factories
|
31
|
+
require 'bambora/factories/response_adapter_factory'
|
32
|
+
|
33
|
+
# Clients
|
34
|
+
require 'bambora/rest/client'
|
35
|
+
require 'bambora/rest/batch_payment_file_upload_client'
|
36
|
+
require 'bambora/rest/json_client'
|
37
|
+
require 'bambora/rest/www_form_client'
|
38
|
+
require 'bambora/rest/xml_client'
|
39
|
+
|
40
|
+
# Resources
|
41
|
+
require 'bambora/v1/batch_payment_resource'
|
42
|
+
require 'bambora/v1/payment_resource'
|
43
|
+
require 'bambora/v1/profile_resource'
|
44
|
+
require 'bambora/bank/payment_profile_resource'
|
45
|
+
require 'bambora/bank/batch_report_messages'
|
46
|
+
require 'bambora/bank/batch_report_resource'
|
47
|
+
|
48
|
+
module Bambora
|
49
|
+
##
|
50
|
+
# The Client class is used to initialize Resource objects that can make requests to the Bambora API.
|
51
|
+
class Client
|
52
|
+
class Error < StandardError; end
|
53
|
+
|
54
|
+
attr_reader :base_url, :merchant_id, :scripts_api_base_url, :sub_merchant_id
|
55
|
+
|
56
|
+
# Initialze a new Bambora::Client.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
#
|
60
|
+
# client = Bambora::Client.new do |c|
|
61
|
+
# c.base_url = ENV.fetch('BAMBORA_BASE_URL')
|
62
|
+
# c.scripts_api_base_url = ENV.fetch('BAMBORA_SCRIPTS_BASE_URL')
|
63
|
+
# c.merchant_id = ENV.fetch('BAMBORA_MERCHANT_ID')
|
64
|
+
# c.sub_merchant_id = ENV.fetch('BAMBORA_SUB_MERCHANT_ID')
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# @param options[base_url] [String] Bambora Base URL
|
68
|
+
# @param options[merchant_id] [String] The Merchant ID for this request.
|
69
|
+
# @param options[sub_merchant_id] [String] The Sub-Merchant ID for this request.
|
70
|
+
def initialize(options = {})
|
71
|
+
options.each do |key, value|
|
72
|
+
instance_variable_set("@#{key}", value)
|
73
|
+
end
|
74
|
+
|
75
|
+
yield(self) if block_given?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Retrieve a client to make requests to the Profiles endpoints.
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# profiles = client.profiles
|
82
|
+
# profiles.delete(customer_code: '02355E2e58Bf488EAB4EaFAD7083dB6A')
|
83
|
+
#
|
84
|
+
# @param api_key [String] API key for the profiles endpoint.
|
85
|
+
#
|
86
|
+
# @return [Bambora::V1::ProfileResource]
|
87
|
+
def profiles(api_key:)
|
88
|
+
@profiles ||= Bambora::V1::ProfileResource.new(client: json_client, api_key: api_key)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Retrieve a client to make requests to the Payments endpoints.
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# payments = client.profiles(api_key: <yourapikey>)
|
95
|
+
# payments.create(
|
96
|
+
# {
|
97
|
+
# amount: 50,
|
98
|
+
# payment_method: 'card',
|
99
|
+
# card: {
|
100
|
+
# name: 'Hup Podling',
|
101
|
+
# number: '4504481742333',
|
102
|
+
# expiry_month: '12',
|
103
|
+
# expiry_year: '20',
|
104
|
+
# cvd: '123',
|
105
|
+
# },
|
106
|
+
# },
|
107
|
+
# )
|
108
|
+
#
|
109
|
+
# @param api_key [String] API key for the payments endpoint.
|
110
|
+
#
|
111
|
+
# @return [Bambora::V1::PaymentResource]
|
112
|
+
def payments(api_key:)
|
113
|
+
@payments ||= Bambora::V1::PaymentResource.new(client: json_client, api_key: api_key)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Retrieve a client to make requests to the Bank Payment Profiles endpoints.
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# profiles = client.bank_profiles(api_key: <yourapikey>)
|
120
|
+
#
|
121
|
+
# data = {
|
122
|
+
# customer_code: '1234',
|
123
|
+
# bank_account_type: 'CA',
|
124
|
+
# bank_account_holder: 'All-Maudra Mayrin',
|
125
|
+
# institution_number: '123',
|
126
|
+
# branch_number: '12345',
|
127
|
+
# account_number: '123456789',
|
128
|
+
# name: 'Brea Princess of Vapra',
|
129
|
+
# email_address: 'brea@theresistance.com',
|
130
|
+
# phone_number: '1231231234',
|
131
|
+
# address_1: 'The Castle',
|
132
|
+
# city: "Ha'rar",
|
133
|
+
# postal_code: 'H0H 0H0',
|
134
|
+
# province: 'Vapra',
|
135
|
+
# country: 'Thra',
|
136
|
+
# sub_merchant_id: 1,
|
137
|
+
# }
|
138
|
+
#
|
139
|
+
# profiles.create(data)
|
140
|
+
#
|
141
|
+
# @param api_key [String] API key for the bank profiles endpoint.
|
142
|
+
#
|
143
|
+
# @return [Bambora::Bank::PaymentProfileResource]
|
144
|
+
def bank_profiles(api_key:)
|
145
|
+
@bank_profiles ||= Bambora::Bank::PaymentProfileResource.new(client: www_form_client, api_key: api_key)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Retrieve a client to make requests to the batch report endpoint.
|
149
|
+
#
|
150
|
+
# @example
|
151
|
+
# batch_reports = client.batch_reports(api_key: <yourapikey>)
|
152
|
+
#
|
153
|
+
# data = {
|
154
|
+
# rpt_filter_by_1: 'batch_id',
|
155
|
+
# rpt_filter_value_1: 1,
|
156
|
+
# rpt_operation_type_1: 'EQ',
|
157
|
+
# rpt_from_date_time: '2019-12-18 00:00:00',
|
158
|
+
# rpt_to_date_time: '2019-12-18 23:59:59',
|
159
|
+
# service_name: 'BatchPaymentsEFT',
|
160
|
+
# }
|
161
|
+
#
|
162
|
+
# batch_reports.show(data)
|
163
|
+
#
|
164
|
+
# @param api_key [String] API key for the bank profiles endpoint.
|
165
|
+
#
|
166
|
+
# @return [Bambora::Bank::PaymentProfileResource]
|
167
|
+
def batch_reports(api_key:)
|
168
|
+
@batch_reports = Bambora::Bank::BatchReportResource.new(
|
169
|
+
client: xml_client,
|
170
|
+
api_key: api_key,
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
def batch_payments(api_key:)
|
175
|
+
@batch_payments ||= Bambora::V1::BatchPaymentResource.new(
|
176
|
+
client: batch_payment_file_upload_client,
|
177
|
+
api_key: api_key,
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def json_client
|
184
|
+
@json_client ||= Bambora::Rest::JSONClient.new(
|
185
|
+
base_url: base_url,
|
186
|
+
merchant_id: merchant_id,
|
187
|
+
sub_merchant_id: sub_merchant_id,
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
191
|
+
def www_form_client
|
192
|
+
@www_form_client ||= Bambora::Rest::WWWFormClient.new(
|
193
|
+
base_url: scripts_api_base_url,
|
194
|
+
merchant_id: merchant_id,
|
195
|
+
sub_merchant_id: sub_merchant_id,
|
196
|
+
)
|
197
|
+
end
|
198
|
+
|
199
|
+
def batch_payment_file_upload_client
|
200
|
+
@batch_payment_file_upload_client ||= Bambora::Rest::BatchPaymentFileUploadClient.new(
|
201
|
+
base_url: base_url,
|
202
|
+
merchant_id: merchant_id,
|
203
|
+
sub_merchant_id: sub_merchant_id,
|
204
|
+
)
|
205
|
+
end
|
206
|
+
|
207
|
+
def xml_client
|
208
|
+
@xml_client ||= Bambora::Rest::XMLClient.new(
|
209
|
+
base_url: scripts_api_base_url,
|
210
|
+
merchant_id: merchant_id,
|
211
|
+
sub_merchant_id: sub_merchant_id,
|
212
|
+
)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bambora
|
4
|
+
##
|
5
|
+
# Selects an adapter for parsing an HTTP response body
|
6
|
+
class ResponseAdapterFactory
|
7
|
+
class << self
|
8
|
+
def for(response)
|
9
|
+
content_type = response.headers['Content-Type'].split(';').first
|
10
|
+
case content_type
|
11
|
+
when 'application/json'
|
12
|
+
Bambora::JSONResponse.new(response)
|
13
|
+
when 'text/html'
|
14
|
+
# Currently, the only endpoint that responds with text/html is /scripts/payment_profiles.asp
|
15
|
+
Bambora::Bank::Adapters::PaymentProfileResponse.new(response)
|
16
|
+
else raise Bambora::Client::Error, "Unknown Content Type: #{content_type}. Response Body: #{response.body}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bambora
|
4
|
+
module Rest
|
5
|
+
class BatchPaymentFileUploadClient < Bambora::Rest::Client
|
6
|
+
CONTENT_DISPOSITION = 'form-data'
|
7
|
+
CONTENT_TYPES = {
|
8
|
+
text_plain: 'text/plain',
|
9
|
+
application_json: 'application/json',
|
10
|
+
}.freeze
|
11
|
+
FILE_TYPE_HEADER = { 'FileType' => 'STD' }.freeze
|
12
|
+
|
13
|
+
# @param args[:file_contents] [String] CSV file contents
|
14
|
+
# @param args[:options] [Hash] Request Parameters as documented by Bambora.
|
15
|
+
# @param args[:api_key] [String]
|
16
|
+
def post(args = {})
|
17
|
+
init_payload(args[:file_contents], args[:options])
|
18
|
+
parse_response_body(
|
19
|
+
super(
|
20
|
+
path: args[:path],
|
21
|
+
body: @payload.body,
|
22
|
+
headers: build_headers(api_key: args[:api_key]),
|
23
|
+
),
|
24
|
+
).to_h
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def init_payload(file_contents, options)
|
30
|
+
return @payload if @payload
|
31
|
+
|
32
|
+
@payload = Bambora::Adapters::MultipartMixedRequest.new(
|
33
|
+
multipart_args: {
|
34
|
+
criteria: {
|
35
|
+
content_disposition: CONTENT_DISPOSITION,
|
36
|
+
content_type: CONTENT_TYPES[:application_json],
|
37
|
+
content: options.to_json,
|
38
|
+
},
|
39
|
+
data: {
|
40
|
+
filename: "merchant_#{sub_merchant_id}.txt",
|
41
|
+
content_type: CONTENT_TYPES[:text_plain],
|
42
|
+
content: file_contents,
|
43
|
+
},
|
44
|
+
},
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_headers(api_key:)
|
49
|
+
headers = Bambora::Builders::Headers.new(
|
50
|
+
content_type: @payload.content_type,
|
51
|
+
api_key: api_key,
|
52
|
+
merchant_id: merchant_id,
|
53
|
+
).build
|
54
|
+
headers.merge(FILE_TYPE_HEADER)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bambora
|
4
|
+
module Rest
|
5
|
+
##
|
6
|
+
# A basic client for making REST requests.
|
7
|
+
class Client
|
8
|
+
attr_reader :base_url, :merchant_id, :sub_merchant_id
|
9
|
+
|
10
|
+
# Initialze a client that makes REST requests.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# rest_client = Bambora::Rest::Client.new(
|
14
|
+
# base_url: ENV.fetch('BAMBORA_BASE_URL'),
|
15
|
+
# merchant_id: ENV.fetch('BAMBORA_MERCHANT_ID'),
|
16
|
+
# sub_merchant_id: ENV.fetch('BAMBORA_SUB_MERCHANT_ID'),
|
17
|
+
# )
|
18
|
+
def initialize(options = {})
|
19
|
+
options.each do |key, value|
|
20
|
+
instance_variable_set("@#{key}", value)
|
21
|
+
end
|
22
|
+
yield(self) if block_given?
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def get(path:, params:, headers:)
|
28
|
+
connection.get(path, params, headers)
|
29
|
+
end
|
30
|
+
|
31
|
+
def post(path:, body:, headers:)
|
32
|
+
connection.post(path, body, headers)
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(path:, headers:)
|
36
|
+
connection.delete(path) do |req|
|
37
|
+
req.headers = headers
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def connection
|
42
|
+
@connection ||= Faraday.new(url: base_url) do |faraday|
|
43
|
+
faraday.request :multipart
|
44
|
+
faraday.request :url_encoded
|
45
|
+
faraday.adapter :excon
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_headers(api_key:, content_type: nil)
|
50
|
+
Bambora::Builders::Headers.new(
|
51
|
+
content_type: content_type,
|
52
|
+
api_key: api_key,
|
53
|
+
merchant_id: merchant_id,
|
54
|
+
sub_merchant_id: sub_merchant_id,
|
55
|
+
).build
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_response_body(response)
|
59
|
+
Bambora::ResponseAdapterFactory.for(response).to_h
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|