transbank-sdk 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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