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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test_and_publish_gem.yml +2 -0
  3. data/Gemfile.lock +25 -23
  4. data/app/assets/javascripts/vpago/vpago_payments/user_informers/firebase.js +1686 -1426
  5. data/app/controllers/spree/admin/payment_methods_controller_decorator.rb +9 -1
  6. data/app/controllers/spree/vpago_payments_controller.rb +12 -2
  7. data/app/helpers/vpago/admin/base_helper_decorator.rb +1 -0
  8. data/app/javascripts/queue_processor.js +33 -0
  9. data/app/javascripts/queue_processor.test.js +88 -0
  10. data/app/javascripts/vpago/vpago_payments/user_informers/firebase.js +92 -13
  11. data/app/jobs/vpago/payment_capturer_job.rb +11 -0
  12. data/app/jobs/vpago/payment_processor_job.rb +3 -0
  13. data/app/models/spree/gateway/vattanac_mini_app.rb +75 -0
  14. data/app/models/vpago/address_decorator.rb +10 -0
  15. data/app/models/vpago/order_decorator.rb +13 -1
  16. data/app/models/vpago/payment_decorator.rb +23 -1
  17. data/app/models/vpago/payment_method_decorator.rb +7 -1
  18. data/app/overrides/spree/admin/payment_methods/index/payment_methods_tabs.html.erb.deface +7 -1
  19. data/app/overrides/spree/admin/payment_methods/index/tenant_body.html.erb.deface +5 -0
  20. data/app/overrides/spree/admin/payment_methods/index/tenant_header.html.erb.deface +5 -0
  21. data/app/serializers/spree/v2/storefront/payment_serializer_decorator.rb +1 -1
  22. data/app/services/vpago/aes_encrypter.rb +56 -0
  23. data/app/services/vpago/payment_finder.rb +19 -3
  24. data/app/services/vpago/payment_processable.rb +54 -0
  25. data/app/services/vpago/payment_processor.rb +58 -41
  26. data/app/services/vpago/payment_url_constructor.rb +1 -1
  27. data/app/services/vpago/rsa_handler.rb +27 -0
  28. data/app/services/vpago/user_informers/firebase.rb +21 -16
  29. data/app/services/vpago/vattanac_mini_app_data_handler.rb +33 -0
  30. data/app/views/spree/admin/payments/source_views/_vattanac_mini_app.html.erb +6 -0
  31. data/app/views/spree/admin/shared/_payment_methods_tabs.html.erb +7 -0
  32. data/app/views/spree/vpago_payments/forms/spree/gateway/_vattanac_mini_app.html.erb +89 -0
  33. data/app/views/spree/vpago_payments/processing.html.erb +36 -23
  34. data/lib/spree_vpago/engine.rb +2 -1
  35. data/lib/spree_vpago/version.rb +1 -1
  36. data/lib/vpago/payway_v2/base.rb +8 -8
  37. data/lib/vpago/payway_v2/checkout.rb +2 -2
  38. data/lib/vpago/vattanac_mini_app/base.rb +52 -0
  39. data/lib/vpago/vattanac_mini_app/checkout.rb +9 -0
  40. data/lib/vpago/vattanac_mini_app/refund_issuer.rb +54 -0
  41. data/node_modules/.yarn-integrity +93 -2
  42. data/package.json +6 -1
  43. data/yarn.lock +556 -2
  44. 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
- attr_accessor :payment, :error
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
- return process_order if payment.completed? || payment.pending?
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
- mark_payment_process_failed
15
- rescue Spree::Core::GatewayError, StateMachines::InvalidTransition => e
16
- mark_payment_process_failed(e.message)
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
- user_informer.payment_is_processing(processing: true)
23
- payment.process!
24
- end
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 mark_order_process_completed
38
- payment.capture! if payment.pending?
39
- user_informer.order_is_completed(processing: false)
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 mark_order_process_failed(message)
43
- user_informer.order_process_failed(processing: payment.pending?, log_message: message)
44
-
45
- if payment.pending?
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 mark_payment_process_failed(message = nil)
54
- message ||= Spree.t(:payment_process_failed)
55
-
56
- user_informer.payment_process_failed(processing: false, log_message: message)
57
- failure(message)
58
- end
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
- def user_informer
61
- @user_informer ||= ::Vpago::UserInformers::Firebase.new(payment.order)
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 success?
65
- @error.nil?
66
- end
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
- def failure(error)
69
- @error = error
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
@@ -21,7 +21,7 @@ module Vpago
21
21
  private
