offsite_payments_migs 1.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 70fe4e7f16a7259ddbd7d02438db6489d87f8dc4
4
+ data.tar.gz: 600547c5546a2c67c907d01aff2c0d5a07716f7b
5
+ SHA512:
6
+ metadata.gz: 43d778c0d1a020cb0294d1dafd4b69480d55ea9928051fc69cb165b78ef689a0e7baaf6ef074fd0683b00e6cca8d070f495a1c19ae5b76f8c100f8ba98b2cff6
7
+ data.tar.gz: 820c77803bbceca7e096fc97c9d8e585b41cc00e4a41d7c5311e2f8bf1da64df3ceb265f16596bca4d5ea8f3ab9080a5c3253692294ce8d055d2f3285cdaef52
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2005-2014 Tobias Luetke
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Offsite Payments
2
+
3
+ Offsite Payments is an extraction from the ecommerce system [Shopify](http://www.shopify.com). Shopify's requirements for a simple and unified API to handle dozens of different offsite payment pages (often called hosted payment pages) with very different exposed APIs was the chief principle in designing the library.
4
+
5
+ It was developed for usage in Ruby on Rails web applications and integrates seamlessly
6
+ as a Rails plugin. It should also work as a stand alone Ruby library, but much of the benefit is in the ActionView helpers which are Rails-specific.
7
+
8
+ This gem provides integration for [MiGS](https://en.wikipedia.org/wiki/Mastercard_Internet_Gateway_Service)
9
+ through the Offsite Payments gem.
10
+
11
+ ## Installation
12
+
13
+ ### From Git
14
+
15
+ You can check out the latest source from git:
16
+
17
+ git clone https://github.com/sealink/offsite_payments_migs.git
18
+
19
+ ### From RubyGems
20
+
21
+ Installation from RubyGems:
22
+
23
+ gem install offsite_payments_migs
24
+
25
+ Or, if you're using Bundler, just add the following to your Gemfile:
26
+
27
+ gem 'offsite_payments_migs'
28
+
29
+ ## Misc.
30
+
31
+ - This library is MIT licensed.
@@ -0,0 +1,236 @@
1
+ module OffsitePayments
2
+ module Integrations
3
+ module Migs
4
+ API_VERSION = 1
5
+
6
+ def self.return(query_string, options = {})
7
+ Return.new(query_string, options)
8
+ end
9
+
10
+ class Helper < OffsitePayments::Helper
11
+ def self.base_url
12
+ 'https://migs.mastercard.com.au/vpcpay'
13
+ end
14
+
15
+ def initialize(order, account, options = {})
16
+ @credentials = { login: account, password: options.fetch(:password) }
17
+ @secure_hash = options.fetch(:secure_hash)
18
+ @options = options.merge(order_id: order)
19
+ end
20
+
21
+ # Generates a URL to redirect user to MiGS to process payment
22
+ # Once user is finished MiGS will redirect back to specified URL
23
+ # With a response hash which can be turned into a Response object
24
+ # with purchase_offsite_response
25
+ #
26
+ # ==== Options
27
+ #
28
+ # * <tt>:order_id</tt> -- A reference for tracking the order (REQUIRED)
29
+ # * <tt>:locale</tt> -- Change the language of the redirected page
30
+ # Values are 2 digit locale, e.g. en, es
31
+ # * <tt>:return_url</tt> -- the URL to return to once the payment is complete
32
+ # * <tt>:card_type</tt> -- Providing this skips the card type step.
33
+ # Values are ActiveMerchant formats: e.g. master, visa, american_express, diners_club
34
+ # * <tt>:unique_id</tt> -- Unique id of transaction to find.
35
+ # If not supplied one will be generated.
36
+ def credential_based_url
37
+ cents = @options.fetch(:cents)
38
+ builder = TransactionBuilder.new(@credentials)
39
+ builder.add_invoice(@options)
40
+ builder.add_creditcard_type(@options[:card_type]) if @options[:card_type]
41
+ builder.add_amount(cents)
42
+ builder.add_standard_parameters('pay', @options[:unique_id])
43
+ post = builder.post.merge(
44
+ Locale: @options[:locale] || 'en',
45
+ ReturnURL: @options.fetch(:return_url)
46
+ )
47
+ post[:SecureHash] = SecureHash.calculate(@secure_hash, post)
48
+ post[:SecureHashType] = 'SHA256'
49
+
50
+ self.class.base_url + '?' + post_data(post)
51
+ end
52
+
53
+ private
54
+
55
+ def post_data(post)
56
+ post.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
57
+ end
58
+ end
59
+
60
+ class Notification < OffsitePayments::Notification
61
+ def initialize(params, options = {})
62
+ @params = params
63
+ @response = parse
64
+ @options = options
65
+ end
66
+
67
+ def parse
68
+ @params.map.with_object({}) { |(key, value), hash|
69
+ hash[key.gsub('vpc_', '').to_sym] = value
70
+ }
71
+ end
72
+
73
+ def avs_response_code
74
+ avs_response_code = @response[:AVSResultCode]
75
+ avs_response_code = 'S' if avs_response_code == "Unsupported"
76
+ avs_response_code
77
+ end
78
+
79
+ def cvv_result_code
80
+ cvv_result_code = @response[:CSCResultCode]
81
+ cvv_result_code = 'P' if cvv_result_code == "Unsupported"
82
+ cvv_result_code
83
+ end
84
+
85
+ def success?
86
+ @response[:TxnResponseCode] == '0'
87
+ end
88
+
89
+ def fraud_review?
90
+ ISSUER_RESPONSE_CODES[@response[:AcqResponseCode]] == 'Suspected Fraud'
91
+ end
92
+
93
+ def acknowledge
94
+ # Failures don't include a secure hash, so return directly
95
+ return true unless success?
96
+
97
+ # Check SecureHash only on success (not returned otherwise)
98
+ unless @response[:SecureHash] == expected_secure_hash
99
+ raise SecurityError, "Secure Hash mismatch, response may be tampered with"
100
+ end
101
+
102
+ true
103
+ end
104
+
105
+ def expected_secure_hash
106
+ SecureHash.calculate(@options[:secure_hash], @response)
107
+ end
108
+
109
+ def gross
110
+ @response[:Amount].to_d / 100.0
111
+ end
112
+
113
+ def card_code
114
+ return unless @response.key?(:Card) # Card doesn't appear on failure
115
+ migs_code = @response[:Card]
116
+ CARD_TYPES.detect { |ct|
117
+ ct.migs_code == migs_code
118
+ }.am_code
119
+ end
120
+
121
+ def order_id
122
+ @response[:OrderInfo]
123
+ end
124
+
125
+ def uid
126
+ @response[:MerchTxnRef]
127
+ end
128
+
129
+ def transaction_id
130
+ @response[:TransactionNo]
131
+ end
132
+
133
+ def message # only when error
134
+ @response['Message']
135
+ end
136
+ end
137
+
138
+ def test?
139
+ # TEST prefix defines if login is for test system, see page 37 of:
140
+ # https://anz.com.au/australia/business/merchant/pdf/MIGSProductGuide.pdf
141
+ @options[:login].start_with?('TEST')
142
+ end
143
+
144
+ class SecureHash
145
+ require 'openssl'
146
+
147
+ DIGEST = OpenSSL::Digest.new('sha256')
148
+
149
+ def self.calculate(secure_hash, post)
150
+ post_without_secure_hash = post.reject { |k, _v| [:SecureHash, :SecureHashType].include? k }
151
+ sorted_values = post_without_secure_hash.sort_by(&:to_s).map { |key, value| "vpc_#{key}=#{value}"}
152
+ input = sorted_values.join('&')
153
+ OpenSSL::HMAC.hexdigest(DIGEST, [secure_hash].pack('H*'), input).upcase
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ class CreditCardType
160
+ attr_accessor :am_code, :migs_code, :migs_long_code, :name
161
+ def initialize(am_code, migs_code, migs_long_code, name)
162
+ @am_code = am_code
163
+ @migs_code = migs_code
164
+ @migs_long_code = migs_long_code
165
+ @name = name
166
+ end
167
+ end
168
+
169
+ CARD_TYPES = [
170
+ # The following are 4 different representations of credit card types
171
+ # am_code: The active merchant code
172
+ # migs_code: Used in response for purchase/authorize/status
173
+ # migs_long_code: Used to pre-select card for server_purchase_url
174
+ # name: The nice display name
175
+ %w(american_express AE Amex American\ Express),
176
+ %w(diners_club DC Dinersclub Diners\ Club),
177
+ %w(jcb JC JCB JCB\ Card),
178
+ %w(maestro MS Maestro Maestro\ Card),
179
+ %w(master MC Mastercard MasterCard),
180
+ %w(na PL PrivateLabelCard Private\ Label\ Card),
181
+ %w(visa VC Visa Visa\ Card')
182
+ ].map { |am_code, migs_code, migs_long_code, name|
183
+ CreditCardType.new(am_code, migs_code, migs_long_code, name)
184
+ }
185
+
186
+ class TransactionBuilder
187
+ attr_reader :post
188
+
189
+ def initialize(options)
190
+ @options = options
191
+ @post = {}
192
+ end
193
+
194
+ def add_invoice(options)
195
+ post[:OrderInfo] = options.fetch(:order_id)
196
+ end
197
+
198
+ def add_amount(cents)
199
+ post[:Amount] = cents.to_s
200
+ end
201
+
202
+ def add_creditcard(creditcard)
203
+ post[:CardNum] = creditcard.number
204
+ post[:CardSecurityCode] = creditcard.verification_value if creditcard.verification_value?
205
+ post[:CardExp] = format(creditcard.year) + format(creditcard.month)
206
+ end
207
+
208
+ def add_creditcard_type(card_type)
209
+ post[:Gateway] = 'ssl'
210
+ post[:card] = CARD_TYPES.detect{|ct| ct.am_code == card_type}.migs_long_code
211
+ end
212
+
213
+ def add_advanced_user
214
+ post[:User] = @options[:advanced_login]
215
+ post[:Password] = @options[:advanced_password]
216
+ end
217
+
218
+ def add_standard_parameters(action, unique_id = nil)
219
+ post.merge!(
220
+ :Version => API_VERSION,
221
+ :Merchant => @options[:login],
222
+ :AccessCode => @options[:password],
223
+ :Command => action,
224
+ :MerchTxnRef => unique_id || SecureRandom.hex(16).slice(0, 40)
225
+ )
226
+ end
227
+
228
+ private
229
+
230
+ def format(number)
231
+ sprintf("%.2i", number.to_i)[-2..-1]
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,3 @@
1
+ module OffsitePaymentsMigs
2
+ VERSION = "1.1.0"
3
+ end
@@ -0,0 +1,12 @@
1
+ module OffsitePaymentsMigs
2
+ require 'offsite_payments'
3
+ require_relative 'offsite_payments/integrations/migs'
4
+
5
+ mattr_accessor :mode
6
+ self.mode = :production
7
+
8
+ # A check to see if we're in test mode
9
+ def self.test?
10
+ self.mode == :test
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: offsite_payments_migs
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Stefan Cooper
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: offsite_payments
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: This gem extends the activemerchant offsite_payments gem providing integration
56
+ of MiGS.
57
+ email: stefan.cooper@sealink.com.au
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - lib/offsite_payments/integrations/migs.rb
65
+ - lib/offsite_payments_migs.rb
66
+ - lib/offsite_payments_migs/version.rb
67
+ homepage: https://github.com/sealink/offsite_payments_migs
68
+ licenses:
69
+ - MIT
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.4.5.1
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: MiGS integration for the activemerchant offsite_payments gem.
91
+ test_files: []