spree_vpago 0.1.0.pre.beta → 2.0.4.pre.beta1
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 +4 -4
- data/.github/workflows/test_and_publish_gem.yml +2 -0
- data/Gemfile.lock +25 -23
- data/app/assets/javascripts/vpago/vpago_payments/user_informers/firebase.js +1686 -1426
- data/app/controllers/spree/admin/payment_methods_controller_decorator.rb +9 -1
- data/app/controllers/spree/vpago_payments_controller.rb +12 -2
- data/app/javascripts/queue_processor.js +33 -0
- data/app/javascripts/queue_processor.test.js +88 -0
- data/app/javascripts/vpago/vpago_payments/user_informers/firebase.js +92 -13
- data/app/jobs/vpago/payment_capturer_job.rb +11 -0
- data/app/jobs/vpago/payment_processor_job.rb +3 -0
- data/app/models/vpago/address_decorator.rb +10 -0
- data/app/models/vpago/order_decorator.rb +13 -1
- data/app/models/vpago/payment_decorator.rb +18 -1
- data/app/overrides/spree/admin/payment_methods/index/payment_methods_tabs.html.erb.deface +7 -1
- data/app/overrides/spree/admin/payment_methods/index/tenant_body.html.erb.deface +5 -0
- data/app/overrides/spree/admin/payment_methods/index/tenant_header.html.erb.deface +5 -0
- data/app/serializers/spree/v2/storefront/payment_serializer_decorator.rb +1 -1
- data/app/services/vpago/payment_processable.rb +54 -0
- data/app/services/vpago/payment_processor.rb +58 -41
- data/app/services/vpago/payment_url_constructor.rb +1 -1
- data/app/services/vpago/user_informers/firebase.rb +21 -16
- data/app/views/spree/admin/shared/_payment_methods_tabs.html.erb +7 -0
- data/app/views/spree/vpago_payments/processing.html.erb +36 -23
- data/lib/spree_vpago/version.rb +1 -1
- data/lib/vpago/payway_v2/base.rb +8 -8
- data/lib/vpago/payway_v2/checkout.rb +2 -2
- data/node_modules/.yarn-integrity +93 -2
- data/package.json +6 -1
- data/yarn.lock +556 -2
- metadata +9 -2
@@ -3,7 +3,15 @@ module Spree
|
|
3
3
|
module PaymentMethodsControllerDecorator
|
4
4
|
def scope
|
5
5
|
scope = current_store.payment_methods_including_vendor.accessible_by(current_ability, :index)
|
6
|
-
|
6
|
+
|
7
|
+
if params[:tab] == 'vendors'
|
8
|
+
scope = scope.where.not(vendor_id: nil)
|
9
|
+
elsif params[:tab] == 'tenants'
|
10
|
+
scope = scope.joins(:vendor)
|
11
|
+
.where.not(vendor_id: nil)
|
12
|
+
.where.not(spree_vendors: { tenant_id: nil })
|
13
|
+
end
|
14
|
+
|
7
15
|
scope
|
8
16
|
end
|
9
17
|
|
@@ -39,7 +39,8 @@ module Spree
|
|
39
39
|
def process_payment
|
40
40
|
return render json: { status: :ok }, status: :ok if request.method != 'POST'
|
41
41
|
|
42
|
-
|
42
|
+
return_params = sanitize_return_params
|
43
|
+
@payment = Vpago::PaymentFinder.new(return_params).find_and_verify
|
43
44
|
return render_not_found unless @payment.present?
|
44
45
|
|
45
46
|
unless @payment.order.paid?
|
@@ -54,7 +55,16 @@ module Spree
|
|
54
55
|
render json: { status: :internal_server_error, message: 'Failed to enqueue payment processor job' }, status: :internal_server_error
|
55
56
|
end
|
56
57
|
|
57
|
-
def
|
58
|
+
def sanitize_return_params
|
59
|
+
sanitized_params = params.permit!.to_h
|
60
|
+
|
61
|
+
# In ABA case, it returns params in side return params.
|
62
|
+
sanitized_params.merge!(JSON.parse(sanitized_params.delete(:return_params))) if sanitized_params[:return_params].present?
|
63
|
+
|
64
|
+
sanitized_params
|
65
|
+
end
|
66
|
+
|
67
|
+
def render_not_found
|
58
68
|
respond_to do |format|
|
59
69
|
format.html { render file: Rails.public_path.join('404.html'), status: :not_found, layout: false }
|
60
70
|
format.json { render json: { status: :not_found }, status: :not_found }
|
@@ -0,0 +1,33 @@
|
|
1
|
+
export default class QueueProcessor {
|
2
|
+
constructor() {
|
3
|
+
this.queues = [];
|
4
|
+
this.processing = false;
|
5
|
+
}
|
6
|
+
|
7
|
+
queueStateChange({ callback, minDelayInMs = 1000 }) {
|
8
|
+
this.queues.push({ callback, minDelayInMs });
|
9
|
+
if (!this.processing) this.#processQueue();
|
10
|
+
}
|
11
|
+
|
12
|
+
async #processQueue() {
|
13
|
+
if (this.queues.length === 0) {
|
14
|
+
this.processing = false;
|
15
|
+
return;
|
16
|
+
}
|
17
|
+
|
18
|
+
this.processing = true;
|
19
|
+
const { callback, minDelayInMs } = this.queues.shift();
|
20
|
+
const startTime = Date.now();
|
21
|
+
|
22
|
+
await callback();
|
23
|
+
|
24
|
+
const elapsedTime = Date.now() - startTime;
|
25
|
+
if (elapsedTime < minDelayInMs) {
|
26
|
+
await new Promise((resolve) =>
|
27
|
+
setTimeout(resolve, minDelayInMs - elapsedTime)
|
28
|
+
);
|
29
|
+
}
|
30
|
+
|
31
|
+
this.#processQueue();
|
32
|
+
}
|
33
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import { expect } from "chai";
|
2
|
+
import QueueProcessor from "./queue_processor.js";
|
3
|
+
|
4
|
+
describe("QueueProcessor", () => {
|
5
|
+
let queueProcessor;
|
6
|
+
let minDelayInMs = 100;
|
7
|
+
|
8
|
+
beforeEach(() => {
|
9
|
+
queueProcessor = new QueueProcessor();
|
10
|
+
});
|
11
|
+
|
12
|
+
it("should initially have an empty queue and not be processing", () => {
|
13
|
+
expect(queueProcessor.queues).to.have.lengthOf(0);
|
14
|
+
expect(queueProcessor.processing).to.equal(false);
|
15
|
+
});
|
16
|
+
|
17
|
+
it("should run queue 1 by 1 while keep min delay 100", async () => {
|
18
|
+
let timeToProcess = 50;
|
19
|
+
|
20
|
+
const mockCallback = () =>
|
21
|
+
new Promise((resolve) => setTimeout(resolve, timeToProcess));
|
22
|
+
|
23
|
+
queueProcessor.queueStateChange({
|
24
|
+
callback: mockCallback,
|
25
|
+
minDelayInMs: minDelayInMs,
|
26
|
+
});
|
27
|
+
|
28
|
+
queueProcessor.queueStateChange({
|
29
|
+
callback: mockCallback,
|
30
|
+
minDelayInMs: minDelayInMs,
|
31
|
+
});
|
32
|
+
|
33
|
+
queueProcessor.queueStateChange({
|
34
|
+
callback: mockCallback,
|
35
|
+
minDelayInMs: minDelayInMs,
|
36
|
+
});
|
37
|
+
|
38
|
+
expect(queueProcessor.queues).to.have.lengthOf(2);
|
39
|
+
expect(queueProcessor.processing).to.equal(true);
|
40
|
+
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, minDelayInMs * 3 + 4)); // +4ms for buffer
|
42
|
+
|
43
|
+
expect(queueProcessor.queues).to.have.lengthOf(0);
|
44
|
+
expect(queueProcessor.processing).to.equal(false);
|
45
|
+
});
|
46
|
+
|
47
|
+
describe("when process take less than the delay", () => {
|
48
|
+
it("should process and wait for remaining delay", async () => {
|
49
|
+
let timeToProcess = 50;
|
50
|
+
|
51
|
+
const mockCallback = () =>
|
52
|
+
new Promise((resolve) => setTimeout(resolve, timeToProcess));
|
53
|
+
|
54
|
+
queueProcessor.queueStateChange({
|
55
|
+
callback: mockCallback,
|
56
|
+
minDelayInMs: minDelayInMs,
|
57
|
+
});
|
58
|
+
|
59
|
+
expect(queueProcessor.queues).to.have.lengthOf(0);
|
60
|
+
expect(queueProcessor.processing).to.equal(true);
|
61
|
+
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, minDelayInMs + 1)); // +1ms for buffer
|
63
|
+
|
64
|
+
expect(queueProcessor.queues).to.have.lengthOf(0);
|
65
|
+
expect(queueProcessor.processing).to.equal(false);
|
66
|
+
});
|
67
|
+
});
|
68
|
+
|
69
|
+
describe("when process take longer than the delay", () => {
|
70
|
+
it("should process and not wait for delay", async () => {
|
71
|
+
let timeToProcess = 200;
|
72
|
+
|
73
|
+
const mockCallback = () =>
|
74
|
+
new Promise((resolve) => setTimeout(resolve, timeToProcess));
|
75
|
+
|
76
|
+
queueProcessor.queueStateChange({
|
77
|
+
callback: mockCallback,
|
78
|
+
minDelayInMs: minDelayInMs,
|
79
|
+
});
|
80
|
+
|
81
|
+
expect(queueProcessor.queues).to.have.lengthOf(0);
|
82
|
+
expect(queueProcessor.processing).to.equal(true);
|
83
|
+
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, timeToProcess + 1)); // +1ms for buffer
|
85
|
+
expect(queueProcessor.processing).to.equal(false);
|
86
|
+
});
|
87
|
+
});
|
88
|
+
});
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import { initializeApp } from "firebase/app";
|
2
2
|
import { getFirestore, doc, onSnapshot, setDoc } from "firebase/firestore";
|
3
|
+
import QueueProcessor from "../../../queue_processor.js";
|
3
4
|
|
4
5
|
async function listenToProcessingState({
|
5
6
|
firebaseConfigs,
|
6
|
-
|
7
|
+
documentReferencePath,
|
7
8
|
onPaymentIsProcessing,
|
8
9
|
onOrderIsProcessing,
|
9
10
|
onOrderIsCompleted,
|
@@ -15,43 +16,121 @@ async function listenToProcessingState({
|
|
15
16
|
const app = initializeApp(firebaseConfigs);
|
16
17
|
const db = getFirestore(app);
|
17
18
|
|
18
|
-
const
|
19
|
-
|
20
|
-
const documentRef = doc(db, "statuses", "cart", currentDate, orderNumber);
|
19
|
+
const documentRef = doc(db, documentReferencePath);
|
21
20
|
await setDoc(documentRef, { listening: true }, { merge: true });
|
22
21
|
|
22
|
+
const queueProcessor = new QueueProcessor();
|
23
|
+
|
23
24
|
onSnapshot(documentRef, (doc) => {
|
24
25
|
let documentData = doc.data();
|
25
26
|
|
27
|
+
let messageCode = documentData["message_code"];
|
26
28
|
let orderState = documentData["order_state"];
|
27
29
|
let paymentState = documentData["payment_state"];
|
28
|
-
let
|
29
|
-
let
|
30
|
+
let processing = documentData["processing"] === true;
|
31
|
+
let reasonCode = documentData["reason_code"];
|
32
|
+
let reasonMessage = documentData["reason_message"];
|
30
33
|
|
31
34
|
let orderCompleted = orderState === "complete";
|
32
35
|
if (orderCompleted) {
|
33
|
-
|
36
|
+
queueProcessor.queueStateChange({
|
37
|
+
minDelayInMs: 1500,
|
38
|
+
callback: async () => {
|
39
|
+
await onCompleted(
|
40
|
+
orderState,
|
41
|
+
paymentState,
|
42
|
+
reasonCode,
|
43
|
+
reasonMessage
|
44
|
+
);
|
45
|
+
},
|
46
|
+
});
|
34
47
|
return;
|
35
48
|
}
|
36
49
|
|
37
50
|
switch (messageCode) {
|
38
51
|
case "payment_is_processing":
|
39
|
-
|
52
|
+
queueProcessor.queueStateChange({
|
53
|
+
minDelayInMs: 1500,
|
54
|
+
callback: async () => {
|
55
|
+
await onPaymentIsProcessing(
|
56
|
+
orderState,
|
57
|
+
paymentState,
|
58
|
+
processing,
|
59
|
+
reasonCode,
|
60
|
+
reasonMessage
|
61
|
+
);
|
62
|
+
},
|
63
|
+
});
|
40
64
|
break;
|
41
65
|
case "order_is_processing":
|
42
|
-
|
66
|
+
queueProcessor.queueStateChange({
|
67
|
+
minDelayInMs: 1500,
|
68
|
+
callback: async () => {
|
69
|
+
await onOrderIsProcessing(
|
70
|
+
orderState,
|
71
|
+
paymentState,
|
72
|
+
processing,
|
73
|
+
reasonCode,
|
74
|
+
reasonMessage
|
75
|
+
);
|
76
|
+
},
|
77
|
+
});
|
43
78
|
break;
|
44
79
|
case "order_is_completed":
|
45
|
-
|
80
|
+
queueProcessor.queueStateChange({
|
81
|
+
minDelayInMs: 1500,
|
82
|
+
callback: async () => {
|
83
|
+
await onOrderIsCompleted(
|
84
|
+
orderState,
|
85
|
+
paymentState,
|
86
|
+
processing,
|
87
|
+
reasonCode,
|
88
|
+
reasonMessage
|
89
|
+
);
|
90
|
+
},
|
91
|
+
});
|
46
92
|
break;
|
47
93
|
case "order_process_failed":
|
48
|
-
|
94
|
+
queueProcessor.queueStateChange({
|
95
|
+
minDelayInMs: 1500,
|
96
|
+
callback: async () => {
|
97
|
+
await onOrderProcessFailed(
|
98
|
+
orderState,
|
99
|
+
paymentState,
|
100
|
+
processing,
|
101
|
+
reasonCode,
|
102
|
+
reasonMessage
|
103
|
+
);
|
104
|
+
},
|
105
|
+
});
|
49
106
|
break;
|
50
107
|
case "payment_is_refunded":
|
51
|
-
|
108
|
+
queueProcessor.queueStateChange({
|
109
|
+
minDelayInMs: 1500,
|
110
|
+
callback: async () => {
|
111
|
+
await onPaymentIsRefunded(
|
112
|
+
orderState,
|
113
|
+
paymentState,
|
114
|
+
processing,
|
115
|
+
reasonCode,
|
116
|
+
reasonMessage
|
117
|
+
);
|
118
|
+
},
|
119
|
+
});
|
52
120
|
break;
|
53
121
|
case "payment_process_failed":
|
54
|
-
|
122
|
+
queueProcessor.queueStateChange({
|
123
|
+
minDelayInMs: 1500,
|
124
|
+
callback: async () => {
|
125
|
+
await onPaymentProcessFailed(
|
126
|
+
orderState,
|
127
|
+
paymentState,
|
128
|
+
processing,
|
129
|
+
reasonCode,
|
130
|
+
reasonMessage
|
131
|
+
);
|
132
|
+
},
|
133
|
+
});
|
55
134
|
break;
|
56
135
|
default:
|
57
136
|
break;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Put :payment_processing at a higher priority in your project: config/sidekiq.yml
|
2
|
+
module Vpago
|
3
|
+
class PaymentCapturerJob < ::ApplicationUniqueJob
|
4
|
+
queue_as :payment_processing
|
5
|
+
|
6
|
+
def perform(payment_id)
|
7
|
+
payment = Spree::Payment.find(payment_id)
|
8
|
+
payment.capture! if payment.pending?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
|
+
# Put :payment_processing at a higher priority in your project: config/sidekiq.yml
|
1
2
|
module Vpago
|
2
3
|
class PaymentProcessorJob < ::ApplicationUniqueJob
|
4
|
+
queue_as :payment_processing
|
5
|
+
|
3
6
|
def perform(options)
|
4
7
|
payment = Spree::Payment.find_by(number: options[:payment_number])
|
5
8
|
Vpago::PaymentProcessor.new(payment: payment).call
|
@@ -48,7 +48,9 @@ module Vpago
|
|
48
48
|
|
49
49
|
# override
|
50
50
|
def available_payment_methods(store = nil)
|
51
|
-
payment_methods = if
|
51
|
+
payment_methods = if respond_to?(:tenant) && tenant.present?
|
52
|
+
tenant_payment_methods
|
53
|
+
elsif vendor_payment_methods.any?
|
52
54
|
vendor_payment_methods
|
53
55
|
else
|
54
56
|
collect_payment_methods(store)
|
@@ -61,6 +63,10 @@ module Vpago
|
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
66
|
+
def tenant_payment_methods
|
67
|
+
tenant.tenant_payment_methods
|
68
|
+
end
|
69
|
+
|
64
70
|
def line_items_count
|
65
71
|
line_items.size
|
66
72
|
end
|
@@ -72,6 +78,12 @@ module Vpago
|
|
72
78
|
def order_adjustment_total
|
73
79
|
adjustments.eligible.sum(:amount)
|
74
80
|
end
|
81
|
+
|
82
|
+
# override this method if you want flexibility
|
83
|
+
# for example, host per payment method or tenant
|
84
|
+
def payment_host
|
85
|
+
ENV.fetch('DEFAULT_URL_HOST')
|
86
|
+
end
|
75
87
|
end
|
76
88
|
end
|
77
89
|
|
@@ -11,13 +11,30 @@ module Vpago
|
|
11
11
|
to: :url_constructor
|
12
12
|
end
|
13
13
|
|
14
|
+
# override:
|
15
|
+
# to give payment another chance to re-process, even if it failed.
|
14
16
|
def process!
|
15
|
-
# give payment another chance to re-process, even if it failed.
|
16
17
|
update!(state: :checkout) if processing? || send(:has_invalid_state?)
|
17
18
|
|
18
19
|
super
|
19
20
|
end
|
20
21
|
|
22
|
+
# override:
|
23
|
+
# to allow capture faraday connection error. gateway_error method already write rails log for this.
|
24
|
+
def protect_from_connection_error
|
25
|
+
yield
|
26
|
+
rescue ActiveMerchant::ConnectionError => e
|
27
|
+
failure!
|
28
|
+
gateway_error(e)
|
29
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
30
|
+
failure!
|
31
|
+
gateway_error(ActiveMerchant::ConnectionError.new(e.message, e))
|
32
|
+
end
|
33
|
+
|
34
|
+
def user_informer
|
35
|
+
@user_informer ||= ::Vpago::UserInformers::Firebase.new(order)
|
36
|
+
end
|
37
|
+
|
21
38
|
def url_constructor
|
22
39
|
@url_constructor ||= Vpago::PaymentUrlConstructor.new(self)
|
23
40
|
end
|
@@ -4,6 +4,12 @@
|
|
4
4
|
|
5
5
|
<% if params[:tab] == 'vendors' %>
|
6
6
|
<div class="alert alert-info mb-3">
|
7
|
-
<%= svg_icon name: "info-circle.svg", classes: 'mr-2', width: '16', height: '16' %>
|
7
|
+
<%= svg_icon name: "info-circle.svg", classes: 'mr-2', width: '16', height: '16' %>
|
8
|
+
Payment methods for each vendor. Once set, only those payment methods will be displayed to users.
|
9
|
+
</div>
|
10
|
+
<% elsif params[:tab] == 'tenants' %>
|
11
|
+
<div class="alert alert-info mb-3">
|
12
|
+
<%= svg_icon name: "info-circle.svg", classes: 'mr-2', width: '16', height: '16' %>
|
13
|
+
Payment methods for each tenant. Once set, only those payment methods will be displayed to users.
|
8
14
|
</div>
|
9
15
|
<% end %>
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Vpago
|
2
|
+
module PaymentProcessable
|
3
|
+
def cancel_pre_auth(reason_code, reason_message)
|
4
|
+
log_process('cancel_pre_auth') do
|
5
|
+
@payment.void_transaction!
|
6
|
+
user_informer.payment_is_refunded(
|
7
|
+
processing: false,
|
8
|
+
reason_code: reason_code,
|
9
|
+
reason_message: reason_message
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Allows canceling pre-authorization if:
|
15
|
+
# 1. The payment is pending or authorized.
|
16
|
+
# 2. Pre-auth is enabled, ensuring funds can be released to user if processing fails.
|
17
|
+
# PaymentProcessor is usually called after payment is made, so canceling pre-auth typically works.
|
18
|
+
def can_cancel_pre_auth?
|
19
|
+
@payment.pending? || @payment.payment_method.enable_pre_auth?
|
20
|
+
end
|
21
|
+
|
22
|
+
def extract_completer_failure_reason_code(error)
|
23
|
+
return :some_line_items_are_out_of_stock if error.respond_to?(:to_h) && error.to_h[:base]&.include?(Spree.t(:insufficient_stock_lines_present))
|
24
|
+
return :some_variants_are_discontinued if error.respond_to?(:to_h) && error.to_h[:base]&.include?(Spree.t(:discontinued_variants_present))
|
25
|
+
|
26
|
+
:unable_to_complete_order
|
27
|
+
end
|
28
|
+
|
29
|
+
# example.
|
30
|
+
# Started Vpago::PaymentProcessor#process_payment! for payment_number: PX81YZX with args: {}
|
31
|
+
# Completed Vpago::PaymentProcessor#process_payment! for payment_number: PX81YZX in 2000ms
|
32
|
+
def log_process(method, *args)
|
33
|
+
start_time = Time.now
|
34
|
+
Rails.logger.error("Started #{self.class}##{method} for payment_number: #{@payment.number} with args: #{args}")
|
35
|
+
|
36
|
+
yield
|
37
|
+
|
38
|
+
duration_ms = (Time.now - start_time) * 1000
|
39
|
+
Rails.logger.error("Completed #{self.class}##{method} for payment_number: #{@payment.number} in #{duration_ms}ms")
|
40
|
+
end
|
41
|
+
|
42
|
+
def user_informer
|
43
|
+
@user_informer ||= ::Vpago::UserInformers::Firebase.new(@payment.order)
|
44
|
+
end
|
45
|
+
|
46
|
+
def success?
|
47
|
+
@error.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def failure(error)
|
51
|
+
@error = error
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -1,6 +1,13 @@
|
|
1
|
+
# Error reason code:
|
2
|
+
# - :some_line_items_are_out_of_stock
|
3
|
+
# - :some_variants_are_discontinued
|
4
|
+
# - :unable_to_complete_order
|
5
|
+
# - :invalid_state_machine_transition
|
6
|
+
# - :unable_to_connect_to_gateway
|
7
|
+
# - :gateway_error
|
1
8
|
module Vpago
|
2
9
|
class PaymentProcessor
|
3
|
-
|
10
|
+
include PaymentProcessable
|
4
11
|
|
5
12
|
def initialize(payment:)
|
6
13
|
@payment = payment
|
@@ -8,65 +15,75 @@ module Vpago
|
|
8
15
|
end
|
9
16
|
|
10
17
|
def call
|
18
|
+
log_process('call!') { call! }
|
19
|
+
end
|
20
|
+
|
21
|
+
def call!
|
11
22
|
process_payment!
|
12
|
-
|
23
|
+
process_order! if @payment.completed? || @payment.pending?
|
24
|
+
rescue Spree::Core::GatewayError => e
|
25
|
+
return handle_payment_failure(:unable_to_connect_to_gateway, e.message) if e.message == Spree.t(:unable_to_connect_to_gateway)
|
13
26
|
|
14
|
-
|
15
|
-
rescue
|
16
|
-
|
27
|
+
handle_payment_failure(:gateway_error, e.message)
|
28
|
+
rescue StateMachines::InvalidTransition => e
|
29
|
+
handle_payment_failure(:invalid_state_machine_transition, e.message)
|
17
30
|
end
|
18
31
|
|
19
32
|
private
|
20
33
|
|
34
|
+
# payment.process! will throw GatewayError if not success.
|
21
35
|
def process_payment!
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def process_order
|
27
|
-
user_informer.order_is_processing(processing: true)
|
28
|
-
completer = Spree::Checkout::Complete.new.call(order: payment.order)
|
29
|
-
|
30
|
-
if completer.success?
|
31
|
-
mark_order_process_completed
|
32
|
-
else
|
33
|
-
mark_order_process_failed(completer.error.to_s)
|
36
|
+
log_process('process_payment!') do
|
37
|
+
user_informer.payment_is_processing(processing: true)
|
38
|
+
@payment.process!
|
34
39
|
end
|
35
40
|
end
|
36
41
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
42
|
+
def process_order!
|
43
|
+
log_process('process_order!') do
|
44
|
+
user_informer.order_is_processing(processing: true)
|
45
|
+
completer = Spree::Checkout::Complete.new.call(order: @payment.order)
|
46
|
+
|
47
|
+
if completer.success?
|
48
|
+
handle_order_process_completed
|
49
|
+
else
|
50
|
+
reason_code = extract_completer_failure_reason_code(completer.error)
|
51
|
+
handle_order_process_failure(reason_code, completer.error.to_s)
|
52
|
+
end
|
53
|
+
end
|
40
54
|
end
|
41
55
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
payment.void_transaction!
|
47
|
-
user_informer.payment_is_refunded(processing: false)
|
56
|
+
def handle_order_process_completed
|
57
|
+
log_process('handle_order_process_completed') do
|
58
|
+
Vpago::PaymentCapturerJob.perform_now(@payment.id) if @payment.pending?
|
59
|
+
user_informer.order_is_completed(processing: false)
|
48
60
|
end
|
49
|
-
|
50
|
-
failure(message)
|
51
61
|
end
|
52
62
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
63
|
+
def handle_order_process_failure(reason_code, reason_message = nil)
|
64
|
+
log_process('handle_order_process_failure', reason_code, reason_message) do
|
65
|
+
user_informer.order_process_failed(
|
66
|
+
processing: can_cancel_pre_auth?,
|
67
|
+
reason_code: reason_code,
|
68
|
+
reason_message: reason_message
|
69
|
+
)
|
59
70
|
|
60
|
-
|
61
|
-
|
71
|
+
cancel_pre_auth(reason_code, reason_message) if can_cancel_pre_auth?
|
72
|
+
failure(reason_message)
|
73
|
+
end
|
62
74
|
end
|
63
75
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
76
|
+
def handle_payment_failure(reason_code, reason_message)
|
77
|
+
log_process('handle_payment_failure', reason_code, reason_message) do
|
78
|
+
user_informer.payment_process_failed(
|
79
|
+
processing: can_cancel_pre_auth?,
|
80
|
+
reason_code: reason_code,
|
81
|
+
reason_message: reason_message
|
82
|
+
)
|
67
83
|
|
68
|
-
|
69
|
-
|
84
|
+
cancel_pre_auth(reason_code, reason_message) if can_cancel_pre_auth?
|
85
|
+
failure(reason_message)
|
86
|
+
end
|
70
87
|
end
|
71
88
|
end
|
72
89
|
end
|