spree_cm_commissioner 1.8.10 → 1.9.0

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +4 -0
  3. data/Gemfile.lock +25 -1
  4. data/app/controllers/spree/admin/user_events_controller.rb +9 -7
  5. data/app/controllers/spree/api/v2/storefront/vattanac_banks_controller.rb +25 -0
  6. data/app/controllers/spree/api/v2/tenant/change_passwords_controller.rb +32 -0
  7. data/app/controllers/spree/api/v2/tenant/id_cards_controller.rb +63 -0
  8. data/app/controllers/spree/api/v2/tenant/reset_passwords_controller.rb +35 -0
  9. data/app/controllers/spree/api/v2/tenant/tickets_controller.rb +25 -0
  10. data/app/controllers/spree/api/v2/tenant/user_contacts_controller.rb +33 -0
  11. data/app/controllers/spree_cm_commissioner/admin/roles_controller_decorator.rb +13 -0
  12. data/app/interactors/spree_cm_commissioner/firebase_email_fetcher.rb +25 -0
  13. data/app/interactors/spree_cm_commissioner/firebase_id_token_provider.rb +12 -1
  14. data/app/interactors/spree_cm_commissioner/user_id_token_authenticator.rb +11 -0
  15. data/app/interactors/spree_cm_commissioner/user_registration_with_id_token.rb +3 -3
  16. data/app/interactors/spree_cm_commissioner/user_vattanac_bank_web_app_authenticator.rb +30 -0
  17. data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +116 -0
  18. data/app/models/spree_cm_commissioner/order_decorator.rb +1 -1
  19. data/app/models/spree_cm_commissioner/role_decorator.rb +9 -3
  20. data/app/models/spree_cm_commissioner/taxon_decorator.rb +0 -1
  21. data/app/models/spree_cm_commissioner/trip.rb +7 -0
  22. data/app/models/spree_cm_commissioner/trip_connection.rb +10 -5
  23. data/app/models/spree_cm_commissioner/user_identity_provider.rb +1 -1
  24. data/app/models/spree_cm_commissioner/variant_decorator.rb +19 -2
  25. data/app/models/spree_cm_commissioner/variant_options.rb +14 -0
  26. data/app/overrides/spree/admin/users/_form/roles_fields.html.erb.deface +2 -0
  27. data/app/queries/spree_cm_commissioner/tickets_searcher_query.rb +21 -0
  28. data/app/queries/spree_cm_commissioner/trip_query.rb +145 -0
  29. data/app/serializers/spree/v2/tenant/id_card_serializer.rb +12 -0
  30. data/app/serializers/spree/v2/tenant/reset_password_serializer.rb +8 -0
  31. data/app/serializers/spree/v2/tenant/ticket_serializer.rb +27 -0
  32. data/app/serializers/spree/v2/tenant/user_contact_serializer.rb +11 -0
  33. data/app/services/spree_cm_commissioner/aes_encryption_service.rb +52 -0
  34. data/app/services/spree_cm_commissioner/role_permissions_constructor.rb +42 -0
  35. data/app/services/spree_cm_commissioner/role_permissions_loader.rb +57 -0
  36. data/app/services/spree_cm_commissioner/rsa_service.rb +27 -0
  37. data/app/services/spree_cm_commissioner/user_authenticator.rb +4 -0
  38. data/app/views/spree/admin/user_events/_events.html.erb +1 -1
  39. data/app/views/spree/admin/user_events/index.html.erb +7 -3
  40. data/config/routes.rb +9 -2
  41. data/db/migrate/20250328072717_add_description_to_spree_roles.rb +1 -1
  42. data/db/migrate/20250328072841_add_vendor_id_to_spree_roles.rb +1 -1
  43. data/db/migrate/20250403025619_add_presentation_to_spree_role.rb +5 -0
  44. data/db/migrate/20250403042255_add_unique_constraint_to_name_in_spree_roles.rb +7 -0
  45. data/db/migrate/20250407092556_add_variant_id_to_cm_trips.rb +7 -0
  46. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +9 -0
  47. data/lib/spree_cm_commissioner/test_helper/factories/role_permission_factory.rb +11 -0
  48. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +6 -0
  49. data/lib/spree_cm_commissioner/test_helper/factories/user_identity_provider_factory.rb +4 -0
  50. data/lib/spree_cm_commissioner/trip_query_result.rb +14 -0
  51. data/lib/spree_cm_commissioner/user_session_jwt_token.rb +5 -0
  52. data/lib/spree_cm_commissioner/version.rb +1 -1
  53. data/lib/spree_cm_commissioner.rb +2 -0
  54. data/spree_cm_commissioner.gemspec +1 -0
  55. metadata +44 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77e091c59843dfdbf104e3428b722dc6473c35378ff68ee8dffb6e2bdd64f38f
