workarea-authorize_cim 2.1.1
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 +7 -0
- data/.editorconfig +20 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.gitignore +13 -0
- data/.rails-rubocop.yml +130 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +90 -0
- data/CODE_OF_CONDUCT.md +3 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +12 -0
- data/LICENSE +52 -0
- data/LICENSE.md +3 -0
- data/README.md +40 -0
- data/Rakefile +50 -0
- data/app/errors/workarea/payment/create_profile_error.rb +19 -0
- data/app/models/workarea/payment.decorator +14 -0
- data/app/models/workarea/payment/authorize/credit_card.decorator +61 -0
- data/app/models/workarea/payment/capture/credit_card.decorator +38 -0
- data/app/models/workarea/payment/profile.decorator +169 -0
- data/app/models/workarea/payment/purchase/credit_card.decorator +61 -0
- data/app/models/workarea/payment/refund.decorator +19 -0
- data/app/models/workarea/payment/refund/credit_card.decorator +39 -0
- data/app/models/workarea/payment/store_credit_card.decorator +122 -0
- data/app/models/workarea/payment/tender/credit_card.decorator +25 -0
- data/app/models/workarea/payment/transaction.decorator +16 -0
- data/bin/rails +17 -0
- data/bin/rake +17 -0
- data/bin/rspec +17 -0
- data/bin/rubocop +17 -0
- data/config/initializers/configuration.rb +3 -0
- data/lib/active_merchant/billing/bogus_authorize_net_cim_gateway.rb +99 -0
- data/lib/workarea/authorize_cim.rb +47 -0
- data/lib/workarea/authorize_cim/engine.rb +10 -0
- data/lib/workarea/authorize_cim/error.rb +12 -0
- data/lib/workarea/authorize_cim/version.rb +7 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/config/manifest.js +4 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/jobs/application_job.rb +2 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +34 -0
- data/test/dummy/bin/update +29 -0
- data/test/dummy/config.ru +5 -0
- data/test/dummy/config/application.rb +20 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/cable.yml +9 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +56 -0
- data/test/dummy/config/environments/production.rb +86 -0
- data/test/dummy/config/environments/test.rb +43 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/new_framework_defaults.rb +21 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/workarea.rb +5 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/puma.rb +47 -0
- data/test/dummy/config/routes.rb +5 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/db/seeds.rb +3 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/test/dummy/public/apple-touch-icon.png +0 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/factories/workarea/factories/authorize_cim_factory.rb +14 -0
- data/test/models/workarea/payment/authorize/credit_card_test.decorator +17 -0
- data/test/models/workarea/payment/authorize_cim_integration_test.rb +137 -0
- data/test/models/workarea/payment/capture/credit_card_test.decorator +80 -0
- data/test/models/workarea/payment/credit_card_integration_test.decorator +30 -0
- data/test/models/workarea/payment/profile_test.rb +60 -0
- data/test/models/workarea/payment/purchase/credit_card_test.decorator +17 -0
- data/test/models/workarea/payment/refund/credit_card_test.decorator +95 -0
- data/test/models/workarea/payment/refund_test.decorator +14 -0
- data/test/models/workarea/payment/store_credit_card_test.decorator +7 -0
- data/test/services/workarea/cancel_order_test.decorator +55 -0
- data/test/support/workarea/authorize_cim_gateway_duplicate_window_patch.rb +14 -0
- data/test/support/workarea/authorize_cim_gateway_vcr_config.rb +22 -0
- data/test/support/workarea/workarea_3_2_backports.rb +57 -0
- data/test/system/workarea/storefront/orders_system_test.decorator +28 -0
- data/test/test_helper.rb +17 -0
- data/test/vcr_cassettes/authorize_net_cim/auth_capture.yml +435 -0
- data/test/vcr_cassettes/authorize_net_cim/auth_void.yml +436 -0
- data/test/vcr_cassettes/authorize_net_cim/purchase_void.yml +436 -0
- data/test/vcr_cassettes/authorize_net_cim/store_auth.yml +371 -0
- data/test/vcr_cassettes/authorize_net_cim/store_purchase.yml +371 -0
- data/test/vcr_cassettes/credit_card/auth_capture.yml +438 -0
- data/test/vcr_cassettes/credit_card/auth_void.yml +439 -0
- data/test/vcr_cassettes/credit_card/purchase_void.yml +439 -0
- data/test/vcr_cassettes/credit_card/store_auth.yml +374 -0
- data/test/vcr_cassettes/credit_card/store_purchase.yml +374 -0
- data/test/workers/workarea/send_refund_email_test.decorator +70 -0
- data/workarea-authorize_cim.gemspec +27 -0
- metadata +187 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
begin
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
rescue LoadError
|
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
require "rdoc/task"
|
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
10
|
+
rdoc.rdoc_dir = "rdoc"
|
|
11
|
+
rdoc.title = "Category Overview Content Block"
|
|
12
|
+
rdoc.options << "--line-numbers"
|
|
13
|
+
rdoc.rdoc_files.include("README.md")
|
|
14
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
|
|
18
|
+
load 'rails/tasks/engine.rake'
|
|
19
|
+
load 'rails/tasks/statistics.rake'
|
|
20
|
+
load "workarea/changelog.rake"
|
|
21
|
+
|
|
22
|
+
require 'rake/testtask'
|
|
23
|
+
Rake::TestTask.new(:test) do |t|
|
|
24
|
+
t.libs << 'lib'
|
|
25
|
+
t.libs << 'test'
|
|
26
|
+
t.pattern = 'test/**/*_test.rb'
|
|
27
|
+
t.verbose = false
|
|
28
|
+
end
|
|
29
|
+
task default: :test
|
|
30
|
+
|
|
31
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
|
32
|
+
require 'workarea/authorize_cim/version'
|
|
33
|
+
|
|
34
|
+
desc "Release version #{Workarea::AuthorizeCim::VERSION} of the gem"
|
|
35
|
+
task :release do
|
|
36
|
+
host = "https://#{ENV['BUNDLE_GEMS__WEBLINC__COM']}@gems.weblinc.com"
|
|
37
|
+
|
|
38
|
+
#Rake::Task["workarea:changelog"].execute
|
|
39
|
+
#system "git add CHANGELOG.md"
|
|
40
|
+
#system 'git commit -m "Update CHANGELOG"'
|
|
41
|
+
#system "git push origin HEAD"
|
|
42
|
+
|
|
43
|
+
system "git tag -a v#{Workarea::AuthorizeCim::VERSION} -m 'Tagging #{Workarea::AuthorizeCim::VERSION}'"
|
|
44
|
+
system 'git push --tags'
|
|
45
|
+
|
|
46
|
+
system 'gem build workarea-authorize_cim.gemspec'
|
|
47
|
+
system "gem push workarea-authorize_cim-#{Workarea::AuthorizeCim::VERSION}.gem"
|
|
48
|
+
system "gem push workarea-authorize_cim-#{Workarea::AuthorizeCim::VERSION}.gem --host #{host}"
|
|
49
|
+
system "rm workarea-authorize_cim-#{Workarea::AuthorizeCim::VERSION}.gem"
|
|
50
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Workarea
|
|
4
|
+
class Payment
|
|
5
|
+
# Thrown when Authorize.net can't create the payment profile for
|
|
6
|
+
# some reason. This is typically caught and hidden from erroring out
|
|
7
|
+
# to the user, instead preferring to notify the developers of the
|
|
8
|
+
# issue via a reporting service.
|
|
9
|
+
#
|
|
10
|
+
# The web application will defer to the frontend to actually report
|
|
11
|
+
# this error to the user.
|
|
12
|
+
class CreateProfileError < StandardError
|
|
13
|
+
def initialize(message, parameters: {})
|
|
14
|
+
@parameters = parameters
|
|
15
|
+
super "Payment profile could not be created: #{message}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Workarea
|
|
3
|
+
decorate Payment, with: :cim do
|
|
4
|
+
def successful_captures
|
|
5
|
+
transactions.select { |t| t.success? && (t.capture? || t.purchase?) }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def eligible_for_refund?
|
|
9
|
+
return true unless credit_card? && successful_captures.present?
|
|
10
|
+
|
|
11
|
+
successful_captures.first.created_at < Time.now - 24.hours
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Workarea
|
|
3
|
+
decorate Payment::Authorize::CreditCard, with: :cim do
|
|
4
|
+
def complete!
|
|
5
|
+
return unless Workarea::Payment::StoreCreditCard.new(tender, options).save!
|
|
6
|
+
|
|
7
|
+
transaction.response = handle_active_merchant_errors do
|
|
8
|
+
gateway.create_customer_profile_transaction(auth_args)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def cancel!
|
|
13
|
+
return unless transaction.success?
|
|
14
|
+
|
|
15
|
+
transaction.cancellation = handle_active_merchant_errors do
|
|
16
|
+
gateway.create_customer_profile_transaction(void_args)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def auth_args
|
|
23
|
+
{
|
|
24
|
+
transaction: {
|
|
25
|
+
type: :auth_only,
|
|
26
|
+
customer_profile_id: customer_profile_id,
|
|
27
|
+
customer_payment_profile_id: customer_payment_profile_id,
|
|
28
|
+
amount: auth_amount,
|
|
29
|
+
order: {
|
|
30
|
+
invoice_number: tender.payment.id.first(20) # auth net has max length 20 for this field
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def void_args
|
|
37
|
+
{
|
|
38
|
+
transaction: {
|
|
39
|
+
type: :void,
|
|
40
|
+
customer_profile_id: customer_profile_id,
|
|
41
|
+
customer_payment_profile_id: customer_payment_profile_id,
|
|
42
|
+
trans_id: transaction.response.authorization
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def customer_profile_id
|
|
48
|
+
tender.gateway_profile_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def customer_payment_profile_id
|
|
52
|
+
tender.token
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# cim requeires dollar amount (not cents)
|
|
56
|
+
# eg $4.25
|
|
57
|
+
def auth_amount
|
|
58
|
+
tender.amount.cents / 100.00
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Workarea
|
|
3
|
+
decorate Payment::Capture::CreditCard, with: :cim do
|
|
4
|
+
def complete!
|
|
5
|
+
validate_reference!
|
|
6
|
+
|
|
7
|
+
transaction.response = handle_active_merchant_errors do
|
|
8
|
+
gateway.create_customer_profile_transaction(capture_args)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def cancel!
|
|
13
|
+
# noop, can't cancel a capture
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def capture_args
|
|
19
|
+
{
|
|
20
|
+
transaction: {
|
|
21
|
+
type: :prior_auth_capture,
|
|
22
|
+
amount: auth_amount,
|
|
23
|
+
trans_id: transaction_ref_id
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def transaction_ref_id
|
|
29
|
+
transaction.reference.response.params['direct_response']['transaction_id']
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# cim requires dollar amount (not cents)
|
|
33
|
+
# eg $4.25
|
|
34
|
+
def auth_amount
|
|
35
|
+
transaction.amount.to_s.to_f
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Workarea
|
|
3
|
+
decorate Payment::Profile, with: :cim do
|
|
4
|
+
decorated do
|
|
5
|
+
before_validation :create_gateway_profile, if: :needs_gateway_profile?, on: :create
|
|
6
|
+
before_destroy :delete_gateway_profile, if: :on_gateway?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
# Create a new payment profile on the gateway, and log an error when
|
|
12
|
+
# this cannot be accomplished. First, this checks to make sure a
|
|
13
|
+
# +Payment::Profile+ doesn't already exist locally for the current
|
|
14
|
+
# email. If that is true, we attempt to either find an existing
|
|
15
|
+
# profile on Authorize.net or create a new one. Eventually,
|
|
16
|
+
# +gateway_id+ should be set to something after this method is
|
|
17
|
+
# called, and if not, a validation error is logged.
|
|
18
|
+
#
|
|
19
|
+
# @protected
|
|
20
|
+
# @return [Boolean] +true+
|
|
21
|
+
def create_gateway_profile
|
|
22
|
+
self.gateway_id = previous_profile_id || remote_profile_id
|
|
23
|
+
errors.add :gateway, 'error occurred' unless gateway_id.present?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# When destroying this record, also delete the payment profile from
|
|
27
|
+
# Authorize.net.
|
|
28
|
+
#
|
|
29
|
+
# @protected
|
|
30
|
+
# @return [Boolean] whether the operation was successful
|
|
31
|
+
def delete_gateway_profile
|
|
32
|
+
gateway.delete_customer_profile(
|
|
33
|
+
customer_profile_id: gateway_id
|
|
34
|
+
).tap do |response|
|
|
35
|
+
errors.add :response, response.inspect unless response.success?
|
|
36
|
+
end.success?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Test whether we need to ask Authorize.Net for a payment profile.
|
|
40
|
+
#
|
|
41
|
+
# @protected
|
|
42
|
+
# @return [Boolean] whether we need a payment profile
|
|
43
|
+
def needs_gateway_profile?
|
|
44
|
+
email.present? && gateway_id.blank?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Test whether we have a gateway ID.
|
|
48
|
+
#
|
|
49
|
+
# @protected
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
def on_gateway?
|
|
52
|
+
gateway_id.present?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# The credit card gateway we're using to communicate with
|
|
58
|
+
# Authorize.Net
|
|
59
|
+
#
|
|
60
|
+
# @private
|
|
61
|
+
# @return [ActiveMerchant::Billing::Gateway]
|
|
62
|
+
def gateway
|
|
63
|
+
Workarea.config.gateways.credit_card
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Find an existing +Payment::Profile+ record with a gateway_id for
|
|
67
|
+
# this User, and use it in this record to save a roundtrip to
|
|
68
|
+
# Authorize.Net.
|
|
69
|
+
#
|
|
70
|
+
# @private
|
|
71
|
+
# @return [String] or +nil+ if none can be found.
|
|
72
|
+
def previous_profile_id
|
|
73
|
+
@ppid ||= Workarea::Payment::Profile.where(
|
|
74
|
+
email: email,
|
|
75
|
+
:gateway_id.ne => nil
|
|
76
|
+
).pluck(:gateway_id).first
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Attempt to create a new customer profile on Authorize.Net, either
|
|
80
|
+
# by doing it outright or going through the duplicate payment
|
|
81
|
+
# profile motions.
|
|
82
|
+
#
|
|
83
|
+
# @private
|
|
84
|
+
# @return [String] The new payment profile ID from Authorize.net or
|
|
85
|
+
# +nil+ if it cannot be created.
|
|
86
|
+
def remote_profile_id
|
|
87
|
+
response = gateway.create_customer_profile(profile: { email: email })
|
|
88
|
+
return response.params['customer_profile_id'] if response.success?
|
|
89
|
+
return duplicate_payment_profile_id(response) if duplicate_profile?(response)
|
|
90
|
+
raise Payment::CreateProfileError.new(
|
|
91
|
+
response.message,
|
|
92
|
+
parameters: {
|
|
93
|
+
email: email,
|
|
94
|
+
response: debug_response(response)
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Attempt to pull the existing customer profile Authorize.Net using
|
|
100
|
+
# the original response's message and another API call.
|
|
101
|
+
#
|
|
102
|
+
# @private
|
|
103
|
+
# @param original_response [ActiveMerchant::Billing::Response]
|
|
104
|
+
# @return [String] Existing profile ID for this user
|
|
105
|
+
# @raise [Payment::CreateProfileError] if still can't be created.
|
|
106
|
+
def duplicate_payment_profile_id(original_response)
|
|
107
|
+
gateway_id = get_id_from_message(original_response)
|
|
108
|
+
response = gateway.get_customer_profile(customer_profile_id: gateway_id)
|
|
109
|
+
|
|
110
|
+
if response.success? && email_match?(response)
|
|
111
|
+
gateway_id
|
|
112
|
+
else
|
|
113
|
+
raise Payment::CreateProfileError, response.message
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Test whether the email in this response matches the email in the
|
|
118
|
+
# +Payment::Profile+ we're trying to create, so as not to
|
|
119
|
+
# accidentally assign the wrong gateway_id to the wrong user.
|
|
120
|
+
#
|
|
121
|
+
# @private
|
|
122
|
+
# @param response [ActiveMerchant::Billing::Response]
|
|
123
|
+
# @return [Boolean] whether the email matches
|
|
124
|
+
def email_match?(response)
|
|
125
|
+
response.params['profile']['email'] == email
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Test whether the response's error code matches the duplicate
|
|
129
|
+
# profile creation error code of +E00039+, which triggers a lookup
|
|
130
|
+
# in the error message of the payment profile ID.
|
|
131
|
+
#
|
|
132
|
+
# @private
|
|
133
|
+
# @param response [ActiveMerchant::Billing::Response]
|
|
134
|
+
# @return [Boolean] whether the error code matches E00039
|
|
135
|
+
def duplicate_profile?(response)
|
|
136
|
+
response.params['messages'].try(:[], 'message').try(:[], 'code') == 'E00039'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Attempt to parse out the gateway ID from the error message that is
|
|
140
|
+
# given for duplicate record creation.
|
|
141
|
+
#
|
|
142
|
+
# @private
|
|
143
|
+
# @param response [ActiveMerchant::Billing::Response]
|
|
144
|
+
# @return [String] ID of the existing profile.
|
|
145
|
+
def get_id_from_message(response)
|
|
146
|
+
text = response.params['messages']['message']['text']
|
|
147
|
+
return unless text.present?
|
|
148
|
+
text.match(/A duplicate record with ID (\d+) already exists/)[1]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Picks out the relevant pieces of the ActiveMerchant response for
|
|
152
|
+
# an exception object.
|
|
153
|
+
#
|
|
154
|
+
# @private
|
|
155
|
+
# @param response [ActiveMerchant::Billing::Response]
|
|
156
|
+
# @return [Hash]
|
|
157
|
+
def debug_response(response)
|
|
158
|
+
{
|
|
159
|
+
authorization: response.authorization,
|
|
160
|
+
avs_result: response.avs_result,
|
|
161
|
+
cvv_result: response.cvv_result,
|
|
162
|
+
error_code: response.error_code,
|
|
163
|
+
message: response.message,
|
|
164
|
+
params: response.params,
|
|
165
|
+
test: response.test
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Workarea
|
|
3
|
+
decorate Payment::Purchase::CreditCard, with: :cim do
|
|
4
|
+
def complete!
|
|
5
|
+
return unless Workarea::Payment::StoreCreditCard.new(tender, options).save!
|
|
6
|
+
|
|
7
|
+
transaction.response = handle_active_merchant_errors do
|
|
8
|
+
gateway.create_customer_profile_transaction(auth_capture_args)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def cancel!
|
|
13
|
+
return unless transaction.success?
|
|
14
|
+
|
|
15
|
+
transaction.cancellation = handle_active_merchant_errors do
|
|
16
|
+
gateway.create_customer_profile_transaction(void_args)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def auth_capture_args
|
|
23
|
+
{
|
|
24
|
+
transaction: {
|
|
25
|
+
type: :auth_capture,
|
|
26
|
+
customer_profile_id: customer_profile_id,
|
|
27
|
+
customer_payment_profile_id: customer_payment_profile_id,
|
|
28
|
+
amount: auth_amount,
|
|
29
|
+
order: {
|
|
30
|
+
invoice_number: tender.payment.id.first(20) # auth net has max length 20 for this field
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def void_args
|
|
37
|
+
{
|
|
38
|
+
transaction: {
|
|
39
|
+
type: :void,
|
|
40
|
+
customer_profile_id: customer_profile_id,
|
|
41
|
+
customer_payment_profile_id: customer_payment_profile_id,
|
|
42
|
+
trans_id: transaction.response.authorization
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def customer_profile_id
|
|
48
|
+
tender.gateway_profile_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def customer_payment_profile_id
|
|
52
|
+
tender.token
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# cim requeires dollar amount (not cents)
|
|
56
|
+
# eg $4.25
|
|
57
|
+
def auth_amount
|
|
58
|
+
tender.amount.to_s.to_f
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Workarea
|
|
2
|
+
decorate Payment::Refund, with: :cim do
|
|
3
|
+
decorated { validate :has_refundable_transactions }
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def has_refundable_transactions
|
|
8
|
+
amounts_with_tenders.each do |tender, _amount|
|
|
9
|
+
next unless tender.slug == :credit_card
|
|
10
|
+
|
|
11
|
+
tender.transactions.successful.not_canceled.captures_or_purchased.each do |transaction|
|
|
12
|
+
if transaction.created_at >= Time.now - 24.hours
|
|
13
|
+
errors.add(:credit_card, "This transaction hasn't been settled yet, and isn't eligble for refunding")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Workarea
|
|
3
|
+
decorate Payment::Refund::CreditCard, with: :cim do
|
|
4
|
+
def complete!
|
|
5
|
+
return false unless tender.valid_capture_date?
|
|
6
|
+
|
|
7
|
+
validate_reference!
|
|
8
|
+
|
|
9
|
+
transaction.response = handle_active_merchant_errors do
|
|
10
|
+
gateway.create_customer_profile_transaction_for_refund(options)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def options
|
|
17
|
+
{
|
|
18
|
+
transaction: {
|
|
19
|
+
customer_profile_id: customer_profile_id,
|
|
20
|
+
customer_payment_profile_id: customer_payment_profile_id,
|
|
21
|
+
amount: refund_amount,
|
|
22
|
+
trans_id: transaction.reference.response.params['direct_response']['transaction_id']
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def refund_amount
|
|
28
|
+
transaction.amount.to_s.to_f
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def customer_profile_id
|
|
32
|
+
tender.gateway_profile_id
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def customer_payment_profile_id
|
|
36
|
+
tender.token
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|