Skip to content
Open
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
3 changes: 2 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@ class User < ApplicationRecord
:do => :add_user_comment
after_transition any => [:flagged_for_tos_violation], :do => :add_product_comment

after_transition any => %i[suspended_for_fraud suspended_for_tos_violation], :do => :suspend_sellers_other_accounts
after_transition any => :suspended_for_fraud, :do => :suspend_sellers_other_accounts
after_transition any => :suspended_for_tos_violation, :do => :probate_sellers_other_accounts
after_transition any => %i[suspended_for_fraud suspended_for_tos_violation], :do => :block_seller_ip!
after_transition any => %i[suspended_for_fraud suspended_for_tos_violation], :do => :delete_custom_domain!
after_transition any => %i[suspended_for_fraud suspended_for_tos_violation], :do => :log_suspension_time_to_mongo
Expand Down
4 changes: 4 additions & 0 deletions app/modules/user/risk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ def suspend_sellers_other_accounts(transition)
SuspendAccountsWithPaymentAddressWorker.perform_in(5.seconds, id)
end

def probate_sellers_other_accounts(_transition)
ProbateAccountsWithPaymentAddressWorker.perform_in(5.seconds, id)
end

def block_seller_ip!
BlockSuspendedAccountIpWorker.perform_in(5.seconds, id)
end
Expand Down
62 changes: 35 additions & 27 deletions app/sidekiq/check_payment_address_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,50 @@ class CheckPaymentAddressWorker
include Sidekiq::Job
sidekiq_options retry: 0, queue: :default

CHECK_PAYMENT_ADDRESS_AUTHOR_NAME = "CheckPaymentAddress"

def perform(user_id)
user = User.find_by(id: user_id)
return unless user&.can_flag_for_fraud?
return if !user.can_flag_for_fraud? || user.payment_address.blank?

should_flag = payment_address_matches_suspended_account?(user) ||
stripe_fingerprint_matches_suspended_account?(user)
banned_accounts_with_same_payment_address = User.where(
payment_address: user.payment_address,
user_risk_state: ["suspended_for_tos_violation", "suspended_for_fraud"]
).order(updated_at: :desc)

user.flag_for_fraud!(author_name: "CheckPaymentAddress") if should_flag
end
blocked_email = BlockedObject.find_active_object(user.payment_address)

private
def payment_address_matches_suspended_account?(user)
return false if user.payment_address.blank?
suspended_for_fraud_uids = banned_accounts_with_same_payment_address.with_user_risk_state(:suspended_for_fraud).pluck(:external_id)
suspended_for_tos_violation_uids = banned_accounts_with_same_payment_address.with_user_risk_state(:suspended_for_tos_violation).pluck(:external_id)
has_fraudulent_activity_on_other_accounts = suspended_for_fraud_uids.present? || blocked_email.present?

