如果API调用失败,如何进行事务回滚,反之亦然?

时间:2014-02-20 21:03:59

标签: ruby-on-rails ruby ruby-on-rails-4 transactions stripe-payments

我想要的两件事:

a)我希望只有在API调用成功时才能在db中保存记录

b)我只想在db记录成功保存的情况下执行API调用

目标是保持本地(在数据库中)存储的数据与Stripe上的数据保持一致。

@payment = Payment.new(...)

begin
  Payment.transaction do
    @payment.save!

    stripe_customer = Stripe::Customer.retrieve(manager.customer_id)

    charge = Stripe::Charge.create(
      amount:   @plan.amount_in_cents,
      currency: 'usd',
      customer: stripe_customer.id
    )
  end
# https://stripe.com/docs/api#errors
rescue Stripe::CardError, Stripe::InvalidRequestError, Stripe::APIError => error
  @payment.errors.add :base, 'There was a problem processing your credit card. Please try again.'
  render :new
rescue => error
  render :new
else
  redirect_to dashboard_root_path, notice: 'Thank you. Your payment is being processed.'
end

以上内容将起作用,因为如果记录(第5行)没有保存,则其余代码不会执行。

但是如果我需要在API调用之后保存@payment对象,那该怎么办?因为我需要为@payment对象分配API结果中的值。举个例子:

@payment = Payment.new(...)

begin
  Payment.transaction do
    stripe_customer = Stripe::Customer.retrieve(manager.customer_id)

    charge = Stripe::Charge.create(
      amount:   @plan.amount_in_cents,
      currency: 'usd',
      customer: stripe_customer.id
    )

    @payment.payment_id = charge[:id]

    @payment.activated_at = Time.now.utc
    @payment.save!
  end
# https://stripe.com/docs/api#errors
rescue Stripe::CardError, Stripe::InvalidRequestError, Stripe::APIError => error
  @payment.errors.add :base, 'There was a problem processing your credit card. Please try again.'
  render :new
rescue => error
  render :new
else
  redirect_to dashboard_root_path, notice: 'Thank you. Your payment is being processed.'
end

您会在API调用后发现@payment.save!。这可能是一个问题,因为在DB尝试保存记录之前,API调用已运行。这可能意味着,一个成功的API调用,但是数据库提交失败。

针对此方案的任何想法/建议?

1 个答案:

答案 0 :(得分:8)

您无法执行API => DB和DB => API同时(听起来像一个无限的执行条件),至少我无法想象如何实现这个工作流程。我了解您的数据一致性需求,因此我建议:

  • 检查记录是否有效@payment.valid?(可能使用自定义方法,如valid_without_payment?
  • 运行api call
  • 仅在api调用成功
  • 时保存记录(payment_id

可替换地:

  • 不使用payment_id
  • 保存记录
  • 运行api call
  • 如果调用已成功
  • ,则使用payment_id(api响应)更新记录
  • 定期运行任务(脚本)(cron)以检查不一致的实例(where(payment_id: nil))并将其删除

我认为这两个选项都是可以接受的,您的数据将保持一致。