solidus-adyen 0.1.2

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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/.rubocop.yml +9 -0
  4. data/.rubocop_todo.yml +325 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +238 -0
  8. data/Rakefile +16 -0
  9. data/app/assets/javascripts/spree/backend/solidus-adyen.js +1 -0
  10. data/app/assets/javascripts/spree/checkout/payment/adyen.js +10 -0
  11. data/app/assets/javascripts/spree/frontend/solidus-adyen.js +1 -0
  12. data/app/assets/stylesheets/spree/backend/solidus-adyen/buttons.scss +15 -0
  13. data/app/assets/stylesheets/spree/backend/solidus-adyen/communication.scss +121 -0
  14. data/app/assets/stylesheets/spree/backend/solidus-adyen/variables.scss +4 -0
  15. data/app/assets/stylesheets/spree/backend/solidus-adyen.css +5 -0
  16. data/app/assets/stylesheets/spree/frontend/solidus-adyen.css +3 -0
  17. data/app/controllers/concerns/spree/adyen/admin/refunds_controller.rb +59 -0
  18. data/app/controllers/spree/adyen/hpps_controller.rb +22 -0
  19. data/app/controllers/spree/adyen_notifications_controller.rb +34 -0
  20. data/app/controllers/spree/adyen_redirect_controller.rb +90 -0
  21. data/app/models/adyen_notification.rb +159 -0
  22. data/app/models/concerns/spree/adyen/order.rb +11 -0
  23. data/app/models/concerns/spree/adyen/payment.rb +95 -0
  24. data/app/models/spree/adyen/hpp_source.rb +101 -0
  25. data/app/models/spree/adyen/notification_processor.rb +102 -0
  26. data/app/models/spree/adyen/presenters/communication.rb +31 -0
  27. data/app/models/spree/adyen/presenters/communications/adyen_notification.rb +26 -0
  28. data/app/models/spree/adyen/presenters/communications/base.rb +50 -0
  29. data/app/models/spree/adyen/presenters/communications/hpp_source.rb +31 -0
  30. data/app/models/spree/adyen/presenters/communications/log_entry.rb +28 -0
  31. data/app/models/spree/adyen/presenters/communications.rb +9 -0
  32. data/app/models/spree/gateway/adyen_hpp.rb +95 -0
  33. data/app/overrides/spree/admin/shared/_order_summary.rb +6 -0
  34. data/app/views/spree/admin/payments/source_forms/_adyen.html.erb +1 -0
  35. data/app/views/spree/admin/payments/source_views/_adyen.html.erb +14 -0
  36. data/app/views/spree/adyen/_manual_refund.html.erb +9 -0
  37. data/app/views/spree/adyen/communication/_communication.html.erb +42 -0
  38. data/app/views/spree/adyen/hpps/directory.html.erb +10 -0
  39. data/app/views/spree/checkout/payment/_adyen.html.erb +33 -0
  40. data/bin/checkout.rb +111 -0
  41. data/bin/regen.sh +1 -0
  42. data/config/initializers/solidus_adyen.rb +1 -0
  43. data/config/locales/en.yml +23 -0
  44. data/config/routes.rb +13 -0
  45. data/db/migrate/20131017040945_create_adyen_notifications.rb +29 -0
  46. data/db/migrate/20150911201942_add_index_adyen_notifications_psp_reference.rb +5 -0
  47. data/db/migrate/20150914162539_create_spree_adyen_hpp_sources.rb +21 -0
  48. data/db/migrate/20151007090519_add_days_to_ship_to_config.rb +5 -0
  49. data/db/migrate/20151020230830_remove_indices_on_adyen_notifications.rb +6 -0
  50. data/db/migrate/20151106093023_allow_merchant_reference_to_be_null_for_adyen_notification.rb +5 -0
  51. data/lib/solidus-adyen.rb +1 -0
  52. data/lib/spree/adyen/engine.rb +32 -0
  53. data/lib/spree/adyen/form.rb +135 -0
  54. data/lib/spree/adyen/hpp_check.rb +11 -0
  55. data/lib/spree/adyen/url.rb +30 -0
  56. data/lib/spree/adyen/version.rb +5 -0
  57. data/lib/spree/adyen.rb +6 -0
  58. data/solidus-adyen.gemspec +51 -0
  59. data/spec/controllers/concerns/spree/adyen/admin/refunds_controller_spec.rb +76 -0
  60. data/spec/controllers/spree/adyen/hpps_controller_spec.rb +43 -0
  61. data/spec/controllers/spree/adyen_notifications_controller_spec.rb +118 -0
  62. data/spec/controllers/spree/adyen_redirect_controller_spec.rb +154 -0
  63. data/spec/factories/active_merchant_billing_response.rb +23 -0
  64. data/spec/factories/adyen_notification.rb +91 -0
  65. data/spec/factories/spree_adyen_hpp_source.rb +15 -0
  66. data/spec/factories/spree_gateway_adyen_hpp.rb +23 -0
  67. data/spec/factories/spree_payment.rb +13 -0
  68. data/spec/lib/spree/adyen/form_spec.rb +214 -0
  69. data/spec/models/adyen_notification_spec.rb +86 -0
  70. data/spec/models/concerns/spree/adyen/order_spec.rb +22 -0
  71. data/spec/models/concerns/spree/adyen/payment_spec.rb +93 -0
  72. data/spec/models/spree/adyen/hpp_source_spec.rb +101 -0
  73. data/spec/models/spree/adyen/notification_processor_spec.rb +205 -0
  74. data/spec/models/spree/adyen/presenters/communication_spec.rb +62 -0
  75. data/spec/models/spree/gateway/adyen_hpp_spec.rb +76 -0
  76. data/spec/spec_helper.rb +65 -0
  77. data/spec/support/shared_contexts/mock_adyen_api.rb +47 -0
  78. metadata +463 -0
