spree_vpago 2.0.8 → 2.0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d952b332b1f3cebefb707805b281c0ae90755496444576599e89bf6fbd3ec8d
4
- data.tar.gz: e02298610af4eb99263b22b9b0a0893444ad96d36bf6b97d79407f18098737bd
3
+ metadata.gz: d6948c5b578075f3a30c3d6b03e19eedebc25a4fcb17b135dc5e94d4dde36101
4
+ data.tar.gz: eddcd8654c96fc90f535b56c3a2d590ac62ca6225293a3c56873f9a58f6721c6
5
5
  SHA512:
6
- metadata.gz: 6d15586610e25f52a3311a9c7e181569c876b0a01a35908b5a53d35b70d69ba86f0bafccb4b1f2387ad79a3e97a55182a718b62cd266c884f49b88c3f379727c
7
- data.tar.gz: e57f32d3047acd6bb245fcc727ba751493ac88360cbeb747cdab45d5559e2a9b5d54770237039a619a7e46c833c8753988591ad1cd5a861667b777754c72bda8
6
+ metadata.gz: c9196c2b45a53620fcc66a375d95c301b1d424133517dfe6787e183fe1795d3d91768e7015d048de79d99b905bedf01dc9a60a5db5837c0aa0e6d6c817526699
7
+ data.tar.gz: c75a4d97aa0ce177c4b7fde7c580964e35e2ea47f625dea08eaf920bae1b6916c12ba477e5aa052705fbffacfe7277cfbea4653bd2d225bd9c7c2f27b6d15530
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- spree_vpago (2.0.8)
4
+ spree_vpago (2.0.9.pre.beta)
5
5
  faraday
6
6
  google-cloud-firestore
7
7
  spree_api (>= 4.5)
@@ -242,7 +242,7 @@ GEM
242
242
  gapic-common (~> 1.0)
243
243
  google-cloud-errors (~> 1.0)
244
244
  google-logging-utils (0.2.0)
245
- google-protobuf (4.31.1)
245
+ google-protobuf (4.31.1-arm64-darwin)
246
246
  bigdecimal
247
247
  rake (>= 13)
248
248
  googleapis-common-protos (1.8.0)
@@ -259,7 +259,7 @@ GEM
259
259
  multi_json (~> 1.11)
260
260
  os (>= 0.9, < 2.0)
261
261
  signet (>= 0.16, < 2.a)
262
- grpc (1.73.0)
262
+ grpc (1.73.0-arm64-darwin)
263
263
  google-protobuf (>= 3.25, < 5.0)
264
264
  googleapis-common-protos-types (~> 1.0)
265
265
  hashdiff (1.1.0)
@@ -1,3 +1,4 @@
1
1
  //= link_tree ../images
2
2
  //= link vpago/vpago_payments/request_process_payment.js
3
3
  //= link vpago/vpago_payments/user_informers/firebase.js
4
+ //= link vpago/vpago_payments/payment_processing_listener.js
@@ -0,0 +1,46 @@
1
+ window.initPaymentProcessingListener = async function (
2
+ firebaseConfigs,
3
+ documentReferencePath,
4
+ successUrl
5
+ ) {
6
+ function log(status, processing, reasonCode, reasonMessage) {
7
+ console.log(`Status: ${status}`);
8
+ console.log(`Reason Code: ${reasonCode}`);
9
+ console.log(`Processing: ${processing ? "Processing..." : "No more process."}`);
10
+ console.log(`Reason Message: ${reasonMessage}`);
11
+ }
12
+
13
+ window.listenToProcessingState({
14
+ firebaseConfigs,
15
+ documentReferencePath,
16
+
17
+ onPaymentIsProcessing(orderState, paymentState, processing, reasonCode, reasonMessage) {
18
+ log("Payment is processing", processing, reasonCode, reasonMessage);
19
+ },
20
+
21
+ onOrderIsProcessing(orderState, paymentState, processing, reasonCode, reasonMessage) {
22
+ log("Order is processing", processing, reasonCode, reasonMessage);
23
+ },
24
+
25
+ onOrderIsCompleted(orderState, paymentState, processing, reasonCode, reasonMessage) {
26
+ log("Order is completed", processing, reasonCode, reasonMessage);
27
+ },
28
+
29
+ onOrderProcessFailed(orderState, paymentState, processing, reasonCode, reasonMessage) {
30
+ log("Order process failed", processing, reasonCode, reasonMessage);
31
+ },
32
+
33
+ onPaymentIsRefunded(orderState, paymentState, processing, reasonCode, reasonMessage) {
34
+ log("Payment is refunded", processing, reasonCode, reasonMessage);
35
+ },
36
+
37
+ onPaymentProcessFailed(orderState, paymentState, processing, reasonCode, reasonMessage) {
38
+ log("Payment process failed", processing, reasonCode, reasonMessage);
39
+ },
40
+
41
+ onCompleted(orderState, paymentState, processing, reasonCode, reasonMessage) {
42
+ log("Completed — redirecting to success URL", processing, reasonCode, reasonMessage);
43
+ window.location.href = successUrl;
44
+ }
45
+ });
46
+ };
@@ -5,7 +5,7 @@ module Spree
5
5
 
