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