Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Style/Documentation:
Metrics/ParameterLists:
Max: 8

Lint/MissingSuper:
Exclude:
- app/services/**/*

Metrics/BlockLength:
Exclude:
- spec/**/*
Expand Down
18 changes: 14 additions & 4 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2026-01-27 11:43:39 UTC using RuboCop version 1.84.0.
# on 2026-01-27 12:23:34 UTC using RuboCop version 1.84.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand All @@ -13,15 +13,25 @@ Lint/RedundantCopDisableDirective:
- 'app/jobs/update_sparta_state_job.rb'
- 'app/services/spl/sparta_loyalty_service.rb'

# Offense count: 8
# Offense count: 3
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 18

# Offense count: 2
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/CyclomaticComplexity:
Max: 8

# Offense count: 13
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Max: 14

# Offense count: 1
# Configuration parameters: CountComments, CountAsOne.
Metrics/ModuleLength:
Max: 125
Max: 156

# Offense count: 2
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
Expand Down Expand Up @@ -82,7 +92,7 @@ Style/IfUnlessModifier:
- 'app/services/assign_sparta_card_number_service.rb'
- 'app/services/spl/validate_card_service.rb'

# Offense count: 4
# Offense count: 5
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
# URISchemes: http, https
Expand Down
312 changes: 310 additions & 2 deletions Gemfile.lock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Z czystej ciekawości, skąd się wzięły podpicia w Gemfile.lock jak nie ma żadnych zmian w Gemfile?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@agnieszkajacek Z EK-685, które pewnie poszło razem z Veroną na wyższą wersję 🤔 (tam również nie ma zmian w gemspec/gemfile). Co ty na to, żeby tutaj podbić w gemspec na spree 5.2?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dla mnie spoko!

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,45 @@ module Spl
module Spree
module Storefront
module CheckoutControllerDecorator
include ErrorHandlingHelper
include BooleanHelper

def self.prepended(base)
base.before_action :load_user_coupons
base.before_action :promotion_switcher
base.before_action :load_user_coupons, except: %i[activate_coupon deactivate_coupon]
base.after_action :perform_update_sparta_state_job, only: %i[confirm complete]
end

private
def activate_coupon
Spl::Coupons::ActivateCouponService.new(@order.user, @order.store, params[:coupon_code]).call
load_user_coupons
rescue StandardError => e
handle_spl_error(e)
raise e
ensure
respond_to do |format|
format.turbo_stream
format.html { redirect_to checkout_path }
end
end

def load_user_coupons
@coupons = Spl::GetCouponsService.new(@order.user, @order.store).call
def deactivate_coupon
Spl::Coupons::DeactivateCouponService
.new(@order.user, @order.store, params[:coupon_code])
.call
load_user_coupons
rescue StandardError => e
handle_spl_error(e)
raise e
ensure
respond_to do |format|
format.turbo_stream
format.html { redirect_to checkout_path }
end
end

private

def promotion_switcher
PromotionSwitcherService.new(@order, checkout_state_allowed?).call
end
Expand All @@ -34,6 +59,13 @@ def perform_update_sparta_state_job
UpdateSpartaStateJob.perform_later(@order.token, 'C', @order.number, @order.store)
end
end

def load_user_coupons
@coupons = Spl::Coupons::GetCouponsService.new(@order.user, @order.store).call
rescue StandardError => e
handle_spl_error(e)
raise e
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Storefront
module ProfileControllerDecorator
include BooleanHelper
include ProfileControllerHelper
include ErrorHandlingHelper

def self.prepended(base)
base.before_action :validate_spl_no_card, only: :update
Expand Down Expand Up @@ -128,9 +129,37 @@ def clear_errors
try_spree_current_user.errors.clear
end

def assign_card_number(user, store, params)
Spl::LoginAccountService.new(user, store, params).call
AssignSpartaCardNumberService.new(user, store).call
def render_login_code_error
render turbo_stream: turbo_stream.replace(
'loyalty_connect_form',
partial: 'spl/loyalty_connect_form',
locals: { user: try_spree_current_user }
),
status: :unprocessable_content
end