6
6
  skip_before_action :verify_authenticity_token, only: [:process_payment]
7
7
 
8
- rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
8
+ rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
9
9
  rescue_from CanCan::AccessDenied, with: :access_denied
10
10
 
11
11
  # GET
@@ -19,6 +19,7 @@ module Vpago
19
19
  payway_cards
20
20
  wingpay
21
21
  vattanac_mini_app
22
+ true_money
22
23
  ]
23
24
  end
24
25
 
@@ -0,0 +1,91 @@
1
+ module Spree
2
+ class Gateway::TrueMoney < PaymentMethod
3
+ preference :check_transaction_url, :string
4
+ preference :refund_url, :string
5
+ preference :generate_payment_url, :string
6
+ preference :access_token_url, :string
7
+
8
+ preference :client_id, :string
9
+ preference :client_secret, :string
10
+ preference :private_key, :text
11
+ preference :return_url_scheme, :string
12
+ preference :android_package_name, :string
13
+ preference :redirect_type, :string
14
+
15
+ def method_type
16
+ 'true_money'
17
+ end
18
+
19
+ def payment_source_class
20
+ Spree::VpagoPaymentSource
21
+ end
22
+
23
+ # force to purchase instead of authorize
24
+ def auto_capture?
25
+ true
26
+ end
27
+
28
+ # override
29
+ # purchase is used when pre auth disabled
30
+ def purchase(_amount, _source, gateway_options = {})
31
+ _, payment_number = gateway_options[:order_id].split('-')
32
+ payment = Spree::Payment.find_by(number: payment_number)
33
+
34
+ checker = check_transaction(payment)
35
+ payment.update(transaction_response: checker.json_response)
36
+
37
+ success = checker.success?
38
+ params = {}
39
+
40
+ params[:payment_response] = payment.transaction_response
41
+
42
+ if success
43
+ ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: Purchased', params)
44
+ else
45
+ ActiveMerchant::Billing::Response.new(false, 'Payway Gateway: Purchasing Failed', params)
46
+ end
47
+ end
48
+
49
+ # override
50
+ def void(_response_code, gateway_options)
51
+ _, payment_number = gateway_options[:order_id].split('-')
52
+ payment = Spree::Payment.find_by(number: payment_number)
53
+
54
+ if payment.true_money_payment?
55
+ params = {}
56
+ success, params[:refund_response] = true_money_refund(payment)
57
+
58
+ if success
59
+ ActiveMerchant::Billing::Response.new(true, 'True money Gateway: successfully canceled.', params)
60
+ else
61
+ ActiveMerchant::Billing::Response.new(false, 'True money Gateway: Failed to canceleed', params)
62
+ end
63
+ else
64
+ ActiveMerchant::Billing::Response.new(true, 'True money Gateway: Payment has been voided.')
65
+ end
66
+ end
67
+
68
+ def true_money_refund(payment)
69
+ refund_issuer = Vpago::TrueMoney::RefundIssuer.new(payment, {})
70
+ refund_issuer.call
71
+
72
+ [refund_issuer.success?, refund_issuer.parsed_response]
73
+ end
74
+
75
+ def cancel(_response_code, _payment)
76
+ # we can use this to send request to payment gateway api to cancel the payment ( void )
77
+ # currently True money does not support to cancel the gateway
78
+
79
+ # in our case don't do anything
80
+ ActiveMerchant::Billing::Response.new(true, '')
81
+ end
82
+
83
+ private
84
+
85
+ def check_transaction(payment)
86
+ checker = Vpago::TrueMoney::TransactionStatus.new(payment)
87
+ checker.call
88
+ checker
89
+ end
90
+ end
91
+ end
@@ -5,6 +5,7 @@ module Vpago
5
5
  base.after_create -> { Vpago::PayoutsGenerator.new(self).call }, if: :should_generate_payouts?
