(Rails)有些用户在从余额中提取资金到银行帐户时获得两次付款。无法复制

时间:2014-11-05 22:05:31

标签: ruby-on-rails ruby debugging payment-gateway balanced-payments

在我的应用中,工作人员可以在其帐户中建立贷方余额,并在他们认为合适时将其提取到他们的银行帐户。有一个MoneyController::withdraw操作在.withdraw_funds上调用了current_worker方法,该方法调用了均衡付款API,如果amount他们是Transaction,则可以记入他们的银行帐户。要求退出的是< =余额中的金额。创建了Account,附加到工作人员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 self.account.balance >= amount

如果工人的余额包含50美元,而且两个请求是50美元,则第一个应该成功,然后第二个应该失败,因为余额现在是0美元(因此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) )。

我也能够查看开发服务器的日志,并找到发生这种情况的日志:

{{1}}

我在日志中注意到这两个请求都具有相同的真实性令牌,但我不确定还有什么可以从中获取。我认为这可能就像工人点击" Withdraw"多次按钮,但我试图在开发和暂存中重新创建从未导致问题的问题。请求刚刚排队,随后的请求总是导致正确的响应,即余额为空。

编辑 我在生产服务器上设置了测试方案,并且可以通过多次单击“提取”按钮来重现问题。任何人都知道为什么会在生产中发生这种情况,而不是在开发或分期中发生?连接速度可能与它有关吗?

1 个答案:

答案 0 :(得分:1)

由于您没有发布客户端代码,我建议您对Worker进行某种锁定,这样他一次只能有一个正在运行的事务......类似于in_process:boolean和{ {1}}(两者合并)。

因此,当您开始一项交易时,请确保process_start:timestampin_process并将其设为false,同时修改true(将其置于意外锁定位置)例如,如果process_start,则假定锁定处于活动状态。

完成此过程后,将in_process && process_start>(Time.now - 10.minutes)标志设置为false。

这样,每个用户只能有一个进程处于活动状态。

当然,如果我们有一些in_process,这可能就没有必要了,但无论如何,在涉及金钱的流程背后都有业务(控制器)逻辑,这是一种很好的做法。

编辑:也许在交易结束后保持html锁定几秒钟也是个好主意。