def render_login_code_success(phone)
render turbo_stream: turbo_stream.replace(
'loyalty_connect_form',
partial: 'spl/otp_code_form',
locals: {
user: try_spree_current_user,
phone_e164: phone.respond_to?(:e164) ? phone.e164 : nil
}
),
status: :ok
end

def render_connect_loyalty_account_error(phone)
render turbo_stream: turbo_stream.replace(
'otp_code_form',
partial: 'spl/otp_code_form',
locals: {
user: try_spree_current_user,
phone_e164: phone.respond_to?(:e164) ? phone.e164 : nil
}
),
status: :unprocessable_content
end

def send_otp(phone, store)
Expand All @@ -152,6 +181,11 @@ def handle_spl_error(error)
try_spree_current_user.errors.add(:base, msg)
end

def assign_card_number(user, store, params)
Spl::LoginAccountService.new(user, store, params).call
AssignSpartaCardNumberService.new(user, store).call
end

def login_code_params
params.require(:user).permit(:phone, :accept_yc_terms)
end
Expand Down
22 changes: 22 additions & 0 deletions app/helpers/error_handling_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module ErrorHandlingHelper
# Parses and translate occurred error, then adds it to user errors.
# Dedicated for storefront controllers.
# @param [StandardError]
def handle_spl_error(error)
payload = Spl::ErrorPayloadParser.parse(error.message) || error
msg = Spl::ErrorTranslator.translate(payload)

clear_errors
try_spree_current_user.errors.add(:base, msg)
end

def clear_errors
try_spree_current_user.errors.clear
end

def token_expired?(err_msg)
err_msg == 'TOKEN_EXPIRED'
end
end
6 changes: 2 additions & 4 deletions app/helpers/login_check_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ module LoginCheckHelper
# Checks if user is logged to SPL basing on local information
# @param user [Spree::User]
# @return [true, false]
def self.logged?(user)
return false unless user&.private_metadata&.fetch('spl_access_token', nil)

true
def logged_user?(user)
user&.private_metadata&.fetch('spl_access_token', nil).present?
end
end
24 changes: 24 additions & 0 deletions app/helpers/spl_service_helper.rb

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Najs 😍
Drobne pytanie techniczne - przetestowałeś koszyk jak podmieniałeś wysyłanie requestu helperem? 🤔

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module SplServiceHelper
def send_request(url, body)
Spl::SendRequestService.new(url, body).call
end

# Refreshes user private token to keep possible
# using customer oriented endpoints
# @param [user: Spree::User]
def refresh_user_token(user, store)
return if user.private_metadata.nil?
return if user.private_metadata['spl_refresh_token'].nil?

response = Spl::OauthTokenService.new(DateTime.current, store).refresh_token(user.private_metadata['spl_refresh_token'])

user.update!(private_metadata: { spl_access_token: response['access_token'],
spl_refresh_token: response['refresh_token'] })
end

def token_refresh_needed(response_body, retry_counter, user, store)
token_expired?(response_body['errorCode']) && retry_counter < 1 && refresh_user_token(user, store)
end
end
2 changes: 1 addition & 1 deletion app/services/apply_sparta_discount_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def initialize(response, order)
@response = response
end

def call # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
def call # rubocop:disable Metrics/AbcSize
return unless response_valid?

line_items.each do |line_item|
Expand Down
51 changes: 51 additions & 0 deletions app/services/spl/coupons/activate_coupon_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require 'json'

module Spl
module Coupons
class ActivateCouponService
class ActivateCouponServiceError < StandardError; end
include SplServiceHelper
include ErrorHandlingHelper
include LoginCheckHelper

def initialize(user, store, coupon_code)
@store = store
@activate_coupons_url = URI.parse(Spl::UrlCreatorService.new(store.private_metadata['spl_url']).coupon_activate)
@user = user
@coupon_code = coupon_code
@retry_counter = 0
end

def call
return unless @user.present? && @user.private_metadata.present?
return unless logged_user?(@user)