6
6
 
7
7
  base.delegate :checkout_url,
8
+ :web_checkout_url,
8
9
  :processing_url,
9
10
  :success_url,
10
11
  :process_payment_url,
@@ -56,10 +57,13 @@ module Vpago
56
57
  pre_auth_status == 'CANCELLED'
57
58
  end
58
59
 
60
+ def true_money_payment?
61
+ payment_method.type_true_money?
62
+ end
63
+
59
64
  def vattanac_mini_app_payment?
60
65
  payment_method.type_vattanac_mini_app?
61
66
  end
62
-
63
67
  end
64
68
  end
65
69
 
@@ -6,6 +6,7 @@ module Vpago
6
6
  TYPE_ACLEDA = 'Spree::Gateway::Acleda'.freeze
7
7
  TYPE_ACLEDA_MOBILE = 'Spree::Gateway::AcledaMobile'.freeze
8
8
  TYPE_VATTANAC_MINI_APP = 'Spree::Gateway::VattanacMiniApp'.freeze
9
+ TYPE_TRUE_MONEY = 'Spree::Gateway::TrueMoney'.freeze
9
10
 
10
11
  def self.prepended(base)
11
12
  base.preference :icon_name, :string, default: 'cheque'
@@ -18,7 +19,8 @@ module Vpago
18
19
  Spree::PaymentMethod::TYPE_WINGSDK,
19
20
  Spree::PaymentMethod::TYPE_ACLEDA,
20
21
  Spree::PaymentMethod::TYPE_ACLEDA_MOBILE,
21
- Spree::PaymentMethod::TYPE_VATTANAC_MINI_APP
22
+ Spree::PaymentMethod::TYPE_VATTANAC_MINI_APP,
23
+ Spree::PaymentMethod::TYPE_TRUE_MONEY
22
24
  ]
23
25
  end
24
26
  end
@@ -91,6 +93,10 @@ module Vpago
91
93
  def type_vattanac_mini_app?
92
94
  type == Spree::PaymentMethod::TYPE_VATTANAC_MINI_APP
93
95
  end
96
+
97
+ def type_true_money?
98
+ type == Spree::PaymentMethod::TYPE_TRUE_MONEY
99
+ end
94
100
  end
95
101
  end
96
102
 
@@ -3,7 +3,7 @@ module Spree
3
3
  module Storefront
4
4
  module PaymentSerializerDecorator
5
5
  def self.prepended(base)
6
- base.attributes :pre_auth_status, :checkout_url, :process_payment_url
6
+ base.attributes :pre_auth_status, :checkout_url, :process_payment_url, :web_checkout_url
7
7
  end
8
8
  end
9
9
  end
@@ -16,7 +16,7 @@ module Vpago
16
16
  # 2. Pre-auth is enabled, ensuring funds can be released to user if processing fails.
17
17
  # PaymentProcessor is usually called after payment is made, so canceling pre-auth typically works.
18
18
  def can_cancel_pre_auth?
19
- @payment.pending? || @payment.payment_method.enable_pre_auth? || @payment.vattanac_mini_app_payment?
19
+ @payment.pending? || @payment.payment_method.enable_pre_auth? || @payment.vattanac_mini_app_payment? || @payment.true_money_payment?
20
20
  end
21
21
 
22
22
  def extract_completer_failure_reason_code(error)
@@ -9,13 +9,23 @@ module Vpago
9
9
  @order = payment.order
10
10
  end
11
11
 
12
- def checkout_url = "#{base_url}/vpago_payments/checkout?#{query}"
12
+ def checkout_url = "#{base_url}/vpago_payments/checkout?#{query}&platform=app"
13
+ def web_checkout_url = "#{base_url}/vpago_payments/checkout?#{query}&platform=web"
13
14
  def processing_url = "#{base_url}/vpago_payments/processing?#{query}"
