trolley 0.2.14 → 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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/trolley/Balance.rb +4 -0
  3. data/lib/trolley/Batch.rb +20 -0
  4. data/lib/trolley/BatchSummary.rb +21 -0
  5. data/lib/trolley/Client.rb +117 -0
  6. data/lib/trolley/Configuration.rb +30 -0
  7. data/lib/trolley/Exceptions.rb +26 -0
  8. data/lib/trolley/Gateway.rb +18 -0
  9. data/lib/trolley/Invoice.rb +22 -0
  10. data/lib/trolley/InvoicePayment.rb +13 -0
  11. data/lib/trolley/OfflinePayment.rb +23 -0
  12. data/lib/trolley/Payment.rb +52 -0
  13. data/lib/trolley/Recipient.rb +44 -0
  14. data/lib/trolley/RecipientAccount.rb +30 -0
  15. data/lib/trolley/gateways/BalanceGateway.rb +15 -0
  16. data/lib/trolley/gateways/BatchGateway.rb +74 -0
  17. data/lib/trolley/gateways/InvoiceGateway.rb +57 -0
  18. data/lib/trolley/gateways/InvoicePaymentGateway.rb +37 -0
  19. data/lib/trolley/gateways/OfflinePaymentGateway.rb +44 -0
  20. data/lib/trolley/gateways/PaymentGateway.rb +42 -0
  21. data/lib/trolley/gateways/RecipientAccountGateway.rb +42 -0
  22. data/lib/trolley/gateways/RecipientGateway.rb +82 -0
  23. data/lib/trolley/utils/PaginatedArray.rb +25 -0
  24. data/lib/trolley/utils/ResponseMapper.rb +80 -0
  25. data/lib/trolley.rb +33 -5
  26. data/spec/integration/BatchTest.rb +126 -0
  27. data/spec/integration/InvoicePaymentTest.rb +92 -0
  28. data/spec/integration/InvoiceTest.rb +128 -0
  29. data/spec/integration/RecipientAccountTest.rb +48 -0
  30. data/spec/integration/RecipientTest.rb +159 -0
  31. data/spec/integration/helper.rb +19 -0
  32. data/spec/unit/ConfigurationTest.rb +47 -0
  33. data/spec/unit/PaginatedArrayTest.rb +23 -0
  34. data/spec/unit/ResponseMapperTest.rb +26 -0
  35. data/spec/unit/TrolleyTest.rb +15 -0
  36. data/trolley.gemspec +22 -12
  37. metadata +103 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ba931e6ff7016ee325fd67c087b7e20bc762c54d8d6e8982abf63a7fb8d507c
4
- data.tar.gz: 62b9f604e9ecb631c8e83129cf70aed9c3a09142cb8a6ad9e4f79cb981256203
3
+ metadata.gz: 262a1acbb09a0cd81c2c73058df52605cfb43e949a742dd5afb40bdcfc5b6b5b
4
+ data.tar.gz: f900a5a7b7bfbdda5591d160e1a7dfdabd8c60c05de618047039b145af7f5b61
5
5
  SHA512:
