简体   繁体   English

(工资)某些用户从其余额中提取钱到银行帐户时会获得两次付款。 无法复制

[英](Rails) Some users getting paid twice when withdrawing money from their balance to bank account. Can't reproduce

In my app a worker can build up a credit balance in their account and withdraw it to their bank account as they see fit. 在我的应用程序中,工作人员可以在他们认为合适的帐户中建立贷方余额并将其提取到银行帐户中。 There is a MoneyController::withdraw action that calls a .withdraw_funds method on the current_worker which makes a call to the Balanced Payments API and credits their bank account if the amount they've requested to withdraw is <= the amount in their balance. 有一个MoneyController::withdraw调用一个动作.withdraw_funds的方法current_worker这使得调用平衡付款API和归功于自己的银行账户,如果amount ,他们已经要求撤出是<=在其资产负债的金额。 a Transaction is created, attached to the worker's Account , that lists the amount that was subtracted from their balance and credited to their bank account. 创建一个附加到工人AccountTransaction ,该Transaction列出了从其余额中减去并记入其银行帐户的金额。

Recently something has been happening where this controller action gets hit and the whole process is taking place twice, even though the request is supposed to be rejected if the balance is empty. 最近发生了一些事情,该控制器动作被击中,并且整个过程进行了两次,即使如果余额为空也应拒绝该请求。 The withdrawal happens twice and two transactions are generated with the same or very close timestamp. 提款发生两次,并以相同或非常接近的时间戳生成两个事务。 However, it only happens for some workers, and I can't reproduce the error on the development or staging servers. 但是,这仅对某些工作人员会发生,并且我无法在开发或登台服务器上重现该错误。 I was hoping someone could give me some advice as to how to proceed with debugging this. 我希望有人可以就如何进行调试提供一些建议。 Here is the relevant code: 以下是相关代码:

MoneyController
  def withdraw
    if current_worker.withdraw_funds((params[:amount].to_d*100).to_i, params[:bbank])
      redirect_to worker_money_path, notice: "Successfully withdrew $#{params[:amount]}"
    else
      redirect_to worker_money_path, alert: "Failed to withdraw funds. Please contact us for assistance."
    end
  end

/ /

worker.rb

  def withdraw_funds(amount, bbank_id)
    bcust = self.get_balanced
    bbank = Balanced::BankAccount.fetch("/bank_accounts/#{bbank_id}")
    if bbank and (bbank.customer.id == bcust.id)
      puts "bank belongs to worker"
      if self.account.balance >= amount
        res = bbank.credit(amount: amount)
        self.account.debit(amount)
        Transaction.create(amount: amount, tag: 'cashout', source_id: self.account.id, destination_id: nil, balanced_id: res.id)
        return true
      else
        puts "worker #{self} doesn't have #{amount} in account"
        return false
      end
    else
      puts "bank does not belong to worker"
      return false
    end
  end

If the worker's balance contains $50 and two requests are made for $50, the first should succeed and then the second should fail because the balance is now $0 (hence if self.account.balance >= amount ). 如果工作人员的余额中包含$ 50,并且有两个请求要求$ 50,则第一个请求应该成功,然后第二个请求应该失败,因为现在的余额为$ 0(因此if self.account.balance >= amount )。

I was able to look through the logs of the development server as well and find the logs for when this happened: 我还可以查看开发服务器的日志,并找到发生这种情况的日志:

Started POST "/money/withdraw" for 68.119.221.188 at 2014-11-05 13:56:41 -0500
Processing by MoneyController#withdraw as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"x5olIpvJf2K37lYRJypIIHYNhAdZUm1ptill13w9Evw=", "amount"=>"48.50", "bbank"=>"BA2...", "commit"=>"Withdraw"}
Started POST "/money/withdraw" for xx.xxx.xxx.xxx at 2014-11-05 13:56:46 -0500
Processing by MoneyController#withdraw as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"x5olIpvJf2K37lYRJypIIHYNhAdZUm1ptill13w9Evw=", "amount"=>"48.50", "bbank"=>"BA2...", "commit"=>"Withdraw"}
Redirected to http://myapp.com/money
Completed 302 Found in 4467.9ms (ActiveRecord: 54.7ms)
Started GET "/worker/money" for xx.xxx.xxx.xxx at 2014-11-05 13:56:50 -0500
Processing by Worker::MoneyController#index as HTML
Redirected to http://myapp.com/money
Completed 302 Found in 9099.1ms (ActiveRecord: 69.6ms)

I notice in the logs that both requests have the same authenticity token but am not sure what else to take away from them. 我在日志中注意到,这两个请求都具有相同的真实性令牌,但不确定从它们身上还能拿走什么。 I thought it could be as simple as the worker clicking the "Withdraw" button multiple times but in my attempts to recreate the issue in development and staging that never caused the problem. 我以为它很简单,就像工人多次单击“撤回”按钮一样,但是在我尝试重新开发未曾引起问题的开发和暂存中的问题时,这很简单。 The requests were just queued and the succeeding requests always caused the proper response that the balance was empty. 这些请求只是排队,随后的请求总是引起适当的响应,即余额为空。

EDIT I set up a test scenario on the production server and was able to reproduce the problem by clicking the Withdraw button multiple times. 编辑我在生产服务器上设置了一个测试方案,并且能够通过多次单击“提款”按钮来重现该问题。 Anyone have any idea as to why this would happen in Production but not in Development or Staging? 任何人都知道为什么会在生产中发生而不是在开发或登台中发生? Could connection speed have anything to do with it? 连接速度可能与它有关吗?

Since you are not posting client code, I could suggest you put some kind of lock to Worker so as he can only have one running transaction at a time... something like in_process:boolean and process_start:timestamp (both combined). 由于您没有发布客户端代码,因此我建议您对Worker某种锁定,以使他一次只能执行一个正在运行的事务……类似于in_process:booleanprocess_start:timestamp (两者结合使用)。

So, when you start a transaction, you make sure that in_process is false and set it to true , also modifying the process_start (put there for accidental locks, for example, assume a lock active if in_process && process_start>(Time.now - 10.minutes) . 因此,在开始事务时,请确保in_processfalse并将其设置为true ,同时还要修改process_start (例如,将意外锁定放在那儿,如果in_process && process_start>(Time.now - 10.minutes)

After the process is finished, you set the in_process flag back to false. 该过程完成后,将in_process标志设置回false。

This way, only one process per user can be active. 这样,每个用户只能激活一个进程。

Of course, if we have some html , this will probably not be necessary, but it's good practice to have business (controller) logic behind processes that involve money anyhow. 当然,如果我们有一些html ,那么这可能不是必需的,但是将业务(控制器)逻辑置于无论如何都涉及金钱的流程后面是一种好习惯。

EDIT: Perhaps it would also be good idea to keep the Worker locked for some seconds after the transaction has ended. 编辑:也许在交易结束后将Worker锁定几秒钟也是一个好主意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM