spree_vpago 2.4.0.pre.pre2 → 2.4.0.pre.pre3

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +27 -26
  3. data/app/controllers/spree/admin/payment_acleda_v2_base_controller.rb +21 -0
  4. data/app/controllers/spree/admin/payment_acleda_v2_checkers_controller.rb +26 -0
  5. data/app/controllers/spree/admin/payment_acleda_v2_queriers_controller.rb +38 -0
  6. data/app/controllers/spree/admin/payment_payway_queriers_controller.rb +7 -0
  7. data/app/controllers/spree/admin/payment_vattanac_base_controller.rb +21 -0
  8. data/app/controllers/spree/admin/payment_vattanac_checkers_controller.rb +26 -0
  9. data/app/controllers/spree/admin/payment_vattanac_queriers_controller.rb +38 -0
  10. data/app/controllers/spree/vpago_payments_controller.rb +15 -3
  11. data/app/helpers/vpago/vpago_payments_helper.rb +6 -0
  12. data/app/models/vpago/payment_decorator.rb +0 -4
  13. data/app/models/vpago/payment_method_decorator.rb +8 -6
  14. data/app/serializers/spree/v2/storefront/payment_method_serializer_decorator.rb +6 -0
  15. data/app/views/spree/admin/payments/source_views/_acleda_v2.html.erb +2 -2
  16. data/app/views/spree/admin/payments/source_views/_vattanac.html.erb +3 -3
  17. data/app/views/spree/vpago_payments/_success_script.html.erb +7 -0
  18. data/app/views/spree/vpago_payments/success.html.erb +2 -0
  19. data/config/locales/en.yml +1 -0
  20. data/config/locales/km.yml +1 -0
  21. data/config/routes.rb +6 -7
  22. data/lib/spree_vpago/engine.rb +0 -1
  23. data/lib/spree_vpago/version.rb +1 -1
  24. data/lib/vpago/acleda_v2/payment_request_updater.rb +54 -0
  25. data/lib/vpago/http_timeouts.rb +6 -0
  26. data/lib/vpago/payway_v2/payment_request_updater.rb +5 -0
  27. data/lib/vpago/payway_v2/transaction_status.rb +2 -0
  28. data/lib/vpago/payway_v2/transaction_status_v2.rb +2 -0
  29. data/lib/vpago/true_money/base.rb +1 -1
  30. data/lib/vpago/true_money/transaction_status.rb +1 -1
  31. data/lib/vpago/vattanac/base.rb +3 -2
  32. data/lib/vpago/vattanac/payment_request_updater.rb +54 -0
  33. data/lib/vpago/vattanac/transaction_status.rb +17 -1
  34. data/spree_vpago.gemspec +1 -1
  35. metadata +16 -12
  36. data/app/controllers/spree/miniapp/acledas_controller.rb +0 -29
  37. data/app/models/spree/gateway/acleda_mini_app.rb +0 -20
  38. data/app/services/vpago/acleda_mini_app/session/create.rb +0 -99
  39. data/app/views/spree/admin/payments/source_views/_acleda_mini_app.html.erb +0 -6
  40. data/app/views/spree/vpago_payments/forms/spree/gateway/_acleda_mini_app.html.erb +0 -93
  41. data/lib/vpago/acleda_mini_app/checkout.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e67f9f17cf4c31b88166afade1d7fb6cca4e5d849db06d3437ca2f06495a2a70
4
- data.tar.gz: 9d4073b8a168900c3492f27d80034e7396526c5e9cbb139539a77cecbac3d79a
3
+ metadata.gz: e361db28503471424737c1633bb7e5fcacccdc7c1a63faddd76ad3e226ceb6f7
4
+ data.tar.gz: cd5936a07fdfcfb314ac0d068d88b0e6137e6f5cb7e37295aa034368bcea33af
5
5
  SHA512:
6
- metadata.gz: 1d23aff10d339d0c4587645678dba14c8cb0e0e199161ae3f96189ef087fc8fb9345bb39dd826b3b030c1b7ccc5f45b84e866c8f9bb3f0baa5329a3ec97af589
7
- data.tar.gz: afdc9d8cd84de339e2ec3323490765e2f2e709c18cbc1655f86dff7ceb60dfdb75fc14f1039b635a7b8877412515ec503395c3176be364af184c91f1008927ce
6
+ metadata.gz: e5691409e7f910c70fd1012358eafc766b45131e4db286106f5a58a8390c0f7bb2ad1fbba892805a6f7ab3b4ec75bb77f8df1e76ccde90f153efadc6e835bed0
7
+ data.tar.gz: 874db861928b7f5cd71ccd9d17172057de242748f2cc0c11735b03a49951c80151d02cde118ee4ea21961209113529bb1e89562c9e6e8e843f38c4369ef03f71
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- spree_vpago (2.4.0.pre.pre2)
4
+ spree_vpago (2.4.0.pre.pre3)
5
5
  faraday
6
6
  google-cloud-firestore
7
7
  spree_api (>= 4.5)
8
8
  spree_backend (>= 4.5)
9
9
  spree_core (>= 4.5)
10
- spree_extension
10
+ spree_extension (< 1.0)
11
11
  spree_multi_vendor (>= 2.4.1)
12
12
 
13
13
  GEM
@@ -128,7 +128,7 @@ GEM
128
128
  babel-source (>= 4.0, < 6)
129
129
  execjs (~> 2.0)
130
130
  base64 (0.3.0)
131
- bcrypt (3.1.21)
131
+ bcrypt (3.1.22)
132
132
  bigdecimal (3.1.8)
133
133
  bootstrap (4.6.2.1)
134
134
  autoprefixer-rails (>= 9.1.0)
@@ -176,10 +176,11 @@ GEM
176
176
  rainbow (>= 2.1.0)
177
177
  diff-lcs (1.5.0)
178
178
  docile (1.4.0)
179
- doorkeeper (5.8.2)
179
+ doorkeeper (5.9.9)
180
180
  railties (>= 5)
181
181
  erubi (1.12.0)
182
- execjs (2.10.0)
182
+ execjs (2.10.2)
183
+ json (>= 2)
183
184
  factory_bot (4.11.1)
184
185
  activesupport (>= 3.0.0)
185
186
  faraday (2.7.4)
@@ -188,7 +189,7 @@ GEM
188
189
  faraday-http-cache (2.5.0)
189
190
  faraday (>= 0.8)
190
191
  faraday-net_http (3.0.2)
191
- faraday-retry (2.3.2)
192
+ faraday-retry (2.4.0)
192
193
  faraday (~> 2.0)
193
194
  ffaker (2.21.0)
194
195
  ffi (1.15.5)
@@ -198,7 +199,7 @@ GEM
198
199
  flatpickr (4.6.13.1)
199
200
  friendly_id (5.5.0)
200
201
  activerecord (>= 4.0.0)
201
- gapic-common (1.2.0)
202
+ gapic-common (1.5.0)
202
203
  faraday (>= 1.9, < 3.a)
