offsite_payments 2.5.0 → 2.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6dbb49e3c6e9875cd51b4b6c3083adacfb7f52ca
4
- data.tar.gz: 3c221668d16554ddd7a6382a863d4e11df4681a7
3
+ metadata.gz: 73115ca84dcd4112cff7a48a4edf5d3be556c741
4
+ data.tar.gz: 5a30a82fdad39b6b31375fffbde56d51990d5713
5
5
  SHA512:
6
- metadata.gz: 42de1a61d962eebcc882dde55931b8510a962d05dc68b1e9655cf2c0ef56c612c15d79fdb8620ef7a4676cde336702d9b02b9a6bed10c0b61729a5b6b859bc6a
7
- data.tar.gz: 569f2135cc94d0edb84aeab8f8a87fc391a59df2035d70de26fd63b6258092a62201fd3cc0f637f81d8128174bfd4622de7d873dc788411829cdc979d186ec1b
6
+ metadata.gz: bef97be330df21fcd7c37d7a6ebcaa66f7712b773d43913b4ca87e9dfe713ff3bfb0e52da520fb0e57ebc183c4b79c1e0d0ad7f6f4103a934ea7cea2bef7b1e4
7
+ data.tar.gz: 274c019786d06ff21eecfa4316cb683e92b8a26296149be08c0fc1296761a1cf86561273f08424d671e395b163828f8b71350d4f3509ae1d3e9e2ce24f4012e6
@@ -89,7 +89,6 @@ module OffsitePayments #:nodoc:
89
89
 
90
90
  def item_id
91
91
  JSON.parse(params['posData'])['orderId']
92
- rescue JSON::ParserError
93
92
  end
94
93
 
95
94
  def status
@@ -132,14 +131,12 @@ module OffsitePayments #:nodoc:
132
131
  retrieved_json = JSON.parse(@raw).tap { |j| j.delete('currentTime') }
133
132
 
134
133
  posted_json == retrieved_json
135
- rescue JSON::ParserError
136
134
  end
137
135
 
138
136
  private
139
137
  def parse(body)
140
138
  @raw = body
141
139
  @params = JSON.parse(@raw)
142
- rescue JSON::ParserError
143
140
  end
144
141
  end
145
142
 
@@ -125,7 +125,7 @@ module OffsitePayments #:nodoc:
125
125
  return false
126
126
  end
127
127
 
128
- %w(txnid orderid amount currency date time hash fraud payercountry issuercountry txnfee subscriptionid paymenttype cardno).each do |attr|
128
+ %w(txnid orderid currency date time hash fraud payercountry issuercountry txnfee subscriptionid paymenttype cardno).each do |attr|
129
129
  define_method(attr) do
130
130
  params[attr]
131
131
  end
@@ -135,10 +135,6 @@ module OffsitePayments #:nodoc:
135
135
  CURRENCY_CODES.invert[params['currency']].to_s
136
136
  end
137
137
 
138
- def amount
139
- Money.new(params['amount'].to_i, currency)
140
- end
141
-
142
138
  def generate_md5string
143
139
  md5string = String.new
144
140
  for line in @raw.split('&')
@@ -14,7 +14,7 @@ module OffsitePayments #:nodoc:
14
14
 
15
15
  def get_request(resource, params = nil)
16
16
  uri = URI.parse(MOLLIE_API_V1_URI + resource)
17
- uri.query = params.map { |k,v| "#{CGI.escape(k)}=#{CGI.escape(v)}}"}.join('&') if params
17
+ uri.query = params.map { |k,v| "#{CGI.escape(k)}=#{CGI.escape(v)}"}.join('&') if params
18
18
  headers = { "Authorization" => "Bearer #{token}", "Content-Type" => "application/json" }
19
19
  JSON.parse(ssl_get(uri.to_s, headers))
20
20
  end
@@ -29,4 +29,4 @@ module OffsitePayments #:nodoc:
29
29
 
30
30
  end
