diff --git a/app/controllers/admin/fraud_payouts_controller.rb b/app/controllers/admin/fraud_payouts_controller.rb index d8d7cd8ac..5d8a651f9 100644 --- a/app/controllers/admin/fraud_payouts_controller.rb +++ b/app/controllers/admin/fraud_payouts_controller.rb @@ -46,7 +46,7 @@ def reject object_changes: { aasm_state: %w[pending_approval rejected] }.to_json ) - redirect_to admin_fraud_payout_path(@run), notice: "Payout run rejected. Orders have been released for the next run." + redirect_to admin_fraud_payout_path(@run), notice: "Payout run rejected. Its payouts have been released for the next run." else redirect_to admin_fraud_payout_path(@run), alert: "Payout run cannot be rejected in its current state." end @@ -55,7 +55,7 @@ def reject def trigger authorize FraudPayoutRun - ::Fraud::CalculatePayoutsJob.perform_later(manual: true, triggered_by: current_user) + ::Fraud::CalculatePayoutsJob.perform_later(triggered_by: current_user) redirect_to admin_fraud_payouts_path, notice: "Manual payout calculation has been queued." end diff --git a/app/jobs/fraud/calculate_payouts_job.rb b/app/jobs/fraud/calculate_payouts_job.rb index e73e3fd98..02f8f5cc9 100644 --- a/app/jobs/fraud/calculate_payouts_job.rb +++ b/app/jobs/fraud/calculate_payouts_job.rb @@ -3,47 +3,25 @@ class Fraud::CalculatePayoutsJob < ApplicationJob queue_as :literally_whenever - def perform(manual: false, triggered_by: nil) - orders = eligible_orders(manual) + def perform(triggered_by: nil) review_payouts = FraudReviewPayout.payable.to_a - return if orders.empty? && review_payouts.empty? - - now = Time.current + return if review_payouts.empty? run = FraudPayoutRun.new( - period_start: manual ? last_run_end : nil, - period_end: now, + period_start: last_run_end, + period_end: Time.current, total_orders: 0, total_amount: 0 ) - grouped = FraudPayoutRun.orders_by_reviewer(orders) - - leaderboard = grouped.map { |user_id, user_orders| { user: user_id, total: user_orders.size } } - bracket_results = BracketCalculator.new(leaderboard, 1000).calculate - payouts_by_user = bracket_results[:results].index_by { |r| r[:user] } - FraudPayoutRun.transaction do run.save! - grouped.each do |user_id, user_orders| - order_count = user_orders.size - amount = payouts_by_user[user_id]&.fetch(:payout, 0).to_i - - line = run.lines.create!( - user_id: user_id, - order_count: order_count, - amount: amount - ) - - ShopOrder.where(id: user_orders.map(&:id)).update_all(fraud_payout_line_id: line.id) - end - - review_total = pay_out_reviews(run, review_payouts) + total = pay_out_reviews(run, review_payouts) run.update!( - total_orders: orders.size + review_payouts.sum(&:item_count), - total_amount: bracket_results[:total_distributed].to_i + review_total, + total_orders: review_payouts.sum(&:item_count), + total_amount: total, approved_by_user: triggered_by ) @@ -54,9 +32,8 @@ def perform(manual: false, triggered_by: nil) private - # Per-person review payouts get their own line per reviewer: their amount is - # already fixed by the formula, so they do not go through the bracket split - # that shares out the per-order pot. + # One line per reviewer: their amount is already fixed by the per-person + # formula, so nothing is shared out between them. def pay_out_reviews(run, review_payouts) review_payouts.group_by(&:reviewer_id).sum do |reviewer_id, payouts| amount = payouts.sum(&:credited_amount) @@ -72,14 +49,7 @@ def pay_out_reviews(run, review_payouts) end end - def eligible_orders(manual) - scope = FraudPayoutRun.payout_eligible_orders - - scope = scope.where("shop_orders.created_at >= ?", last_run_end) if manual && last_run_end - scope.to_a - end - def last_run_end - @last_run_end ||= FraudPayoutRun.order(period_end: :desc).pick(:period_end) + FraudPayoutRun.order(period_end: :desc).pick(:period_end) end end diff --git a/app/models/fraud_payout_line.rb b/app/models/fraud_payout_line.rb index 5b9d18751..e47dfcf1f 100644 --- a/app/models/fraud_payout_line.rb +++ b/app/models/fraud_payout_line.rb @@ -28,15 +28,11 @@ class FraudPayoutLine < ApplicationRecord belongs_to :fraud_payout_run belongs_to :user - has_many :shop_orders, dependent: :nullify has_many :fraud_review_payouts, dependent: :nullify - # Per-person review payouts and the per-order bracket payouts land in - # separate lines, so the ledger entry says which one the reviewer is reading. def payout_reason people = fraud_review_payouts.size - return "Fraud squad payout for #{people} #{'person'.pluralize(people)} fully reviewed" if people.positive? - "Fraud squad payout for #{order_count} #{'order'.pluralize(order_count)} reviewed" + "Fraud squad payout for #{people} #{'person'.pluralize(people)} fully reviewed" end end diff --git a/app/models/fraud_payout_run.rb b/app/models/fraud_payout_run.rb index 2fdc40069..6f666d953 100644 --- a/app/models/fraud_payout_run.rb +++ b/app/models/fraud_payout_run.rb @@ -23,51 +23,6 @@ class FraudPayoutRun < ApplicationRecord has_many :lines, class_name: "FraudPayoutLine", dependent: :destroy belongs_to :approved_by_user, class_name: "User", optional: true - REVIEW_STATES = %w[awaiting_periodical_fulfillment rejected on_hold].freeze - - def self.payout_eligible_orders - ShopOrder - .where(aasm_state: REVIEW_STATES) - .where(fraud_payout_line_id: nil) - .where(fraud_review_payout_id: nil) - end - - # Base scope for PaperTrail versions that could represent a fraud review. - def self.reviewer_versions - ::PaperTrail::Version - .where(item_type: "ShopOrder") - .where.not(whodunnit: nil) - .where("object_changes ? 'aasm_state'") - end - - # Returns the reviewer user ID if the version represents a fraud-review - # state transition, nil otherwise. - def self.reviewer_from_version(version) - changes = version.object_changes - return nil if changes.is_a?(String) && changes.start_with?("---") - changes = JSON.parse(changes) if changes.is_a?(String) - state_change = changes["aasm_state"] - return nil unless state_change.is_a?(Array) && state_change[1].in?(REVIEW_STATES) - version.whodunnit.to_i - end - - def self.orders_by_reviewer(orders) - orders = orders.to_a - reviewer_by_order_id = {} - - reviewer_versions - .where(item_id: orders.map(&:id)) - .order(:created_at, :id) - .each do |version| - reviewer_id = reviewer_from_version(version) - reviewer_by_order_id[version.item_id.to_i] ||= reviewer_id if reviewer_id - end - - orders - .group_by { |order| reviewer_by_order_id[order.id] } - .reject { |reviewer_id, _| reviewer_id.nil? } - end - aasm timestamps: true do state :pending_approval, initial: true state :approved @@ -80,7 +35,7 @@ def self.orders_by_reviewer(orders) event :reject do transitions from: :pending_approval, to: :rejected - after { release_orders! } + after { release_payouts! } end end @@ -110,8 +65,7 @@ def distribute_payouts! end end - def release_orders! - ShopOrder.where(fraud_payout_line: lines).update_all(fraud_payout_line_id: nil) + def release_payouts! FraudReviewPayout.where(fraud_payout_line: lines).update_all(fraud_payout_line_id: nil) end end diff --git a/app/views/admin/fraud_payouts/index.html.erb b/app/views/admin/fraud_payouts/index.html.erb index 83cf86986..5d2d80f17 100644 --- a/app/views/admin/fraud_payouts/index.html.erb +++ b/app/views/admin/fraud_payouts/index.html.erb @@ -10,14 +10,14 @@ <%= button_to "Trigger Manual Run", trigger_admin_fraud_payouts_path, method: :post, class: "aorder__btn aorder__btn--secondary", - data: { confirm: "Calculate payouts for all unpaid reviewed orders since the last run. Continue?" } %> + data: { confirm: "Pay out every completed review that has not been paid yet. Continue?" } %> <% end %>