14
15
  def success_url = "#{base_url}/vpago_payments/success?#{query}"
15
16
  def process_payment_url = "#{base_url}/vpago_payments/process_payment?#{query}"
16
17
 
17
18
  def query
18
- { payment_number: payment.number, order_number: order.number, order_jwt_token: order_jwt_token }.to_query
19
+ {
20
+ payment_number: payment.number,
21
+ order_number: order.number,
22
+ order_jwt_token: order_jwt_token,
23
+ offsite_payment: offsite_payment? ? true : nil
24
+ }.compact.to_query
25
+ end
26
+
27
+ def offsite_payment?
28
+ payment.payment_method.type_true_money?
19
29
  end
20
30
 
21
31
  private
@@ -23,5 +23,11 @@ module Vpago
23
23
  signature_bytes = Base64.decode64(signature)
24
24
  public_key_object.verify(OpenSSL::Digest.new('SHA256'), signature_bytes, data)
25
25
  end
26
+
27
+ def generate_signature(payload)
28
+ sign_hash = OpenSSL::Digest.new('SHA256')
29
+ private_key = OpenSSL::PKey::RSA.new(@private_key)
30
+ Base64.strict_encode64(private_key.sign(sign_hash, payload))
31
+ end
26
32
  end
27
- end
33
+ end
@@ -8,6 +8,7 @@
8
8
  <%= csrf_meta_tags %>
9
9
  <%= csp_meta_tag %>
10
10
 
11
+ <%= javascript_include_tag "vpago/vpago_payments/payment_processing_listener", 'data-turbo-track': 'reload' %>
11
12
  <%= javascript_include_tag "vpago/vpago_payments/request_process_payment", 'data-turbo-track': 'reload' %>
12
13
  <%= javascript_include_tag "vpago/vpago_payments/user_informers/#{user_informer}", 'data-turbo-track': 'reload' %>
13
14
  </head>
@@ -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
+ %>
@@ -0,0 +1,36 @@
1
+ <% @checkout = ::Vpago::TrueMoney::Checkout.new(@payment) %>
2
+ <% redirect_urls = @checkout.generate_payment_urls(params[:platform]) %>
3
+ <% @payment.user_informer.payment_is_processing(processing: true) %>
4
+ <h1> <%= params[:platform] %> </h1>
5
+ <script>
6
+ document.addEventListener("DOMContentLoaded", () => {
7
+ const platform = "<%= params[:platform] %>";
8
+ const redirectUrls = <%= redirect_urls.to_json.html_safe %>;
9
+ const confirmButton = document.getElementById("confirm-payment-button");
10
+ const firebaseConfigs = <%= Rails.application.credentials.firebase_web_config.to_json.html_safe %>;
11
+ const docRefPath = "<%= @payment.user_informer.document_reference_path %>";
12
+ const successUrl = "<%= @payment.success_url %>";
13
+
14
+ function setupConfirmPaymentButton() {
15
+ if (platform === "app") {
16
+ if (confirmButton) confirmButton.style.display = "none";
17
+ window.location.href = redirectUrls.webview;
18
+ window.top.location.href = redirectUrls.deeplink;
19
+ } else if (confirmButton) {
20
+ confirmButton.addEventListener("click", () => window.open(redirectUrls.webview , "_blank"));
21
+
22
+ }
23
+ }
24
+
25
+ function setupPaymentProcessingListener() {
26
+ if (window.initPaymentProcessingListener) {
27
+ window.initPaymentProcessingListener(firebaseConfigs, docRefPath, successUrl);
28
+ } else {
29
+ console.warn("initPaymentProcessingListener is not defined");
30
+ }
31
+ }
32
+
33
+ setupConfirmPaymentButton();
34
+ setupPaymentProcessingListener();
35
+ });
36
+ </script>
@@ -2,92 +2,51 @@
2
2
  <% @payment.user_informer.payment_is_processing(processing: true) %>
3
3
 
4
4
  <p id="unsupported-message"