response = send_request(@activate_coupons_url, body)
response_body = JSON.parse(response.body)
Rails.logger.debug response_body
raise ActivateCouponServiceError, response_body['msg'] if response_body['errorCode'] != '0'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jak tak to zwrócisz, to błąd zawsze będzie po angielsku. Więc do rozważenia, żeby to zrobić 'na modłę' z rejestracji i nadpisać wyświetlanie błędów w kontrolerze z użyciem parsera i translatora

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorki ale co znaczy na modłę? xD i wyświetlenie błędu masz na myśli jako flash message?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Na modłę w sensie w ten sam sposób, podobnie do 🙌
W sumie ja tam nie robiłam blędów jako flash message tylko dodawałam po prostu błąd do obiektu (w profile controllerze do usera dodawałam błąd), błąd pojawiał się na czerwonym tle i nie znikał.


response_body['response']
rescue ActivateCouponServiceError => e
raise e unless token_refresh_needed(response_body, @retry_counter, @user, @store)

@retry_counter += 1
retry
end

private

def body
{
context: {
prgCode: @store.private_metadata['spl_prg_code'],
oauthToken: @user.private_metadata['spl_access_token']
},
couponCode: @coupon_code
}
end
end
end
end
51 changes: 51 additions & 0 deletions app/services/spl/coupons/deactivate_coupon_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require 'json'

module Spl
module Coupons
class DeactivateCouponService
class DeactivateCouponServiceError < StandardError; end
include SplServiceHelper
include ErrorHandlingHelper
include LoginCheckHelper

def initialize(user, store, coupon_code)
@store = store
@deactivate_coupons_url = URI.parse(Spl::UrlCreatorService.new(store.private_metadata['spl_url']).coupon_deactivate)
@user = user
@coupon_code = coupon_code
@retry_counter = 0
end

def call
return unless @user.present? && @user.private_metadata.present?
return unless logged_user?(@user)

response = send_request(@deactivate_coupons_url, body)
response_body = JSON.parse(response.body)
Rails.logger.debug response_body
raise DeactivateCouponServiceError, response_body['msg'] if response_body['errorCode'] != '0'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu jak wyżej z parserem i translatorem 🙌
(nie będę do wszystkich miejsc z tym raisem dodawać pinga, ale do wszystkich serwisów tyczących się kuponów go uznaje)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nie ukrywam, że jestem trochę sceptycznie nastawiony do tłumaczenia kodów błędów. Dzisiaj zwracają coś takiego, jutro zwrócą jakiś inny i trzeba będzie to aktualizować :/ Do tego wiadomości błędu związane z kuponami akurat są o zerowej przydatności - CANNOT_BE_MODIFIED xd jakby dzięki za info, ale może byście powiedzieli dlaczego. Także tutaj nie jestem przekonany

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Z jednej strony masz rację, aczkolwiek też zabezpieczyłam się przed tym, że podeślą coś czego nie znamy (bo ofc dokumentacja nie mówi o wszystkim co nam dostawca może zwrócić) po prostu tym, że mamy błąd generic. Więc użytkownik na pewno nie dostanie translation missing, bo gdy nie znajdzie klucza, to wrzuci generic.
W przypadku kuponów możemy się zdecydować na to, żeby CANNOT_BE_MODIFIED dawało nam np wiadomość Nie można użyć tego kuponu, a jak zwróci cokolwiek innego to handlować z generic. Tylko wiadomość generic trzeba uspójnić tak aby była uniwersalna (bo narazie odpowiada tylko do rejestracji/łączenia kont), więc bym zmieniła na taką treść: generic: Nie udało się wykonać akcji. Spróbuj ponownie.
Albo coś podobnego jeśli lepsze wymyślisz ;)


response_body['response']
rescue DeactivateCouponServiceError => e
raise e unless token_refresh_needed(response_body, @retry_counter, @user, @store)

@retry_counter += 1
retry
end

private

def body
{
context: {
prgCode: @store.private_metadata['spl_prg_code'],
oauthToken: @user.private_metadata['spl_access_token']
},
couponCode: @coupon_code
}
end
end
end
end
Loading