Your Estimated Payout

- Issued automatically on the last day of each month + Issued when a payout run is triggered
@@ -101,7 +101,7 @@ Run Status Period - Orders + Items Total Tickets Approved By @@ -131,7 +131,7 @@ <% else %>

- No payout runs yet. Trigger a manual run or wait for the scheduled job. + No payout runs yet. Trigger a run to pay out completed reviews.

<% end %>
diff --git a/app/views/admin/fraud_payouts/show.html.erb b/app/views/admin/fraud_payouts/show.html.erb index fa3c08fff..a5aa1129f 100644 --- a/app/views/admin/fraud_payouts/show.html.erb +++ b/app/views/admin/fraud_payouts/show.html.erb @@ -24,7 +24,7 @@
- Orders reviewed + Items reviewed <%= @run.total_orders %>
@@ -65,7 +65,7 @@ <%= button_to "Reject", reject_admin_fraud_payout_path(@run), method: :post, class: "aorder__btn aorder__btn--reject", - data: { confirm: "Reject this payout run and release orders for the next run?" } %> + data: { confirm: "Reject this payout run and release its payouts for the next run?" } %>
@@ -83,7 +83,7 @@ Reviewer - Orders Reviewed + Items Reviewed Tickets Earned diff --git a/config/recurring.yml b/config/recurring.yml index 8dc071ed1..873afdfdb 100644 --- a/config/recurring.yml +++ b/config/recurring.yml @@ -145,12 +145,6 @@ production: concurrency: 1 description: "Calculate weekly fulfillment payouts (3 tickets per fulfilled order)" - fraud_calculate_payouts: - class: Fraud::CalculatePayoutsJob - schedule: "0 23 L * * America/New_York" - concurrency: 1 - description: "Calculate monthly fraud squad payouts on the last day of the month at 11pm EST (bracket-based, max 1000 tickets)" - support_vibecheck_est: class: SupportVibecheckJob schedule: every day at 12pm America/New_York diff --git a/test/controllers/admin/fraud_payouts_controller_test.rb b/test/controllers/admin/fraud_payouts_controller_test.rb index 7137e20a8..05365cc51 100644 --- a/test/controllers/admin/fraud_payouts_controller_test.rb +++ b/test/controllers/admin/fraud_payouts_controller_test.rb @@ -54,7 +54,7 @@ class Admin::FraudPayoutsControllerTest < ActionDispatch::IntegrationTest sign_in @admin assert_enqueued_with(job: ::Fraud::CalculatePayoutsJob, - args: [ { manual: true, triggered_by: @admin } ]) do + args: [ { triggered_by: @admin } ]) do post trigger_admin_fraud_payouts_path end diff --git a/test/jobs/fraud/calculate_payouts_job_test.rb b/test/jobs/fraud/calculate_payouts_job_test.rb index 62bac6ec4..ead12b41c 100644 --- a/test/jobs/fraud/calculate_payouts_job_test.rb +++ b/test/jobs/fraud/calculate_payouts_job_test.rb @@ -4,39 +4,13 @@ class Fraud::CalculatePayoutsJobTest < ActiveJob::TestCase setup do @reviewer1 = create_user(slack_id: "UREVIEWER1", display_name: "fraudreviewer1") @reviewer2 = create_user(slack_id: "UREVIEWER2", display_name: "fraudreviewer2") - @buyer = create_user(slack_id: "UFRAUDBUYER", display_name: "fraudbuyer") - @buyer.update!(has_gotten_free_stickers: true) # clears the shop-tutorial gate - - @item = ShopItem.new( - name: "Test Item", - description: "A test item reviewers' orders are attached to", - ticket_cost: 0, - type: "ShopItem::ThirdPartyPhysical", - enabled: true - ) - @item.image.attach( - io: StringIO.new(Base64.decode64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=")), - filename: "px.png", - content_type: "image/png" - ) - @item.save! - - @order1 = create_order(@buyer, @item) - @order2 = create_order(@buyer, @item) - @order3 = create_order(@buyer, @item) - - review!(@order1, @reviewer1, "rejected") - review!(@order2, @reviewer1, "awaiting_periodical_fulfillment") - review!(@order3, @reviewer2, "on_hold") + @subject = create_user(slack_id: "UFRAUDSUBJECT", display_name: "fraudsubject") end test "a completed per-person review payout is credited in its own line" do - clearer = create_user(slack_id: "UREVIEWER3", display_name: "fraudreviewer3") - payout = FraudReviewPayout.create!(reviewer: clearer, subject: @buyer, flag_count: 1, - order_count: 1, integrity_count: 1, amount: 4.81, - completed_at: Time.current) + payout = complete_payout(@reviewer1, flag_count: 1, order_count: 1, integrity_count: 1, amount: 4.81) - assert_difference -> { clearer.ledger_entries.count }, 1 do + assert_difference -> { @reviewer1.ledger_entries.count }, 1 do Fraud::CalculatePayoutsJob.perform_now end @@ -46,10 +20,35 @@ class Fraud::CalculatePayoutsJobTest < ActiveJob::TestCase assert_match "1 person fully reviewed", line.ledger_entries.sole.reason end - test "a manual run records who triggered it" do + test "records the totals of everything it swept" do + complete_payout(@reviewer1, flag_count: 2, order_count: 1, integrity_count: 0, amount: 3.4) + complete_payout(@reviewer2, flag_count: 1, order_count: 0, integrity_count: 1, amount: 2.5) + + Fraud::CalculatePayoutsJob.perform_now + + run = FraudPayoutRun.sole + assert_equal "approved", run.aasm_state + assert_equal 5, run.total_orders, "three items for one reviewer, two for the other" + assert_equal 6, run.total_amount, "3.4 and 2.5 each round at credit time" + end + + test "a reviewer's payouts are collapsed into a single line" do + complete_payout(@reviewer1, flag_count: 1, order_count: 0, integrity_count: 0, amount: 1.1) + complete_payout(@reviewer1, flag_count: 0, order_count: 2, integrity_count: 0, amount: 2.2) + + Fraud::CalculatePayoutsJob.perform_now + + line = FraudPayoutRun.sole.lines.sole + assert_equal @reviewer1.id, line.user_id + assert_equal 3, line.order_count + assert_equal 3, line.amount + end + + test "a run records who triggered it" do trigger = create_user(slack_id: "UREVIEWER_TRIGGER", display_name: "fraudtrigger") + complete_payout(@reviewer1, flag_count: 1, order_count: 0, integrity_count: 0, amount: 1.1) - Fraud::CalculatePayoutsJob.perform_now(manual: true, triggered_by: trigger) + Fraud::CalculatePayoutsJob.perform_now(triggered_by: trigger) run = FraudPayoutRun.sole @@ -60,21 +59,21 @@ class Fraud::CalculatePayoutsJobTest < ActiveJob::TestCase event: "approved").whodunnit end - test "a scheduled run has no approver to record" do + test "a run nobody triggered has no approver to record" do + complete_payout(@reviewer1, flag_count: 1, order_count: 0, integrity_count: 0, amount: 1.1) + Fraud::CalculatePayoutsJob.perform_now assert_nil FraudPayoutRun.sole.approved_by_user end - test "a manual run pays every completed review payout, however old" do - clearer = create_user(slack_id: "UREVIEWER_OLD", display_name: "fraudreviewerold") - old = FraudReviewPayout.create!(reviewer: clearer, subject: @buyer, flag_count: 2, - order_count: 0, integrity_count: 0, amount: 1.2, - completed_at: 90.days.ago) + test "pays every completed review payout, however old" do + old = complete_payout(@reviewer1, flag_count: 2, order_count: 0, integrity_count: 0, amount: 1.2, + completed_at: 90.days.ago) old.update_columns(created_at: 90.days.ago) - assert_difference -> { clearer.ledger_entries.count }, 1 do - Fraud::CalculatePayoutsJob.perform_now(manual: true) + assert_difference -> { @reviewer1.ledger_entries.count }, 1 do + Fraud::CalculatePayoutsJob.perform_now end assert_not_nil old.reload.fraud_payout_line_id @@ -82,121 +81,41 @@ class Fraud::CalculatePayoutsJobTest < ActiveJob::TestCase end test "a review payout that is not complete is left for a later run" do - FraudReviewPayout.create!(reviewer: @reviewer1, subject: @buyer, flag_count: 1, + FraudReviewPayout.create!(reviewer: @reviewer1, subject: @subject, flag_count: 1, order_count: 0, integrity_count: 0, amount: 1.1) Fraud::CalculatePayoutsJob.perform_now + assert_equal 0, FraudPayoutRun.count assert_nil FraudReviewPayout.sole.fraud_payout_line_id end - test "creates a payout run with totals from the bracket calculator" do + test "a payout a previous run already swept is not paid twice" do + complete_payout(@reviewer1, flag_count: 1, order_count: 0, integrity_count: 0, amount: 1.1) Fraud::CalculatePayoutsJob.perform_now - run = FraudPayoutRun.last - assert_equal "approved", run.aasm_state - assert_equal 3, run.total_orders - # reviewer1 reviewed 2/2 (the leader, 100% bracket -> full $1000) - # reviewer2 reviewed 1/2 (50% of the leader -> 45-59% bracket -> $550) - assert_equal 1550, run.total_amount - end - - test "pays reviewers relative to the leader, not a fixed rate per order" do - Fraud::CalculatePayoutsJob.perform_now - - run = FraudPayoutRun.last - line1 = run.lines.find_by(user: @reviewer1) - line2 = run.lines.find_by(user: @reviewer2) - - assert_equal 2, line1.order_count - assert_equal 1000, line1.amount - - assert_equal 1, line2.order_count - assert_equal 550, line2.amount - end - - test "associates orders with their reviewer's payout line" do - Fraud::CalculatePayoutsJob.perform_now - - run = FraudPayoutRun.last - line1 = run.lines.find_by(user: @reviewer1) - line2 = run.lines.find_by(user: @reviewer2) - - assert_equal line1.id, @order1.reload.fraud_payout_line_id - assert_equal line1.id, @order2.reload.fraud_payout_line_id - assert_equal line2.id, @order3.reload.fraud_payout_line_id - end - - test "credits the first reviewer to touch an order, not whoever left it in its final state" do - order = create_order(@buyer, @item) - review!(order, @reviewer2, "on_hold") - review!(order, @reviewer1, "rejected") - - Fraud::CalculatePayoutsJob.perform_now - - run = FraudPayoutRun.last - assert_equal run.lines.find_by(user: @reviewer2).id, order.reload.fraud_payout_line_id - end - - test "approving the run pays out reviewers via ledger entries" do - Fraud::CalculatePayoutsJob.perform_now - - run = FraudPayoutRun.last - line1 = run.lines.find_by(user: @reviewer1) - - entry = @reviewer1.ledger_entries.find_by(ledgerable: line1) - assert_not_nil entry - assert_equal 1000, entry.amount - end - - test "skips orders already paid out by a previous run" do - Fraud::CalculatePayoutsJob.perform_now - assert_equal 1, FraudPayoutRun.count - - order4 = create_order(@buyer, @item) - review!(order4, @reviewer1, "rejected") + complete_payout(@reviewer2, flag_count: 3, order_count: 0, integrity_count: 0, amount: 1.3) Fraud::CalculatePayoutsJob.perform_now assert_equal 2, FraudPayoutRun.count second_run = FraudPayoutRun.order(:created_at).last - assert_equal 1, second_run.total_orders + assert_equal 3, second_run.total_orders + assert_equal [ @reviewer2.id ], second_run.lines.pluck(:user_id) end - test "manual run only includes orders created since the last run" do + test "rejecting a run releases its payouts for the next one" do + payout = complete_payout(@reviewer1, flag_count: 1, order_count: 0, integrity_count: 0, amount: 1.1) Fraud::CalculatePayoutsJob.perform_now - new_order = travel_to(1.hour.from_now) { create_order(@buyer, @item) } - review!(new_order, @reviewer1, "rejected") - - old_order = travel_to(1.week.ago) { create_order(@buyer, @item) } - review!(old_order, @reviewer2, "rejected") - - Fraud::CalculatePayoutsJob.perform_now(manual: true) - - second_run = FraudPayoutRun.order(:created_at).last - assert_equal 1, second_run.total_orders - assert_not_nil new_order.reload.fraud_payout_line_id - assert_nil old_order.reload.fraud_payout_line_id - end - - test "counts an order toward total_orders even when its reviewer can't be attributed" do - system_order = create_order(@buyer, @item) - system_order.update!( - aasm_state: "rejected", # no PaperTrail.request whodunnit set - internal_rejection_reason: "flagged as fraud", - fraud_related_project_id: projects(:one).id - ) - - Fraud::CalculatePayoutsJob.perform_now + run = FraudPayoutRun.sole + run.update_columns(aasm_state: "pending_approval") + run.reject! - run = FraudPayoutRun.last - assert_equal 4, run.total_orders - assert_nil system_order.reload.fraud_payout_line_id + assert_nil payout.reload.fraud_payout_line_id + assert_includes FraudReviewPayout.payable, payout end - test "does nothing when no eligible orders" do - ShopOrder.update_all(aasm_state: "pending") - + test "does nothing when nothing is payable" do Fraud::CalculatePayoutsJob.perform_now assert_equal 0, FraudPayoutRun.count @@ -204,26 +123,15 @@ class Fraud::CalculatePayoutsJobTest < ActiveJob::TestCase private - def create_order(buyer, item) - ShopOrder.create!( - user: buyer, - shop_item: item, - quantity: 1, - frozen_item_price: 0, - frozen_address: { "country" => "US" }.to_json, - aasm_state: "pending" + def complete_payout(reviewer, flag_count:, order_count:, integrity_count:, amount:, completed_at: Time.current) + FraudReviewPayout.create!( + reviewer: reviewer, + subject: @subject, + flag_count: flag_count, + order_count: order_count, + integrity_count: integrity_count, + amount: amount, + completed_at: completed_at ) end - - def review!(order, reviewer, state) - attrs = { aasm_state: state } - if state == "rejected" - attrs[:internal_rejection_reason] = "flagged as fraud" - attrs[:fraud_related_project_id] = projects(:one).id - end - - PaperTrail.request(whodunnit: reviewer.id) do - order.update!(attrs) - end - end end diff --git a/test/models/fraud_payout_run_test.rb b/test/models/fraud_payout_run_test.rb deleted file mode 100644 index d536cdca8..000000000 --- a/test/models/fraud_payout_run_test.rb +++ /dev/null @@ -1,46 +0,0 @@ -require "test_helper" - -# == Schema Information -# -# Table name: fraud_payout_runs -# -# id :bigint not null, primary key -# aasm_state :string -# approved_at :datetime -# period_end :datetime -# period_start :datetime -# total_amount :integer -# total_orders :integer -# created_at :datetime not null -# updated_at :datetime not null -# approved_by_user_id :bigint -# -class FraudPayoutRunTest < ActiveSupport::TestCase - def create_review_version(whodunnit:, to_state:) - ::PaperTrail::Version.create!( - item_type: "ShopOrder", - item_id: SecureRandom.random_number(1_000_000).to_s, - event: "update", - whodunnit: whodunnit, - object_changes: { aasm_state: [ "awaiting_verification", to_state ] } - ) - end - - test "reviewer_versions only includes ShopOrder aasm_state changes with a whodunnit" do - review = create_review_version(whodunnit: "1", to_state: "rejected") - create_review_version(whodunnit: nil, to_state: "rejected") - ::PaperTrail::Version.create!(item_type: "ShopOrder", item_id: "999", event: "update", whodunnit: "1", object_changes: { tracking_number: [ nil, "abc" ] }.to_json) - - assert_equal [ review.id ], FraudPayoutRun.reviewer_versions.pluck(:id) - end - - test "reviewer_from_version returns the whodunnit as an integer for review-state transitions" do - version = create_review_version(whodunnit: "42", to_state: "on_hold") - assert_equal 42, FraudPayoutRun.reviewer_from_version(version) - end - - test "reviewer_from_version returns nil for non-review-state transitions" do - version = create_review_version(whodunnit: "42", to_state: "fulfilled") - assert_nil FraudPayoutRun.reviewer_from_version(version) - end -end diff --git a/test/models/fraud_review_payout_test.rb b/test/models/fraud_review_payout_test.rb index b128e820b..e52654951 100644 --- a/test/models/fraud_review_payout_test.rb +++ b/test/models/fraud_review_payout_test.rb @@ -193,17 +193,6 @@ class FraudReviewPayoutTest < ActiveSupport::TestCase assert_equal 1.1, second.amount.to_f end - test "an order paid by a review payout is not paid again by a payout run" do - order = order_for_subject - order.update_columns(aasm_state: "rejected") - - assert_includes FraudPayoutRun.payout_eligible_orders, order - - FraudReviewPayout.claim!(order, reviewer: @reviewer, subject: @subject) - - assert_not_includes FraudPayoutRun.payout_eligible_orders, order - end - test "a first review on a two-approval order is paid as the order it reviewed" do order = order_for_subject order.update_columns(frozen_item_price: FraudReviewPayout::HIGH_VALUE_ORDER_STARDUST + 1)