banned_accounts_with_same_payment_address = User.where(
payment_address: user.payment_address,
user_risk_state: User::Risk::SUSPENDED_STATES
if suspended_for_tos_violation_uids.present? && (user.compliant? || user.not_reviewed?) && !has_fraudulent_activity_on_other_accounts
user.put_on_probation!(
author_name: CHECK_PAYMENT_ADDRESS_AUTHOR_NAME,
content: "Probated (payouts suspended) automatically on #{self.class.formatted_date} because of usage of payment address #{user.payment_address} (from suspended for TOS violation #{self.class.pluralize_user_label(suspended_for_tos_violation_uids.count)} #{self.class.format_uids(suspended_for_tos_violation_uids)})"
)
elsif has_fraudulent_activity_on_other_accounts
source = if suspended_for_fraud_uids.present?
"from suspended for fraud #{self.class.pluralize_user_label(suspended_for_fraud_uids.count)} #{self.class.format_uids(suspended_for_fraud_uids)}"
else
"from a fraudulent purchase"
end
user.flag_for_fraud!(
author_name: CHECK_PAYMENT_ADDRESS_AUTHOR_NAME,
content: "Flagged for fraud automatically on #{self.class.formatted_date} because of usage of payment address #{user.payment_address} (#{source})"
)

blocked_email = BlockedObject.find_active_object(user.payment_address)

banned_accounts_with_same_payment_address.exists? || blocked_email.present?
end
end

def stripe_fingerprint_matches_suspended_account?(user)
fingerprints = user.alive_bank_accounts.where.not(stripe_fingerprint: [nil, ""]).distinct.pluck(:stripe_fingerprint)
return false if fingerprints.empty?

suspended_accounts_with_same_fingerprint = BankAccount
.joins(:user)
.where(stripe_fingerprint: fingerprints)
.where.not(user_id: user.id)
.where(users: { user_risk_state: User::Risk::SUSPENDED_STATES })
def self.format_uids(uids)
uids.map { "##{_1}" }.to_sentence
end

return true if suspended_accounts_with_same_fingerprint.exists?
def self.formatted_date
Time.current.to_fs(:formatted_date_full_month)
end

BlockedObject.find_active_objects(fingerprints).present?
end
def self.pluralize_user_label(count)
count == 1 ? "User" : "Users"
end
end
21 changes: 21 additions & 0 deletions app/sidekiq/probate_accounts_with_payment_address_worker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

class ProbateAccountsWithPaymentAddressWorker
include Sidekiq::Job
sidekiq_options retry: 5, queue: :default

PROBATE_ACCOUNTS_WITH_PAYMENT_ADDRESS_AUTHOR_NAME = "probate_sellers_other_accounts"

def perform(user_id)
suspended_user = User.with_user_risk_state(:suspended_for_tos_violation).find(user_id)

return if suspended_user.payment_address.blank?

User.where(payment_address: suspended_user.payment_address).with_user_risk_state(:not_reviewed, :compliant).where.not(id: suspended_user.id).find_each(batch_size: 100) do |user|
user.put_on_probation!(
author_name: PROBATE_ACCOUNTS_WITH_PAYMENT_ADDRESS_AUTHOR_NAME,
content: "Probated (payouts suspended) automatically on #{Time.current.to_fs(:formatted_date_full_month)} because of usage of payment address #{suspended_user.payment_address} (from suspended for TOS violation User##{suspended_user.id})"
)
end
end
end
47 changes: 10 additions & 37 deletions app/sidekiq/suspend_accounts_with_payment_address_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,23 @@ class SuspendAccountsWithPaymentAddressWorker
include Sidekiq::Job
sidekiq_options retry: 5, queue: :default

def perform(user_id)
suspended_user = User.find(user_id)

suspend_users_with_same_payment_address(suspended_user)
suspend_users_with_same_stripe_fingerprint(suspended_user)
end

private
def suspend_users_with_same_payment_address(suspended_user)
return if suspended_user.payment_address.blank?

User.not_suspended
.where(payment_address: suspended_user.payment_address)
.where.not(id: suspended_user.id)
.find_each do |user|
flag_and_suspend_user(user, suspended_user, "payment address", suspended_user.payment_address)
end
end
SUSPEND_ACCOUNTS_WITH_PAYMENT_ADDRESS_AUTHOR_NAME = "suspend_sellers_other_accounts"

def suspend_users_with_same_stripe_fingerprint(suspended_user)
fingerprints = suspended_user.bank_accounts.where.not(stripe_fingerprint: [nil, ""]).distinct.pluck(:stripe_fingerprint)
return if fingerprints.empty?
def perform(user_id)
suspended_user = User.with_user_risk_state(:suspended_for_fraud).find(user_id)

user_ids_with_same_fingerprint = BankAccount.alive
.where(stripe_fingerprint: fingerprints)
.where.not(user_id: suspended_user.id)
.distinct
.pluck(:user_id)
return if suspended_user.payment_address.blank?

User.not_suspended.where(id: user_ids_with_same_fingerprint).find_each do |user|
matching_fingerprint = (fingerprints & user.alive_bank_accounts.pluck(:stripe_fingerprint)).first
flag_and_suspend_user(user, suspended_user, "bank account fingerprint", matching_fingerprint)
end
end

def flag_and_suspend_user(user, suspended_user, identifier_type, identifier_value)
User.where(payment_address: suspended_user.payment_address).where.not(id: suspended_user.id).not_suspended.find_each do |user|
user.flag_for_fraud(
author_name: "suspend_sellers_other_accounts",
content: "Flagged for fraud automatically on #{Time.current.to_fs(:formatted_date_full_month)} because of usage of #{identifier_type} #{identifier_value} (from User##{suspended_user.id})"
author_name: SUSPEND_ACCOUNTS_WITH_PAYMENT_ADDRESS_AUTHOR_NAME,
content: "Flagged for fraud automatically on #{Time.current.to_fs(:formatted_date_full_month)} because of usage of payment address #{suspended_user.payment_address} (from User##{suspended_user.id})"
)
user.suspend_for_fraud(
author_name: "suspend_sellers_other_accounts",
content: "Suspended for fraud automatically on #{Time.current.to_fs(:formatted_date_full_month)} because of usage of #{identifier_type} #{identifier_value} (from User##{suspended_user.id})",
author_name: SUSPEND_ACCOUNTS_WITH_PAYMENT_ADDRESS_AUTHOR_NAME,
content: "Suspended for fraud automatically on #{Time.current.to_fs(:formatted_date_full_month)} because of usage of payment address #{suspended_user.payment_address} (from User##{suspended_user.id})",
skip_transition_callback: :suspend_sellers_other_accounts
)
end
end
end
4 changes: 3 additions & 1 deletion spec/models/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1882,9 +1882,11 @@ def sample_string_of_length(n)
expect(@user.reload.comments.count).to eq(2)
end

it "does not suspend all the others sellers accounts if suspended for tos violation" do
it "probates all the others sellers accounts if suspended for tos violation", :sidekiq_inline do
expect(@user).to receive(:probate_sellers_other_accounts).and_call_original
@user.flag_for_tos_violation(author_id: @admin_user.id, product_id: @product_1.id)
@user.suspend_for_tos_violation(author_id: @admin_user.id)
expect(@user_2.reload.on_probation?).to be(true)
expect(@user_2.reload.suspended?).to be(false)
end

Expand Down
2 changes: 1 addition & 1 deletion spec/modules/user/risk_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

context "when user has PayPal as payout processor" do
it "calls SuspendAccountsWithPaymentAddressWorker only once for all related accounts" do
user = create(:user, payment_address: "test@example.com")
user = create(:user, payment_address: "test@example.com", user_risk_state: "suspended_for_fraud")
create(:user, payment_address: "test@example.com")

expect do
Expand Down
Loading