activemerchant-payrix 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/release.yml +24 -0
- data/.github/workflows/run-tests.yml +20 -0
- data/.gitignore +6 -0
- data/.rvmrc +1 -0
- data/Gemfile +3 -0
- data/LICENSE +7 -0
- data/Rakefile +40 -0
- data/Readme.md +52 -0
- data/activemerchant-payrix.gemspec +26 -0
- data/lib/activemerchant/billing/gateways/payrix.rb +396 -0
- data/lib/activemerchant/payrix/version.rb +5 -0
- data/lib/activemerchant-payrix.rb +3 -0
- data/test/comm_stub.rb +57 -0
- data/test/fixtures.yml +5 -0
- data/test/remote/gateways/payrix_test.rb +92 -0
- data/test/test_helper.rb +404 -0
- data/test/unit/gateways/payrix_test.rb +158 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b5f27c2329b7dd97eea8fa718529e9cfe446496aec74279f3ded59b760a70d0c
|
4
|
+
data.tar.gz: e18c98fc51a9e46d4da61555ae51913e2c402a9ab17e5f1175d3129aecfa6c34
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aae551c3f73eac29a2537d8bbfc5dd4e78cf79fea954a699de6ad3b6364d95b5e34cd78f38e67e398ce95a4e336fb10ce8fc19095089d2bb85ab38b34c231be6
|
7
|
+
data.tar.gz: 8da97305ef6a13caa2844619446eee91016389ad40b655db10568495b7e05f5fb1f7408bfad0c6cbca718874a5413cdf0ff1af95c3562d21e0bc80424607b64f
|
@@ -0,0 +1,24 @@
|
|
1
|
+
on:
|
2
|
+
push:
|
3
|
+
branches: [main]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
push:
|
7
|
+
name: Push gem to RubyGems.org
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
|
10
|
+
permissions:
|
11
|
+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
12
|
+
contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag
|
13
|
+
|
14
|
+
steps:
|
15
|
+
# Set up
|
16
|
+
- uses: actions/checkout@v4
|
17
|
+
- name: Set up Ruby
|
18
|
+
uses: ruby/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
bundler-cache: true
|
21
|
+
ruby-version: ruby
|
22
|
+
|
23
|
+
# Release
|
24
|
+
- uses: rubygems/release-gem@v1
|
@@ -0,0 +1,20 @@
|
|
1
|
+
on:
|
2
|
+
push:
|
3
|
+
branches: [main]
|
4
|
+
pull_request:
|
5
|
+
branches: [main]
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
test:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@v3
|
13
|
+
- name: Set up Ruby
|
14
|
+
uses: ruby/setup-ruby@359bebbc29cbe6c87da6bc9ea3bc930432750108
|
15
|
+
with:
|
16
|
+
ruby-version: '3.1'
|
17
|
+
- name: Install dependencies
|
18
|
+
run: bundle install
|
19
|
+
- name: Run tests
|
20
|
+
run: bundle exec rake test:units
|
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use @activemerchant-payrix --create
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright 2022 Mark Haussmann
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
require 'activemerchant/payrix/version'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'bundler'
|
6
|
+
Bundler.setup
|
7
|
+
rescue LoadError => e
|
8
|
+
puts "Error loading bundler (#{e.message}): \"gem install bundler\" for bundler support."
|
9
|
+
require 'rubygems'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'rake'
|
13
|
+
require 'rake/testtask'
|
14
|
+
require 'bundler/gem_tasks'
|
15
|
+
|
16
|
+
task :tag_release do
|
17
|
+
system "git tag 'v#{ActiveMerchant::Payrix::VERSION}'"
|
18
|
+
system 'git push --tags'
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Run the unit test suite'
|
22
|
+
task default: 'test:units'
|
23
|
+
task test: 'test:units'
|
24
|
+
|
25
|
+
namespace :test do
|
26
|
+
Rake::TestTask.new(:units) do |t|
|
27
|
+
t.pattern = 'test/unit/**/*_test.rb'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Run all tests that do not require network access'
|
33
|
+
task local: %w[test:units]
|
34
|
+
|
35
|
+
Rake::TestTask.new(:remote) do |t|
|
36
|
+
t.pattern = 'test/remote/**/*_test.rb'
|
37
|
+
t.libs << 'test'
|
38
|
+
t.verbose = true
|
39
|
+
end
|
40
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# activemerchant-payrix
|
2
|
+
|
3
|
+
ActiveMerchant Payrix is an add-on for ActiveMerchant which provides a gateway
|
4
|
+
for [Payrix's](https://www.payrix.com/)
|
5
|
+
[HPP service](https://docs.rest.paymentsapi.io/#5bd000e6-e156-4242-962b-3b93e9ed8f9e).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Before installing the gem you should have a Payrix account ready to use. If not
|
10
|
+
then [Contact Payrix for more info.](https://www.payrix.com/)
|
11
|
+
|
12
|
+
To install simply add the following line to your `Gemfile` and then run
|
13
|
+
`bundle install`:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'activemerchant-payrix'
|
17
|
+
```
|
18
|
+
|
19
|
+
The gateway can be initialised by passing your login details like so:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gateway = ActiveMerchant::Billing::PayrixGateway.new(:login => 'login', :password => 'pass', :business_id => 'num', :service => 'hpp')
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Once you have an initialised gateway, you can use the `setup_purchase` method:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
amount = 1000 # 1000 cents is $10.00 AUD
|
31
|
+
options = { return_url: return_url } # Pass the return url to Payrix
|
32
|
+
|
33
|
+
response = gateway.purchase(amount, options)
|
34
|
+
|
35
|
+
if response.sucess?
|
36
|
+
puts "All OK!"
|
37
|
+
else
|
38
|
+
puts response.message # Output the error message
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
## License
|
43
|
+
|
44
|
+
activemerchant-payrix is distributed under a standard MIT license, see
|
45
|
+
[LICENSE](https://github.com/mhssmnn/activemerchant-payrix/blob/master/LICENSE)
|
46
|
+
for further information.
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
Fork on GitHub and after you’ve committed tested patches, send a pull request.
|
51
|
+
|
52
|
+
To get tests running simply run `bundle install` and then `rake test:units`.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'activemerchant/payrix/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'activemerchant-payrix'
|
7
|
+
s.version = ActiveMerchant::Payrix::VERSION
|
8
|
+
s.authors = ['Mark Haussmann']
|
9
|
+
s.email = ['mark.haussmann@gmail.com']
|
10
|
+
s.homepage = ''
|
11
|
+
s.summary = 'ActiveMerchant Payrix Plugin'
|
12
|
+
s.description = 'An ActiveMerchant plugin that provides a Payrix gateway'
|
13
|
+
|
14
|
+
s.rubyforge_project = 'activemerchant-payrix'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables =
|
19
|
+
`git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
|
22
|
+
s.add_development_dependency('test-unit', '~> 3')
|
23
|
+
s.add_development_dependency('mocha', '~> 2')
|
24
|
+
s.add_development_dependency('rake')
|
25
|
+
s.add_runtime_dependency 'activemerchant', '>= 1.20.0'
|
26
|
+
end
|
@@ -0,0 +1,396 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Billing #:nodoc:
|
3
|
+
class PayrixGateway < Gateway
|
4
|
+
self.test_url = 'https://sandbox.rest.paymentsapi.io'
|
5
|
+
self.live_url = 'https://rest.paymentsapi.io'
|
6
|
+
|
7
|
+
class_attribute :test_auth_url
|
8
|
+
self.test_auth_url = 'https://sandbox.auth.paymentsapi.io/login'
|
9
|
+
|
10
|
+
class_attribute :auth_url
|
11
|
+
self.auth_url = 'https://auth.paymentsapi.io/login'
|
12
|
+
|
13
|
+
self.supported_countries = %w[US AU NZ]
|
14
|
+
self.default_currency = 'USD'
|
15
|
+
self.supported_cardtypes = %i[visa master american_express discover]
|
16
|
+
self.money_format = :dollars
|
17
|
+
|
18
|
+
self.homepage_url = 'https://www.payrix.com/'
|
19
|
+
self.display_name = 'Payrix'
|
20
|
+
|
21
|
+
STANDARD_ERROR_CODE_MAPPING = {
|
22
|
+
'EXPIRED' => STANDARD_ERROR_CODE[:expired_card],
|
23
|
+
'FRAUD_CHECK_DECLINE' => STANDARD_ERROR_CODE[:invalid_cvc],
|
24
|
+
'FRAUD_CHECK_ERROR' => STANDARD_ERROR_CODE[:processing_error],
|
25
|
+
'ERROR' => STANDARD_ERROR_CODE[:processing_error],
|
26
|
+
'BadRequest' => STANDARD_ERROR_CODE[:processing_error],
|
27
|
+
'FunctionNotAllowed' => STANDARD_ERROR_CODE[:unsupported_feature],
|
28
|
+
'InternalServerError' => STANDARD_ERROR_CODE[:processing_error],
|
29
|
+
'InvalidApiToken' => STANDARD_ERROR_CODE[:config_error]
|
30
|
+
}
|
31
|
+
|
32
|
+
TRANSACTION_TYPES = {
|
33
|
+
# Full sale - Funds are fully collected and ready to be settled to your business
|
34
|
+
purchase: 'COMPLETE',
|
35
|
+
# Pre-authorisation only - Funds are reserved on the person's card and will not be collected until you use the Capture function. Pre-auths that aren't captured automatically expire (usually occurs within 2-3 days)
|
36
|
+
authorize: 'PREAUTH',
|
37
|
+
# This allows you to perform a zero-dollar pre-authorisation for the purpose of checking that a particular card is valid and active.
|
38
|
+
# It also assists in meeting card-on-file storage obligations, and customer-initiated-transaction/merchant-initiated-transaction mandates, by allowing you to verify a card using customer authentication before storage, and obtain a reference that can be provided with future recurring transactions on that card to link them to the initial authorisation.
|
39
|
+
verify: 'VERIFY'
|
40
|
+
}
|
41
|
+
|
42
|
+
ENDPOINTS = {
|
43
|
+
hpp: 'hpp/',
|
44
|
+
eddr: 'eddr/',
|
45
|
+
vault: 'vault/',
|
46
|
+
access_token: 'login'
|
47
|
+
}
|
48
|
+
|
49
|
+
TOKEN_STATUS = {
|
50
|
+
waiting: 'WAITING',
|
51
|
+
validated: 'VALIDATED',
|
52
|
+
processed_successful: 'PROCESSED_SUCCESSFUL',
|
53
|
+
processed_rejected: 'PROCESSED_REJECTED',
|
54
|
+
cancelled: 'CANCELLED',
|
55
|
+
error: 'ERROR',
|
56
|
+
fraud_check_decline: 'FRAUD_CHECK_DECLINE',
|
57
|
+
fraud_check_error: 'FRAUD_CHECK_ERROR',
|
58
|
+
expired: 'EXPIRED'
|
59
|
+
}
|
60
|
+
|
61
|
+
TOKEN_STATUS_DESCRIPTIONS = {
|
62
|
+
'WAITING' => 'Token URL not yet accessed',
|
63
|
+
'VALIDATED' => 'Token URL loaded',
|
64
|
+
'PROCESSED_SUCCESSFUL' => 'URL completed successfully',
|
65
|
+
'PROCESSED_REJECTED' => 'URL Opened, not completed and closed',
|
66
|
+
'CANCELLED' => 'URL Completed but cancelled on confirmation page',
|
67
|
+
'ERROR' => 'Internal error received',
|
68
|
+
'FRAUD_CHECK_DECLINE' =>
|
69
|
+
'Payment attempted but declined by Fraud check',
|
70
|
+
'FRAUD_CHECK_ERROR' =>
|
71
|
+
'Payment attempted but Fraud Check error received',
|
72
|
+
'EXPIRED' => 'Token has expired and is no longer useable'
|
73
|
+
}
|
74
|
+
|
75
|
+
TRANSACTION_STATUS = { cleared: 'C', settled: 'S', rejected: 'R' }
|
76
|
+
|
77
|
+
REJECTION_REASONS = {
|
78
|
+
'R1' => 'Insufficient funds',
|
79
|
+
'R2' => 'Invalid bank account number',
|
80
|
+
'R3' => 'Invalid credit card',
|
81
|
+
'R4' => 'Expired credit card',
|
82
|
+
'R5' => 'Invalid transaction',
|
83
|
+
'R6' => 'Transaction declined',
|
84
|
+
'R7' => 'Authority revoked by payer',
|
85
|
+
'R8' => 'Payer deceased',
|
86
|
+
'R9' => 'Invalid bank account',
|
87
|
+
'R10' => 'Bank account closed',
|
88
|
+
'R11' => 'Invalid payer contact details',
|
89
|
+
'R12' => 'Direct debit claim',
|
90
|
+
'R13' => 'Credit card chargeback',
|
91
|
+
'R14' => 'Stolen/pick up card',
|
92
|
+
'R15' => 'Limits exceeded',
|
93
|
+
'R16' => 'Pre-auth expired',
|
94
|
+
'R17' => 'Not processed (payer inactive)',
|
95
|
+
'R18' => 'Fraud protection triggered',
|
96
|
+
'R19' => 'Voided',
|
97
|
+
'R20' => 'Fraud Check / 3-D Secure: Authentication Failed',
|
98
|
+
'R21' => 'Fraud Check / 3-D Secure: Unable to Authenticate',
|
99
|
+
'R22' => 'Fraud Check / 3-D Secure: Transaction Abandoned',
|
100
|
+
'R23' => 'Fraud Check / 3-D Secure: Error Occurred',
|
101
|
+
'RF' => 'Partially refunded',
|
102
|
+
'UR' => 'Under review'
|
103
|
+
}
|
104
|
+
|
105
|
+
def initialize(options = {})
|
106
|
+
requires!(options, :login, :password, :business_id)
|
107
|
+
|
108
|
+
@business_id = options[:business_id]
|
109
|
+
@service = (options[:service]&.to_sym || :hpp)
|
110
|
+
@token = nil
|
111
|
+
|
112
|
+
unless ENDPOINTS.key?(@service)
|
113
|
+
raise ArgumentError, "Unsupported service `#{@service}'."
|
114
|
+
end
|
115
|
+
|
116
|
+
super
|
117
|
+
end
|
118
|
+
|
119
|
+
def setup_purchase(money, options = {})
|
120
|
+
requires!(options, :return_url, :transaction_reference)
|
121
|
+
requires!(options, :unique_reference, :name) if store?
|
122
|
+
|
123
|
+
post = new_post
|
124
|
+
|
125
|
+
add_transaction(post, money, options)
|
126
|
+
add_customer_data(post, options)
|
127
|
+
add_address(post, options)
|
128
|
+
add_template(post, options)
|
129
|
+
add_audit(post, options)
|
130
|
+
add_return_url(post, options)
|
131
|
+
|
132
|
+
MultiResponse.run do |r|
|
133
|
+
r.process { commit :access_token, create_access_token_request }
|
134
|
+
r.process { commit :purchase, post, access_token: access_token(r) }
|
135
|
+
end.primary_response
|
136
|
+
end
|
137
|
+
|
138
|
+
def setup_authorize(money, options = {})
|
139
|
+
requires!(options, :return_url, :transaction_reference)
|
140
|
+
requires!(options, :unique_reference, :name) if store?
|
141
|
+
|
142
|
+
post = new_post
|
143
|
+
|
144
|
+
add_transaction(post, money, options)
|
145
|
+
add_customer_data(post, options)
|
146
|
+
add_address(post, options)
|
147
|
+
add_template(post, options)
|
148
|
+
add_audit(post, options)
|
149
|
+
add_return_url(post, options)
|
150
|
+
|
151
|
+
MultiResponse.run do |r|
|
152
|
+
r.process { commit :access_token, create_access_token_request }
|
153
|
+
r.process { commit :authorize, post, access_token: access_token(r) }
|
154
|
+
end.primary_response
|
155
|
+
end
|
156
|
+
|
157
|
+
def setup_verify(options = {})
|
158
|
+
requires!(options, :return_url, :transaction_reference)
|
159
|
+
requires!(options, :unique_reference, :name) if store?
|
160
|
+
|
161
|
+
post = new_post
|
162
|
+
|
163
|
+
# add_transaction(post, money, options)
|
164
|
+
add_customer_data(post, options)
|
165
|
+
add_address(post, options)
|
166
|
+
add_template(post, options)
|
167
|
+
add_audit(post, options)
|
168
|
+
add_return_url(post, options)
|
169
|
+
|
170
|
+
MultiResponse.run do |r|
|
171
|
+
r.process { commit :access_token, create_access_token_request }
|
172
|
+
r.process { commit :verify, post, access_token: access_token(r) }
|
173
|
+
end.primary_response
|
174
|
+
end
|
175
|
+
|
176
|
+
# Swap token for details about success / failure of transaction
|
177
|
+
def details_for(token_id)
|
178
|
+
payload = { id: token_id }
|
179
|
+
|
180
|
+
MultiResponse.run do |r|
|
181
|
+
r.process { commit :access_token, create_access_token_request }
|
182
|
+
r.process { commit :token, payload, access_token: access_token(r) }
|
183
|
+
end.primary_response
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def new_post
|
189
|
+
{ payer: {}, transaction: {} }
|
190
|
+
end
|
191
|
+
|
192
|
+
def add_transaction(post, money, options)
|
193
|
+
t = post[:transaction]
|
194
|
+
|
195
|
+
t[:reference] = trim(options[:transaction_reference])
|
196
|
+
t[:description] = options[:description] || ''
|
197
|
+
t[:amount] = amount(money)
|
198
|
+
t[:currency_code] = (options[:currency] || currency(money))
|
199
|
+
|
200
|
+
post[:transaction] = t
|
201
|
+
end
|
202
|
+
|
203
|
+
def add_customer_data(post, options)
|
204
|
+
payer = post[:payer]
|
205
|
+
|
206
|
+
payer[:save_payer] = (options[:store] || false)
|
207
|
+
payer[:unique_reference] = trim(options[:customer_reference]) if store?
|
208
|
+
payer[:group_reference] = trim(options[:group_reference]) if store?
|
209
|
+
payer[:family_or_business_name] = options[:name] if store?
|
210
|
+
payer[:email] = options[:email]
|
211
|
+
payer[:phone] = options[:phone]
|
212
|
+
payer[:mobile] = options[:mobile]
|
213
|
+
payer[:date_of_birth] = options[:date_of_birth]
|
214
|
+
|
215
|
+
post[:payer] = payer
|
216
|
+
end
|
217
|
+
|
218
|
+
def add_address(post, options)
|
219
|
+
if (address = (options[:billing_address] || options[:address]))
|
220
|
+
post[:payer].tap do |h|
|
221
|
+
h[:address] = {}
|
222
|
+
h[:address][:line1] = address[:address1] if address[:address1]
|
223
|
+
h[:address][:line2] = address[:address2] if address[:address2]
|
224
|
+
h[:address][:state] = address[:state] if address[:state]
|
225
|
+
h[:address][:country] = address[:country] if address[:country]
|
226
|
+
h[:address][:post_code] = address[:zip] if address[:zip]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def add_template(post, options)
|
232
|
+
post[:template] = (options[:template] || 'Basic')
|
233
|
+
end
|
234
|
+
|
235
|
+
def add_audit(post, options)
|
236
|
+
return unless options[:ip].present?
|
237
|
+
|
238
|
+
post[:audit] = {}
|
239
|
+
post[:audit][:user_i_p] = options[:ip]
|
240
|
+
end
|
241
|
+
|
242
|
+
def add_return_url(post, options)
|
243
|
+
post[:return_url] = options[:return_url]
|
244
|
+
end
|
245
|
+
|
246
|
+
def parse(body)
|
247
|
+
JSON.parse(body).deep_transform_keys { |key| key.underscore.to_sym }
|
248
|
+
end
|
249
|
+
|
250
|
+
def headers(options = {})
|
251
|
+
headers = {}
|
252
|
+
headers['Authorization'] =
|
253
|
+
"Bearer #{options[:access_token]}" if options[:access_token]
|
254
|
+
headers['Content-Type'] = 'application/json'
|
255
|
+
headers
|
256
|
+
end
|
257
|
+
|
258
|
+
def commit(action, parameters, options = {})
|
259
|
+
url =
|
260
|
+
build_url(
|
261
|
+
action,
|
262
|
+
parameters.merge(service: options[:service] || @service)
|
263
|
+
)
|
264
|
+
|
265
|
+
begin
|
266
|
+
response =
|
267
|
+
parse(
|
268
|
+
ssl_request(
|
269
|
+
verb(action),
|
270
|
+
url,
|
271
|
+
post_data(action, parameters),
|
272
|
+
headers(options)
|
273
|
+
)
|
274
|
+
)
|
275
|
+
rescue ResponseError => e
|
276
|
+
# No gateway error message so we throw
|
277
|
+
raise if e.response.body.blank?
|
278
|
+
|
279
|
+
response = parse(e.response.body)
|
280
|
+
end
|
281
|
+
|
282
|
+
if action == :token
|
283
|
+
PayrixResponse.new(
|
284
|
+
token_success_from(response),
|
285
|
+
token_message_from(response),
|
286
|
+
response,
|
287
|
+
error_code: token_error_code_from(response),
|
288
|
+
fraud_review: token_fraud_review_from(response),
|
289
|
+
test: test?
|
290
|
+
)
|
291
|
+
else
|
292
|
+
PayrixResponse.new(
|
293
|
+
success_from(response),
|
294
|
+
message_from(response),
|
295
|
+
response,
|
296
|
+
error_code: error_code_from(response),
|
297
|
+
test: test?
|
298
|
+
)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def create_access_token_request
|
303
|
+
{ username: options[:login], password: options[:password] }
|
304
|
+
end
|
305
|
+
|
306
|
+
def verb(action)
|
307
|
+
return :get if action == :token
|
308
|
+
|
309
|
+
:post
|
310
|
+
end
|
311
|
+
|
312
|
+
def build_url(action, parameters)
|
313
|
+
return test? ? test_auth_url : auth_url if action == :access_token
|
314
|
+
|
315
|
+
base_url = (test? ? test_url : live_url)
|
316
|
+
endpoint = ENDPOINTS[@service]
|
317
|
+
endpoint = parameters[:id] if parameters[:id] && action == :token
|
318
|
+
|
319
|
+
"#{base_url}/businesses/#{@business_id}/services/tokens/#{endpoint}"
|
320
|
+
end
|
321
|
+
|
322
|
+
def success_from(response)
|
323
|
+
!response.key?(:error_code)
|
324
|
+
end
|
325
|
+
|
326
|
+
def message_from(response)
|
327
|
+
success_from(response) ? 'Succeeded' : response[:error_message]
|
328
|
+
end
|
329
|
+
|
330
|
+
def authorization_from(response)
|
331
|
+
nil
|
332
|
+
end
|
333
|
+
|
334
|
+
def error_code_from(response)
|
335
|
+
unless success_from(response)
|
336
|
+
STANDARD_ERROR_CODE_MAPPING[response[:error_code]] ||
|
337
|
+
response[:error_code].to_s.underscore
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def token_error_code_from(response)
|
342
|
+
unless token_success_from(response)
|
343
|
+
STANDARD_ERROR_CODE_MAPPING[response[:status]] || response[:status]
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def token_success_from(response)
|
348
|
+
response[:status] == TOKEN_STATUS[:processed_successful]
|
349
|
+
end
|
350
|
+
|
351
|
+
def token_message_from(response)
|
352
|
+
if response[:transaction].nil?
|
353
|
+
TOKEN_STATUS_DESCRIPTIONS[response[:status_description]]
|
354
|
+
else
|
355
|
+
REJECTION_REASONS[response[:transaction][:sub_status_code]]
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def token_fraud_review_from(response)
|
360
|
+
response[:status] == TOKEN_STATUS[:fraud_check_decline]
|
361
|
+
end
|
362
|
+
|
363
|
+
def access_token(r)
|
364
|
+
r.params.dig('access_token')
|
365
|
+
end
|
366
|
+
|
367
|
+
def post_data(action, parameters = {})
|
368
|
+
return nil if verb(action) == :get
|
369
|
+
|
370
|
+
if TRANSACTION_TYPES[action]
|
371
|
+
parameters[:transaction][:process_type] = TRANSACTION_TYPES[action]
|
372
|
+
end
|
373
|
+
|
374
|
+
parameters.deep_transform_keys { |key| key.to_s.camelize }.to_json
|
375
|
+
end
|
376
|
+
|
377
|
+
def trim(str, length = 100)
|
378
|
+
truncate(str, length)
|
379
|
+
end
|
380
|
+
|
381
|
+
def store?
|
382
|
+
!!@options[:store]
|
383
|
+
end
|
384
|
+
|
385
|
+
class PayrixResponse < Response
|
386
|
+
def token
|
387
|
+
@params['token']
|
388
|
+
end
|
389
|
+
|
390
|
+
def redirect_url
|
391
|
+
@params['redirect_to_url']
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
data/test/comm_stub.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module CommStub
|
2
|
+
class Stub
|
3
|
+
def initialize(gateway, method_to_stub, action)
|
4
|
+
@gateway = gateway
|
5
|
+
@action = action
|
6
|
+
@complete = false
|
7
|
+
@method_to_stub = method_to_stub
|
8
|
+
@check = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def check_request(&block)
|
12
|
+
@check = block
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_with(*responses)
|
17
|
+
@complete = true
|
18
|
+
check = @check
|
19
|
+
singleton_class =
|
20
|
+
(
|
21
|
+
class << @gateway
|
22
|
+
self
|
23
|
+
end
|
24
|
+
)
|
25
|
+
singleton_class.send(:undef_method, @method_to_stub)
|
26
|
+
singleton_class.send(:define_method, @method_to_stub) do |*args|
|
27
|
+
check&.call(*args)
|
28
|
+
(responses.size == 1 ? responses.last : responses.shift)
|
29
|
+
end
|
30
|
+
@action.call
|
31
|
+
end
|
32
|
+
|
33
|
+
def complete?
|
34
|
+
@complete
|
35
|
+
end
|
36
|
+
|
37
|
+
class Complete
|
38
|
+
def complete?
|
39
|
+
true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def last_comm_stub
|
45
|
+
@last_comm_stub ||= Stub::Complete.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def stub_comms(gateway = @gateway, method_to_stub = :ssl_post, &action)
|
49
|
+
assert last_comm_stub.complete?,
|
50
|
+
"Tried to stub communications when there's a stub already in progress."
|
51
|
+
@last_comm_stub = Stub.new(gateway, method_to_stub, action)
|
52
|
+
end
|
53
|
+
|
54
|
+
def teardown
|
55
|
+
assert(last_comm_stub.complete?)
|
56
|
+
end
|
57
|
+
end
|