4
- data.tar.gz: 26f93d1d95b6a88148bff4af7e2002da525c10fec0ec8a3d6bcbd066fd44390e
3
+ metadata.gz: 6aa3cfdf9976b9d40fa5c53533193d93ea1503b79b288c926e5efe8f371db0b5
4
+ data.tar.gz: f77a64037130b4d871c346534131a3fa664f5fb7cb344834b730e9a1c02e2177
5
5
  SHA512:
6
- metadata.gz: e011a57a947400dadb38391e3f030a4a9247ea7227172e477b1d5de26039e362b5294b3e6863a75edd7040140ff3cea22e6f0ceea16642a565284de231732af9
7
- data.tar.gz: d6ecf636a49e10b089db0a68175492f8e3f35c0bdc033aa290a832ef129afa0dcb93d756f413beaadce8f6ff60a3a84d625ce8c295c5aae1163d1f7e5b486683
6
+ metadata.gz: 7296a57cfe2b468ff16941f59f8fe744157721df84d05532ee618a4736b5db2c7802975145a607d825b42ea009d460b114479a81e8009c2c5b1a359a7d576902
7
+ data.tar.gz: 6c8a2a04159368014afdbd2203281f647f2b054281ef1044817967d0e108517e9ddccc0fa04f42aec25ba6af9e24eef7b00f4c253a5d301cb53f145307ebd8fa
data/.env.example CHANGED
@@ -20,3 +20,7 @@ WAITING_ROOM_SESSION_SIGNATURE="e6b2********************26e3"
20
20
  WAITING_ROOM_SESSION_EXPIRE_DURATION_IN_SECOND=
21
21
  WAITING_ROOM_MIN_SESSIONS_COUNT=5
22
22
  WAITING_ROOM_DISABLED=no
23
+
24
+ # Vattanac Bank
25
+ VATTANAC_AES_SECRET_KEY= ""
26
+ VATTANAC_PUBLIC_KEY=""
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (1.8.10)
37
+ spree_cm_commissioner (1.9.0)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -46,6 +46,7 @@ PATH
46
46
  dry-validation (~> 1.10)
47
47
  elasticsearch (~> 8.5)
48
48
  exception_notification
49
+ firebase-admin-sdk (~> 0.3.1)
49
50
  font-awesome-sass (~> 6.4.0)
50
51
  google-cloud-firestore
51
52
  google-cloud-recaptcha_enterprise
@@ -269,6 +270,7 @@ GEM
269
270
  database_cleaner-core (~> 2.0.0)
270
271
  database_cleaner-core (2.0.1)
271
272
  date (3.3.3)
273
+ declarative (0.0.20)
272
274
  deface (1.9.0)
273
275
  actionview (>= 5.2)
274
276
  nokogiri (>= 1.6)
@@ -370,6 +372,11 @@ GEM
370
372
  rake
371
373
  fiber-annotation (0.2.0)
372
374
  fiber-local (1.0.0)
375
+ firebase-admin-sdk (0.3.1)
376
+ faraday (> 1, < 3)
377
+ google-apis-fcm_v1 (>= 0.19.0, < 1.0)
378
+ googleauth (> 0.16, < 2.0)
379
+ jwt (>= 1.5, < 3.0)
373
380
  flag-icons-rails (3.4.6.1)
374
381
  sass-rails
375
382
  flatpickr (4.6.13.0)