31
31
  end
32
- end
32
+ end
@@ -2,7 +2,7 @@ module OffsitePayments #:nodoc:
2
2
  module Integrations #:nodoc:
3
3
  module Moneybookers
4
4
  mattr_accessor :production_url
5
- self.production_url = 'https://www.moneybookers.com/app/payment.pl'
5
+ self.production_url = 'https://pay.skrill.com'
6
6
 
7
7
  def self.service_url
8
8
  self.production_url
@@ -1,151 +1,216 @@
1
1
  module OffsitePayments #:nodoc:
2
2
  module Integrations #:nodoc:
3
3
  module Paytm
4
-
4
+ CIPHER = 'AES-128-CBC'
5
+ SALT_ALPHABET = ['a'..'z', 'A'..'Z', '0'..'9'].flat_map { |i| i.to_a }
6
+ SALT_LENGTH = 4
7
+ STATIC_IV = '@@@@&&&&####$$$$'
8
+
9
+ mattr_accessor :test_url
10
+ mattr_accessor :production_url
11
+
12
+ self.test_url = 'https://pguat.paytm.com/oltp-web/processTransaction'
13
+ self.production_url = 'https://secure.paytm.in/oltp-web/processTransaction'
14
+
15
+ def self.service_url
16
+ OffsitePayments.mode == :production ? production_url : test_url
17
+ end
18
+
5
19
  def self.notification(post, options = {})
6
20
  Notification.new(post, options)
7
21
  end
8
22
 
9
- def self.return(query_string, options = {})
10
- Return.new(query_string, options)
23
+ def self.return(post, options = {})
24
+ Return.new(post, options)
25
+ end
26
+
27
+ def self.checksum(hash, salt = nil)
28
+ if salt.nil?
29
+ salt = SALT_LENGTH.times.map { SALT_ALPHABET[SecureRandom.random_number(SALT_ALPHABET.length)] }.join
30
+ end
31
+
32
+ values = hash.sort.to_h.values
33
+ values << salt
34
+ Digest::SHA256.hexdigest(values.join('|')) + salt
11
35
  end
12
36
 
13
- def self.sign(fields, key)
14
- Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, fields.sort.join)).delete("\n")
37
+ def self.encrypt(data, key)
38
+ aes = OpenSSL::Cipher.new(CIPHER)
39
+ aes.encrypt
40
+ aes.key = key
41
+ aes.iv = STATIC_IV
42
+
43
+ encrypted_data = aes.update(data) + aes.final
44
+ Base64.strict_encode64(encrypted_data)
15
45
  end
16
46
 
17
47
  class Helper < OffsitePayments::Helper
18
- CURRENCY_SPECIAL_MINOR_UNITS = {
19
- 'BIF' => 0,
20
- 'BYR' => 0,
21
- 'CLF' => 0,
22
- 'CLP' => 0,
23
- 'CVE' => 0,
24
- 'DJF' => 0,
25
- 'GNF' => 0,
26
- 'HUF' => 0,
27
- 'ISK' => 0,
28
- 'JPY' => 0,
29
- 'KMF' => 0,
30
- 'KRW' => 0,
31
- 'PYG' => 0,
32
- 'RWF' => 0,
33
- 'UGX' => 0,
34
- 'UYI' => 0,
35
- 'VND' => 0,
36
- 'VUV' => 0,
37
- 'XAF' => 0,
38
- 'XOF' => 0,
39
- 'XPF' => 0,
40
- 'BHD' => 3,
41
- 'IQD' => 3,
42
- 'JOD' => 3,
43
- 'KWD' => 3,
44
- 'LYD' => 3,
45
- 'OMR' => 3,
46
- 'TND' => 3,
47
- 'COU' => 4
48
- }
48
+ CHECKSUM_FIELDS = %w(MID ORDER_ID CALLBACK_URL CUST_ID TXN_AMOUNT CHANNEL_ID INDUSTRY_TYPE_ID WEBSITE MERC_UNQ_REF).freeze
49
49
 
