transbank-sdk 1.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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +8 -0
  4. data/CHANGELOG.md +13 -0
  5. data/Dockerfile +6 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE.md +11 -0
  8. data/Makefile +24 -0
  9. data/README.md +87 -0
  10. data/Rakefile +10 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/docker-compose.yml +20 -0
  14. data/lib/transbank/sdk.rb +23 -0
  15. data/lib/transbank/sdk/onepay/base.rb +115 -0
  16. data/lib/transbank/sdk/onepay/errors/errors.rb +17 -0
  17. data/lib/transbank/sdk/onepay/errors/integration_type_error.rb +8 -0
  18. data/lib/transbank/sdk/onepay/errors/invalid_options_error.rb +8 -0
  19. data/lib/transbank/sdk/onepay/errors/item_error.rb +8 -0
  20. data/lib/transbank/sdk/onepay/errors/refund_create_error.rb +8 -0
  21. data/lib/transbank/sdk/onepay/errors/response_error.rb +8 -0
  22. data/lib/transbank/sdk/onepay/errors/shopping_cart_error.rb +8 -0
  23. data/lib/transbank/sdk/onepay/errors/signature_error.rb +8 -0
  24. data/lib/transbank/sdk/onepay/errors/transaction_commit_error.rb +8 -0
  25. data/lib/transbank/sdk/onepay/errors/transaction_create_error.rb +8 -0
  26. data/lib/transbank/sdk/onepay/errors/transbank_error.rb +9 -0
  27. data/lib/transbank/sdk/onepay/models/channels.rb +15 -0
  28. data/lib/transbank/sdk/onepay/models/item.rb +103 -0
  29. data/lib/transbank/sdk/onepay/models/models.rb +10 -0
  30. data/lib/transbank/sdk/onepay/models/refund.rb +51 -0
  31. data/lib/transbank/sdk/onepay/models/shopping_cart.rb +65 -0
  32. data/lib/transbank/sdk/onepay/models/transaction.rb +141 -0
  33. data/lib/transbank/sdk/onepay/requests/refund_create_request.rb +45 -0
  34. data/lib/transbank/sdk/onepay/requests/request.rb +18 -0
  35. data/lib/transbank/sdk/onepay/requests/requests.rb +9 -0
  36. data/lib/transbank/sdk/onepay/requests/transaction_commit_request.rb +48 -0
  37. data/lib/transbank/sdk/onepay/requests/transaction_create_request.rb +80 -0
  38. data/lib/transbank/sdk/onepay/responses/refund_create_response.rb +24 -0
  39. data/lib/transbank/sdk/onepay/responses/response.rb +18 -0
  40. data/lib/transbank/sdk/onepay/responses/responses.rb +9 -0
  41. data/lib/transbank/sdk/onepay/responses/transaction_commit_response.rb +39 -0
  42. data/lib/transbank/sdk/onepay/responses/transaction_create_response.rb +32 -0
  43. data/lib/transbank/sdk/onepay/utils/json_utils.rb +73 -0
  44. data/lib/transbank/sdk/onepay/utils/net_helper.rb +38 -0
  45. data/lib/transbank/sdk/onepay/utils/request_builder.rb +88 -0
  46. data/lib/transbank/sdk/onepay/utils/signature_utils.rb +49 -0
  47. data/lib/transbank/sdk/onepay/utils/utils.rb +9 -0
  48. data/lib/transbank/sdk/version.rb +5 -0
  49. data/sdk_test.sh +2 -0
  50. data/transbank-sdk.gemspec +33 -0
  51. metadata +220 -0