5
- style="margin-top: 10px;
6
- font-weight: bold;
7
- color: red;
8
- display: none;">
5
+ style="margin-top: 10px; font-weight: bold; color: red; display: none;">
9
6
  </p>
10
7
 
11
8
  <script>
12
- function detectMobileOS() {
13
- const ua = navigator.userAgent || navigator.vendor || window.opera;
14
- if (/iPad|iPhone|iPod/.test(ua) && !window.MSStream) return 'iOS';
15
- if (/Android/i.test(ua)) return 'Android';
16
- return 'unknown';
17
- }
18
-
19
- document.addEventListener('DOMContentLoaded', () => {
20
- const os = detectMobileOS();
21
- const payload = {
22
- data: "<%= j @checkout.signed_payload %>",
23
- paymentId: "<%= @checkout.payment_id %>"
9
+ document.addEventListener("DOMContentLoaded", () => {
10
+
11
+ const setupMiniAppPayment = () => {
12
+ const detectMobileOS = () => {
13
+ const ua = navigator.userAgent || navigator.vendor || window.opera;
14
+ if (/iPad|iPhone|iPod/.test(ua) && !window.MSStream) return 'iOS';
15
+ if (/Android/i.test(ua)) return 'Android';
16
+ return 'unknown';
17
+ };
18
+
19
+ const os = detectMobileOS();
20
+ const payload = {
21
+ data: "<%= j @checkout.signed_payload %>",
22
+ paymentId: "<%= @checkout.payment_id %>"
23
+ };
24
+
25
+ if (os === 'iOS' && window.webkit?.messageHandlers?.startPayment) {
26
+ window.webkit.messageHandlers.startPayment.postMessage(JSON.stringify(payload));
27
+ } else if (os === 'Android' && window.AndroidInterface?.startPayment) {
28
+ window.AndroidInterface.startPayment(JSON.stringify(payload));
29
+ } else {
30
+ const unsupported = document.getElementById('unsupported-message');
31
+ unsupported.style.display = 'block';
32
+ unsupported.innerText = 'Unsupported OS';
33
+ }
24
34
  };
25
35
 
26
- if (os === 'iOS' && window.webkit?.messageHandlers?.startPayment) {
27
- window.webkit.messageHandlers.startPayment.postMessage(JSON.stringify(payload));
28
- } else if (os === 'Android' && window.AndroidInterface?.startPayment) {
29
- window.AndroidInterface.startPayment(JSON.stringify(payload));
30
- } else {
31
- const unsupported = document.getElementById('unsupported-message');
32
- unsupported.style.display = 'block';
33
- unsupported.innerText = 'Unsupported OS';
34
- }
35
-
36
- const firebaseConfigs = <%= Rails.application.credentials.firebase_web_config.to_json.html_safe %>;
37
-
38
- window.listenToProcessingState({
39
- firebaseConfigs: firebaseConfigs,
40
- documentReferencePath: "<%= @payment.user_informer.document_reference_path %>",
41
-
42
- onPaymentIsProcessing: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
43
- console.log("Status: Payment is processing");
44
- console.log("Reason Code:", reasonCode);
45
- console.log("Processing:", processing ? "Processing..." : "No more process.");
46
- console.log("Reason Message:", reasonMessage);
47
- },
48
-
49
- onOrderIsProcessing: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
50
- console.log("Status: Order is processing");
51
- console.log("Reason Code:", reasonCode);
52
- console.log("Processing:", processing ? "Processing..." : "No more process.");
53
- console.log("Reason Message:", reasonMessage);
54
- },
36
+ const setupPaymentProcessingListener = () => {
37
+ const firebaseConfigs = <%= Rails.application.credentials.firebase_web_config.to_json.html_safe %>;
38
+ const documentReferencePath = "<%= @payment.user_informer.document_reference_path %>";
39
+ const successUrl = "<%= @payment.success_url %>";
55
40
 
56
- onOrderIsCompleted: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
57
- console.log("Status: Order is completed");
58
- console.log("Reason Code:", reasonCode);
59
- console.log("Processing:", processing ? "Processing..." : "No more process.");
60
- console.log("Reason Message:", reasonMessage);
61
- },
62
-
63
- onOrderProcessFailed: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
64
- console.log("Status: Order process failed");
65
- console.log("Reason Code:", reasonCode);
66
- console.log("Processing:", processing ? "Processing..." : "No more process.");
67
- console.log("Reason Message:", reasonMessage);
68
- },
69
-
70
- onPaymentIsRefunded: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
71
- console.log("Status: Payment is refunded");
72
- console.log("Reason Code:", reasonCode);
73
- console.log("Processing:", processing ? "Processing..." : "No more process.");
74
- console.log("Reason Message:", reasonMessage);
75
- },
76
-
77
- onPaymentProcessFailed: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
78
- console.log("Status: Payment process failed");
79
- console.log("Reason Code:", reasonCode);
80
- console.log("Processing:", processing ? "Processing..." : "No more process.");
81
- console.log("Reason Message:", reasonMessage);
82
- },
83
-
84
- onCompleted: function (orderState, paymentState, processing, reasonCode, reasonMessage) {
85
- console.log("Status: Completed — redirecting to success URL");
86
- console.log("Reason Code:", reasonCode);
87
- console.log("Reason Message:", reasonMessage);
88
- let successPath = "<%= @payment.success_url %>";
89
- window.location.href = successPath;
90
- },
91
- });
41
+ if (window.initPaymentProcessingListener) {
42
+ window.initPaymentProcessingListener(firebaseConfigs, documentReferencePath, successUrl);
43
+ } else {
44
+ console.log("initPaymentProcessingListener is not defined");
45
+ }
46
+ };
47
+
48
+ setupMiniAppPayment();
49
+ setupPaymentProcessingListener();
50
+
92
51
  });