22
22
 
23
23
  def base_url
24
- ENV.fetch('DEFAULT_URL_HOST')
24
+ order.payment_host
25
25
  end
26
26
 
27
27
  def order_jwt_token
@@ -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:, log_message: nil) = notify(:payment_is_processing, processing, log_message)
13
- def order_is_processing(processing:, log_message: nil) = notify(:order_is_processing, processing, log_message)
14
- def order_is_completed(processing:, log_message: nil) = notify(:order_is_completed, processing, log_message)
15
- def order_process_failed(processing:, log_message: nil) = notify(:order_process_failed, processing, log_message)
16
- def payment_is_refunded(processing:, log_message: nil) = notify(:payment_is_refunded, processing, log_message)
17
- def payment_process_failed(processing:, log_message: nil) = notify(:payment_process_failed, processing, log_message)
18
-
19
- def notify(message, processing, log_message = nil)
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: message,
25
- log_message: log_message,
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(message).set(data)
33
+ firestore_reference.col('histories').doc(message_code).set(data)
33
34
  end
34
35
 
35
36
  def firestore_reference
36
- current_date = Time.current.strftime('%Y-%m-%d')
37
- firestore.col('statuses')
38
- .doc('cart')
39
- .col(current_date)
40
- .doc(order.number)
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
@@ -0,0 +1,6 @@
1
+ <%= render 'spree/admin/payments/source_views/vpago_payment_tmpl',
2
+ payment: payment,
3
+ check_status_url: '',
4
+ heal_payment_url: '',
5
+ manual_update_payment_url: ''
6
+ %>
@@ -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="message"></p>
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
- let orderNumber = "<%= @order.number %>";
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
- orderNumber: orderNumber,
12
- onPaymentIsProcessing: function (orderState, paymentState, logMessage) {
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("message").innerText = logMessage;
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, logMessage) {
26
+ onOrderIsProcessing: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
17
27
  document.getElementById("status").innerText = "Order is processing";
18
- document.getElementById("message").innerText = logMessage;
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, logMessage) {
32
+ onOrderIsCompleted: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
21
33
  document.getElementById("status").innerText = "Order is completed";
22
- document.getElementById("message").innerText = logMessage;
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, logMessage) {
38
+ onOrderProcessFailed: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
25
39
  document.getElementById("status").innerText = "Order process failed";
26
- document.getElementById("message").innerText = logMessage;
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, logMessage) {
44
+ onPaymentIsRefunded: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
29
45
  document.getElementById("status").innerText = "Payment is refunded";
30
- document.getElementById("message").innerText = logMessage
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, logMessage) {
50
+ onPaymentProcessFailed: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
33
51
  document.getElementById("status").innerText = "Payment process failed";
34
- document.getElementById("message").innerText = logMessage;
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, logMessage) {
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>
@@ -21,7 +21,8 @@ module SpreeVpago
21
21
  Spree::Gateway::PaywayV2,
22
22
  Spree::Gateway::WingSdk,
23
23
  Spree::Gateway::Acleda,
24
- Spree::Gateway::AcledaMobile
24
+ Spree::Gateway::AcledaMobile,
25
+ Spree::Gateway::VattanacMiniApp
25
26
  )
26
27
  end
27
28
 
@@ -1,7 +1,7 @@
1
1
  module SpreeVpago
2
2
  module_function
3
3
 
4
- VERSION = '0.1.0-beta'.freeze
4
+ VERSION = '2.0.5-beta'.freeze
5
5
 
6
6
  def version
7
7
  Gem::Version.new VERSION
@@ -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.first_name.strip
55
+ @payment.order.billing_address&.first_name&.strip
55
56
  end
56
57
 
57
58
  def last_name
58
- @payment.order.billing_address.last_name.strip
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
- def phone_country_code
83
- '+855'
84
- end
85
-
83
+ # optional
86
84
  def phone
87
- @payment.order.billing_address.phone
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}#{email}#{phone}"
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
@@ -0,0 +1,9 @@
1
+ module Vpago
2
+ module VattanacMiniApp
3
+ class Checkout < Base
4
+ def signed_payload
5
+ Vpago::VattanacMiniAppDataHandler.new.encrypted_data(payload)
6
+ end
7
+ end
8
+ end
9
+ end