50
- def initialize(order, account, options = {})
51
- mid_param = { MID: account }.to_query
52
- @forward_url_stag = "https://pguat.paytm.com/oltp-web/genericPT?#{mid_param}"
53
- @forward_url_prod = "https://secure.paytm.in/oltp-web/genericPT?#{mid_param}"
54
- @key = options[:credential2]
55
- @currency = options[:currency]
50
+ mapping :amount, 'TXN_AMOUNT'
51
+ mapping :account, 'MID'
52
+ mapping :order, 'MERC_UNQ_REF'
53
+
54
+ mapping :customer, :email => 'CUST_ID'
55
+
56
+
57
+ mapping :credential3, 'INDUSTRY_TYPE_ID'
58
+ mapping :credential4, 'WEBSITE'
59
+ mapping :channel_id, 'CHANNEL_ID'
60
+ mapping :return_url, 'CALLBACK_URL'
61
+ mapping :checksum, 'CHECKSUMHASH'
56
62
 
63
+ def initialize(order, account, options = {})
57
64
  super
58
- add_field 'x_test', @test.to_s
59
- end
65
+ @options = options
66
+ @timestamp = Time.now.strftime('%Y%m%d%H%M%S')
60
67
 
61
- def credential_based_url
62
- @test ? @forward_url_stag : @forward_url_prod
68
+ add_field(mappings[:channel_id], "WEB")
69
+ add_field 'ORDER_ID', "#{order}-#{@timestamp.to_i}"
70
+
71
+ self.pg = 'CC'
63
72
  end
64
73
 
65
74
  def form_fields
66
- sign_fields
75
+ sanitize_fields
76
+ @fields.merge(mappings[:checksum] => encrypt_checksum)
67
77
  end
68
78
 
69
- def amount=(amount)
70
- add_field 'x_amount', format_amount(amount, @currency)
79
+ def encrypt_checksum
80
+ payload_items = CHECKSUM_FIELDS.reduce({}) do |items, field|
81
+ items[field] = @fields[field]
82
+ end
83
+ Paytm.encrypt(Paytm.checksum(payload_items), @options[:credential2])
71
84
  end
72
85
 
73
- def sign_fields
74
- @fields.merge!('x_signature' => generate_signature)
86
+ def sanitize_fields
87
+ %w(email phone).each do |field|
88
+ @fields[field].gsub!(/[^a-zA-Z0-9\-_@\/\s.]/, '') if @fields[field]
89
+ end
75
90
  end
91
+ end
76
92
 
77
- def generate_signature
78
- Paytm.sign(@fields, @key)
93
+ class Notification < OffsitePayments::Notification
94
+ PAYTM_RESPONSE_PARAMS = %w(MID BANKTXNID TXNAMOUNT CURRENCY STATUS RESPCODE RESPMSG TXNDATE GATEWAYNAME BANKNAME PAYMENTMODE PROMO_CAMP_ID PROMO_STATUS PROMO_RESPCODE ORDERID TXNID REFUNDAMOUNT REFID MERC_UNQ_REF CUSTID).freeze
95
+
96
+ def initialize(post, options = {})
97
+ super
98
+ @secret_key = options[:credential2]
79
99
  end
80
100
 
81
- mapping :account, 'x_account_id'
82
- mapping :currency, 'x_currency'
83
- mapping :order, 'x_reference'
84
- mapping :description, 'x_description'
85
- mapping :invoice, 'x_invoice'
86
- mapping :credential3, 'x_credential3'
87
- mapping :credential4, 'x_credential4'
101
+ def complete?
102
+ status == 'Completed'
103
+ end
88
104
 
89
- mapping :customer, :email => 'x_customer_email',
90
- :phone => 'x_customer_phone'
105
+ def status
106
+ if transaction_status.casecmp("TXN_SUCCESS").zero?
107
+ 'Completed'
108
+ elsif transaction_status.casecmp("pending").zero?
109
+ 'Pending'
110
+ else
111
+ 'Failed'
112
+ end
113
+ end
91
114
 