93
52
  </script>
@@ -22,7 +22,8 @@ module SpreeVpago
22
22
  Spree::Gateway::WingSdk,
23
23
  Spree::Gateway::Acleda,
24
24
  Spree::Gateway::AcledaMobile,
25
- Spree::Gateway::VattanacMiniApp
25
+ Spree::Gateway::VattanacMiniApp,
26
+ Spree::Gateway::TrueMoney
26
27
  )
27
28
  end
28
29
 
@@ -1,7 +1,7 @@
1
1
  module SpreeVpago
2
2
  module_function
3
3
 
4
- VERSION = '2.0.8'.freeze
4
+ VERSION = '2.0.9-beta'.freeze
5
5
 
6
6
  def version
7
7
  Gem::Version.new VERSION
@@ -0,0 +1,155 @@
1
+ module Vpago
2
+ module TrueMoney
3
+ class Base
4
+ TOKEN_HEADERS = { 'Content-Type' => 'application/x-www-form-urlencoded' }.freeze
5
+ CONTENT_TYPE_JSON = 'application/json'.freeze
6
+ DEFAULT_ALGORITHM = 'rsa-sha256'.freeze
7
+ DEFAULT_KEY_VERSION = 1
8
+
9
+ def initialize(payment, options = {})
10
+ @payment = payment
11
+ @options = options
12
+ end
13
+
14
+ def client_id
15
+ payment_method.preferred_client_id
16
+ end
17
+
18
+ def client_secret
19
+ payment_method.preferred_client_secret
20
+ end
21
+
22
+ def private_key
23
+ payment_method.preferred_private_key
24
+ end
25
+
26
+ def redirect_type
27
+ payment_method.preferred_redirect_type
28
+ end
29
+
30
+ def external_ref_id
31
+ @payment.number
32
+ end
33
+
34
+ def service_type = '01'
35
+ def currency = 'USD'
36
+ def user_type = 'CUSTOMER'
37
+
38
+ def amount
39
+ @payment.amount
40
+ end
41
+
42
+ def timestamp
43
+ @timestamp ||= Time.now.to_i
44
+ end
45
+
46
+ def access_token
47
+ @access_token ||= fetch_access_token
48
+ end
49
+
50
+ def payload
51
+ {
52
+ service_type: service_type,
53
+ external_ref_id: external_ref_id,
54
+ amount: amount,
55
+ currency: currency,
56
+ user_type: user_type
57
+ }
58
+ end
59
+
60
+ def signature
61
+ Vpago::RsaHandler
62
+ .new(private_key: private_key)
63
+ .generate_signature(signature_input)
64
+ end
65
+
66
+ def default_headers
67
+ {
68
+ 'Client-Id' => client_id,
69
+ 'Authorization' => "Bearer #{access_token}",
70
+ 'Signature' => "algorithm=#{algorithm};keyVersion=#{key_version};signature=#{signature}",
71
+ 'Timestamp' => timestamp.to_s,
72
+ 'Content-Type' => CONTENT_TYPE_JSON
73
+ }
74
+ end
75
+
76
+ def check_transaction_url
77
+ "#{payment_method.preferred_check_transaction_url}/#{external_ref_id}"
78
+ end
79
+
80
+ def refund_url
81
+ payment_method.preferred_refund_url
82
+ end
83
+
84
+ def generate_payment_url
85
+ payment_method.preferred_generate_payment_url
86
+ end
87
+
88
+ def access_token_url
89
+ payment_method.preferred_access_token_url
90
+ end
91
+
92
+ def android_package_name
93
+ payment_method.preferred_android_package_name
94
+ end
95
+
96
+ def parse_json(body)
97
+ JSON.parse(body)
98
+ rescue JSON::ParserError
99
+ {}
100
+ end
101
+
102
+ def fetch_access_token
103
+ response = Faraday.post(
104
+ access_token_url,
105
+ URI.encode_www_form(
106
+ grant_type: 'client_credentials',
107
+ client_id: client_id,
108
+ client_secret: client_secret
109
+ ),
110
+ TOKEN_HEADERS
111
+ )
112
+
113
+ raise "Access Token Error: #{response.status} - #{response.body}" unless response.success?
114
+
115
+ JSON.parse(response.body).fetch('access_token')
116
+ end
117
+
118
+ def return_url_scheme
119
+ payment_method.preferred_return_url_scheme
120
+ end
121
+
122
+ def return_deeplink
123
+ "#{return_url_scheme}/vpago_payments/processing?payment_number=#{external_ref_id}&order_number=#{order.number}&order_jwt_token=#{order_jwt_token}"
124
+ end
125
+
126
+ def order_jwt_token
127
+ @order_jwt_token ||= JWT.encode(jwt_payload, order.token, 'HS256')
128
+ end
129
+
130
+ def order
131
+ @payment.order
132
+ end
133
+
134
+ def jwt_payload
135
+ { order_number: order.number, order_id: order.id }
136
+ end
137
+
138
+ def payment_method
139
+ @payment.payment_method
140
+ end
141
+
142
+ def algorithm
143
+ DEFAULT_ALGORITHM
144
+ end
145
+
146
+ def key_version
147
+ DEFAULT_KEY_VERSION
148
+ end
149
+
150
+ def signature_input
151
+ "#{timestamp}#{payload.to_json}"
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,34 @@
1
+ module Vpago
2
+ module TrueMoney
3
+ class Checkout < Base
4
+ def generate_payment_urls(platform)
5
+ request_body = {
6
+ payment_info: payload.to_json,
7
+ redirectionType: redirect_type,
8
+ merchantDeepLink: return_deeplink,
9
+ merchantAndroidPackageName: android_package_name,
10
+ refererLink: @payment.processing_url
11
+ }
12
+
13
+ response = Faraday.post(generate_payment_url) do |req|
14
+ req.headers = default_headers
15
+ req.body = request_body.to_json
16
+ end
17
+
18
+ body = parse_json(response.body)
19
+
20
+ if platform == 'app'
21
+ {
22
+ webview: body['data']['webview'],
23
+ deeplink: body['data']['deeplink']
24
+ }
25
+ else
26
+ body['data']['deeplink']
27
+ end
28
+ rescue Faraday::Error, JSON::ParserError, NoMethodError => e
29
+ Rails.logger.error("Failed to generate payment URL: #{e.class} - #{e.message}")
30
+ raise
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ module Vpago
2
+ module TrueMoney
3
+ class RefundIssuer < Base
4
+ def call
5
+ @response = Faraday.post(refund_url, payload.to_json, default_headers)
6
+ end
7
+
8
+ def success?
9
+ parsed_response.dig('status', 'code') == '000001'
10
+ end
11
+
12
+ def payload
13
+ { external_ref_id: external_ref_id }
14
+ end
15
+
16
+ def parsed_response
17
+ @parsed_response ||= parse_json(@response.body)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ module Vpago
2
+ module TrueMoney
3
+ class TransactionStatus < Base
4
+ def call
5
+ @response = Faraday.get(check_transaction_url, nil, default_headers)
6
+ end
7
+
8
+ def success?
9
+ response_code == '000001'
10
+ end
11
+
12
+ def response_code
13
+ json_response.dig('status', 'code')
14
+ end
15
+
16
+ def signature_input
17
+ timestamp.to_s
18
+ end
19
+
20
+ def json_response
21
+ @json_response ||= parse_json(@response.body)
22
+ end
23
+ end
24
+ end
25
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_vpago
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.8
4
+ version: 2.0.9.pre.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-18 00:00:00.000000000 Z
11
+ date: 2025-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -134,6 +134,7 @@ files:
134
134
  - app/assets/images/backend-process.svg