@@ -398,6 +405,16 @@ GEM
398
405
  globalid (1.2.1)
399
406
  activesupport (>= 6.1)
400
407
  glyphicons (1.0.2)
408
+ google-apis-core (0.11.3)
409
+ addressable (~> 2.5, >= 2.5.1)
410
+ googleauth (>= 0.16.2, < 2.a)
411
+ httpclient (>= 2.8.1, < 3.a)
412
+ mini_mime (~> 1.0)
413
+ representable (~> 3.0)
414
+ retriable (>= 2.0, < 4.a)
415
+ rexml
416
+ google-apis-fcm_v1 (0.23.0)
417
+ google-apis-core (>= 0.11.0, < 2.a)
401
418
  google-cloud-core (1.6.0)
402
419
  google-cloud-env (~> 1.0)
403
420
  google-cloud-errors (~> 1.0)
@@ -646,9 +663,14 @@ GEM
646
663
  redis-client (0.17.0)
647
664
  connection_pool
648
665
  regexp_parser (2.8.2)
666
+ representable (3.2.0)
667
+ declarative (< 0.1.0)
668
+ trailblazer-option (>= 0.1.1, < 0.2.0)
669
+ uber (< 0.2.0)
649
670
  responders (3.1.1)
650
671
  actionpack (>= 5.2)
651
672
  railties (>= 5.2)
673
+ retriable (3.1.2)
652
674
  rexml (3.2.6)
653
675
  rqrcode (2.2.0)
654
676
  chunky_png (~> 1.0)
@@ -858,6 +880,7 @@ GEM
858
880
  tinymce-rails (5.10.7.1)
859
881
  railties (>= 3.1.1)
860
882
  traces (0.11.1)
883
+ trailblazer-option (0.1.2)
861
884
  turbo-rails (1.5.0)
862
885
  actionpack (>= 6.0.0)
863
886
  activejob (>= 6.0.0)
@@ -868,6 +891,7 @@ GEM
868
891
  nokogiri (>= 1.6, < 2.0)
869
892
  tzinfo (2.0.6)
870
893
  concurrent-ruby (~> 1.0)
894
+ uber (0.1.0)
871
895
  unf (0.1.4)
872
896
  unf_ext
873
897
  unf_ext (0.0.8.2)
@@ -2,22 +2,20 @@ module Spree
2
2
  module Admin
3
3
  class UserEventsController < Spree::Admin::ResourceController
4
4
  before_action :load_parents, if: -> { params[:taxon_id].present? }
5
+ before_action :load_user, if: -> { params[:user_id].present? }
5
6
 
6
7
  # override
7
8
  def index
8
9
  if params[:taxon_id].present?
9
10
  @user_events = @taxon.user_events
10
- else
11
- @user = Spree::User.find(params[:user_id])
11
+ elsif params[:user_id].present?
12
12
  @user_events = @user.user_events
13
+ else
14
+ flash[:error] = 'Invalid taxon or user' # rubocop:disable Rails/I18nLocaleTexts
15
+ redirect_back(fallback_location: collection_url)
13
16
  end
14
17
  end
15
18
 
16
- # override
17
- def new
18
- @user = spree_current_user
19
- end
20
-
21
19
  # override
22
20
  def create
23
21
  user_event = SpreeCmCommissioner::UserEvent.new(permitted_resource_params)
@@ -32,6 +30,10 @@ module Spree
32
30
 
33
31
  private
34
32
 
33
+ def load_user
34
+ @user = Spree::User.find(params[:user_id])
35
+ end
36
+
35
37
  def load_parents
36
38
  @taxon = Spree::Taxon.find(params[:taxon_id])