203
204
  faraday-retry (>= 1.0, < 3.a)
204
205
  google-cloud-env (~> 2.2)
@@ -207,7 +208,7 @@ GEM
207
208
  googleapis-common-protos (~> 1.6)
208
209
  googleapis-common-protos-types (~> 1.15)
209
210
  googleauth (~> 1.12)
210
- grpc (~> 1.66)
211
+ grpc (>= 1.83, < 2.a)
211
212
  gem-release (2.2.2)
212
213
  github_changelog_generator (1.16.4)
213
214
  activesupport
@@ -221,45 +222,45 @@ GEM
221
222
  globalid (1.1.0)
222
223
  activesupport (>= 5.0)
223
224
  glyphicons (1.0.2)
224
- google-cloud-core (1.8.0)
225
+ google-cloud-core (1.9.0)
225
226
  google-cloud-env (>= 1.0, < 3.a)
226
227
  google-cloud-errors (~> 1.0)
227
- google-cloud-env (2.3.1)
228
+ google-cloud-env (2.4.0)
228
229
  base64 (~> 0.2)
229
230
  faraday (>= 1.0, < 3.a)
230
- google-cloud-errors (1.5.0)
231
- google-cloud-firestore (3.1.1)
231
+ google-cloud-errors (1.7.0)
232
+ google-cloud-firestore (3.2.0)
232
233
  bigdecimal (~> 3.0)
233
234
  concurrent-ruby (~> 1.0)
234
235
  google-cloud-core (~> 1.7)
235
236
  google-cloud-firestore-v1 (~> 2.0)
236
237
  rbtree (~> 0.4.2)
237
- google-cloud-firestore-v1 (2.3.0)
238
- gapic-common (~> 1.2)
238
+ google-cloud-firestore-v1 (2.4.0)
239
+ gapic-common (~> 1.3)
239
240
  google-cloud-errors (~> 1.0)
240
241
  google-cloud-location (~> 1.0)
241
- google-cloud-location (1.2.1)
242
- gapic-common (~> 1.2)
242
+ google-cloud-location (1.5.1)
243
+ gapic-common (~> 1.3)
243
244
  google-cloud-errors (~> 1.0)
244
245
  google-logging-utils (0.2.0)
245
- google-protobuf (4.33.4)
246
+ google-protobuf (4.33.6)
246
247
  bigdecimal
247
248
  rake (>= 13)
248
- googleapis-common-protos (1.9.0)
249
+ googleapis-common-protos (1.10.0)
249
250
  google-protobuf (~> 4.26)
250
251
  googleapis-common-protos-types (~> 1.21)
251
252
  grpc (~> 1.41)
252
- googleapis-common-protos-types (1.22.0)
253
+ googleapis-common-protos-types (1.23.0)
253
254
  google-protobuf (~> 4.26)
254
- googleauth (1.16.1)
255
+ googleauth (1.17.4)
255
256
  faraday (>= 1.0, < 3.a)
256
257
  google-cloud-env (~> 2.2)
257
258
  google-logging-utils (~> 0.1)
258
259
  jwt (>= 1.4, < 4.0)
259
- multi_json (~> 1.11)
260
260
  os (>= 0.9, < 2.0)
261
+ pstore (~> 0.1)
261
262
  signet (>= 0.16, < 2.a)
262
- grpc (1.76.0)
263
+ grpc (1.84.0)
263
264
  google-protobuf (>= 3.25, < 5.0)
264
265
  googleapis-common-protos-types (~> 1.0)
265
266
  hashdiff (1.1.0)
@@ -369,6 +370,7 @@ GEM
369
370
  pry (0.14.2)
370
371
  coderay (~> 1.1)
371
372
  method_source (~> 1.0)
373
+ pstore (0.2.1)
372
374
  public_suffix (5.0.1)
373
375
  puma (6.2.2)
374
376
  nio4r (~> 2.0)
@@ -412,13 +414,13 @@ GEM
412
414
  activerecord (>= 6.0.4)
413
415
  activesupport (>= 6.0.4)
414
416
  i18n
415
- rbtree (0.4.6)
417
+ rbtree (0.4.7)
416
418
  redis (5.0.6)
417
419
  redis-client (>= 0.9.0)
418
420
  redis-client (0.14.1)
419
421
  connection_pool
420
422
  regexp_parser (2.11.3)
421
- responders (3.2.0)
423
+ responders (3.2.1)
422
424
  actionpack (>= 7.0)
423
425
  railties (>= 7.0)
424
426
  rexml (3.2.9)
@@ -492,11 +494,10 @@ GEM
492
494
  websocket (~> 1.0)
493
495
  shoulda-matchers (6.2.0)
494
496
  activesupport (>= 5.2.0)
495
- signet (0.21.0)
497
+ signet (0.22.0)
496
498
  addressable (~> 2.8)
497
499
  faraday (>= 0.17.5, < 3.a)
498
500
  jwt (>= 1.5, < 4.0)
499
- multi_json (~> 1.10)
500
501
  simplecov (0.22.0)
501
502
  docile (~> 1.1)
502
503
  simplecov-html (~> 0.11)
@@ -0,0 +1,21 @@
1
+ module Spree
2
+ module Admin
3
+ class PaymentAcledaV2BaseController < Spree::Admin::BaseController
4
+ include Spree::Backend::Callbacks
5
+
6
+ before_action :load_payment, only: %i[show update]
7
+ before_action :validate_order, only: [:update]
8
+
9
+ def validate_order
10
+ return unless @payment.order.completed?
11
+
12
+ flash[:error] = Spree.t('vpago.payments.not_allow_for_order_completed')
13
+ redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
14
+ end
15
+
16
+ def load_payment
17
+ @payment = Payment.find_by!(number: params[:id])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module Spree
2
+ module Admin
3
+ class PaymentAcledaV2CheckersController < PaymentAcledaV2BaseController
4
+ include Spree::Backend::Callbacks
5
+
6
+ def update
7
+ options = {
8
+ updated_by_user_id: try_spree_current_user.id,
9
+ updated_reason: Spree.t('vpago.payments.checker_updated_by_description')
10
+ }
11
+ spree_updater = Vpago::AcledaV2::PaymentRequestUpdater.new(@payment, options)
12
+ spree_updater.call
13
+
14
+ @payment.reload
15
+
16
+ if @payment.order.completed?
17
+ flash[:success] = Spree.t(:successfully_updated, resource: Spree.t(:payments))
18
+ else
19
+ flash[:error] = Spree.t(:unsuccessfully_updated, resource: Spree.t(:payments))
20
+ end
21
+
22
+ redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ module Spree
2
+ module Admin
3
+ class PaymentAcledaV2QueriersController < PaymentAcledaV2BaseController
4
+ include Spree::Backend::Callbacks
5
+
6
+ around_action :set_writing_role, only: %i[show]
7
+
8
+ def show
9
+ tran_status = @payment.payment_method.check_transaction(@payment)
10
+
11
+ if tran_status.success?
12
+ @payment.update_column(:gateway_status, true)
13
+ flash[:success] =
14
+ Spree.t('vpago.payments.payment_found_with_result', result: tran_status.json_response)
15
+ else
16
+ flash[:error] = Spree.t('vpago.payments.payment_not_found_with_error', error: tran_status.error_message)
17
+ end
18
+
19
+ redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
20
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
21
+ VpagoLogger.error(
22
+ label: 'Spree::Admin::PaymentAcledaV2QueriersController#show gateway_timeout',
23
+ data: { payment_number: @payment.number, error_class: e.class.name, error_message: e.message }
24
+ )
25
+ flash[:error] = Spree.t('vpago.payments.gateway_timeout')
26
+ redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
27
+ end
28
+
29
+ private
30
+
31
+ def set_writing_role
32
+ ActiveRecord::Base.connected_to(role: :writing) do
33
+ yield if block_given?
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -16,6 +16,13 @@ module Spree
16
16
  flash[:error] = Spree.t('vpago.payments.payment_not_found_with_error', error: tran_status.error_message)