6
- metadata.gz: e55a122666265822edac8d924cfbeeeda8c3beef272bc0f244fbd125885d4bec6998bfc8351c57e6628b57f1bae7cde1896519130a938453e6c51090cadbf97e
7
- data.tar.gz: '08bf65fcf30c78570d790a76401dca50df8ae87f72821cbef6f48363733505cc0fc606a4e0d8b108cade33f13e0a99330ecdd612b6c80a363057ef4081adabe9'
6
+ metadata.gz: c9dea4abf4ad7ed4c11c936646b42991c70094000185a000a4725354ae374694c8ec599d51e607853cf6bf2075269545dcf573caff5b6c62c5ea5117b78f70c0
7
+ data.tar.gz: dcb1e1788bc0435aebacf59518d6184048f6f6fc64d9e734d56176c958e5d1ed9a8b98ec47dbbb4a8ffadc1d721783f2aaa970518c3facec821504108fc22751
@@ -0,0 +1,4 @@
1
+ module Trolley
2
+ class Balance
3
+ end
4
+ end
@@ -0,0 +1,20 @@
1
+ module Trolley
2
+ class Batch
3
+ attr_accessor(
4
+ :id,
5
+ :amount,
6
+ :completedAt,
7
+ :createdAt,
8
+ :currency,
9
+ :description,
10
+ :sentAt,
11
+ :status,
12
+ :totalPayments,
13
+ :updatedAt,
14
+ :quoteExpiredAt,
15
+ :payments,
16
+ :tags,
17
+ :coverFees
18
+ )
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module Trolley
2
+ class BatchSummary
3
+ attr_accessor(
4
+ :id,
5
+ :amount,
6
+ :completedAt,
7
+ :createdAt,
8
+ :currency,
9
+ :description,
10
+ :sentAt,
11
+ :status,
12
+ :totalPayments,
13
+ :updatedAt,
14
+ :methods,
15
+ :detail,
16
+ :total,
17
+ :balances,
18
+ :accounts
19
+ )
20
+ end
21
+ end
@@ -0,0 +1,117 @@
1
+ # require_relative 'Exceptions.rb'
2
+ require 'digest'
3
+ require 'net/http'
4
+ require 'openssl'
5
+ require 'uri'
6
+ require 'json'
7
+ require "rubygems"
8
+
9
+ module Trolley
10
+ class Client
11
+ def initialize(config)
12
+ @config = config
13
+ end
14
+
15
+ def get(endPoint)
16
+ send_request(endPoint, 'GET')
17
+ end
18
+
19
+ def post(endPoint, body)
20
+ body = body.to_json
21
+ send_request(endPoint, 'POST', body)
22
+ end
23
+
24
+ def delete(endPoint, body = '')
25
+ body = body.to_json if body != ''
26
+ send_request(endPoint, 'DELETE', body)
27
+ end
28
+
29
+ def patch(endPoint, body)
30
+ body = body.to_json
31
+ send_request(endPoint, 'PATCH', body)
32
+ end
33
+
34
+ private
35
+
36
+ # rubocop:disable Metrics/CyclomaticComplexity
37
+ # rubocop:disable Metrics/PerceivedComplexity
38
+ def send_request(endPoint, method, body = '')
39
+ uri = URI.parse(@config.api_base + endPoint)
40
+ http = Net::HTTP.new(
41
+ uri.host, uri.port,
42
+ @config.proxy&.host, @config.proxy&.port, @config.proxy&.user, @config.proxy&.password
43
+ )
44
+ http.use_ssl = @config.useSsl?
45
+
46
+ time = Time.now.to_i
47
+ headers = {'X-PR-Timestamp': time.to_s,
48
+ 'Authorization': generate_authorization(time, endPoint, method, body),
49
+ 'Content-Type': 'application/json',
50
+ 'Trolley-Source': "ruby-sdk_#{::Trolley::VERSION}"}
51
+
52
+ if method === "GET"
53
+ request = Net::HTTP::Get.new(uri.request_uri, headers)
54
+ elsif method === "POST"
55
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
56
+ request.body = body
57
+ elsif method === "PATCH"
58
+ request = Net::HTTP::Patch.new(uri.request_uri, headers)
59
+ request.body = body
60
+ elsif method === "DELETE"
61
+ request = Net::HTTP::Delete.new(uri.request_uri, headers)
62
+ request.body = body
63
+ end
64
+
65
+ response = http.request(request)
66
+
67
+ if response.code != '200' && response.code != '204'
68
+ throw_status_code_exception("#{response.message} #{response.body}" , response.code, response.body)
69
+ end
70
+ response.body
71
+ end
72
+ # rubocop:enable Metrics/CyclomaticComplexity
73
+ # rubocop:enable Metrics/PerceivedComplexity
74
+
75
+ private
76
+ def generate_authorization(time, endPoint, method, body)
77
+ message = "#{[time.to_s, method, endPoint, body].join("\n")}\n"
78
+ signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @config.privateKey, message)
79
+ "prsign #{@config.publicKey}:#{signature}"
80
+ end
81
+
82
+ private
83
+
84
+ # rubocop:disable Metrics/CyclomaticComplexity
85
+ def throw_status_code_exception(message, code, body = nil)
86
+ validation_errors = []
87
+ unless body.nil?
88
+ begin
89
+ body = JSON.parse(body)
90
+ validation_errors = body['errors']
91
+ rescue JSON::ParserError
92
+ # no-op
93
+ end
94
+ end
95
+
96
+ case code
97
+ when '400'
98
+ raise MalformedRequestError.new(message, validation_errors)
99
+ when '401'
100
+ raise AuthenticationError.new(message, validation_errors)
101
+ when '403'
102
+ raise AuthorizationError.new(message, validation_errors)
103
+ when '404'
104
+ raise NotFoundError.new(message, validation_errors)
105
+ when '429'
106
+ raise TooManyRequestsError.new(message, validation_errors)
107
+ when '500'
108
+ raise ServerError.new(message, validation_errors)
109
+ when '503'
110
+ raise DownForMaintenanceError.new(message, validation_errors)
111
+ else
112
+ raise UnexpectedError.new(message, validation_errors)
113
+ end
114
+ end
115
+ # rubocop:enable Metrics/CyclomaticComplexity
116
+ end
117
+ end
@@ -0,0 +1,30 @@
1
+ module Trolley
2
+ class Configuration
3
+ class InvalidProxyAddress < StandardError; end
4
+ attr_reader :api_base, :proxy, :environment
5
+
6
+ DEFAULT_API_BASE = 'https://api.trolley.com'.freeze
7
+
8
+ def initialize(publicKey, privateKey, **optionals)
9
+ raise ArgumentError, 'Both key/secret must be a nonempty string' if publicKey.to_s&.empty? || privateKey.to_s&.empty?
10
+
11
+ @publicKey = publicKey
12
+ @privateKey = privateKey
13
+ @api_base = optionals[:api_base] || DEFAULT_API_BASE
14
+ # failfast on a bad proxy
15
+ proxy_uri = optionals[:proxy_uri]
16
+ begin
17
+ @proxy = proxy_uri.nil? ? nil : URI.parse(proxy_uri)
18
+ rescue URI::InvalidURIError
19
+ raise InvalidProxyAddress, "Invalid proxy provided to configuration: #{proxy_uri}"
20
+ end
21
+ end
22
+
23
+ def useSsl?
24
+ api_base.start_with? 'https'
25
+ end
26
+
27
+ attr_accessor :publicKey, :privateKey
28
+
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ module Trolley
2
+ class TrolleyError < ::StandardError
3
+ attr_reader :validation_errors
4
+
5
+ def initialize(message, validation_errors)
6
+ super(message)
7
+ @validation_errors = validation_errors
8
+ end
9
+ end
10
+
11
+ class AuthenticationError < TrolleyError; end
12
+
13
+ class AuthorizationError < TrolleyError; end
14
+
15
+ class DownForMaintenanceError < TrolleyError; end
16
+
17
+ class NotFoundError < TrolleyError; end
18
+
19
+ class ServerError < TrolleyError; end
20
+
21
+ class TooManyRequestsError < TrolleyError; end
22
+
23
+ class UnexpectedError < TrolleyError; end
24
+
25
+ class MalformedRequestError < TrolleyError; end
26
+ end
@@ -0,0 +1,18 @@
1
+ module Trolley
2
+ class Gateway
3
+ attr_accessor :config, :client, :recipient, :recipient_account, :batch, :payment, :balance, :offline_payment, :invoice, :invoice_payment
4
+
5
+ def initialize(config)
6
+ @config = config
7
+ @client = Client.new(config)
8
+ @recipient = RecipientGateway.new(client)
9
+ @recipient_account = RecipientAccountGateway.new(client)
10
+ @batch = BatchGateway.new(client)
11
+ @payment = PaymentGateway.new(client)
12
+ @balance = BalanceGateway.new(client)
13
+ @offline_payment = OfflinePaymentGateway.new(client)
14
+ @invoice = InvoiceGateway.new(client)
15
+ @invoice_payment = InvoicePaymentGateway.new(client)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module Trolley
2
+ class Invoice
3
+ attr_accessor(
4
+ :id,
5
+ :description,
6
+ :externalId,
7
+ :invoiceDate,
8
+ :dueDate,
9
+ :invoiceNumber,
10
+ :status,
11
+ :releaseAfter,
12
+ :createdAt,
13
+ :updatedAt,
14
+ :totalAmount,
15
+ :paidAmount,
16
+ :dueAmount,
17
+ :tags,
18
+ :recipientId,
19
+ :lines
20
+ )
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Trolley
2
+ class InvoicePayment
3
+ attr_accessor(
4
+ :id,
5
+ :batchId,
6
+ :paymentId,
7
+ :invoiceId,
8
+ :invoiceLineId,
9
+ :amount,
10
+ :invoicePayments
11
+ )
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module Trolley
2
+ class OfflinePayment
3
+ attr_accessor(
4
+ :id,
5
+ :recipientId,
6
+ :externalId,
7
+ :memo,
8
+ :tags,
9
+ :taxReportable,
10
+ :category,
11
+ :amount,
12
+ :currency,
13
+ :withholdingAmount,
14
+ :withholdingCurrency,
15
+ :processedAt,
16
+ :equivalentWithholdingAmount,
17
+ :equivalentWithholdingCurrency,
18
+ :updatedAt,
19
+ :createdAt,
20
+ :deletedAt
21
+ )
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ module Trolley
2
+ class Payment
3
+ attr_accessor(
4
+ :id,
5
+ :status,
6
+ :isSupplyPayment,
7
+ :returnedAmount,
8
+ :sourceAmount,
9
+ :sourceCurrency,
10
+ :targetAmount,
11
+ :targetCurrency,
12
+ :exchangeRate,
13
+ :fees,
14
+ :recipientFees,
15
+ :fxRate,
16
+ :memo,
17
+ :externalId,
18
+ :processedAt,
19
+ :createdAt,
20
+ :updatedAt,
21
+ :merchantFees,
22
+ :compliance,
23
+ :payoutMethod,
24
+ :estimatedDeliveryAt,
25
+ :recipient,
26
+ :withholdingAmount,
27
+ :withholdingCurrency,
28
+ :equivalentWithholdingAmount,
29
+ :equivalentWithholdingCurrency,
30
+ :methodDisplay,
31
+ :batch,
32
+ :coverFees,
33
+ :category,
34
+ :amount,
35
+ :currency,
36
+ :taxReportable,
37
+ :taxBasisAmount,
38
+ :taxBasisCurrency,
39
+ :tags,
40
+ :account,
41
+ :initiatedAt,
42
+ :settledAt,
43
+ :returnedAt,
44
+ :returnedNote,
45
+ :returnedReason,
46
+ :failureMessage,
47
+ :merchantId,
48
+ :checkNumber,
49
+ :forceUsTaxActivity
50
+ )
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ module Trolley
2
+ class Recipient
3
+ attr_accessor(
4
+ :id,
5
+ :routeMinimum,
6
+ :routeType,
7
+ :estimatedFees,
8
+ :referenceId,
9
+ :email,
10
+ :name,
11
+ :lastName,
12
+ :firstName,
13
+ :type,
14
+ :taxType,
15
+ :status,
16
+ :language,
17
+ :complianceStatus,
18
+ :dob,
19
+ :contactEmails,
20
+ :passport,
21
+ :updatedAt,
22
+ :createdAt,
23
+ :gravatarUrl,
24
+ :governmentId,
25
+ :ssn,
26
+ :primaryCurrency,
27
+ :merchantId,
28
+ :payout,
29
+ :compliance,
30
+ :accounts,
31
+ :address,
32
+ :taxWithholdingPercentage,
33
+ :taxForm,
34
+ :taxFormStatus,
35
+ :inactiveReasons,
36
+ :payoutMethod,
37
+ :placeOfBirth,
38
+ :tags,
39
+ :taxDeliveryType,
40
+ :riskScore,
41
+ :isPortalUser
42
+ )
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ module Trolley
2
+ class RecipientAccount
3
+ attr_accessor(
4
+ :id,
5
+ :primary,
6
+ :currency,
7
+ :recipientAccountId,
8
+ :recipientId,
9
+ :recipientReferenceId,
10
+ :routeType,
11
+ :recipientFees,
12
+ :emailAddress,
13
+ :country,
14
+ :type,
15
+ :iban,
16
+ :accountNum,
17
+ :accountHolderName,
18
+ :swiftBic,
19
+ :branchId,
20
+ :bankId,
21
+ :bankName,
22
+ :bankAddress,
23
+ :bankCity,
24
+ :bankRegionCode,
25
+ :bankPostalCode,
26
+ :status,
27
+ :disabledAt
28
+ )
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../Client'
2
+
3
+ module Trolley
4
+ class BalanceGateway
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def find(term = '')
10
+ response = @client.get("/v1/balances/#{term}" )
11
+ JSON.parse(response, object_class: OpenStruct)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,74 @@
1
+ require_relative '../Client'
2
+
3
+ module Trolley
4
+ class BatchGateway
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def find(batch_id)
10
+ response = @client.get("/v1/batches/#{batch_id}")
11
+ batch_builder(response)
12
+ end
13
+
14
+ def all
15
+ response = @client.get('/v1/batches/')
16
+ batch_list_builder(response)
17
+ end
18
+
19
+ def create(body)
20
+ response = @client.post('/v1/batches/', body)
21
+ batch_builder(response)
22
+ end
23
+
24
+ def update(batch_id, body)
25
+ @client.patch("/v1/batches/#{batch_id}", body)
26
+ true
27
+ end
28
+
29
+ # @param batch_id [String] or [Array] The id (or array of ids) of the batch to delete
30
+ def delete(batch_id)
31
+ path = '/v1/batches/'
32
+ body = ''
33
+
34
+ if batch_id.is_a?(String)
35
+ path += batch_id
36
+ elsif batch_id.is_a?(Array)
37
+ body = { ids: batch_id }
38
+ else
39
+ raise 'Invalid batch_id type, please pass a string or array of strings'
40
+ end
41
+
42
+ @client.delete(path, body)
43
+ true
44
+ end
45
+
46
+ def generate_quote(batch_id)
47
+ response = @client.post("/v1/batches/#{batch_id}/generate-quote", {})
48
+ batch_builder(response)
49
+ end
50
+
51
+ def start_processing(batch_id)
52
+ response = @client.post("/v1/batches/#{batch_id}/start-processing", {})
53
+ batch_builder(response)
54
+ end
55
+
56
+ def search(page = 1, page_size = 10, term = '')
57
+ response = @client.get("/v1/batches?page=#{page}&pageSize=#{page_size}&search=#{term}")
58
+ batch_list_builder(response)
59
+ end
60
+
61
+ def batch_builder(response)
62
+ Utils::ResponseMapper.build(response, Batch)
63
+ end
64
+
65
+ def summary(batch_id)
66
+ response = @client.get("/v1/batches/#{batch_id}/summary")
67
+ Utils::ResponseMapper.build(response, BatchSummary)
68
+ end
69
+
70
+ def batch_list_builder(response)
71
+ Utils::PaginatedArray.from_response(response, Batch)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,57 @@
1
+ require_relative '../Client'
2
+
3
+ module Trolley
4
+ class InvoiceGateway
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def find(body)
10
+ response = @client.post('/v1/invoices/get', body)
11
+ invoice_builder(response)
12
+ end
13
+
14
+ def create(body)
15
+ response = @client.post('/v1/invoices/create', body)
16
+ invoice_builder(response)
17
+ end
18
+
19
+ def create_line(body)
20
+ response = @client.post('/v1/invoices/create-lines', body)
21
+ invoice_builder(response)
22
+ end
23
+
24
+ def search(body)
25
+ response = @client.post('/v1/invoices/search', body)
26
+ invoice_list_builder(response)
27
+ end
28
+
29
+ def update(body)
30
+ @client.post('/v1/invoices/update', body)
31
+ true
32
+ end
33
+
34
+ def update_line(body)
35
+ @client.post('/v1/invoices/update-lines', body)
36
+ true
37
+ end
38
+
39
+ def delete(body)
40
+ @client.post('/v1/invoices/delete', body)
41
+ true
42
+ end
43
+
44
+ def delete_line(body)
45
+ @client.post('/v1/invoices/delete-lines', body)
46
+ true
47
+ end
48
+
49
+ def invoice_builder(response)
50
+ Utils::ResponseMapper.build(response, Invoice)
51
+ end
52
+
53
+ def invoice_list_builder(response)
54
+ Utils::PaginatedArray.from_response(response, Invoice)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,37 @@
1
+ require_relative '../Client'
2
+
3
+ module Trolley
4
+ class InvoicePaymentGateway
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def create(body)
10
+ response = @client.post('/v1/invoices/payment/create', body)
11
+ invoice_payment_builder(response)
12
+ end
13
+
14
+ def update(body)
15
+ @client.post('/v1/invoices/payment/update', body)
16
+ true
17
+ end
18
+
19
+ def delete(body)
20
+ @client.post('/v1/invoices/payment/delete', body)
21
+ true
22
+ end
23
+
24
+ def search(body)
25
+ response = @client.post('/v1/invoices/payment/search', body)
26
+ invoice_payments_list_builder(response)
27
+ end
28
+
29
+ def invoice_payment_builder(response)
30
+ Utils::ResponseMapper.build(response, InvoicePayment)
31
+ end
32
+
33
+ def invoice_payments_list_builder(response)
34
+ Utils::PaginatedArray.from_response(response, InvoicePayment)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ require_relative '../Client'
2
+
3
+ module Trolley
4
+ class OfflinePaymentGateway
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def create(recipient_id, body)
10
+ response = @client.post("/v1/recipients/#{recipient_id}/offlinePayments", body)
11
+ offline_payment_builder(response)
12
+ end
13
+
14
+ def update(recipient_id, offline_payment_id, body)
15
+ @client.patch("/v1/recipients/#{recipient_id}/offlinePayments/#{offline_payment_id}", body)
16
+ true
17
+ end
18
+
19
+ def delete(recipient_id, offline_payment_id)
20
+ @client.delete("/v1/recipients/#{recipient_id}/offlinePayments/#{offline_payment_id}")
21
+ true
22
+ end
23
+
24
+ # rubocop:disable Metrics/ParameterLists
25
+ def search(recipient_id = '', page = 1, page_size = 10, term = '')
26
+ if recipient_id === ''
27
+ response = @client.get("/v1/offline-payments?page=#{page}&pageSize=#{page_size}&search=#{term}")
28
+ else
29
+ response = @client.get("/v1/recipients/#{recipient_id}/offlinePayments?page=#{page}&pageSize=#{page_size}&search=#{term}")
30
+ end
31
+
32
+ offline_payments_list_builder(response)
33
+ end
34
+ # rubocop:enable Metrics/ParameterLists
35
+
36
+ def offline_payment_builder(response)
37
+ Utils::ResponseMapper.build(response, OfflinePayment)
38
+ end
39
+
40
+ def offline_payments_list_builder(response)
41
+ Utils::PaginatedArray.from_response(response, OfflinePayment)
42
+ end
43
+ end
44
+ end