spree_vpago 0.1.0.pre.beta → 2.0.5.pre.beta
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/helpers/vpago/admin/base_helper_decorator.rb +1 -0
- 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/spree/gateway/vattanac_mini_app.rb +75 -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 +23 -1
- data/app/models/vpago/payment_method_decorator.rb +7 -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/aes_encrypter.rb +56 -0
- data/app/services/vpago/payment_finder.rb +19 -3
- 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/rsa_handler.rb +27 -0
- data/app/services/vpago/user_informers/firebase.rb +21 -16
- data/app/services/vpago/vattanac_mini_app_data_handler.rb +33 -0
- data/app/views/spree/admin/payments/source_views/_vattanac_mini_app.html.erb +6 -0
- data/app/views/spree/admin/shared/_payment_methods_tabs.html.erb +7 -0
- data/app/views/spree/vpago_payments/forms/spree/gateway/_vattanac_mini_app.html.erb +89 -0
- data/app/views/spree/vpago_payments/processing.html.erb +36 -23
- data/lib/spree_vpago/engine.rb +2 -1
- 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/lib/vpago/vattanac_mini_app/base.rb +52 -0
- data/lib/vpago/vattanac_mini_app/checkout.rb +9 -0
- data/lib/vpago/vattanac_mini_app/refund_issuer.rb +54 -0
- data/node_modules/.yarn-integrity +93 -2
- data/package.json +6 -1
- data/yarn.lock +556 -2
- metadata +18 -2
@@ -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? || @payment.vattanac_mini_app_payment?
|
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
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Vpago
|
5
|
+
class RsaHandler
|
6
|
+
def initialize(private_key: nil, public_key: nil)
|
7
|
+
@private_key = private_key
|
8
|
+
@public_key = public_key
|
9
|
+
end
|
10
|
+
|
11
|
+
def sign(data)
|
12
|
+
raise 'Private key is required to sign data' unless @private_key
|
13
|
+
|
14
|
+
private_key_object = OpenSSL::PKey::RSA.new(@private_key)
|
15
|
+
signature = private_key_object.sign(OpenSSL::Digest.new('SHA256'), data)
|
16
|
+
"#{data}.#{Base64.strict_encode64(signature)}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def verify(data, signature)
|
20
|
+
raise 'Public key is required to verify signature' unless @public_key
|
21
|
+
|
22
|
+
public_key_object = OpenSSL::PKey::RSA.new(@public_key)
|
23
|
+
signature_bytes = Base64.decode64(signature)
|
24
|
+
public_key_object.verify(OpenSSL::Digest.new('SHA256'), signature_bytes, data)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -9,35 +9,40 @@ module Vpago
|
|
9
9
|
@order = order
|
10
10
|
end
|
11
11
|
|
12
|
-
def payment_is_processing(processing
|
13
|
-
def order_is_processing(processing
|
14
|
-
def order_is_completed(processing
|
15
|
-
def order_process_failed(processing:,
|
16
|
-
def payment_is_refunded(processing:,
|
17
|
-
def payment_process_failed(processing:,
|
18
|
-
|
19
|
-
def notify(
|
12
|
+
def payment_is_processing(processing:) = notify(:payment_is_processing, processing)
|
13
|
+
def order_is_processing(processing:) = notify(:order_is_processing, processing)
|
14
|
+
def order_is_completed(processing:) = notify(:order_is_completed, processing)
|
15
|
+
def order_process_failed(processing:, reason_code:, reason_message: nil) = notify(:order_process_failed, processing, reason_code, reason_message)
|
16
|
+
def payment_is_refunded(processing:, reason_code:, reason_message: nil) = notify(:payment_is_refunded, processing, reason_code, reason_message)
|
17
|
+
def payment_process_failed(processing:, reason_code:, reason_message: nil) = notify(:payment_process_failed, processing, reason_code, reason_message)
|
18
|
+
|
19
|
+
def notify(message_code, processing, reason_code = nil, reason_message = nil)
|
20
20
|
order.reload
|
21
21
|
|
22
22
|
data = {
|
23
23
|
processing: processing,
|
24
|
-
message_code:
|
25
|
-
|
24
|
+
message_code: message_code,
|
25
|
+
reason_code: reason_code,
|
26
|
+
reason_message: reason_message,
|
26
27
|
order_state: order.state,
|
27
28
|
payment_state: order.payment_state,
|
28
29
|
updated_at: Time.current
|
29
30
|
}.compact
|
30
31
|
|
31
32
|
firestore_reference.set(data, merge: true)
|
32
|
-
firestore_reference.col('histories').doc(
|
33
|
+
firestore_reference.col('histories').doc(message_code).set(data)
|
33
34
|
end
|
34
35
|
|
35
36
|
def firestore_reference
|
36
|
-
|
37
|
-
firestore.col('statuses')
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
order_created_date = order.created_at.strftime('%Y-%m-%d')
|
38
|
+
@firestore_reference ||= firestore.col('statuses')
|
39
|
+
.doc('cart')
|
40
|
+
.col(order_created_date)
|
41
|
+
.doc(order.number)
|
42
|
+
end
|
43
|
+
|
44
|
+
def document_reference_path
|
45
|
+
firestore_reference.path.split('/documents').last
|
41
46
|
end
|
42
47
|
|
43
48
|
def firestore
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Vpago
|
2
|
+
class VattanacMiniAppDataHandler
|
3
|
+
def decrypt_data(data)
|
4
|
+
encrypted_data, signature = data.to_s.split('.', 2)
|
5
|
+
|
6
|
+
return nil unless Vpago::RsaHandler.new(public_key: vattanac_public_key).verify(encrypted_data, signature)
|
7
|
+
|
8
|
+
decrypted = Vpago::AesEncrypter.decrypt(encrypted_data, aes_key)
|
9
|
+
JSON.parse(decrypted) rescue nil
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def encrypted_data(payload)
|
14
|
+
json_payload = payload.to_json
|
15
|
+
encrypted_data = Vpago::AesEncrypter.encrypt(json_payload, aes_key)
|
16
|
+
rsa_service = Vpago::RsaHandler.new(private_key: bookmeplus_private_key)
|
17
|
+
signed_data = rsa_service.sign(encrypted_data)
|
18
|
+
signed_data
|
19
|
+
end
|
20
|
+
|
21
|
+
def vattanac_public_key
|
22
|
+
ENV['VATTANAC_PUBLIC_KEY'].presence || Rails.application.credentials.vattanac.public_key
|
23
|
+
end
|
24
|
+
|
25
|
+
def bookmeplus_private_key
|
26
|
+
ENV['BOOKMEPLUS_PRIVATE_KEY'].presence || Rails.application.credentials.bookmeplus.private_key
|
27
|
+
end
|
28
|
+
|
29
|
+
def aes_key
|
30
|
+
ENV['VATTANAC_AES_SECRET_KEY'].presence || Rails.application.credentials.vattanac.aes_secret_key
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -12,4 +12,11 @@
|
|
12
12
|
admin_payment_methods_url(tab: :vendors),
|
13
13
|
class: "nav-link #{'active' if params[:tab] == 'vendors' }" %>
|
14
14
|
<% end if can?(:admin, Spree::PaymentMethod) %>
|
15
|
+
|
16
|
+
<%= content_tag :li, class: 'nav-item' do %>
|
17
|
+
<%= link_to_with_icon 'building.svg',
|
18
|
+
Spree.t(:tenants),
|
19
|
+
admin_payment_methods_url(tab: :tenants),
|
20
|
+
class: "nav-link #{'active' if params[:tab] == 'tenants' }" %>
|
21
|
+
<% end if can?(:admin, Spree::PaymentMethod) %>
|
15
22
|
<% end %>
|
@@ -0,0 +1,89 @@
|
|
1
|
+
<% @checkout = ::Vpago::VattanacMiniApp::Checkout.new(@payment) %>
|
2
|
+
<% @payment.user_informer.payment_is_processing(processing: true) %>
|
3
|
+
|
4
|
+
<p id="unsupported-message"
|
5
|
+
style="margin-top: 10px;
|
6
|
+
font-weight: bold;
|
7
|
+
color: red;
|
8
|
+
display: none;">
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<h1 id="status">THIS IS CURRENT STATE</h1>
|
12
|
+
<p id="reason_code"></p>
|
13
|
+
<p id="processing"></p>
|
14
|
+
<p id="reason_message"></p>
|
15
|
+
|
16
|
+
<script>
|
17
|
+
|
18
|
+
function detectMobileOS() {
|
19
|
+
const ua = navigator.userAgent || navigator.vendor || window.opera;
|
20
|
+
if (/iPad|iPhone|iPod/.test(ua) && !window.MSStream) return 'iOS';
|
21
|
+
if (/Android/i.test(ua)) return 'Android';
|
22
|
+
return 'unknown';
|
23
|
+
}
|
24
|
+
|
25
|
+
document.addEventListener('DOMContentLoaded', () => {
|
26
|
+
const os = detectMobileOS();
|
27
|
+
const payload = {
|
28
|
+
data: "<%= j @checkout.signed_payload %>",
|
29
|
+
paymentId: "<%= @checkout.payment_id %>"
|
30
|
+
};
|
31
|
+
|
32
|
+
if (os === 'iOS' && window.webkit?.messageHandlers?.startPayment) {
|
33
|
+
window.webkit.messageHandlers.startPayment.postMessage(JSON.stringify(payload));
|
34
|
+
} else if (os === 'Android' && window.AndroidInterface?.startPayment) {
|
35
|
+
window.AndroidInterface.startPayment(JSON.stringify(payload));
|
36
|
+
} else {
|
37
|
+
const unsupported = document.getElementById('unsupported-message');
|
38
|
+
unsupported.style.display = 'block';
|
39
|
+
unsupported.innerText = 'Unsupported OS';
|
40
|
+
}
|
41
|
+
|
42
|
+
const firebaseConfigs = <%= Rails.application.credentials.firebase_web_config.to_json.html_safe %>;
|
43
|
+
|
44
|
+
window.listenToProcessingState({
|
45
|
+
firebaseConfigs: firebaseConfigs,
|
46
|
+
documentReferencePath: "<%= @payment.user_informer.document_reference_path %>",
|
47
|
+
onPaymentIsProcessing: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
48
|
+
document.getElementById("status").innerText = "Payment is processing";
|
49
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
50
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
51
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
52
|
+
},
|
53
|
+
onOrderIsProcessing: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
54
|
+
document.getElementById("status").innerText = "Order is processing";
|
55
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
56
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
57
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
58
|
+
},
|
59
|
+
onOrderIsCompleted: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
60
|
+
document.getElementById("status").innerText = "Order is completed";
|
61
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
62
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
63
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
64
|
+
},
|
65
|
+
onOrderProcessFailed: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
66
|
+
document.getElementById("status").innerText = "Order process failed";
|
67
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
68
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
69
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
70
|
+
},
|
71
|
+
onPaymentIsRefunded: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
72
|
+
document.getElementById("status").innerText = "Payment is refunded";
|
73
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
74
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
75
|
+
document.getElementById("reason_message").innerText = reasonMessage
|
76
|
+
},
|
77
|
+
onPaymentProcessFailed: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
78
|
+
document.getElementById("status").innerText = "Payment process failed";
|
79
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
80
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
81
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
82
|
+
},
|
83
|
+
onCompleted: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
84
|
+
let successPath = "<%= @payment.success_url %>";
|
85
|
+
window.location.href = successPath;
|
86
|
+
},
|
87
|
+
});
|
88
|
+
});
|
89
|
+
</script>
|
@@ -1,49 +1,62 @@
|
|
1
1
|
<h1 id="status">THIS IS CURRENT STATE</h1>
|
2
|
-
<p id="
|
2
|
+
<p id="reason_code"></p>
|
3
|
+
<p id="processing"></p>
|
4
|
+
<p id="reason_message"></p>
|
3
5
|
|
4
6
|
<script>
|
5
7
|
document.addEventListener("DOMContentLoaded", function() {
|
6
8
|
let firebaseConfigs = <%= Rails.application.credentials.firebase_web_config.to_json.html_safe %>;
|
7
|
-
|
9
|
+
|
10
|
+
window.requestProcessPayment({
|
11
|
+
url: "<%= @payment.process_payment_url %>",
|
12
|
+
params: <%= params.except(:action, :controller, :amp).to_json.html_safe %>,
|
13
|
+
maxRetries: 5,
|
14
|
+
retryDelayMs: 1000,
|
15
|
+
})
|
8
16
|
|
9
17
|
window.listenToProcessingState({
|
10
18
|
firebaseConfigs: firebaseConfigs,
|
11
|
-
|
12
|
-
onPaymentIsProcessing: function (orderState, paymentState,
|
19
|
+
documentReferencePath: "<%= payment.user_informer.document_reference_path %>",
|
20
|
+
onPaymentIsProcessing: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
13
21
|
document.getElementById("status").innerText = "Payment is processing";
|
14
|
-
document.getElementById("
|
22
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
23
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
24
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
15
25
|
},
|
16
|
-
onOrderIsProcessing: function (orderState, paymentState,
|
26
|
+
onOrderIsProcessing: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
17
27
|
document.getElementById("status").innerText = "Order is processing";
|
18
|
-
document.getElementById("
|
28
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
29
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
30
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
19
31
|
},
|
20
|
-
onOrderIsCompleted: function (orderState, paymentState,
|
32
|
+
onOrderIsCompleted: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
21
33
|
document.getElementById("status").innerText = "Order is completed";
|
22
|
-
document.getElementById("
|
34
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
35
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
36
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
23
37
|
},
|
24
|
-
onOrderProcessFailed: function (orderState, paymentState,
|
38
|
+
onOrderProcessFailed: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
25
39
|
document.getElementById("status").innerText = "Order process failed";
|
26
|
-
document.getElementById("
|
40
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
41
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
42
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
27
43
|
},
|
28
|
-
onPaymentIsRefunded: function (orderState, paymentState,
|
44
|
+
onPaymentIsRefunded: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
29
45
|
document.getElementById("status").innerText = "Payment is refunded";
|
30
|
-
document.getElementById("
|
46
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
47
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
48
|
+
document.getElementById("reason_message").innerText = reasonMessage
|
31
49
|
},
|
32
|
-
onPaymentProcessFailed: function (orderState, paymentState,
|
50
|
+
onPaymentProcessFailed: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
33
51
|
document.getElementById("status").innerText = "Payment process failed";
|
34
|
-
document.getElementById("
|
52
|
+
document.getElementById("reason_code").innerText = reasonCode;
|
53
|
+
document.getElementById("processing").innerText = processing ? "Processing..." : "No more process.";
|
54
|
+
document.getElementById("reason_message").innerText = reasonMessage;
|
35
55
|
},
|
36
|
-
onCompleted: function (orderState, paymentState,
|
56
|
+
onCompleted: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
|
37
57
|
let successPath = "<%= @payment.success_url %>";
|
38
58
|
window.location.href = successPath;
|
39
59
|
},
|
40
60
|
});
|
41
61
|
});
|
42
|
-
|
43
|
-
window.requestProcessPayment({
|
44
|
-
url: "<%= @payment.process_payment_url %>",
|
45
|
-
params: <%= params.except(:action, :controller, :amp).to_json.html_safe %>,
|
46
|
-
maxRetries: 5,
|
47
|
-
retryDelayMs: 1000,
|
48
|
-
})
|
49
62
|
</script>
|
data/lib/spree_vpago/engine.rb
CHANGED
data/lib/spree_vpago/version.rb
CHANGED
data/lib/vpago/payway_v2/base.rb
CHANGED
@@ -46,16 +46,17 @@ module Vpago
|
|
46
46
|
@payment.number
|
47
47
|
end
|
48
48
|
|
49
|
+
# optional
|
49
50
|
def email
|
50
51
|
@payment.order.email.presence || ENV.fetch('DEFAULT_EMAIL_FOR_PAYMENT', nil)
|
51
52
|
end
|
52
53
|
|
53
54
|
def first_name
|
54
|
-
@payment.order.billing_address
|
55
|
+
@payment.order.billing_address&.first_name&.strip
|
55
56
|
end
|
56
57
|
|
57
58
|
def last_name
|
58
|
-
@payment.order.billing_address
|
59
|
+
@payment.order.billing_address&.last_name&.strip
|
59
60
|
end
|
60
61
|
|
61
62
|
def return_url
|
@@ -79,12 +80,9 @@ module Vpago
|
|
79
80
|
Vpago::Payway::CARD_TYPES.index(card_option).nil? ? Vpago::Payway::CARD_TYPE_ABAPAY : card_option
|
80
81
|
end
|
81
82
|
|
82
|
-
|
83
|
-
'+855'
|
84
|
-
end
|
85
|
-
|
83
|
+
# optional
|
86
84
|
def phone
|
87
|
-
@payment.order.billing_address
|
85
|
+
@payment.order.billing_address&.phone
|
88
86
|
end
|
89
87
|
|
90
88
|
def api_key
|
@@ -128,8 +126,10 @@ module Vpago
|
|
128
126
|
end
|
129
127
|
|
130
128
|
def hash_data
|
131
|
-
result = "#{req_time}#{merchant_id}#{transaction_id}#{amount}#{first_name}#{last_name}
|
129
|
+
result = "#{req_time}#{merchant_id}#{transaction_id}#{amount}#{first_name}#{last_name}"
|
132
130
|
|
131
|
+
result += email if email.present?
|
132
|
+
result += phone if phone.present?
|
133
133
|
result += type if type.present?
|
134
134
|
result += payment_option if payment_option.present?
|
135
135
|
result += return_url if return_url.present?
|
@@ -10,8 +10,6 @@ module Vpago
|
|
10
10
|
type: type,
|
11
11
|
firstname: first_name,
|
12
12
|
lastname: last_name,
|
13
|
-
email: email,
|
14
|
-
phone: phone,
|
15
13
|
payment_option: payment_option,
|
16
14
|
return_url: return_url,
|
17
15
|
continue_success_url: continue_success_url,
|
@@ -19,6 +17,8 @@ module Vpago
|
|
19
17
|
hash: hash_hmac
|
20
18
|
}
|
21
19
|
|
20
|
+
result[:email] = email unless email.nil?
|
21
|
+
result[:phone] = phone unless phone.nil?
|
22
22
|
result[:return_deeplink] = return_deeplink unless return_deeplink.nil?
|
23
23
|
result[:view_type] = view_type unless view_type.nil?
|
24
24
|
result[:payout] = payout unless payout.nil?
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Vpago
|
2
|
+
module VattanacMiniApp
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def initialize(payment, options = {})
|
6
|
+
@options = options
|
7
|
+
@payment = payment
|
8
|
+
end
|
9
|
+
|
10
|
+
def payload
|
11
|
+
{
|
12
|
+
paymentId: payment_id,
|
13
|
+
amount: amount,
|
14
|
+
currency: currency || 'USD',
|
15
|
+
expiredIn: expired_at
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def encrypt_data(payload)
|
20
|
+
SpreeCmCommissioner::AesEncryptionService.encrypt(payload.to_json, aes_key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def amount
|
24
|
+
@payment.amount
|
25
|
+
end
|
26
|
+
|
27
|
+
def payment_id
|
28
|
+
@payment.number
|
29
|
+
end
|
30
|
+
|
31
|
+
def currency
|
32
|
+
'USD'
|
33
|
+
end
|
34
|
+
|
35
|
+
def transaction_id
|
36
|
+
@payment.number
|
37
|
+
end
|
38
|
+
|
39
|
+
def expired_at
|
40
|
+
(Time.now + 30.minutes).to_i * 1000
|
41
|
+
end
|
42
|
+
|
43
|
+
def partner_code
|
44
|
+
ENV['VATTANAC_PARTNER_CODE'].presence
|
45
|
+
end
|
46
|
+
|
47
|
+
def refund_url
|
48
|
+
ENV['VATTANAC_REFUND_URL'].presence
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|