17
17
  end
18
18
 
19
+ redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
20
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
21
+ VpagoLogger.error(
22
+ label: 'Spree::Admin::PaymentPaywayQueriersController#show gateway_timeout',
23
+ data: { payment_number: @payment.number, error_class: e.class.name, error_message: e.message }
24
+ )
25
+ flash[:error] = Spree.t('vpago.payments.gateway_timeout')
19
26
  redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
20
27
  end
21
28
 
@@ -0,0 +1,21 @@
1
+ module Spree
2
+ module Admin
3
+ class PaymentVattanacBaseController < Spree::Admin::BaseController
4
+ include Spree::Backend::Callbacks
5
+
6
+ before_action :load_payment, only: %i[show update]
7
+ before_action :validate_order, only: [:update]
8
+
9
+ def validate_order
10
+ return unless @payment.order.completed?
11
+
12
+ flash[:error] = Spree.t('vpago.payments.not_allow_for_order_completed')
13
+ redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
14
+ end
15
+
16
+ def load_payment
17
+ @payment = Payment.find_by!(number: params[:id])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module Spree
2
+ module Admin
3
+ class PaymentVattanacCheckersController < PaymentVattanacBaseController
4
+ include Spree::Backend::Callbacks
5
+
6
+ def update
7
+ options = {
8
+ updated_by_user_id: try_spree_current_user.id,
9
+ updated_reason: Spree.t('vpago.payments.checker_updated_by_description')
10
+ }
11
+ spree_updater = Vpago::Vattanac::PaymentRequestUpdater.new(@payment, options)
12
+ spree_updater.call
13
+
14
+ @payment.reload
15
+
16
+ if @payment.order.completed?
17
+ flash[:success] = Spree.t(:successfully_updated, resource: Spree.t(:payments))
18
+ else
19
+ flash[:error] = Spree.t(:unsuccessfully_updated, resource: Spree.t(:payments))
20
+ end
21
+
22
+ redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ module Spree
2
+ module Admin
3
+ class PaymentVattanacQueriersController < PaymentVattanacBaseController
4
+ include Spree::Backend::Callbacks
5
+
6
+ around_action :set_writing_role, only: %i[show]
7
+
8
+ def show
9
+ tran_status = @payment.payment_method.check_transaction(@payment)
10
+
11
+ if tran_status.success?
12
+ @payment.update_column(:gateway_status, true)
13
+ flash[:success] =
14
+ Spree.t('vpago.payments.payment_found_with_result', result: tran_status.json_response)
15
+ else
16
+ flash[:error] = Spree.t('vpago.payments.payment_not_found_with_error', error: tran_status.error_message)
17
+ end
18
+
19
+ redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
20
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
21
+ VpagoLogger.error(
22
+ label: 'Spree::Admin::PaymentVattanacQueriersController#show gateway_timeout',
23
+ data: { payment_number: @payment.number, error_class: e.class.name, error_message: e.message }
24
+ )
25
+ flash[:error] = Spree.t('vpago.payments.gateway_timeout')
26
+ redirect_to admin_order_payment_path(order_id: @payment.order.number, id: @payment.number)
27
+ end
28
+
29
+ private
30
+
31
+ def set_writing_role
32
+ ActiveRecord::Base.connected_to(role: :writing) do
33
+ yield if block_given?
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -24,6 +24,12 @@ module Spree
24
24
  def processing
25
25
  @order = @payment.order
26
26
 
27
+ # Common, not an edge case: the gateway callback or the checkout page's transaction
28
+ # checker often completes the order before the browser lands here. Skipping this page
29
+ # also skips its Firebase listener, so success must post order_is_completed itself
30
+ # (render_success_script) or an embedding iframe modal never closes.
31
+ return redirect_to @payment.success_url, allow_other_host: true if @order.completed?
32
+
27
33
  VpagoLogger.log(label: 'Spree::VpagoPaymentsController#processing', data: vpago_log_context)
28
34
  end
29
35
 
@@ -60,6 +66,12 @@ module Spree
60
66
  else
61
67
  render json: { status: :pending }, status: :ok
62
68
  end
69
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
70
+ VpagoLogger.error(
71
+ label: 'Spree::VpagoPaymentsController#check_transaction gateway_timeout',
72
+ data: vpago_log_context(error_class: e.class.name, error_message: e.message)
73
+ )
74
+ render json: { status: :pending }, status: :ok
63
75
  end
64
76
 
65
77
  # POST
@@ -146,9 +158,9 @@ module Spree
146
158
  raise ActiveRecord::RecordNotFound unless @payment.present?
147
159
  end
148
160
 
149
- # frame-ancestors (set above) supersedes X-Frame-Options in modern
150
- # browsers, but the default SAMEORIGIN header would still block legacy
151
- # browsers from embedding these pages, so drop it for the iframe actions.
161
+ # These pages are embedded in iframes on many partner sites we don't
162
+ # enumerate, so framing is left unrestricted: no frame-ancestors policy, and
163
+ # Rails' default SAMEORIGIN header is dropped for the iframe actions.
152
164
  def allow_iframe_embedding
153
165
  response.headers.delete('X-Frame-Options')
154
166
  end
@@ -13,6 +13,12 @@ module Vpago
13
13
  render partial: 'spree/vpago_payments/transaction_checker'
14
14
  end
15
15
 
16
+ # Host apps override success.html.erb; call this from every override so an
17
+ # embedding iframe modal still learns the order completed.
18
+ def render_success_script
19
+ render partial: 'spree/vpago_payments/success_script'
20
+ end
21
+
16
22
  # Each payment method may have their own additional processing script,
17
23
  # so we will look for the partial based on the payment method class name.
18
24
  #
@@ -109,10 +109,6 @@ module Vpago
109
109
  payment_method.type_vattanac_mini_app?
110
110
  end
111
111
 