@@ -0,0 +1,8 @@
1
+ module Transbank
2
+ module Onepay
3
+ module Errors
4
+ class ResponseError < TransbankError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Transbank
2
+ module Onepay
3
+ module Errors
4
+ class ShoppingCartError < TransbankError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Transbank
2
+ module Onepay
3
+ module Errors
4
+ class SignatureError < TransbankError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Transbank
2
+ module Onepay
3
+ module Errors
4
+ class TransactionCommitError < TransbankError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Transbank
2
+ module Onepay
3
+ module Errors
4
+ class TransactionCreateError < TransbankError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Transbank
2
+ module Onepay
3
+ module Errors
4
+ class TransbankError < StandardError
5
+ end
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,15 @@
1
+ module Transbank
2
+ module Onepay
3
+ class Channel
4
+ ## Contains static values for all possible channel values
5
+
6
+ WEB = 'WEB'.freeze
7
+ MOBILE = 'MOBILE'.freeze
8
+ APP = 'APP'.freeze
9
+
10
+ def self.values
11
+ constants.map { |const| const_get const }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,103 @@
1
+ module Transbank
2
+ module Onepay
3
+ class Item
4
+ include Utils::JSONUtils
5
+ # An Item to be purchased by the user, and to be added to a [ShoppingCart]
6
+
7
+ # @return [String] An item's description
8
+ attr_reader :description
9
+
10
+ # @return quantity [Integer] How many of units of [Item]
11
+ attr_reader :quantity
12
+
13
+ # @return amount [Integer] The value of each unit of [Item]
14
+ attr_reader :amount
15
+
16
+ # @return additional_data [String] A string with whatever additional data the
17
+ # Merchant might want to add
18
+ attr_reader :additional_data
19
+
20
+ # @return expire [Integer] Expiry for the Item
21
+ attr_reader :expire
22
+ # @param [Hash] opts options Hash
23
+ # @param description [String] The item's description
24
+ # @param quantity [Integer] How many of units of [Item]
25
+ # @param amount [Integer] The value of each unit of [Item]
26
+ # @param additional_data [String] A string with whatever additional data the
27
+ # Merchant might want to add
28
+ # @param expire [Integer] Expiry for the Item
29
+ # @raise [ItemError] when opts is not a [Hash]
30
+ def initialize(opts = {})
31
+ raise Errors::ItemError, 'Item must be a Hash' unless opts.is_a? Hash
32
+ opts = transform_hash_keys opts
33
+ self.description = opts.fetch(:description)
34
+ self.quantity = opts.fetch(:quantity)
35
+ self.amount = opts.fetch(:amount)
36
+ self.additional_data = opts.fetch(:additional_data, nil)
37
+ self.expire = opts.fetch(:expire, nil)
38
+ end
39
+
40
+ # @param description [String] An item's description
41
+ # @raise [ItemError] when description is null
42
+ def description=(description)
43
+ raise Errors::ItemError, "Description cannot be null" if description.nil?
44
+ @description = description
45
+ end
46
+
47
+ # @param quantity [Integer] How many of units of [Item]
48
+ # @raise [ItemError] when given quantity is nil or less than zero.
49
+ def quantity=(quantity)
50
+ raise Errors::ItemError, "Quantity cannot be null" if quantity.nil?
51
+ if quantity < 0
52
+ raise Errors::ItemError, "Quantity cannot be less than zero"
53
+ end
54
+ @quantity = quantity
55
+ end
56
+
57
+ # @param amount [Integer] The value of each unit of [Item]
58
+ # @raise [ItemError] when amount cannot be null.
59
+ # @raise [ArgumentError] when amount is not an Integer.
60
+ def amount=(amount)
61
+ raise Errors::ItemError, "Amount cannot be null" if amount.nil?
62
+ if amount != Integer(amount)
63
+ raise ArgumentError, "Amount is not an Integer"
64
+ end
65
+ @amount = amount
66
+ end
67
+
68
+ # @param additional_data [String] A string with whatever additional data the
69
+ # Merchant might want to add
70
+ def additional_data=(additional_data)
71
+ additional_data = '' if additional_data.nil?
72
+ @additional_data = additional_data
73
+ end
74
+
75
+ # @param expire [Integer] Expiry for the Item
76
+ def expire=(expire)
77
+ expire = expire || 0
78
+ @expire = expire
79
+ end
80
+
81
+ # Return the total amount to pay for the Item, that is, amount * quantity
82
+ # @return [Numeric] the total amount to pay for the [Item]
83
+ def total
84
+ self.quantity * self.amount
85
+ end
86
+
87
+ # Override == to allow comparison between [Item]s
88
+ # @return [boolean] true if equal, false otherwise
89
+ def ==(another_item)
90
+ self.description == another_item.description &&
91
+ self.quantity == another_item.quantity &&
92
+ self.amount == another_item.amount &&
93
+ self.additional_data == another_item.additional_data &&
94
+ self.expire == another_item.expire
95
+ end
96
+
97
+ # Alias for #==
98
+ def eql?(another_item)
99
+ self.==(another_item)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,10 @@
1
+ require 'transbank/sdk/onepay/models/item'
2
+ require 'transbank/sdk/onepay/models/channels'
3
+ require 'transbank/sdk/onepay/models/refund'
4
+ require 'transbank/sdk/onepay/models/shopping_cart'
5
+ require 'transbank/sdk/onepay/models/transaction'
6
+
7
+ module Transbank
8
+ module Onepay
9
+ end
10
+ end
@@ -0,0 +1,51 @@
1
+ module Transbank
2
+ module Onepay
3
+ class Refund
4
+ extend Utils::NetHelper, Utils::RequestBuilder
5
+ # Manages Refunds
6
+ REFUND_TRANSACTION = 'nullifytransaction'.freeze
7
+ TRANSACTION_BASE_PATH = '/ewallet-plugin-api-services/services/transactionservice/'.freeze
8
+
9
+ class << self
10
+ # Create a request for a Refund
11
+ # @param amount [Integer] Amount to be refunded. Must be the full amount of the
12
+ # [Transaction] to be refunded
13
+ # @param occ [String] Merchant purchase order
14
+ # @param external_unique_number [String] Unique identifier (per Merchant) of the [Transaction] that
15
+ # will be refunded
16
+ # @param authorization_code [String] Authorization code. This is given when the [Transaction]
17
+ # is successfully committed (with the #commit method of [Transaction])
18
+ # @param options[Hash, nil] an optional Hash with configuration overrides
19
+ # @raise [RefundCreateError] if the response is nil or has no response code
20
+ # @raise [RefundCreateError] if response from the service is not equal to 'OK'
21
+ def create(amount:, occ:, external_unique_number:, authorization_code:, options: nil)
22
+ refund_request = refund_transaction(refund_amount: amount,
23
+ occ: occ,
24
+ external_unique_number: external_unique_number,
25
+ authorization_code: authorization_code,
26
+ options: options)
27
+ response = http_post(refund_path, refund_request.to_h)
28
+
29
+ if response.nil? || !response['responseCode']
30
+ raise Errors::RefundCreateError, 'Could not obtain a response from the service.'
31
+ end
32
+
33
+ refund_create_response = RefundCreateResponse.new(response)
34
+
35
+ unless refund_create_response.response_ok?
36
+ raise Errors::RefundCreateError, refund_create_response.full_description
37
+ end
38
+
39
+ refund_create_response
40
+ end
41
+
42
+ # Return the string url to POST to
43
+ # @return [String] the url to POST to
44
+ def refund_path
45
+ Base.current_integration_type_url + TRANSACTION_BASE_PATH + REFUND_TRANSACTION
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,65 @@
1
+ module Transbank
2
+ module Onepay
3
+ # Represents a Shopping Cart, which contains [Item]s that the user wants to buy
4
+ class ShoppingCart
5
+ include Utils::JSONUtils
6
+
7
+ # @return [Array<Item>] An [Array<Item>] with the [ShoppingCart] contents
8
+ attr_reader :items
9
+
10
+ # @param items [Array, nil] an array of Hashes that can be converted to [Item]
11
+ # if nil, an empty shopping cart is created
12
+ def initialize(items = [])
13
+ # An [Array<Item>] with the [ShoppingCart] contents
14
+ @items = []
15
+ return if items.nil? || items.empty?
16
+
17
+ items.each do |it|
18
+ it = transform_hash_keys it
19
+ item = Item.new it
20
+ self << item
21
+ end
22
+ end
23
+
24
+ # @param item [Item] an instance of [Item]
25
+ # @return [boolean] return true if item is successfully added
26
+ def add(item)
27
+ new_total = total + item.total
28
+ if new_total < 0
29
+ raise Errors::ShoppingCartError, "Total amount cannot be less than zero."
30
+ end
31
+ @items << item
32
+ end
33
+
34
+ # Alias for #add
35
+ def << item
36
+ add item
37
+ end
38
+
39
+ # Remove an [Item] from self
40
+ # @raise [ShoppingCartError] if item is not found
41
+ def remove(item)
42
+ if @items.delete(item).nil?
43
+ raise Errors::ShoppingCartError, "Item not found"
44
+ end
45
+ end
46
+
47
+ # Clear the cart, setting @items to []
48
+ def remove_all
49
+ @items = []
50
+ end
51
+
52
+ # @return [Integer] The amount in CLP of the [Item]s included in the [ShoppingCart]
53
+ def total
54
+ # Array#sum is Ruby 2.4+
55
+ @items.reduce(0) { |total, item| total + item.total }
56
+ end
57
+
58
+ # Sum the quantity of items in the cart
59
+ def items_quantity
60
+ # Array#sum is Ruby 2.4+
61
+ @items.reduce(0) { |total, item| total + item.quantity }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,141 @@
1
+ module Transbank
2
+ module Onepay
3
+ ## Class Transaction
4
+ # This class creates or commits a Transaction (that is, a purchase)
5
+ class Transaction
6
+ extend Utils::NetHelper, Utils::RequestBuilder
7
+
8
+ SEND_TRANSACTION = 'sendtransaction'.freeze
9
+ COMMIT_TRANSACTION = 'gettransactionnumber'.freeze
10
+ TRANSACTION_BASE_PATH = '/ewallet-plugin-api-services/services/transactionservice/'.freeze
11
+
12
+ class << self
13
+ # Create a [Transaction], initiating the purchase process.
14
+ # @param shopping_cart [ShoppingCart] contains the [Item]s to be purchased
15
+ # @param channel [String] The channel that the transaction is going to be done through. Valid values are contained on the [Transbank::Onepay::Channel] class
16
+ # @param external_unique_number [String] a unique value (per Merchant, not global) that is used to identify a Transaction
17
+ # @param options[Hash, nil] an optional Hash with configuration overrides
18
+ # @return [TransactionCreateResponse] the response to your request.
19
+ # Includes data that you will need to #commit your [Transaction]
20
+ # @raise [ShoppingCartError] if shopping cart is nil or empty
21
+ # @raise [TransactionCreateError] if channel is not valid
22
+ # @raise [TransactionCreateError] if no response is gotten, or responseCode of the response is not 'OK'
23
+ def create(shopping_cart:, channel: nil, external_unique_number: nil,
24
+ options: nil)
25
+ if is_options_hash?(channel)
26
+ options = channel
27
+ channel = nil
28
+ end
29
+
30
+ if is_options_hash?(external_unique_number)
31
+ options = external_unique_number
32
+ external_unique_number = nil
33
+ end
34
+
35
+ validate_channel!(channel)
36
+ validate_shopping_cart!(shopping_cart)
37
+
38
+ options = complete_options(options)
39
+ create_request = create_transaction(shopping_cart: shopping_cart,
40
+ channel: channel,
41
+ external_unique_number: external_unique_number,
42
+ options: options)
43
+ response = http_post(transaction_create_path, create_request.to_h)
44
+ validate_create_response!(response)
45
+ transaction_create_response = TransactionCreateResponse.new response
46
+ signature_is_valid = transaction_create_response.valid_signature?(options.fetch(:shared_secret))
47
+ unless signature_is_valid
48
+ raise Errors::SignatureError, "The response's signature is not valid."
49
+ end
50
+ transaction_create_response
51
+ end
52
+
53
+ # Commit a [Transaction]. It is MANDATORY for this to be done, and you have
54
+ # 30 seconds to do so, otherwise the [Transaction] is automatically REVERSED
55
+ # @param occ [String] Merchant purchase order
56
+ # @param external_unique_number [String] a unique value (per Merchant, not global) that is used to identify a Transaction
57
+ # @param options[Hash, nil] an optional Hash with configuration overrides
58
+ # @return [TransactionCommitResponse] The response to your commit request.
59
+ # @raise [TransactionCommitError] if response is nil or responseCode of the response is not 'OK'
60
+ def commit(occ:, external_unique_number:, options: nil)
61
+ options = complete_options(options)
62
+ commit_request = commit_transaction(occ: occ,
63
+ external_unique_number: external_unique_number,
64
+ options: options)
65
+ response = http_post(transaction_commit_path, commit_request.to_h)
66
+ validate_commit_response!(response)
67
+ transaction_commit_response = TransactionCommitResponse.new(response)
68
+ signature_is_valid = transaction_commit_response.valid_signature?(options.fetch(:shared_secret))
69
+ unless signature_is_valid
70
+ raise Errors::SignatureError, "The response's signature is not valid."
71
+ end
72
+ transaction_commit_response
73
+ end
74
+
75
+ private
76
+
77
+ def is_options_hash?(hash)
78
+ return false unless hash.respond_to? :keys
79
+ # Intersection of the two arrays
80
+ ([:api_key, :shared_secret] & hash.keys).any?
81
+ end
82
+
83
+ def validate_channel!(channel)
84
+ if channel_is_app?(channel) && Base::app_scheme.nil?
85
+ raise Errors::TransactionCreateError, 'You need to set an app_scheme if you want to use the APP channel'
86
+ end
87
+
88
+ if channel_is_mobile?(channel) && Base::callback_url.nil?
89
+ raise Errors::TransactionCreateError, 'You need to set a valid callback if you want to use the MOBILE channel'
90
+ end
91
+ end
92
+
93
+ def validate_shopping_cart!(shopping_cart)
94
+ if shopping_cart.items.nil? || shopping_cart.items.empty?
95
+ raise Errors::ShoppingCartError, 'Shopping cart is null or empty.'
96
+ end
97
+ end
98
+
99
+ def validate_commit_response!(response)
100
+ unless response
101
+ raise Errors::TransactionCommitError, 'Could not obtain a response from the service.'
102
+ end
103
+
104
+ unless response.fetch('responseCode') == 'OK'
105
+ msg = "#{response.fetch('responseCode')} : #{response.fetch('description')}"
106
+ raise Errors::TransactionCommitError, msg
107
+ end
108
+ response
109
+ end
110
+
111
+ def validate_create_response!(response)
112
+ unless response
113
+ raise Errors::TransactionCreateError, 'Could not obtain a response from the service.'
114
+ end
115
+
116
+ unless response.fetch('responseCode') == 'OK'
117
+ msg = "#{response.fetch('responseCode')} : #{response['description']}"
118
+ raise Errors::TransactionCreateError, msg
119
+ end
120
+ response
121
+ end
122
+
123
+ def channel_is_app?(channel)
124
+ channel && channel == Transbank::Onepay::Channel::APP
125
+ end
126
+
127
+ def channel_is_mobile?(channel)
128
+ channel && channel == Transbank::Onepay::Channel::MOBILE
129
+ end
130
+
131
+ def transaction_create_path
132
+ Base.current_integration_type_url + TRANSACTION_BASE_PATH + SEND_TRANSACTION
133
+ end
134
+
135
+ def transaction_commit_path
136
+ Base.current_integration_type_url + TRANSACTION_BASE_PATH + COMMIT_TRANSACTION
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end