135
135
  - app/assets/images/vpago/payway/abapay.png
136
136
  - app/assets/images/vpago/payway/cards.png
137
+ - app/assets/javascripts/vpago/vpago_payments/payment_processing_listener.js
137
138
  - app/assets/javascripts/vpago/vpago_payments/request_process_payment.js
138
139
  - app/assets/javascripts/vpago/vpago_payments/user_informers/firebase.js
139
140
  - app/controllers/.gitkeep
@@ -179,6 +180,7 @@ files:
179
180
  - app/models/spree/gateway/acleda_mobile.rb
180
181
  - app/models/spree/gateway/payway.rb
181
182
  - app/models/spree/gateway/payway_v2.rb
183
+ - app/models/spree/gateway/true_money.rb
182
184
  - app/models/spree/gateway/vattanac_mini_app.rb
183
185
  - app/models/spree/gateway/wing_sdk.rb
184
186
  - app/models/spree/payout.rb
@@ -262,6 +264,7 @@ files:
262
264
  - app/views/spree/admin/payments/source_views/_acleda_mobile.html.erb
263
265
  - app/views/spree/admin/payments/source_views/_payment_payway.html.erb
264
266
  - app/views/spree/admin/payments/source_views/_payway_v2.html.erb
267
+ - app/views/spree/admin/payments/source_views/_true_money.html.erb
265
268
  - app/views/spree/admin/payments/source_views/_vattanac_mini_app.html.erb