112
- def acleda_mini_app_payment?
113
- payment_method.type_acleda_mini_app?
114
- end
115
-
116
112
  def check_payment?
117
113
  payment_method.type_check?
118
114
  end
@@ -6,7 +6,6 @@ module Vpago
6
6
  TYPE_ACLEDA = 'Spree::Gateway::Acleda'.freeze
7
7
  TYPE_ACLEDA_V2 = 'Spree::Gateway::AcledaV2'.freeze
8
8
  TYPE_ACLEDA_MOBILE = 'Spree::Gateway::AcledaMobile'.freeze
9
- TYPE_ACLEDA_MINI_APP = 'Spree::Gateway::AcledaMiniApp'.freeze
10
9
  TYPE_TRUE_MONEY = 'Spree::Gateway::TrueMoney'.freeze
11
10
  TYPE_VATTANAC = 'Spree::Gateway::Vattanac'.freeze
12
11
  TYPE_VATTANAC_MINI_APP = 'Spree::Gateway::VattanacMiniApp'.freeze
@@ -25,7 +24,6 @@ module Vpago
25
24
  Spree::PaymentMethod::TYPE_ACLEDA,
26
25
  Spree::PaymentMethod::TYPE_ACLEDA_V2,
27
26
  Spree::PaymentMethod::TYPE_ACLEDA_MOBILE,
28
- Spree::PaymentMethod::TYPE_ACLEDA_MINI_APP,
29
27
  Spree::PaymentMethod::TYPE_VATTANAC,
30
28
  Spree::PaymentMethod::TYPE_VATTANAC_MINI_APP,
31
29
  Spree::PaymentMethod::TYPE_TRUE_MONEY,
@@ -82,6 +80,10 @@ module Vpago
82
80
  ::Vpago::Acleda::PaymentRequestUpdater
83
81
  elsif type_acleda_mobile?
84
82
  ::Vpago::AcledaMobile::PaymentRequestUpdater
83
+ elsif type_acleda_v2?
84
+ ::Vpago::AcledaV2::PaymentRequestUpdater
85
+ elsif type_vattanac?
86
+ ::Vpago::Vattanac::PaymentRequestUpdater
85
87
  end
86
88
  end
87
89
 
@@ -97,10 +99,6 @@ module Vpago
97
99
  type == Spree::PaymentMethod::TYPE_ACLEDA_V2
98
100
  end
99
101
 
100
- def type_acleda_mini_app?
101
- type == Spree::PaymentMethod::TYPE_ACLEDA_MINI_APP
102
- end
103
-
104
102
  def type_payway?
105
103
  type == Spree::PaymentMethod::TYPE_PAYWAY
106
104
  end
@@ -113,6 +111,10 @@ module Vpago
113
111
  type == Spree::PaymentMethod::TYPE_WINGSDK
114
112
  end
115
113
 
114
+ def type_vattanac?
115
+ type == Spree::PaymentMethod::TYPE_VATTANAC
116
+ end
117
+
116
118
  def type_vattanac_mini_app?
117
119
  type == Spree::PaymentMethod::TYPE_VATTANAC_MINI_APP
118
120
  end
@@ -14,6 +14,12 @@ module Spree
14
14
 
15
15
  pref.blank? || pref[:payment_option].blank? ? payment_method.method_type : pref[:payment_option]
16
16
  end
17
+
18
+ # Overried the payment method type to show all payment methods as one list on App.
19
+ # Controlled by ENV["PAYMENT_METHOD_VIEW"] = "split" | "join" (default: "join").
20
+ base.attribute :type do |payment_method|
21
+ ENV.fetch('PAYMENT_METHOD_VIEW', 'join') == 'split' ? payment_method.type : 'payment_method'
22
+ end
17
23
  end
18
24
  end
19
25
  end
@@ -1,5 +1,5 @@
1
1
  <%= render 'spree/admin/payments/source_views/vpago_payment_tmpl',
2
2
  payment: payment,
3
- check_status_url: '',
4
- heal_payment_url: '',
3
+ check_status_url: admin_payment_acleda_v2_querier_path(payment),
4
+ heal_payment_url: admin_payment_acleda_v2_checker_path(payment),
5
5
  manual_update_payment_url: '' %>
@@ -1,6 +1,6 @@
1
1
  <%= render 'spree/admin/payments/source_views/vpago_payment_tmpl',
2
- payment: payment,
3
- check_status_url: '',
4
- heal_payment_url: '',
2
+ payment: payment,
3
+ check_status_url: admin_payment_vattanac_querier_path(payment),
4
+ heal_payment_url: admin_payment_vattanac_checker_path(payment),
5
5
  manual_update_payment_url: ''
6
6
  %>
@@ -0,0 +1,7 @@
1
+ <script>
2
+ // processing redirects here without rendering once the order is complete, so its
3
+ // Firebase listener never posts this. success only renders for completed orders.
4
+ if (window.parent !== window) {
5
+ window.parent.postMessage("order_is_completed", "*");
6
+ }
7
+ </script>
@@ -1,3 +1,5 @@
1
1
  <h1>
2
2
  Success
3
3
  </h1>
4
+
5
+ <%= render_success_script %>
@@ -35,5 +35,6 @@ en:
35
35
  failed_require_updated_reason: "You must provide update reason"
36
36
  payment_found_with_result: "Found payment with result: %{result}"
37
37
  payment_not_found_with_error: "Payment not found with error: %{error}"
38
+ gateway_timeout: "The payment gateway did not respond in time. Please try again shortly."
38
39
  query_status: "Query payment status"
39
40
  confirm_manual_update: "Are you sure to update this order to complete"
@@ -31,5 +31,6 @@ spree:
31
31
  failed_require_updated_reason: "អ្នកត្រូវតែផ្តល់ហេតុផល"
32
32
  payment_found_with_result: "រកឃើញការទូទាត់ជាមួយលទ្ធផល %{result}"
33
33
  payment_not_found_with_error: "ការទូទាត់មិនត្រូវបានរកឃើញដោយកំហុស: %{error}"
34
+ gateway_timeout: "ផ្នែកទូទាត់មិនបានឆ្លើយតបទាន់ពេលទេ។ សូមព្យាយាមម្តងទៀតក្នុងពេលឆាប់ៗនេះ។"
34
35
  query_status: "សួរស្ថានភាពបង់ប្រាក់"
35
36
  confirm_manual_update: "តើអ្នកប្រាកដជាធ្វើបច្ចុប្បន្នភាពការបញ្ជាទិញនេះដើម្បីបញ្ចប់"
data/config/routes.rb CHANGED
@@ -17,13 +17,6 @@ Spree::Core::Engine.add_routes do
17
17
  resources :transactions, only: [:show]
18
18
  end
19
19
 
20
- # ACLEDA Mini App session init (ACLEDA Mini App Integration Spec, Step 01).
21
- # ACLEDA calls POST /miniapp/acleda with { phone, first_name, last_name }
22
- # and receives a session-based miniAppUrl.
23
- namespace :miniapp do
24
- resources :acleda, only: [:create], controller: 'acledas'
25
- end
26
-
27
20
  resource :vpago_payments do