92
- mapping :notify_url, 'x_url_callback'
93
- mapping :return_url, 'x_url_complete'
94
- mapping :cancel_return_url, 'x_url_cancel'
115
+ def invoice_ok?(order_id)
116
+ order_id.to_s == invoice.to_s
117
+ end
95
118
 
96
- private
119
+ # Order amount should be equal to gross
120
+ def amount_ok?(order_amount)
121
+ BigDecimal.new(original_gross) == order_amount
122
+ end
97
123
 
98
- def format_amount(amount, currency)
99
- units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2
100
- sprintf("%.#{units}f", amount)
124
+ # Status of transaction return from the Paytm. List of possible values:
125
+ # <tt>TXN_SUCCESS</tt>::
126
+ # <tt>PENDING</tt>::
127
+ # <tt>TXN_FAILURE</tt>::
128
+ def transaction_status
129
+ @params['STATUS']
101
130
  end
102
- end
103
131
 
104
- class Notification < OffsitePayments::Notification
105
- def initialize(post, options = {})
106
- super
107
- @key = options[:credential2]
132
+ # ID of this transaction (Paytm transaction id)
133
+ def transaction_id
134
+ @params['TXNID']
108
135
  end
109
136
 
110
- def acknowledge(authcode = nil)
111
- signature = @params['x_signature']
112
- signature == generate_signature ? true : false
137
+ # Mode of Payment
138
+ #
139
+ # 'CC' for credit-card
140
+ # 'NB' for net-banking
141
+ # 'PPI' for wallet
142
+ def type
143
+ @params['PAYMENTMODE']
144
+ end
145
+
146
+ # What currency have we been dealing with
147
+ def currency
148
+ @params['CURRENCY']
113
149
  end
114
150
 
115
151
  def item_id
116
- @params['x_reference']
152
+ @params['MERC_UNQ_REF']
117
153
  end
118
154
 
119
- def currency
120
- @params['x_currency']
155
+ # This is the invoice which you passed to Paytm
156
+ def invoice
157
+ @params['MERC_UNQ_REF']
121
158
  end
122
159
 
123
- def gross
124
- @params['x_amount']
160
+ # Merchant Id provided by the Paytm
161
+ def account
162
+ @params['MID']
125
163
  end
126
164
 
127
- def transaction_id
128
- @params['x_gateway_reference']
165
+ # original amount send by merchant
166
+ def original_gross
167
+ @params['TXNAMOUNT']
129
168
  end
130
169
 
131
- def status
132
- result = @params['x_result']
133
- result && result.capitalize
170
+ def gross
171
+ parse_and_round_gross_amount(@params['TXNAMOUNT'])
134
172
  end
135
173
 
136
174
  def message
137
- @params['x_message']
175
+ @params['RESPMSG']
176
+ end
177
+
178
+ def checksum
179
+ @params['CHECKSUMHASH']
138
180
  end
139
181
 
140
- def test?
141
- @params['x_test'] == 'true'
182
+ def acknowledge
183
+ checksum_ok?
184
+ end
185
+
186
+ def checksum_ok?
187
+ normalized_data = checksum.delete("\n").tr(' ', '+')
188
+ encrypted_data = Base64.strict_decode64(normalized_data)
189
+
190
+ aes = OpenSSL::Cipher::Cipher.new(CIPHER)
191
+ aes.decrypt
192
+ aes.key = @secret_key
193
+ aes.iv = STATIC_IV
194
+ received_checksum = aes.update(encrypted_data) + aes.final
195
+
196
+ salt = received_checksum[-SALT_LENGTH..-1]
197
+ expected_params = @params.keep_if { |k| PAYTM_RESPONSE_PARAMS.include?(k) }.sort.to_h
198
+ expected_checksum = Paytm.checksum(expected_params, salt)
199
+
200
+ if received_checksum == expected_checksum
201
+ @message = @params['RESPMSG']
202
+ @params['RESPCODE'] == '01'
203
+ else
204
+ @message = 'Return checksum not matching the data provided'
205
+ false
206
+ end
142
207
  end
