offsite_payments 2.5.0 → 2.6.0

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