28
21
  collection do
29
22
  get :checkout
@@ -80,6 +73,12 @@ Spree::Core::Engine.add_routes do
80
73
  resources :payment_payway_checkers
81
74
  resources :payment_payway_markers
82
75
 
76
+ resources :payment_acleda_v2_queriers, only: [:show]
77
+ resources :payment_acleda_v2_checkers, only: [:update]
78
+
79
+ resources :payment_vattanac_queriers, only: [:show]
80
+ resources :payment_vattanac_checkers, only: [:update]
81
+
83
82
  resources :products do
84
83
  resources :payout_profile_products
85
84
  end
@@ -23,7 +23,6 @@ module SpreeVpago
23
23
  Spree::Gateway::Acleda,
24
24
  Spree::Gateway::AcledaV2,
25
25
  Spree::Gateway::AcledaMobile,
26
- Spree::Gateway::AcledaMiniApp,
27
26
  Spree::Gateway::Vattanac,
28
27
  Spree::Gateway::VattanacMiniApp,
29
28
  Spree::Gateway::TrueMoney,
@@ -1,7 +1,7 @@
1
1
  module SpreeVpago
2
2
  module_function
3
3
 
4
- VERSION = '2.4.0-pre2'.freeze
4
+ VERSION = '2.4.0-pre3'.freeze
5
5
 
6
6
  def version
7
7
  Gem::Version.new VERSION
@@ -0,0 +1,54 @@
1
+ module Vpago
2
+ module AcledaV2
3
+ class PaymentRequestUpdater < ::Vpago::PaymentRequestUpdater
4
+ def call
5
+ return if @payment.order.paid?
6
+
7
+ if items_eligible?
8
+ process_payment_status
9
+ else
10
+ mark_items_as_ineligible
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def process_payment_status
17
+ checker = @payment.payment_method.check_transaction(@payment)
18
+
19
+ if checker.success?
20
+ mark_payment_as_success
21
+ elsif checker.failed? && !ignore_on_failed?
22
+ mark_payment_as_failed(checker.error_message)
23
+ end
24
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
25
+ VpagoLogger.error(
26
+ label: 'Vpago::AcledaV2::PaymentRequestUpdater#process_payment_status gateway_timeout',
27
+ data: { payment_number: @payment.number, error_class: e.class.name, error_message: e.message }
28
+ )
29
+ end
30
+
31
+ def items_eligible?
32
+ @payment&.order&.line_items&.all?(&:sufficient_stock?) == true # rubocop:disable Style/SafeNavigationChainLength
33
+ end
34
+
35
+ def mark_items_as_ineligible
36
+ @error_message = 'Items are not eligible due to insufficient stock'
37
+ marker_options = @options.merge(status: false, description: @error_message)
38
+ ::Vpago::PaymentStatusMarker.new(@payment, marker_options).call
39
+ end
40
+
41
+ def mark_payment_as_success
42
+ @error_message = nil
43
+ marker_options = @options.merge(status: true, description: nil)
44
+ ::Vpago::PaymentStatusMarker.new(@payment, marker_options).call
45
+ end
46
+
47
+ def mark_payment_as_failed(error_message)
48
+ @error_message = error_message
49
+ marker_options = @options.merge(status: false, description: @error_message)
50
+ ::Vpago::PaymentStatusMarker.new(@payment, marker_options).call
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,6 @@
1
+ module Vpago
2
+ module HttpTimeouts
3
+ OPEN_TIMEOUT = 5
4
+ TIMEOUT = 15
5
+ end
6
+ end
@@ -28,6 +28,11 @@ module Vpago
28
28
  elsif checker.failed?
29
29
  mark_payment_as_failed(checker.error_message)
30
30
  end
31
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
32
+ VpagoLogger.error(
33
+ label: 'Vpago::PaywayV2::PaymentRequestUpdater#process_payment_status gateway_timeout',
34
+ data: { payment_number: @payment.number, error_class: e.class.name, error_message: e.message }
35
+ )
31
36
  end
32
37
 
33
38
  def mark_payment_as_success(checker)
@@ -37,6 +37,8 @@ module Vpago
37
37
  def check_remote_status
38
38
  conn = Faraday::Connection.new do |faraday|
39
39
  faraday.request :url_encoded
40
+ faraday.options.open_timeout = Vpago::HttpTimeouts::OPEN_TIMEOUT
41
+ faraday.options.timeout = Vpago::HttpTimeouts::TIMEOUT
40
42
  end
41
43
 