37
39
  @taxonomy = @taxon.taxonomy
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class VattanacBanksController < Spree::Api::V2::BaseController
6
+ def create
7
+ result = SpreeCmCommissioner::VattanacBankInitiator.call(params: params)
8
+
9
+ if result.success?
10
+ render json: {
11
+ message: 'SUCCESS',
12
+ data: result.data
13
+ }
14
+ else
15
+ render json: {
16
+ message: 'FAILED',
17
+ error: result.error
18
+ }, status: result.status || :unprocessable_entity
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class ChangePasswordsController < BaseController
6
+ before_action :require_spree_current_user
7
+
8
+ def update
9
+ context = SpreeCmCommissioner::PasswordChanger.call(change_password_params)
10
+
11
+ if context.success?
12
+ render_serialized_payload { serialize_resource(context.user) }
13
+ else
14
+ render_error_payload(context.message)
15
+ end
16
+ end
17
+
18
+ def resource_serializer
19
+ Spree::V2::Tenant::UserSerializer
20
+ end
21
+
22
+ def change_password_params
23
+ result = params.permit(:current_password, :password, :password_confirmation)
24
+ result[:user] = spree_current_user
25
+
26
+ result.to_h
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,63 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class IdCardsController < BaseController
6
+ def create
7
+ context = SpreeCmCommissioner::GuestIdCardManager.call(
8
+ card_type: id_card_params[:card_type],
9
+ front_image_url: id_card_params[:front_image_url],
10
+ back_image_url: id_card_params[:back_image_url],
11
+ guest_id: id_card_params[:guest_id]
12
+ )
13
+
14
+ if context.success?
15
+ render_serialized_payload { serialize_resource(context.result) }
16
+ else
17
+ render_error_payload(context.message)
18
+ end
19
+ end
20
+
21
+ def update
22
+ context = SpreeCmCommissioner::GuestIdCardManager.call(
23
+ card_type: id_card_params[:card_type],
24
+ front_image_url: id_card_params[:front_image_url],
25
+ back_image_url: id_card_params[:back_image_url],
26
+ guest_id: id_card_params[:guest_id]
27
+ )
28
+
29
+ if context.success?
30
+ render_serialized_payload { serialize_resource(context.result) }
31
+ else
32
+ render_error_payload(context.message)
33
+ end
34
+ end
35
+
36
+ def destroy
37
+ id_card = SpreeCmCommissioner::IdCard.find(params[:id])
38
+
39
+ if id_card.destroy
40
+ head :no_content
41
+ else
42
+ render_error_payload('Failed to destroy ID card', 400)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def id_card_params
49
+ params.permit(:card_type, :front_image_url, :back_image_url, :guest_id)
50
+ end
51
+
52
+ def model_class
53
+ SpreeCmCommissioner::IdCard
54
+ end
55
+
56
+ def resource_serializer
57
+ Spree::V2::Tenant::IdCardSerializer
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,35 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class ResetPasswordsController < BaseController
6
+ def update
7
+ context = SpreeCmCommissioner::UserForgottenPasswordUpdater.call(update_params)
8
+
9
+ if context.success?
10
+ render_serialized_payload { serialize_resource(context.user) }
11
+ else
12
+ render_error_payload(context.message)
13
+ end
14
+ end
15
+
16
+ def resource_serializer
17
+ Spree::V2::Tenant::ResetPasswordSerializer
18
+ end
19
+
20
+ def update_params
21
+ params.permit(
22
+ :email,
23
+ :phone_number,
24
+ :country_code,
25
+ :pin_code,
26
+ :pin_code_token,
27
+ :password,
28
+ :password_confirmation
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class TicketsController < BaseController
6
+ def index
7
+ render_serialized_payload do
8
+ serialize_collection(paginated_collection)
9
+ end
10
+ end
11
+
12
+ def collection
13
+ SpreeCmCommissioner::TicketsSearcherQuery.new(params, MultiTenant.current_tenant_id).call
14
+ end
15
+
16
+ private
17
+
18
+ def collection_serializer
19
+ Spree::V2::Tenant::TicketSerializer
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class UserContactsController < BaseController
6
+ def update
7
+ options = user_with_pin_code_params
8
+
9
+ generator_context = ::SpreeCmCommissioner::UserContactUpdater.call(options)
10
+
11
+ if generator_context.success?
12
+ render_serialized_payload { serialize_resource(generator_context.user) }
13
+ else
14
+ render_error_payload(generator_context.message)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def user_with_pin_code_params
21
+ results = params.permit(:pin_code, :pin_code_token, :email, :phone_number)
22
+ results[:user] = spree_current_user
23
+ results.to_h
24
+ end
25
+
26
+ def resource_serializer
27
+ Spree::V2::Tenant::UserContactSerializer
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ module SpreeCmCommissioner
2
+ module Admin
3
+ module RolesControllerDecorator
4
+ def index
5
+ @roles = Spree::Role.non_vendor.page(params[:page])
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ unless Spree::Admin::RolesController.ancestors.include?(SpreeCmCommissioner::Admin::RolesControllerDecorator)
12
+ Spree::Admin::RolesController.prepend(SpreeCmCommissioner::Admin::RolesControllerDecorator)
13
+ end
@@ -0,0 +1,25 @@
1
+ require 'firebase-admin-sdk'
2
+
3
+ module SpreeCmCommissioner
4
+ class FirebaseEmailFetcher < BaseInteractor
5
+ delegate :user_id, to: :context
6
+
7
+ def call
8
+ manager = initialize_firebase_manager
9
+ user = manager.get_user_by(uid: context.user_id)
10
+
11
+ context.email = user.provider_data.first&.email
12
+ end
13
+
14
+ private
15
+
16
+ def service_account
17
+ @service_account ||= Rails.application.credentials.cloud_firestore_service_account
18
+ end
19
+
20
+ def initialize_firebase_manager
21
+ @credentials ||= Firebase::Admin::Credentials.from_json(service_account.to_json)
22
+ @manager ||= Firebase::Admin::Auth::UserManager.new(service_account[:project_id], @credentials)
23
+ end
24
+ end
25
+ end
@@ -85,8 +85,9 @@ module SpreeCmCommissioner
85
85
  return nil if claim.nil?
86
86
 
87
87
  provider_name = claim['firebase']['sign_in_provider']
88
+ user_id = claim['user_id']
88
89
  sub = claim['firebase']['identities'][provider_name].first
89
- email = claim['email']
90
+ email = claim['email'] || get_user_email(user_id)
90
91
  name = claim['name']
91
92
 
92
93
  {
@@ -97,6 +98,16 @@ module SpreeCmCommissioner
97
98
  }
98
99
  end
99
100
 
101
+ def get_user_email(user_id)
102
+ return nil if user_id.blank?
103
+
104
+ firebase_context = FirebaseEmailFetcher.call(user_id: user_id)
105
+
106
+ return unless firebase_context.success?
107
+
108
+ firebase_context.email
109
+ end
110
+
100
111
  delegate :id_token, to: :context
101
112
  end
102
113
  end
@@ -7,6 +7,9 @@ module SpreeCmCommissioner
7
7
  else
8
8
  checker.user
9
9
  end
10
+
11
+ update_user_email if context.user.email.blank?
12
+
10
13
  context.fail!(message: 'account_temporarily_deleted') if context.user.soft_deleted?
11
14
  end
12
15
 
@@ -15,6 +18,14 @@ module SpreeCmCommissioner
15
18
  register_context.user
16
19
  end
17
20
 
21
+ def update_user_email
22
+ firebase_context = FirebaseIdTokenProvider.call(id_token: context.id_token)
23
+ return unless firebase_context.success?
24
+
25
+ email = firebase_context.provider[:email]
26
+ context.user.update(email: email) if email.present?
27
+ end
28
+
18
29
  def checker
19
30
  @checker ||= SpreeCmCommissioner::UserIdTokenChecker.call(id_token: context.id_token)
20
31
  @checker
@@ -6,7 +6,7 @@ module SpreeCmCommissioner
6
6
 
7
7
  if firebase_context.success?
8
8
  ActiveRecord::Base.transaction do
9
- register_user!(firebase_context.provider[:name])
9
+ register_user!(firebase_context.provider[:name], firebase_context.provider[:email])
10
10
  link_user_account!(firebase_context.provider)
11
11
  end
12
12
  else
@@ -14,8 +14,8 @@ module SpreeCmCommissioner
14
14
  end
15
15
  end
16
16
 
17
- def register_user!(name)
18
- user = Spree.user_class.new(password: SecureRandom.base64(16), **name_attributes(name))
17
+ def register_user!(name, email)
18
+ user = Spree.user_class.new(password: SecureRandom.base64(16), email: email, **name_attributes(name))
19
19
  if user.save(validate: false)
20
20
  context.user = user
21
21
  else
@@ -0,0 +1,30 @@
1
+ module SpreeCmCommissioner
2
+ class UserVattanacBankWebAppAuthenticator < BaseInteractor
3
+ delegate :session_id, to: :context
4
+
5
+ def call
6
+ return context.fail!(message: 'Invalid token') unless payload
7
+ return context.fail!(message: 'User not found') unless user
8
+
9
+ verify_token!
10
+
11
+ context.user = user
12
+ rescue JWT::DecodeError => e
13
+ context.fail!(message: "Decode error: #{e.message}")
14
+ end
15
+
16
+ private
17
+
18
+ def payload
19
+ @payload ||= SpreeCmCommissioner::UserSessionJwtToken.decode(session_id)
20
+ end
21
+
22
+ def user
23
+ @user ||= Spree::User.find_by(id: payload['user_id']) if payload
24
+ end
25
+
26
+ def verify_token!
27
+ SpreeCmCommissioner::UserSessionJwtToken.decode(session_id, user.secure_token)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,116 @@
1
+ module SpreeCmCommissioner
2
+ class VattanacBankInitiator
3
+ include Interactor
4
+
5
+ def call
6
+ extract_encrypted_data_and_signature
7
+ verify_signature
8
+ decrypt_payload
9
+ find_or_create_user
10
+ construct_data
11
+ end
12
+
13
+ private
14
+
15
+ def extract_encrypted_data_and_signature
16
+ data_param = context.params[:data]
17
+ context.fail!(message: 'Invalid payload format', status: :bad_request) if data_param.blank?
18
+
19
+ parts = data_param.to_s.split('.', 2)
20
+ context.fail!(message: 'Invalid payload format', status: :bad_request) unless parts.size == 2
21
+
22
+ encrypted_data, signature = parts
23
+ context.encrypted_data = encrypted_data
24
+ context.signature = signature
25
+ end
26
+
27
+ def verify_signature
28
+ public_key = ENV['VATTANAC_PUBLIC_KEY'].presence || Rails.application.credentials.vattanac.public_key
29
+
30
+ rsa_service = SpreeCmCommissioner::RsaService.new(
31
+ public_key: public_key
32
+ )
33
+
34
+ return if rsa_service.verify(context.encrypted_data, context.signature)
35
+
36
+ context.fail!(message: 'Invalid signature', status: :unauthorized)
37
+ end
38
+
39
+ def decrypt_payload
40
+ aes_key = ENV['VATTANAC_AES_SECRET_KEY'].presence || Rails.application.credentials.vattanac.aes_secret_key
41
+
42
+ context.fail!(message: 'Invalid AES key length', status: :unprocessable_entity) unless aes_key
43
+
44
+ begin
45
+ decrypted_json = SpreeCmCommissioner::AesEncryptionService.decrypt(context.encrypted_data, aes_key)
46
+ context.user_data = JSON.parse(decrypted_json)
47
+ rescue StandardError => e
48
+ context.fail!(message: 'Decryption failed', error: e.message, status: :unprocessable_entity)
49
+ end
50
+ end
51
+
52
+ def find_or_create_user
53
+ find_user_by_identity || find_existing_user || create_new_user
54
+ end
55
+
56
+ def find_user_by_identity
57
+ user_data = context.user_data
58
+
59
+ identity = SpreeCmCommissioner::UserIdentityProvider.vattanac_bank
60
+ .find_or_initialize_by(sub: user_data['id'])
61
+
62
+ if identity.persisted?
63
+ context.user = identity.user
64
+ else
65
+ context.identity = identity
66
+ end
67
+
68
+ context.user
69
+ end
70
+
71
+ def find_existing_user
72
+ user_data = context.user_data
73
+
74
+ context.user = Spree::User.find_by(
75
+ 'email = ? OR phone_number = ?', user_data['email'], user_data['phoneNum']
76
+ )
77
+ end
78
+
79
+ def create_new_user
80
+ user_data = context.user_data
81
+ full_name = "#{user_data['firstName']} #{user_data['lastName']}"
82
+ identity = context.identity
83
+
84
+ identity.name = full_name
85
+
86
+ context.user = Spree::User.new(
87
+ first_name: user_data['firstName'],
88
+ last_name: user_data['lastName'],
89
+ email: user_data['email'],
90
+ phone_number: user_data['phoneNum'],
91
+ password: SecureRandom.base64(16),
92
+ user_identity_providers: [identity]
93
+ )
94
+
95
+ return if context.user.save
96
+
97
+ context.fail!(message: "User creation failed: #{context.user.errors.full_messages.join(', ')}")
98
+ end
99
+
100
+ def construct_data
101
+ user = context.user
102
+ context.data = {
103
+ sessionId: session_id,
104
+ name: user.full_name,
105
+ phone: user.phone_number,
106
+ email: user.email,
107
+ webUrl: "#{Spree::Store.default.formatted_url}/vattanac_bank_web_app?session_id=#{session_id}"
108
+ }
109
+ end
110
+
111
+ def session_id
112
+ payload = { user_id: context.user.id }
113
+ SpreeCmCommissioner::UserSessionJwtToken.encode(payload, context.user.reload.secure_token)
114
+ end
115
+ end
116
+ end
@@ -46,7 +46,7 @@ module SpreeCmCommissioner
46
46
 
47
47
  base.delegate :customer, to: :user, allow_nil: true
48
48
 
49
- base.whitelisted_ransackable_associations |= %w[customer taxon payments invoice]
49
+ base.whitelisted_ransackable_associations |= %w[customer taxon payments guests invoice]
50
50
  base.whitelisted_ransackable_attributes |= %w[intel_phone_number phone_number email number state]
51
51
 
52
52
  def base.search_by_qr_data!(data)
@@ -10,10 +10,16 @@ module SpreeCmCommissioner
10
10
 
11
11
  base.accepts_nested_attributes_for :role_permissions, allow_destroy: true
12
12
 
13
- base._validators.reject! { |key, _| key == :name }
14
- base._validate_callbacks.each { |c| c.filter.attributes.delete(:name) if c.filter.respond_to?(:attributes) }
13
+ base.before_validation :generate_unique_name, if: -> { vendor_id.present? }
15
14
 
16
- base.validates :name, uniqueness: { scope: :vendor_id, allow_blank: true }
15
+ base.validates :presentation, uniqueness: { scope: :vendor_id }, presence: true, if: -> { vendor_id.present? }
16
+ end
17
+
18
+ def generate_unique_name
19
+ return if name.present?
20
+ return if presentation.blank?
21
+
22
+ self.name = "#{vendor.slug}-#{presentation.downcase.parameterize(separator: '-')}"
17
23
  end
18
24
  end
19
25
  end
@@ -7,7 +7,6 @@ module SpreeCmCommissioner
7
7
 
8
8
  base.preference :background_color, :string
9
9
  base.preference :foreground_color, :string
10
- base.preference :reports, :array, default: []
11
10
 
12
11
  base.has_many :taxon_vendors, class_name: 'SpreeCmCommissioner::TaxonVendor'
13
12
  base.has_many :vendors, through: :taxon_vendors
@@ -4,6 +4,7 @@ module SpreeCmCommissioner
4
4
  attr_accessor :hours, :minutes, :seconds
5
5
 
6
6
  before_validation :convert_duration_to_seconds
7
+ belongs_to :variant, class_name: 'Spree::Variant'
7
8
 
8
9
  belongs_to :route, class_name: 'Spree::Product'
9
10
  belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle'
@@ -39,6 +40,12 @@ module SpreeCmCommissioner
39
40
  { hours: hours, minutes: minutes, seconds: seconds }
40
41
  end
41
42
 
43
+ def arrival_time
44
+ return nil if departure_time.nil? || duration.nil?
45
+
46
+ departure_time + duration.seconds
47
+ end
48
+
42
49
  private
43
50
 
44
51
  def origin_and_destination_cannot_be_the_same