266
269
  - app/views/spree/admin/payments/source_views/_vpago_payment_tmpl.html.erb
267
270
  - app/views/spree/admin/payments/source_views/_wingsdk.html.erb
@@ -309,6 +312,7 @@ files:
309
312
  - app/views/spree/payway_v2_card_popups/show.html.erb
310
313
  - app/views/spree/vpago_payments/checkout.html.erb
311
314
  - app/views/spree/vpago_payments/forms/spree/gateway/_payway_v2.html.erb
315
+ - app/views/spree/vpago_payments/forms/spree/gateway/_true_money.html.erb
312
316
  - app/views/spree/vpago_payments/forms/spree/gateway/_vattanac_mini_app.html.erb
313
317
  - app/views/spree/vpago_payments/processing.html.erb
314
318
  - app/views/spree/vpago_payments/success.html.erb
@@ -378,6 +382,10 @@ files:
378
382
  - lib/vpago/payway_v2/pre_auth_canceler.rb
379
383
  - lib/vpago/payway_v2/pre_auth_completer.rb
380
384
  - lib/vpago/payway_v2/transaction_status.rb
385
+ - lib/vpago/true_money/base.rb
386
+ - lib/vpago/true_money/checkout.rb
387
+ - lib/vpago/true_money/refund_issuer.rb
388
+ - lib/vpago/true_money/transaction_status.rb
381
389
  - lib/vpago/vattanac_mini_app/base.rb
382
390
  - lib/vpago/vattanac_mini_app/checkout.rb
383
391
  - lib/vpago/vattanac_mini_app/refund_issuer.rb
@@ -408,9 +416,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
408
416
  version: 3.2.0
409
417
  required_rubygems_version: !ruby/object:Gem::Requirement
410
418
  requirements:
411
- - - ">="
419
+ - - ">"
412
420
  - !ruby/object:Gem::Version
413
- version: '0'
421
+ version: 1.3.1
414
422
  requirements:
415
423
  - none
416
424
  rubygems_version: 3.4.1