42
44
  data = {
@@ -52,6 +52,8 @@ module Vpago
52
52
  def check_remote_status
53
53
  conn = Faraday::Connection.new do |faraday|
54
54
  faraday.request :url_encoded
55
+ faraday.options.open_timeout = Vpago::HttpTimeouts::OPEN_TIMEOUT
56
+ faraday.options.timeout = Vpago::HttpTimeouts::TIMEOUT
55
57
  end
56
58
 
57
59
  data = {
@@ -100,7 +100,7 @@ module Vpago
100
100
  end
101
101
 
102
102
  def fetch_access_token
103
- response = Faraday.post(
103
+ response = Faraday.new(request: { open_timeout: Vpago::HttpTimeouts::OPEN_TIMEOUT, timeout: Vpago::HttpTimeouts::TIMEOUT }).post(
104
104
  access_token_url,
105
105
  URI.encode_www_form(
106
106
  grant_type: 'client_credentials',
@@ -2,7 +2,7 @@ module Vpago
2
2
  module TrueMoney
3
3
  class TransactionStatus < Base
4
4
  def call
5
- @response = Faraday.get(check_transaction_url, nil, default_headers)
5
+ @response = Faraday.new(request: { open_timeout: Vpago::HttpTimeouts::OPEN_TIMEOUT, timeout: Vpago::HttpTimeouts::TIMEOUT }).get(check_transaction_url, nil, default_headers)
6
6
  end
7
7
 
8
8
  def success?
@@ -70,7 +70,8 @@ module Vpago
70
70
  private
71
71
 
72
72
  def fetch_access_token
73
- response = Faraday.post(access_token_url, { username: username, password: password }.to_json, { 'Content-Type' => CONTENT_TYPE_JSON })
73
+ response = Faraday.new(request: { open_timeout: Vpago::HttpTimeouts::OPEN_TIMEOUT, timeout: Vpago::HttpTimeouts::TIMEOUT })
74
+ .post(access_token_url, { username: username, password: password }.to_json, { 'Content-Type' => CONTENT_TYPE_JSON })
74
75
 
75
76
  raise "Access Token Error: #{response.status} - #{response.body}" unless response.success?
76
77
 
@@ -78,7 +79,7 @@ module Vpago
78
79
  end
79
80
 
80
81
  def post(url, body)
81
- response = Faraday.post(url, body.to_json, default_headers)
82
+ response = Faraday.new(request: { open_timeout: Vpago::HttpTimeouts::OPEN_TIMEOUT, timeout: Vpago::HttpTimeouts::TIMEOUT }).post(url, body.to_json, default_headers)
82
83
  parse_json(response.body)
83
84
  end
84
85
  end
@@ -0,0 +1,54 @@
1
+ module Vpago
2
+ module Vattanac
3
+ class PaymentRequestUpdater < ::Vpago::PaymentRequestUpdater
4
+ def call
5
+ return if @payment.order.paid?
6
+
7
+ if items_eligible?
8
+ process_payment_status
9
+ else
10
+ mark_items_as_ineligible
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def process_payment_status
17
+ checker = @payment.payment_method.check_transaction(@payment)
18
+
19
+ if checker.success?
20
+ mark_payment_as_success
21
+ elsif checker.failed? && !ignore_on_failed?
22
+ mark_payment_as_failed(checker.error_message)
23
+ end
24
+ rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
25
+ VpagoLogger.error(
26
+ label: 'Vpago::Vattanac::PaymentRequestUpdater#process_payment_status gateway_timeout',
27
+ data: { payment_number: @payment.number, error_class: e.class.name, error_message: e.message }
28
+ )
29
+ end
30
+
31
+ def items_eligible?
32
+ @payment&.order&.line_items&.all?(&:sufficient_stock?) == true # rubocop:disable Style/SafeNavigationChainLength
33
+ end
34
+
35
+ def mark_items_as_ineligible
36
+ @error_message = 'Items are not eligible due to insufficient stock'
37
+ marker_options = @options.merge(status: false, description: @error_message)
38
+ ::Vpago::PaymentStatusMarker.new(@payment, marker_options).call
39
+ end
40
+
41
+ def mark_payment_as_success
42
+ @error_message = nil
43
+ marker_options = @options.merge(status: true, description: nil)
44
+ ::Vpago::PaymentStatusMarker.new(@payment, marker_options).call
45
+ end
46
+
47
+ def mark_payment_as_failed(error_message)
48
+ @error_message = error_message
49
+ marker_options = @options.merge(status: false, description: @error_message)
50
+ ::Vpago::PaymentStatusMarker.new(@payment, marker_options).call
51
+ end
52
+ end
53
+ end
54
+ end
@@ -13,6 +13,22 @@ module Vpago
13
13
  @response.dig('data', 'status') == 'SUCCESS'
14
14
  end
15
15
 
16
+ def error_message
17
+ @response.dig('msgEntity', 'message') || @response.dig('data', 'message') || 'Unknown error'
18
+ end
19
+
20
+ # msgEntity.code is the API-envelope result code ("0" = call succeeded; check
21
+ # data.status for the actual transaction state). Any other code is a gateway-level
22
+ # error with no transaction data (e.g. "03" / "Transaction not found") and is
23
+ # treated as terminal failure.
24
+ def failed?
25
+ @response.dig('msgEntity', 'code').to_s != '0'
26
+ end
27
+
28
+ def pending?
29
+ !success? && !failed?
30
+ end
31
+
16
32
  private
17
33
 
18
34
  def payload
@@ -20,4 +36,4 @@ module Vpago
20
36
  end
21
37
  end
22
38
  end
23
- end
39
+ end
data/spree_vpago.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
26
26
  s.add_dependency 'spree_api', spree_version
27
27
  s.add_dependency 'spree_backend', spree_version
28
28
  s.add_dependency 'spree_core', spree_version
29
- s.add_dependency 'spree_extension'
29
+ s.add_dependency 'spree_extension', '< 1.0'
30
30
  s.add_dependency 'spree_multi_vendor', '>= 2.4.1'
31
31
 
32
32
  s.metadata['rubygems_mfa_required'] = 'true'
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.4.0.pre.pre2
4
+ version: 2.4.0.pre.pre3
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-17 00:00:00.000000000 Z
11
+ date: 2026-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -84,16 +84,16 @@ dependencies:
84
84
  name: spree_extension
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "<"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '1.0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "<"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '1.0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: spree_multi_vendor
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -151,11 +151,17 @@ files:
151
151
  - app/assets/javascripts/vpago/vpago_payments/user_informers/firebase.js
152
152
  - app/controllers/.gitkeep
153
153
  - app/controllers/spree/acleda_redirects_controller.rb
154
+ - app/controllers/spree/admin/payment_acleda_v2_base_controller.rb
155
+ - app/controllers/spree/admin/payment_acleda_v2_checkers_controller.rb
156
+ - app/controllers/spree/admin/payment_acleda_v2_queriers_controller.rb
154
157
  - app/controllers/spree/admin/payment_methods_controller_decorator.rb
155
158
  - app/controllers/spree/admin/payment_payway_base_controller.rb
156
159
  - app/controllers/spree/admin/payment_payway_checkers_controller.rb
157
160
  - app/controllers/spree/admin/payment_payway_markers_controller.rb
158
161
  - app/controllers/spree/admin/payment_payway_queriers_controller.rb
162
+ - app/controllers/spree/admin/payment_vattanac_base_controller.rb
163
+ - app/controllers/spree/admin/payment_vattanac_checkers_controller.rb
164
+ - app/controllers/spree/admin/payment_vattanac_queriers_controller.rb
159
165
  - app/controllers/spree/admin/payment_wing_sdk_base_controller.rb
160
166
  - app/controllers/spree/admin/payment_wing_sdk_checkers_controller.rb
161
167
  - app/controllers/spree/admin/payment_wing_sdk_markers_controller.rb
@@ -167,7 +173,6 @@ files:
167
173
  - app/controllers/spree/admin/payouts_controller.rb
168
174
  - app/controllers/spree/admin/suspicious_orders_controller.rb
169
175
  - app/controllers/spree/api/v2/storefront/checkout_controller_decorator.rb
170
- - app/controllers/spree/miniapp/acledas_controller.rb
171
176
  - app/controllers/spree/payway_card_popups_controller.rb
172
177
  - app/controllers/spree/payway_results_controller.rb
173
178
  - app/controllers/spree/payway_v2_card_popups_controller.rb
@@ -193,7 +198,6 @@ files:
193
198
  - app/models/.gitkeep
194
199
  - app/models/concerns/vpago/payoutable.rb
195
200
  - app/models/spree/gateway/acleda.rb
196
- - app/models/spree/gateway/acleda_mini_app.rb
197
201
  - app/models/spree/gateway/acleda_mobile.rb
198
202
  - app/models/spree/gateway/acleda_v2.rb
199
203
  - app/models/spree/gateway/payway.rb
@@ -258,7 +262,6 @@ files:
258
262
  - app/serializers/spree/v2/storefront/payment_serializer_decorator.rb
259
263
  - app/serializers/spree/v2/storefront/vpago_payment_source_serializer.rb
260
264
  - app/services/.gitkeep
261
- - app/services/vpago/acleda_mini_app/session/create.rb
262
265
  - app/services/vpago/aes_encrypter.rb
263
266
  - app/services/vpago/payment_finder.rb
264
267
  - app/services/vpago/payment_processable.rb
@@ -286,7 +289,6 @@ files:
286
289
  - app/views/spree/admin/payments/source_forms/_payment_payway.html.erb
287
290
  - app/views/spree/admin/payments/source_forms/_payway_v2.html.erb
288
291
  - app/views/spree/admin/payments/source_views/_acleda.html.erb
289
- - app/views/spree/admin/payments/source_views/_acleda_mini_app.html.erb
290
292
  - app/views/spree/admin/payments/source_views/_acleda_mobile.html.erb
291
293
  - app/views/spree/admin/payments/source_views/_acleda_v2.html.erb
292
294
  - app/views/spree/admin/payments/source_views/_payment_payway.html.erb
@@ -342,9 +344,9 @@ files:
342
344
  - app/views/spree/payway_results/success.html.erb
343
345
  - app/views/spree/payway_v2_card_popups/_form.html.erb
344
346
  - app/views/spree/payway_v2_card_popups/show.html.erb
347
+ - app/views/spree/vpago_payments/_success_script.html.erb
345
348
  - app/views/spree/vpago_payments/_transaction_checker.html.erb
346
349
  - app/views/spree/vpago_payments/checkout.html.erb
347
- - app/views/spree/vpago_payments/forms/spree/gateway/_acleda_mini_app.html.erb
348
350
  - app/views/spree/vpago_payments/forms/spree/gateway/_acleda_v2.html.erb
349
351
  - app/views/spree/vpago_payments/forms/spree/gateway/_payway_v2.html.erb
350
352
  - app/views/spree/vpago_payments/forms/spree/gateway/_true_money.html.erb
@@ -396,7 +398,6 @@ files:
396
398
  - lib/vpago/acleda/deeplink_checkout.rb
397
399
  - lib/vpago/acleda/payment_request_updater.rb
398
400
  - lib/vpago/acleda/transaction_status.rb
399
- - lib/vpago/acleda_mini_app/checkout.rb
400
401
  - lib/vpago/acleda_mobile/base.rb
401
402
  - lib/vpago/acleda_mobile/callback_validator.php
402
403
  - lib/vpago/acleda_mobile/callback_validator.rb
@@ -406,7 +407,9 @@ files:
406
407
  - lib/vpago/acleda_mobile/transaction_status.rb
407
408
  - lib/vpago/acleda_v2/base.rb
408
409
  - lib/vpago/acleda_v2/checkout.rb
410
+ - lib/vpago/acleda_v2/payment_request_updater.rb
409
411
  - lib/vpago/acleda_v2/transaction_status.rb
412
+ - lib/vpago/http_timeouts.rb
410
413
  - lib/vpago/payment_amount_calculator.rb
411
414
  - lib/vpago/payment_request_updater.rb
412
415
  - lib/vpago/payment_status_marker.rb
@@ -430,6 +433,7 @@ files:
430
433
  - lib/vpago/true_money/transaction_status.rb
431
434
  - lib/vpago/vattanac/base.rb
432
435
  - lib/vpago/vattanac/checkout.rb
436
+ - lib/vpago/vattanac/payment_request_updater.rb
433
437
  - lib/vpago/vattanac/transaction_status.rb
434
438
  - lib/vpago/vattanac_mini_app/base.rb
435
439
  - lib/vpago/vattanac_mini_app/checkout.rb
@@ -1,29 +0,0 @@
1
- module Spree
2
- module Miniapp
3
- # Partner Session Initialization API (ACLEDA Mini App Integration Spec, Step 01).
4
- # ACLEDA (server-to-server) POSTs { phone, first_name, last_name }; we return a
5
- # session-based miniAppUrl for ACLEDA to open in the WebView.
6
- class AcledasController < Spree::Api::V2::BaseController
7
- # POST /miniapp/acleda
8
- def create
9
- result = ::Vpago::AcledaMiniApp::Session::Create.call(
10
- phone: session_params[:phone],
11
- first_name: session_params[:first_name],
12
- last_name: session_params[:last_name]
13
- )
14
-
15
- if result.success?
16
- render json: { message: 'SUCCESS', miniAppUrl: result.value[:mini_app_url] }
17
- else
18
- render json: { message: 'FAILED', error: result.error.to_s }, status: :unprocessable_entity
19
- end
20
- end
21
-
22
- private
23
-
24
- def session_params
25
- params.permit(:phone, :first_name, :last_name)
26
- end
27
- end
28
- end
29
- end
@@ -1,20 +0,0 @@
1
- module Spree
2
- # ACLEDA Mini App payment method.
3
- #
4
- # Reuses the ACLEDA V2 integration in deeplink mode: the deeplink produced by
5
- # ACLEDA's openSessionV2 API is handed to the ACLEDA super-app through the
6
- # `acledaJSBridge` (startPayment) instead of redirecting the page. Server-side
7
- # verification reuses AcledaV2's #check_transaction (getTxnStatus).
8
- class Gateway::AcledaMiniApp < Gateway::AcledaV2
9
- # override: partial to render in admin / checkout form lookup
10
- def method_type
11
- 'acleda_mini_app'
12
- end
13
-
14
- # The mini app always uses the deeplink + JS bridge, so admins don't need to
15
- # set the ACLEDA V2 mode.
16
- def deeplink?
17
- true
18
- end
19
- end
20
- end
@@ -1,99 +0,0 @@
1
- module Vpago
2
- module AcledaMiniApp
3
- module Session
4
- # Partner Session Initialization (ACLEDA Mini App Integration Spec, Step 01).
5
- #
6
- # ACLEDA calls POST /miniapp/acleda with { phone, first_name, last_name }.
7
- # We find or create the customer (tagged with the acleda_bank identity),
8
- # mint a session JWT, and return a session-based miniAppUrl for ACLEDA to
9
- # open in the WebView. The generic session_id OAuth grant validates the JWT
10
- # when the WebView later authenticates.
11
- class Create
12
- prepend Spree::ServiceModule::Base
13
-
14
- def call(phone: nil, first_name: nil, last_name: nil)
15
- return failure(nil, 'phone is required') if phone.blank?
16
-
17
- @phone = phone
18
- @first_name = first_name
19
- @last_name = last_name
20
-
21
- user = find_or_create_user
22
- return failure(user, 'user creation failed') if user.nil? || !user.persisted?
23
-
24
- success(mini_app_url: mini_app_url(user))
25
- end
26
-
27
- private
28
-
29
- attr_reader :phone, :first_name, :last_name
30
-
31
- def find_or_create_user
32
- identity = acleda_identity
33
-
34
- # Returning customer: the ACLEDA identity already points at a user.
35
- return identity.user if identity.persisted?
36
-
37
- # New to ACLEDA: reuse a customer matched by phone, otherwise build one,
38
- # then tag them with the acleda_bank identity so the lookup above hits
39
- # next time.
40
- user = find_existing_user || build_user
41
- identity.name = full_name
42
- user.user_identity_providers << identity
43
- user.save
44
-
45
- user
46
- end
47
-
48
- def acleda_identity
49
- SpreeCmCommissioner::UserIdentityProvider.acleda_bank.find_or_initialize_by(sub: phone)
50
- end
51
-
52
- def find_existing_user
53
- return if intel_phone.blank?
54
-
55
- Spree::User.by_non_tenant.find_by(intel_phone_number: intel_phone)
56
- end
57
-
58
- def build_user
59
- Spree::User.new(
60
- first_name: first_name,
61
- last_name: last_name,
62
- phone_number: phone,
63
- intel_phone_number: intel_phone,
64
- password: SecureRandom.base64(16),
65
- confirmed_at: Time.zone.now
66
- )
67
- end
68
-
69
- def mini_app_url(user)
70
- # A JWT signed with the user's secure_token. Passed as `session_key` (not
71
- # `session_id`) so the OAuth grant routes it to the ACLEDA authenticator,
72
- # which enforces the signature.
73
- session_key = SpreeCmCommissioner::UserSessionJwtToken.encode(
74
- { user_id: user.id },
75
- user.reload.secure_token
76
- )
77
-
78
- "#{Spree::Store.default.formatted_url}/mini_app/acleda?session_key=#{session_key}&ds=#{CGI.escape(disable_services)}"
79
- end
80
-
81
- def full_name
82
- [first_name, last_name].compact.join(' ').presence
83
- end
84
-
85
- def intel_phone
86
- return @intel_phone if defined?(@intel_phone)
87
-
88
- @intel_phone = SpreeCmCommissioner::PhoneNumberParser.call(phone_number: phone).intel_phone_number
89
- end
90
-
91
- # Services to disable in the mini app storefront. Configurable per ACLEDA's
92
- # requirement; defaults to none.
93
- def disable_services
94
- ENV.fetch('ACLEDA_MINI_APP_DISABLE_SERVICES', '')
95
- end
96
- end
97
- end
98
- end
99
- end
@@ -1,6 +0,0 @@
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
- %>
@@ -1,93 +0,0 @@
1
- <%
2
- opr_device = request.user_agent.to_s.downcase.match?(/iphone|ipad|ipod/) ? 'ios' : 'android'
3
- checkout = ::Vpago::AcledaMiniApp::Checkout.new(@payment, { platform: 'app', opr_device: opr_device })
4
-
5
- begin
6
- checkout.call
7
- rescue => e
8
- Rails.logger.error("Acleda Mini App checkout.call failed: #{e.message}")
9
- end
10
- %>
11
-
12
- <p id="unsupported-message"
13
- style="margin-top: 10px; font-weight: bold; color: red; display: none;">
14
- </p>
15
-
16
- <% if checkout.failed? %>
17
- <script>
18
- console.error("[Vpago] Acleda Mini App checkout error: <%= j checkout.error_message.to_s %>");
19
- document.addEventListener("DOMContentLoaded", () => {
20
- const loadingContainer = document.getElementById('vpago_loading_container');
21
- if (loadingContainer) {
22
- loadingContainer.textContent = "We couldn't start your payment. Please go back and try again.";
23
- }
24
- });
25
- </script>
26
- <% else %>
27
- <script>
28
- // ACLEDA JS bridge (unified iOS + Android). Defined before use so it is
29
- // available when the native layer injects its handlers.
30
- window.acledaJSBridge = window.acledaJSBridge || {
31
- call: function (action, data = {}) {
32
- const payload = { action, data };
33
-
34
- // iOS WKWebView
35
- if (
36
- window.webkit &&
37
- window.webkit.messageHandlers &&
38
- window.webkit.messageHandlers.acledaIOSJSBridge
39
- ) {
40
- window.webkit.messageHandlers.acledaIOSJSBridge.postMessage(payload);
41
- return true;
42
- }
43
-
44
- // Android WebView
45
- if (
46
- window.acledaAndroidJSBridge &&
47
- typeof window.acledaAndroidJSBridge.postMessage === "function"
48
- ) {
49
- window.acledaAndroidJSBridge.postMessage(JSON.stringify(payload));
50
- return true;
51
- }
52
-
53
- console.warn("[acledaJSBridge] Native bridge not found");
54
- return false;
55
- }
56
- };
57
-
58
- function showRedirectContainer(buttonLabel, onClick) {
59
- const loadingContainer = document.getElementById('vpago_loading_container');
60
- const redirectContainer = document.getElementById('vpago_redirect_container');
61
- const redirectButton = document.getElementById('vpago_redirect_button');
62
-
63
- redirectButton.textContent = buttonLabel;
64
- redirectButton.addEventListener('click', onClick);
65
-
66
- loadingContainer.style.display = 'none';
67
- redirectContainer.style.removeProperty('display');
68
- }
69
-
70
- document.addEventListener("DOMContentLoaded", () => {
71
- const acledaDeeplink = "<%= raw checkout.acleda_deeplink %>";
72
- const processingUrl = "<%= raw @payment.processing_url %>";
73
-
74
- // Native -> JS after payment completes. Primary completion signal; the
75
- // transaction checker polling is the fallback.
76
- window.onPaymentResult = function (result) {
77
- console.log("[acledaJSBridge] Payment result:", result);
78
- // result.batchId, result.status = "success" | "failed" | "cancelled"
79
- window.location.href = processingUrl;
80
- };
81
-
82
- const startPayment = () => window.acledaJSBridge.call("startPayment", { acleda_deeplink: acledaDeeplink });
83
-
84
- if (startPayment()) {
85
- showRedirectContainer("Continue", startPayment);
86
- } else {
87
- const unsupported = document.getElementById('unsupported-message');
88
- unsupported.style.display = 'block';
89
- unsupported.innerText = 'Unsupported environment';
90
- }
91
- });
92
- </script>
93
- <% end %>
@@ -1,23 +0,0 @@
1
- module Vpago
2
- module AcledaMiniApp
3
- # ACLEDA Mini App checkout. Reuses ACLEDA V2's openSessionV2 flow to obtain a
4
- # deeplink, which is passed to the ACLEDA super-app via the acledaJSBridge
5
- # (startPayment) instead of redirecting the page.
6
- class Checkout < Vpago::AcledaV2::Checkout
7
- # The mini app always uses the deeplink + JS bridge, regardless of the
8
- # gateway's configured mode.
9
- def deeplink?
10
- true
11
- end
12
-
13
- # The deeplink string handed to
14
- # acledaJSBridge.call('startPayment', { acleda_deeplink: ... })
15
- # Ensures openSessionV2 has run (persisting paymentTokenid for later
16
- # getTxnStatus verification) before returning the deeplinkUrl.
17
- def acleda_deeplink
18
- call if @response.nil?
19
- deeplink_url
20
- end
21
- end
22
- end
23
- end