143
208
 
144
209
  private
145
210
 
146
- def generate_signature
147
- signature_params = @params.select { |k| k.start_with? 'x_' }.reject { |k| k == 'x_signature' }
148
- Paytm.sign(signature_params, @key)
211
+ def parse_and_round_gross_amount(amount)
212
+ rounded_amount = (amount.to_f * 100.0).round
213
+ sprintf('%.2f', rounded_amount / 100.00)
149
214
  end
150
215
  end
151
216
 
@@ -155,8 +220,20 @@ module OffsitePayments #:nodoc:
155
220
  @notification = Notification.new(query_string, options)
156
221
  end
157
222
 
223
+ def transaction_id
224
+ @notification.transaction_id
225
+ end
226
+
227
+ def status(order_id, order_amount)
228
+ if @notification.invoice_ok?(order_id) && @notification.amount_ok?(BigDecimal.new(order_amount))
229
+ @notification.status
230
+ else
231
+ 'Mismatch'
232
+ end
233
+ end
234
+
158
235
  def success?
159
- @notification.acknowledge
236
+ status(@params['MERC_UNQ_REF'], @params['TXNAMOUNT']) == 'Completed'
160
237
  end
161
238
 
162
239
  def message
@@ -165,4 +242,4 @@ module OffsitePayments #:nodoc:
165
242
  end
166
243
  end
167
244
  end
168
- end
245
+ end
@@ -127,6 +127,8 @@ module OffsitePayments #:nodoc:
127
127
  end
128
128
 
129
129
  def form_fields
130
+ fields.delete('locale')
131
+
130
132
  map_billing_address_to_shipping_address unless @shipping_address_set
131
133
 
132
134
  fields['DeliveryFirstnames'] ||= fields['BillingFirstnames']
@@ -30,11 +30,10 @@ module OffsitePayments #:nodoc:
30
30
  (gross.to_f * 100.0).round
31
31
  end
32
32
 
33
- # This combines the gross and currency and returns a proper Money object.
34
- # this requires the money library located at http://rubymoney.github.io/money/
35
33
  def amount
36
- return Money.new(gross_cents, currency) rescue ArgumentError
37
- return Money.new(gross_cents) # maybe you have an own money object which doesn't take a currency?
34
+ amount = gross ? gross.to_d : 0
35
+ return Money.from_amount(amount, currency) rescue ArgumentError
36
+ return Money.from_amount(amount) # maybe you have an own money object which doesn't take a currency?
38
37
  end
39
38
 
40
39
  # reset the notification.
@@ -1,3 +1,3 @@
1
1
  module OffsitePayments
2
- VERSION = "2.5.0"
2
+ VERSION = "2.6.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: offsite_payments
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Luetke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-17 00:00:00.000000000 Z
11
+ date: 2017-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 3.2.14
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 6.x
22
+ version: '5.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 3.2.14
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: 6.x
32
+ version: '5.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: i18n
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -115,7 +115,7 @@ dependencies:
115
115
  version: 3.2.20
116
116
  - - "<"
117
117
  - !ruby/object:Gem::Version
118
- version: 6.x
118
+ version: '5.2'
119
119
  type: :runtime
120
120
  prerelease: false
121
121
  version_requirements: !ruby/object:Gem::Requirement
@@ -125,7 +125,7 @@ dependencies:
125
125
  version: 3.2.20
126
126
  - - "<"
127
127
  - !ruby/object:Gem::Version
128
- version: 6.x
128
+ version: '5.2'
129
129
  - !ruby/object:Gem::Dependency
130
130
  name: rake
131
131
  requirement: !ruby/object:Gem::Requirement