@@ -0,0 +1,121 @@
1
+ @import 'variables';
2
+ @import 'bourbon';
3
+
4
+ $color-received-bg: mix($color-3, white, 10%);
5
+ $color-sent-bg: mix($color-3, white, 100%);
6
+ $notch-size-side: 12px;
7
+ $notch-size-top: 18px;
8
+ $timestamp-fade: 0.25;
9
+
10
+ @mixin notch($side, $color) {
11
+ #{$side}: -($notch-size-side);
12
+ border-#{$side}: $notch-size-side solid transparent;
13
+ border-top: $notch-size-top solid $color;
14
+ content: '';
15
+ padding-left: 0;
16
+ position: absolute;
17
+ top: 0;
18
+ }
19
+
20
+ @mixin message($side, $color-bg, $color-text) {
21
+ background-color: $color-bg;
22
+ border-top-#{$side}-radius: 0;
23
+ box-shadow: 2px 2px 1px -1px fade-out(mix($color-bg, black, 70%), 0.2);
24
+ color: $color-text;
25
+ line-height: 1.6em;
26
+
27
+ margin-right: $notch-size-side;
28
+ margin-left: $notch-size-side;
29
+
30
+ @if $side == right {
31
+ float: right;
32
+ }
33
+
34
+ &:before {
35
+ @include notch($side, $color-bg);
36
+ }
37
+
38
+ .adyen-comm-timestamp {
39
+ color: fade-out($color-text, $timestamp-fade);
40
+ }
41
+ }
42
+
43
+ /* cannot use a real table here because spree clobbers all default style for
44
+ * tables and is super specific.
45
+ */
46
+ .adyen-comm-content {
47
+ display: table;
48
+ border-spacing: 20px 10px
49
+ }
50
+
51
+ .adyen-comm-content-cell {
52
+ display: table-cell;
53
+
54
+ &:first-child {
55
+ font-weight: bold;
56
+ }
57
+ }
58
+
59
+ .adyen-comm-content-row {
60
+ display: table-row;
61
+ }
62
+
63
+ .adyen-comm {
64
+ border-radius: 10px;
65
+ display: inline-block;
66
+ margin-bottom: 10px;
67
+ min-width: 350px;
68
+ padding: 5px 20px;
69
+ position: relative;
70
+
71
+ .adyen-comm-sent & {
72
+ @include message(right, $color-sent-bg, white);
73
+ }
74
+
75
+ .adyen-comm-received & {
76
+ @include message(left, $color-received-bg, black);
77
+ }
78
+ }
79
+
80
+ .adyen-comm-received,
81
+ .adyen-comm-sent {
82
+ & + & .adyen-comm {
83
+ border-top-right-radius: 10px;
84
+ border-top-left-radius: 10px;
85
+ &:before {
86
+ display: none;
87
+ }
88
+ }
89
+ }
90
+
91
+ .adyen-comm-icon-success {
92
+ color: $color-success;
93
+ }
94
+
95
+ .adyen-comm-icon-failure {
96
+ color: $color-error;
97
+ }
98
+
99
+ .adyen-comm-icon-unprocessed {
100
+ color: $color-safety-yellow;
101
+ }
102
+
103
+ .adyen-comm-icon {
104
+ @include display(flex);
105
+ @include flex-direction(column);
106
+ @include justify-content(space-between);
107
+ font-size: 30px;
108
+ }
109
+
110
+ .adyen-comm-body {
111
+ @include display(flex);
112
+ @include align-items(center);
113
+
114
+ .adyen-comm-sent & {
115
+ @include flex-direction(row-reverse);
116
+ }
117
+ }
118
+
119
+ .adyen-comm-timestamp {
120
+ text-align: right;
121
+ }
@@ -0,0 +1,4 @@
1
+ @import 'spree/backend/globals/variables';
2
+
3
+ $color-safety-yellow: #eed202;
4
+ $color-warning: $color-safety-yellow;
@@ -0,0 +1,5 @@
1
+ /*
2
+ *= require spree/backend
3
+ *= require spree/backend/solidus-adyen/buttons
4
+ *= require spree/backend/solidus-adyen/communication
5
+ */
@@ -0,0 +1,3 @@
1
+ /*
2
+ *=require spree/frontend
3
+ */
@@ -0,0 +1,59 @@
1
+ module Spree
2
+ module Adyen
3
+ module Admin
4
+ module RefundsController
5
+ extend ActiveSupport::Concern
6
+ include Spree::Adyen::HppCheck
7
+
8
+ included do
9
+ before_filter :adyen_create, only: [:create]
10
+ end
11
+
12
+ def adyen_create
13
+ if hpp_payment?(@payment)
14
+ # this sucks, but the attributes are not assigned until after
15
+ # callbacks if we're here we aren't going down the normal flow
16
+ # anyways
17
+ @refund.attributes = permitted_resource_params
18
+
19
+ # early exit if @refund is invalid, .create will have the error
20
+ # messages
21
+ return if @refund.invalid?
22
+
23
+ @payment.refunds.reset # we don't want to save the refund
24
+ @payment.credit!(cents, currency: currency)
25
+
26
+ respond
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def currency
33
+ money.currency.iso_code
34
+ end
35
+
36
+ def cents
37
+ money.cents
38
+ end
39
+
40
+ def money
41
+ @refund.money.money
42
+ end
43
+
44
+ # This is directly copied from .create's response, no way to make it
45
+ # any less awful than this as we still want it to have the same
46
+ # response.
47
+ def respond
48
+ respond_with(@object) do |format|
49
+ format.html do
50
+ flash[:success] = "Refund request was received"
51
+ redirect_to location_after_save
52
+ end
53
+ format.js { render :layout => false }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,22 @@
1
+ module Spree
2
+ module Adyen
3
+ class HppsController < ::ActionController::Base
4
+ load_resource :order, class: "Spree::Order", id_param: :order_id
5
+ load_resource(
6
+ :payment_method,
7
+ class: "Spree::PaymentMethod",
8
+ id_param: :payment_method_id)
9
+
10
+ def directory
11
+ @brands = Adyen::Form.payment_methods_from_directory(
12
+ @order,
13
+ @payment_method)
14
+
15
+ respond_to do |format|
16
+ format.html
17
+ format.json { render json: @brands }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ module Spree
2
+ class AdyenNotificationsController < StoreController
3
+ skip_before_filter :verify_authenticity_token
4
+
5
+ before_filter :authenticate
6
+
7
+ def notify
8
+ notification = AdyenNotification.build(params)
9
+
10
+ if notification.duplicate?
11
+ accept and return
12
+ end
13
+
14
+ notification.save!
15
+
16
+ Spree::Adyen::NotificationProcessor.new(notification).process!
17
+ accept
18
+ end
19
+
20
+ protected
21
+ # Enable HTTP basic authentication
22
+ def authenticate
23
+ authenticate_or_request_with_http_basic do |username, password|
24
+ username == ENV["ADYEN_NOTIFY_USER"] &&
25
+ password == ENV["ADYEN_NOTIFY_PASSWD"]
26
+ end
27
+ end
28
+
29
+ private
30
+ def accept
31
+ render text: "[accepted]"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,90 @@
1
+ module Spree
2
+ class AdyenRedirectController < StoreController
3
+ before_filter :restore_session
4
+ before_filter :check_signature, only: :confirm
5
+
6
+ skip_before_filter :verify_authenticity_token
7
+
8
+ # This is the entry point after an Adyen HPP payment is completed
9
+ def confirm
10
+ source = Adyen::HppSource.new(source_params(params))
11
+
12
+ unless source.authorised?
13
+ flash.notice = Spree.t(:payment_processing_failed)
14
+ redirect_to checkout_state_path(@order.state)
15
+ return
16
+ end
17
+
18
+ # payment is created in a 'checkout' state so that the payment method
19
+ # can attempt to auth it. The payment of course is already auth'd and
20
+ # adyen hpp's authorize implementation just returns a dummy response.
21
+ payment =
22
+ @order.payments.create!(
23
+ amount: @order.total,
24
+ payment_method: @payment_method,
25
+ source: source,
26
+ response_code: params[:pspReference],
27
+ state: "checkout",
28
+ # Order is explicitly defined here because as of writing the
29
+ # Order -> Payments association does not have the inverse of defined
30
+ # when we call `order.complete` below payment.order will still
31
+ # refer to a previous state of the record.
32
+ #
33
+ # If the payment is auto captured only then the payment will completed
34
+ # in `process_outstanding!`, and because Payment calls
35
+ # .order.update_totals after save the order is saved with its
36
+ # previous values, causing payment_state and shipment_state to revert
37
+ # to nil.
38
+ order: @order
39
+ )
40
+
41
+ if @order.complete
42
+ # We may have already recieved the authorization notification, so process
43
+ # it now
44
+ Spree::Adyen::NotificationProcessor.process_outstanding!(payment)
45
+
46
+ flash.notice = Spree.t(:order_processed_successfully)
47
+ redirect_to order_path(@order)
48
+ else
49
+ #TODO void/cancel payment
50
+ redirect_to checkout_state_path(@order.state)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def check_signature
57
+ unless ::Adyen::Form.redirect_signature_check(params, @payment_method.shared_secret)
58
+ raise "Payment Method not found."
59
+ end
60
+ end
61
+
62
+ # We pass the guest token and payment method id in, pipe seperated in the
63
+ # merchantReturnData parameter so that we can recover the session.
64
+ def restore_session
65
+ guest_token, payment_method_id =
66
+ params.fetch(:merchantReturnData).split("|")
67
+
68
+ cookies.permanent.signed[:guest_token] = guest_token
69
+
70
+ @payment_method = Spree::PaymentMethod.find(payment_method_id)
71
+
72
+ @order =
73
+ Spree::Order.
74
+ incomplete.
75
+ find_by!(guest_token: cookies.signed[:guest_token])
76
+ end
77
+
78
+ def source_params params
79
+ params.permit(
80
+ :authResult,
81
+ :pspReference,
82
+ :merchantReference,
83
+ :skinCode,
84
+ :merchantSig,
85
+ :paymentMethod,
86
+ :shopperLocale,
87
+ :merchantReturnData)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,159 @@
1
+ # The +AdyenNotification+ class handles notifications sent by Adyen to your servers.
2
+ #
3
+ # Because notifications contain important payment status information, you should store
4
+ # these notifications in your database. For this reason, +AdyenNotification+ inherits
5
+ # from +ActiveRecord::Base+, and a migration is included to simply create a suitable table
6
+ # to store the notifications in.
7
+ #
8
+ # Adyen can either send notifications to you via HTTP POST requests, or SOAP requests.
9
+ # Because SOAP is not really well supported in Rails and setting up a SOAP server is
10
+ # not trivial, only handling HTTP POST notifications is currently supported.
11
+ #
12
+ # @example
13
+ # @notification = AdyenNotification.log(request)
14
+ # if @notification.successful_authorisation?
15
+ # @invoice = Invoice.find(@notification.merchant_reference)
16
+ # @invoice.set_paid!
17
+ # end
18
+ class AdyenNotification < ActiveRecord::Base
19
+ AUTO_CAPTURE_ONLY_METHODS = ["ideal", "c_cash", "directEbanking"].freeze
20
+
21
+ AUTHORISATION = "AUTHORISATION".freeze
22
+ CANCELLATION = "CANCELLATION".freeze
23
+ REFUND = "REFUND".freeze
24
+ CANCEL_OR_REFUND = "CANCEL_OR_REFUND".freeze
25
+ CAPTURE = "CAPTURE".freeze
26
+ CAPTURE_FAILED = "CAPTURE_FAILED".freeze
27
+ REFUND_FAILED = "REFUND_FAILED".freeze
28
+ REFUNDED_REVERSED = "REFUNDED_REVERSED".freeze
29
+
30
+ belongs_to :prev,
31
+ class_name: self,
32
+ foreign_key: :original_reference,
33
+ primary_key: :psp_reference,
34
+ inverse_of: :next
35
+
36
+ # Auth will have no original reference, all successive notifications with
37
+ # reference the first auth notification
38
+ has_many :next,
39
+ class_name: self,
40
+ foreign_key: :original_reference,
41
+ primary_key: :psp_reference,
42
+ inverse_of: :prev
43
+
44
+ belongs_to :order,
45
+ class_name: Spree::Order,
46
+ primary_key: :number,
47
+ foreign_key: :merchant_reference
48
+
49
+ scope :as_dispatched, -> { order(event_date: :desc) }
50
+ scope :processed, -> { where processed: true }
51
+ scope :unprocessed, -> { where processed: false }
52
+ scope :authorisation, -> { where event_code: "AUTHORISATION" }
53
+
54
+ validates_presence_of :event_code
55
+ validates_presence_of :psp_reference
56
+ validates_uniqueness_of :success, scope: [:psp_reference, :event_code]
57
+
58
+ # Logs an incoming notification into the database.
59
+ #
60
+ # @param [Hash] params The notification parameters that should be stored in the database.
61
+ # @return [Adyen::Notification] The initiated and persisted notification instance.
62
+ # @raise This method will raise an exception if the notification cannot be stored.
63
+ # @see Adyen::Notification::HttpPost.log
64
+ def self.build(params)
65
+ # Assign explicit each attribute from CamelCase notation to notification
66
+ # For example, merchantReference will be converted to merchant_reference
67
+ self.new.tap do |notification|
68
+ params.each do |key, value|
69
+ setter = "#{key.to_s.underscore}="
70
+
71
+ # don't assign if value is empty string.
72
+ if notification.respond_to?(setter) && value.present?
73
+ notification.send(setter, value)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def payment
80
+ Spree::Payment.find_by response_code: original_reference || psp_reference
81
+ end
82
+
83
+ # Returns true if this notification is an AUTHORISATION notification
84
+ # @return [true, false] true iff event_code == 'AUTHORISATION'
85
+ # @see Adyen.notification#successful_authorisation?
86
+ def authorisation?
87
+ event_code == AUTHORISATION
88
+ end
89
+
90
+ def capture?
91
+ event_code == CAPTURE
92
+ end
93
+
94
+ def cancel_or_refund?
95
+ event_code == CANCEL_OR_REFUND
96
+ end
97
+
98
+ def refund?
99
+ event_code == REFUND
100
+ end
101
+
102
+ def processed!
103
+ update! processed: true
104
+ end
105
+
106
+ def actions
107
+ if operations
108
+ operations.
109
+ split(",").
110
+ map(&:downcase)
111
+
112
+ else
113
+ []
114
+
115
+ end
116
+ end
117
+
118
+ # https://docs.adyen.com/display/TD/Notification+fields
119
+ def modification_event?
120
+ [ CANCELLATION,
121
+ REFUND,
122
+ CANCEL_OR_REFUND,
123
+ CAPTURE,
124
+ CAPTURE_FAILED,
125
+ REFUND_FAILED,
126
+ REFUNDED_REVERSED
127
+ ].member? self.event_code
128
+ end
129
+
130
+ def normal_event?
131
+ AUTHORISATION == self.event_code
132
+ end
133
+
134
+ def bank_transfer?
135
+ self.payment_method.match(/^bankTransfer/)
136
+ end
137
+
138
+ def duplicate?
139
+ self.class.exists?(
140
+ psp_reference: self.psp_reference,
141
+ event_code: self.event_code,
142
+ success: self.success
143
+ )
144
+ end
145
+
146
+ def auto_captured?
147
+ payment_method_auto_capture_only? || bank_transfer?
148
+ end
149
+
150
+ def payment_method_auto_capture_only?
151
+ AUTO_CAPTURE_ONLY_METHODS.member?(self.payment_method)
152
+ end
153
+
154
+ def money
155
+ ::Money.new(value, currency)
156
+ end
157
+
158
+ alias_method :authorization?, :authorisation?
159
+ end
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module Adyen
3
+ module Order
4
+ def requires_manual_refund?
5
+ canceled? && payments.any? do |payment|
6
+ payment.source.try(:requires_manual_refund?)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,95 @@
1
+ # Because adyen payment modifications are delayed we don't actually know if
2
+ # the request succeeded after doing it. For that reason we can't use the
3
+ # standard capture! and friends as they change the payment state and would
4
+ # result is false positives (payment could fail after capture).
5
+ module Spree
6
+ module Adyen
7
+ module Payment
8
+ extend ActiveSupport::Concern
9
+ include Spree::Adyen::HppCheck
10
+
11
+ # capture! :: bool | error
12
+ def capture!
13
+ if hpp_payment?
14
+ amount = money.money.cents
15
+ process do
16
+ payment_method.send(
17
+ :capture, amount, response_code, gateway_options)
18
+ end
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ # credit! :: bool | error
25
+ #
26
+ # Issue a request to credit the payment, this does NOT perform validation
27
+ # on the amount to be credited, which is assumed to have been done prior
28
+ # to this.
29
+ #
30
+ # credit! is only implemented for hpp payments, because of the delayed
31
+ # manner of Adyen api communications. If this method is called on a
32
+ # payment that is not from Adyen then it should fail. This is crummy way
33
+ # of getting around the fact that Payment methods cannot specifiy these
34
+ # methods.
35
+ def credit! amount, options
36
+ if hpp_payment?
37
+ process { payment_method.credit(amount, response_code, options) }
38
+ else
39
+ fail NotImplementedError, "Spree::Payment does not implement credit!"
40
+ end
41
+ end
42
+
43
+ # cancel! :: bool | error
44
+ #
45
+ # Borrowed from handle_void_response, this has been modified so that it
46
+ # won't actually void the payment _yet_.
47
+ def cancel!
48
+ if hpp_payment?
49
+ if source.requires_manual_refund?
50
+ log_manual_refund
51
+ else
52
+ process { payment_method.cancel response_code }
53
+ end
54
+ else
55
+ super
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def log_manual_refund
62
+ message = I18n.t("solidus-adyen.manual_refund.log_message")
63
+ record_response(
64
+ OpenStruct.new(
65
+ success?: false,
66
+ message: message))
67
+ end
68
+
69
+ def process &block
70
+ check_environment
71
+ response = nil
72
+
73
+ Spree::Payment.transaction do
74
+ protect_from_connection_error do
75
+ started_processing!
76
+ response = yield(block)
77
+ fail ActiveRecord::Rollback unless response.success?
78
+ end
79
+ end
80
+
81
+ record_response(response)
82
+
83
+ if response.success?
84
+ # The payments controller's fire action expects a truthy value to
85
+ # indicate success
86
+ true
87
+ else
88
+ # this is done to be consistent with capture, but we might actually
89
+ # want them to just return to the previous state
90
+ gateway_error(response)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end